From 93f624a8a59aa3d054cc8933de70b0b15821af2b Mon Sep 17 00:00:00 2001 From: imodeljs-admin Date: Tue, 4 Dec 2018 07:47:20 -0800 Subject: [PATCH] 0.171.0 --- README.md | 8 +- .../rush/browser-approved-packages.json | 60 +- common/config/rush/command-line.json | 2 +- common/config/rush/common-versions.json | 2 +- common/config/rush/npm-shrinkwrap.json | 5664 ++++++++--------- common/config/rush/version-policies.json | 2 +- common/scripts/audit.js | 20 +- common/scripts/install-run-rush.js | 7 +- common/scripts/install-run.js | 10 +- common/scripts/mocha-reporter-tweaks.js | 58 + common/scripts/utils.js | 61 + core/backend/CHANGELOG.json | 174 + core/backend/CHANGELOG.md | 84 +- core/backend/package.json | 26 +- core/backend/src/AutoPush.ts | 4 +- core/backend/src/BisCore.ts | 2 +- core/backend/src/BriefcaseManager.ts | 650 +- core/backend/src/Category.ts | 86 +- core/backend/src/ChangeSummaryManager.ts | 13 +- core/backend/src/ClassRegistry.ts | 56 +- core/backend/src/CodeSpecs.ts | 36 +- core/backend/src/ConcurrencyControl.ts | 16 +- core/backend/src/Element.ts | 142 +- core/backend/src/ElementAspect.ts | 6 + core/backend/src/Entity.ts | 2 + core/backend/src/ExpressServer.ts | 9 +- core/backend/src/IModelDb.ts | 541 +- core/backend/src/IModelHost.ts | 4 + core/backend/src/LinkTableRelationship.ts | 228 - core/backend/src/Model.ts | 77 +- core/backend/src/NavigationRelationship.ts | 91 + core/backend/src/PromiseMemoizer.ts | 6 +- core/backend/src/Relationship.ts | 222 + core/backend/src/RpcBackend.ts | 2 +- core/backend/src/SqliteStatement.ts | 4 +- core/backend/src/ViewDefinition.ts | 277 +- core/backend/src/backend.ts | 3 +- .../backend/src/domains/FunctionalElements.ts | 42 +- .../src/imodeljs-native-platform-api.ts | 953 +-- .../backend/src/rpc-impl/IModelReadRpcImpl.ts | 4 +- .../src/rpc-impl/OpenIModelDbMemoizer.ts | 15 +- core/backend/src/test/IModelTestUtils.ts | 118 +- core/backend/src/test/MockAssetUtil.ts | 93 +- .../src/test/assets/TestBim.ecschema.xml | 14 + .../test/assets/TestFunctional.ecschema.xml | 16 + .../src/test/assets/TestSchema.ecschema.json | 1415 ++-- .../src/test/assets/TestSchema.ecschema.xml | 128 +- .../test/integration/ApplyChangeSets.test.ts | 51 +- .../test/integration/BriefcaseManager.test.ts | 231 +- .../test/integration/ChangeSummary.test.ts | 130 +- .../test/integration/DebugHubIssues.test.ts | 162 +- .../src/test/integration/HubUtility.ts | 96 +- .../src/test/integration/IModelWrite.test.ts | 95 +- .../src/test/integration/IModelWriter.ts | 112 +- .../src/test/integration/OpenMemoizer.test.ts | 5 +- .../src/test/integration/PushRetry.test.ts | 24 +- .../src/test/integration/TestPushUtility.ts | 26 +- .../src/test/standalone/ClassRegistry.test.ts | 4 +- .../standalone/ECSchemaXmlContext.test.ts | 2 +- .../test/standalone/FunctionalDomain.test.ts | 63 +- .../src/test/standalone/GenericDomain.test.ts | 12 +- .../test/standalone/GeometryStream.test.ts | 71 +- .../src/test/standalone/IModel.test.ts | 245 +- .../test/standalone/IModelImporter.test.ts | 87 + .../test/standalone/PromiseMemoizer.test.ts | 2 +- .../src/test/standalone/TxnManager.test.ts | 197 + core/bentley/CHANGELOG.json | 78 + core/bentley/CHANGELOG.md | 52 +- core/bentley/package.json | 6 +- core/bentley/src/BeSQLite.ts | 2 +- core/bentley/src/BentleyError.ts | 10 + core/bentley/src/Disposable.ts | 2 +- core/bentley/src/FluentdBunyanLoggerConfig.ts | 55 + core/bentley/src/FluentdLoggerStream.ts | 136 + core/bentley/src/Id.ts | 50 +- core/bentley/src/Logger.ts | 6 +- core/bentley/src/PromiseUtil.ts | 87 - core/bentley/src/bentleyjs-core.ts | 1 - core/bentley/src/test/Disposable.test.ts | 4 +- core/clients-backend/CHANGELOG.json | 57 + core/clients-backend/CHANGELOG.md | 45 +- core/clients-backend/package.json | 17 +- core/clients-backend/src/index.ts | 4 +- .../src/{ => oidc}/OidcAgentClient.ts | 0 .../src/{ => oidc}/OidcBackendClient.ts | 20 +- .../src/{ => oidc}/OidcDelegationClient.ts | 0 .../src/oidc/OidcDeviceClient.ts | 216 + core/clients-backend/src/oidc/index.ts | 8 + .../src/test/OidcBackendClient.test.ts | 2 +- core/clients/CHANGELOG.json | 96 + core/clients/CHANGELOG.md | 58 +- core/clients/package.json | 22 +- core/clients/src/BIMReviewShareClient.ts | 8 +- core/clients/src/Client.ts | 6 +- core/clients/src/ConnectClients.ts | 12 +- core/clients/src/ECJsonTypeMap.ts | 60 +- core/clients/src/FormDataManagementClient.ts | 12 +- .../IModelBankDummyAuthorizationClient.ts | 10 +- .../IModelBankFileSystemContextClient.ts | 4 +- core/clients/src/IModelClient.ts | 34 +- core/clients/src/IModelCloudEnvironment.ts | 4 +- core/clients/src/ImsClients.ts | 8 +- core/clients/src/RealityDataServicesClient.ts | 2 +- core/clients/src/Request.ts | 4 +- core/clients/src/SettingsClient.ts | 14 +- core/clients/src/Token.ts | 40 +- core/clients/src/UserInfo.ts | 35 + core/clients/src/UserProfile.ts | 23 - core/clients/src/WsgClient.ts | 4 +- .../clients/src/imodelhub/AzureFileHandler.ts | 79 +- core/clients/src/imodelhub/BaseHandler.ts | 14 +- core/clients/src/imodelhub/Briefcases.ts | 6 +- core/clients/src/imodelhub/ChangeSets.ts | 4 +- core/clients/src/imodelhub/Client.ts | 5 +- core/clients/src/imodelhub/Codes.ts | 8 +- core/clients/src/imodelhub/Events.ts | 8 +- core/clients/src/imodelhub/EventsBase.ts | 12 +- core/clients/src/imodelhub/GlobalEvents.ts | 10 +- core/clients/src/imodelhub/Locks.ts | 32 +- core/clients/src/imodelhub/Thumbnails.ts | 6 +- core/clients/src/imodelhub/Users.ts | 18 +- core/clients/src/imodelhub/Versions.ts | 8 +- core/clients/src/imodelhub/iModels.ts | 160 +- core/clients/src/index.ts | 2 +- core/clients/src/oidc/OidcClient.ts | 2 +- core/clients/src/oidc/OidcFrontendClient.ts | 68 +- .../src/test/BIMReviewShareClient.test.ts | 13 +- core/clients/src/test/ConnectClients.test.ts | 2 +- core/clients/src/test/ImsClients.test.ts | 8 +- .../test/RealityDataServicesClient.test.ts | 2 +- core/clients/src/test/SettingsClient.test.ts | 191 +- core/clients/src/test/TestConfig.ts | 76 +- core/clients/src/test/UrlValidator.test.ts | 40 - .../src/test/imodelhub/Briefcases.test.ts | 58 +- .../src/test/imodelhub/ChangeSet.test.ts | 80 +- core/clients/src/test/imodelhub/Codes.test.ts | 58 +- .../clients/src/test/imodelhub/Events.test.ts | 52 +- .../src/test/imodelhub/GlobalEvents.test.ts | 28 +- .../src/test/imodelhub/IModelHubCloudEnv.ts | 6 +- core/clients/src/test/imodelhub/Locks.test.ts | 58 +- .../src/test/imodelhub/Performance.test.ts | 38 +- core/clients/src/test/imodelhub/TestUtils.ts | 46 +- .../src/test/imodelhub/Thumbnails.test.ts | 32 +- .../src/test/imodelhub/UrlValidator.test.ts | 98 + .../src/test/imodelhub/UserInfo.test.ts | 58 +- .../src/test/imodelhub/UserStatistics.test.ts | 68 +- .../src/test/imodelhub/Versions.test.ts | 44 +- .../src/test/imodelhub/iModels.test.ts | 231 +- core/clients/tslint.json | 10 +- core/common/CHANGELOG.json | 108 + core/common/CHANGELOG.md | 62 +- core/common/package.json | 40 +- core/common/src/EntityProps.ts | 7 - core/common/src/IModel.ts | 10 +- core/common/src/IModelVersion.ts | 6 +- core/common/src/Render.ts | 97 +- core/common/src/RenderSchedule.ts | 163 + core/common/src/RpcInterface.ts | 2 +- core/common/src/RpcManager.ts | 2 +- core/common/src/Snapping.ts | 6 +- core/common/src/ViewProps.ts | 384 +- core/common/src/common.ts | 1 + core/common/src/geometry/Cartographic.ts | 41 +- core/common/src/geometry/GeometryStream.ts | 4 +- core/common/src/rpc/IModelReadRpcInterface.ts | 38 +- core/common/src/rpc/IModelTileRpcInterface.ts | 6 +- .../src/rpc/IModelUnitTestRpcInterface.ts | 2 +- .../common/src/rpc/IModelWriteRpcInterface.ts | 8 +- .../src/rpc/StandaloneIModelRpcInterface.ts | 4 +- core/common/src/rpc/WipRpcInterface.ts | 8 +- core/common/src/rpc/core/RpcConfiguration.ts | 4 +- core/common/src/rpc/core/RpcConstants.ts | 1 - core/common/src/rpc/core/RpcControl.ts | 8 +- core/common/src/rpc/core/RpcInvocation.ts | 4 +- core/common/src/rpc/core/RpcPendingQueue.ts | 2 +- core/common/src/rpc/core/RpcProtocol.ts | 2 +- core/common/src/rpc/core/RpcRegistry.ts | 2 +- core/common/src/rpc/core/RpcRequest.ts | 7 +- .../src/rpc/electron/ElectronRpcRequest.ts | 4 +- .../src/rpc/mobile/MobileRpcProtocol.ts | 266 +- .../common/src/rpc/mobile/MobileRpcRequest.ts | 14 +- .../src/rpc/web/BentleyCloudRpcProtocol.ts | 10 +- core/common/src/rpc/web/RpcMultipart.ts | 45 +- core/common/src/rpc/web/WebAppRpcProtocol.ts | 6 +- core/common/src/rpc/web/WebAppRpcRequest.ts | 25 +- .../rpc/web/multipart/RpcMultipartParser.ts | 2 +- .../common/src/test}/FeatureIndex.test.ts | 4 +- .../common/src/test}/OctEncodedNormal.test.ts | 2 +- .../common/src/test}/QPoint.test.ts | 2 +- .../common/src/test}/Render.test.ts | 2 +- core/ecschema-metadata/CHANGELOG.json | 54 + core/ecschema-metadata/CHANGELOG.md | 44 +- core/ecschema-metadata/package.json | 14 +- core/ecschema-metadata/src/Context.ts | 2 +- .../src/Deserialization/AbstractParser.ts | 68 +- .../src/Deserialization/Helper.ts | 696 +- .../src/Deserialization/JsonParser.ts | 980 ++- .../src/Deserialization/JsonProps.ts | 14 +- core/ecschema-metadata/src/ECObjects.ts | 2 +- core/ecschema-metadata/src/Metadata/Class.ts | 4 +- core/ecschema-metadata/src/Metadata/Format.ts | 240 +- core/ecschema-metadata/src/Metadata/Schema.ts | 49 +- .../src/Metadata/SchemaItem.ts | 10 +- core/ecschema-metadata/src/SchemaKey.ts | 6 +- .../src/utils/FormatEnums.ts | 122 +- .../test/Deserialization/JsonParser.test.ts | 710 +++ .../test/Metadata/Class.test.ts | 55 +- .../test/Metadata/Constant.test.ts | 16 +- .../Metadata/CustomAttributeClass.test.ts | 33 +- .../test/Metadata/Deserialization.test.ts | 22 +- .../test/Metadata/EntityClass.test.ts | 50 +- .../test/Metadata/Enumeration.test.ts | 138 +- .../test/Metadata/Format.test.ts | 2874 +++------ .../test/Metadata/InvertedUnit.test.ts | 104 +- .../test/Metadata/KindOfQuantity.test.ts | 47 +- .../test/Metadata/Mixin.test.ts | 32 +- .../test/Metadata/Phenomenon.test.ts | 113 +- .../test/Metadata/Property.test.ts | 247 +- .../test/Metadata/PropertyCategory.test.ts | 20 +- .../test/Metadata/Relationship.test.ts | 60 +- .../Metadata/RelationshipConstraint.test.ts | 81 +- .../test/Metadata/Schema.test.ts | 876 ++- .../test/Metadata/SchemaItem.test.ts | 69 +- .../test/Metadata/Unit.test.ts | 36 +- .../test/Metadata/UnitSystem.test.ts | 97 +- .../ecschema-metadata/test/ParseUtils.test.ts | 2 +- core/ecschema-metadata/tslint.json | 3 + core/frontend/CHANGELOG.json | 159 + core/frontend/CHANGELOG.md | 79 +- core/frontend/package.json | 30 +- core/frontend/src/AccuDraw.ts | 5 +- core/frontend/src/AccuSnap.ts | 92 +- core/frontend/src/ContextRealityModelState.ts | 71 + core/frontend/src/DisplayStyleState.ts | 318 +- core/frontend/src/ElementLocateManager.ts | 105 +- core/frontend/src/EntityState.ts | 2 +- core/frontend/src/HitDetail.ts | 15 +- core/frontend/src/IModelApp.ts | 2 +- core/frontend/src/IModelConnection.ts | 10 +- core/frontend/src/Marker.ts | 4 +- core/frontend/src/ModelSelectorState.ts | 2 +- core/frontend/src/ModelState.ts | 15 +- core/frontend/src/NoRenderApp.ts | 2 +- core/frontend/src/NotificationManager.ts | 2 +- core/frontend/src/QuantityFormatter.ts | 4 +- core/frontend/src/SelectionSet.ts | 26 +- core/frontend/src/Sheet.ts | 2 +- core/frontend/src/Sprites.ts | 4 +- core/frontend/src/TentativePoint.ts | 2 +- core/frontend/src/ViewManager.ts | 3 +- core/frontend/src/ViewState.ts | 3479 +++++----- core/frontend/src/Viewport.ts | 68 +- core/frontend/src/frontend.ts | 1 + .../src/public/locales/en/iModelJs.json | 19 +- core/frontend/src/render/FeatureSymbology.ts | 7 + core/frontend/src/render/System.ts | 35 +- .../src/render/primitives/VertexTable.ts | 20 + core/frontend/src/render/webgl/BranchState.ts | 124 +- .../src/render/webgl/CachedGeometry.ts | 9 +- core/frontend/src/render/webgl/DrawCommand.ts | 43 +- core/frontend/src/render/webgl/Graphic.ts | 250 +- core/frontend/src/render/webgl/Handle.ts | 20 +- core/frontend/src/render/webgl/RenderFlags.ts | 15 +- .../src/render/webgl/SceneCompositor.ts | 378 +- .../src/render/webgl/ShaderBuilder.ts | 11 +- .../src/render/webgl/ShaderProgram.ts | 9 +- core/frontend/src/render/webgl/System.ts | 26 + core/frontend/src/render/webgl/Target.ts | 66 +- core/frontend/src/render/webgl/Technique.ts | 44 +- .../src/render/webgl/TechniqueFlags.ts | 2 +- .../render/webgl/glsl/ClearPickAndColor.ts | 1 - .../src/render/webgl/glsl/Clipping.ts | 8 +- core/frontend/src/render/webgl/glsl/Color.ts | 19 +- core/frontend/src/render/webgl/glsl/Common.ts | 22 +- .../src/render/webgl/glsl/Composite.ts | 18 +- .../src/render/webgl/glsl/CopyPickBuffers.ts | 19 +- core/frontend/src/render/webgl/glsl/Edge.ts | 19 +- .../src/render/webgl/glsl/FeatureSymbology.ts | 442 +- .../src/render/webgl/glsl/Fragment.ts | 39 +- .../src/render/webgl/glsl/Lighting.ts | 367 +- .../src/render/webgl/glsl/Monochrome.ts | 29 +- .../src/render/webgl/glsl/PointCloud.ts | 4 +- .../src/render/webgl/glsl/PointString.ts | 4 +- .../frontend/src/render/webgl/glsl/Surface.ts | 115 +- .../src/render/webgl/glsl/Translucency.ts | 9 +- core/frontend/src/render/webgl/glsl/Vertex.ts | 32 +- .../frontend/src/tile/RealityModelTileTree.ts | 16 +- core/frontend/src/tile/TileIO.ts | 2 +- core/frontend/src/tile/TileTree.ts | 13 +- core/frontend/src/tile/WebMercatorTileTree.ts | 59 +- core/frontend/src/tools/AccuDrawTool.ts | 4 +- core/frontend/src/tools/EditManipulator.ts | 8 +- core/frontend/src/tools/EventController.ts | 2 +- core/frontend/src/tools/IdleTool.ts | 4 +- core/frontend/src/tools/PrimitiveTool.ts | 20 +- core/frontend/src/tools/SelectTool.ts | 136 +- core/frontend/src/tools/Tool.ts | 14 +- core/frontend/src/tools/ToolAdmin.ts | 14 +- core/frontend/src/tools/ViewTool.ts | 16 +- core/frontend/tslint.json | 5 +- core/geometry/CHANGELOG.json | 96 + core/geometry/CHANGELOG.md | 58 +- .../ConstructingRotationMatricesFromAngles.md | 6 +- core/geometry/package.json | 6 +- core/geometry/src/Geometry.ts | 17 + core/geometry/src/bspline/BSpline1dNd.ts | 18 + core/geometry/src/bspline/BSplineCurve.ts | 37 +- core/geometry/src/bspline/BSplineCurve3dH.ts | 56 +- core/geometry/src/bspline/BSplineSurface.ts | 149 +- core/geometry/src/bspline/Bezier1dNd.ts | 57 + core/geometry/src/bspline/BezierCurve3d.ts | 6 + core/geometry/src/bspline/BezierCurveBase.ts | 38 +- core/geometry/src/bspline/KnotVector.ts | 66 +- core/geometry/src/clipping/ClipPlane.ts | 2 +- core/geometry/src/clipping/ClipUtils.ts | 3 +- .../src/clipping/ConvexClipPlaneSet.ts | 2 +- .../clipping/UnionOfConvexClipPlaneSets.ts | 2 +- core/geometry/src/curve/Arc3d.ts | 91 +- ...ndex.ts => CurveChainWithDistanceIndex.ts} | 825 +-- core/geometry/src/curve/CurveCollection.ts | 2 +- .../src/curve/CurveCurveIntersectXY.ts | 2 +- .../geometry/src/curve/CurveLocationDetail.ts | 148 +- core/geometry/src/curve/CurvePrimitive.ts | 178 +- core/geometry/src/curve/LineSegment3d.ts | 21 +- core/geometry/src/curve/LineString3d.ts | 178 +- core/geometry/src/curve/TransitionSpiral.ts | 5 +- core/geometry/src/geometry-core.ts | 5 +- core/geometry/src/geometry3d/AngleSweep.ts | 2 +- core/geometry/src/geometry3d/FrameBuilder.ts | 4 +- .../src/geometry3d/GrowableBlockedArray.ts | 152 + .../src/geometry3d/GrowableFloat64Array.ts | 243 + .../{GrowableArray.ts => GrowableXYZArray.ts} | 1377 ++-- core/geometry/src/geometry3d/Matrix3d.ts | 47 +- .../geometry3d/Plane3dByOriginAndVectors.ts | 30 + .../src/geometry3d/Point3dVector3d.ts | 46 +- core/geometry/src/geometry3d/PointHelpers.ts | 36 +- core/geometry/src/geometry3d/Range.ts | 2 +- core/geometry/src/geometry3d/Segment1d.ts | 7 + core/geometry/src/geometry3d/Transform.ts | 10 +- core/geometry/src/geometry4d/Point4d.ts | 55 + .../src/numerics/BezierPolynomials.ts | 4 +- .../geometry/src/numerics/ClusterableArray.ts | 3 +- core/geometry/src/numerics/Polynomials.ts | 21 +- core/geometry/src/numerics/Quadrature.ts | 9 + core/geometry/src/numerics/Range1dArray.ts | 40 +- core/geometry/src/polyface/Polyface.ts | 391 +- core/geometry/src/polyface/PolyfaceBuilder.ts | 2 +- core/geometry/src/polyface/PolyfaceData.ts | 369 ++ .../src/serialization/GeometrySamples.ts | 84 +- .../src/serialization/IModelJsonSchema.ts | 58 +- core/geometry/src/test/AnalyticRoots.test.ts | 62 +- core/geometry/src/test/Angle.test.ts | 14 +- core/geometry/src/test/Arc3d.test.ts | 100 +- core/geometry/src/test/BSplineSurface.test.ts | 52 +- core/geometry/src/test/BezierCurve.test.ts | 83 + core/geometry/src/test/BsplineCurve.test.ts | 227 +- core/geometry/src/test/Checker.ts | 33 +- .../geometry/src/test/ConvexPolygon2d.test.ts | 2 +- core/geometry/src/test/Curve.test.ts | 180 +- .../src/test/CurveLocationDetail.test.ts | 8 +- core/geometry/src/test/GeometryCoreTestIO.ts | 23 + core/geometry/src/test/GrowableArray.test.ts | 225 +- core/geometry/src/test/LineSegment3d.test.ts | 48 +- core/geometry/src/test/LineString3d.test.ts | 122 + .../test/Plane3dByOriginAndVectors.test.ts | 31 +- core/geometry/src/test/PointHelper.test.ts | 280 +- core/geometry/src/test/Polyface.test.ts | 28 + .../geometry/src/test/PolyfaceAuxData.test.ts | 59 + core/geometry/src/test/PolyfaceData.test.ts | 56 + core/geometry/src/test/Polynomial.test.ts | 130 +- core/geometry/src/test/Range1dArray.test.ts | 130 +- core/geometry/src/test/Ray3d.test.ts | 5 + core/geometry/src/test/RotMatrix.test.ts | 159 +- core/geometry/src/test/Transform.test.ts | 61 + .../src/test/TransitionSpiral3d.test.ts | 14 +- core/geometry/src/test/performance.test.ts | 3 +- core/geometry/src/topology/Merging.ts | 2 +- core/geometry/src/topology/Triangulation.ts | 2 +- core/i18n/CHANGELOG.json | 60 + core/i18n/CHANGELOG.md | 46 +- core/i18n/package.json | 10 +- core/i18n/src/Localization.ts | 8 +- core/quantity/CHANGELOG.json | 54 + core/quantity/CHANGELOG.md | 44 +- core/quantity/package.json | 14 +- core/quantity/src/Parser.ts | 2 +- .../bis/ec/differences-between-ec2-and-ec3.md | 37 + docs/core/bis/ec/index.md | 4 +- docs/core/bis/intro/bis-schema-validation.md | 5 +- docs/core/bis/intro/information-hierarchy.md | 116 +- docs/core/bis/intro/modeling-perspectives.md | 175 + docs/core/bis/intro/top-of-the-world.md | 87 + docs/core/bis/leftNav.md | 3 +- .../0.163.0.md} | 54 +- docs/core/changehistory/0.164.0.md | 18 + docs/core/changehistory/0.165.0.md | 8 + docs/core/changehistory/0.166.0.md | 8 + docs/core/changehistory/0.167.0.md | 8 + docs/core/changehistory/0.168.0.md | 8 + docs/core/changehistory/0.169.0.md | 9 + docs/core/changehistory/0.170.0.md | 66 + docs/core/changehistory/0.171.0.md | 8 + docs/core/changehistory/NextVersion.md | 5 + docs/core/changehistory/index.md | 3 + docs/core/changehistory/leftNav.md | 21 + docs/core/config/docSites.json | 27 +- .../core/config/layouts/agentApplication.html | 4 +- .../config/layouts/browserApplication.html | 4 +- .../partials/landing_middle_content.html | 13 +- .../layouts/partials/landing_page_header.html | 4 + docs/core/config/layouts/sampleProject.html | 4 +- .../config/layouts/styles/landingPage.css | 26 +- .../config/layouts/updateApplication.html | 4 +- docs/core/config/layouts/updateProject.html | 4 +- docs/core/learning/ECSQL.md | 5 +- .../learning/ECSQLTutorial/FirstExamples.md | 2 +- .../core/learning/ECSQLTutorial/KeyToECSQL.md | 2 +- docs/core/learning/ECSQLTutorial/index.md | 4 +- docs/core/learning/WriteABridge.md | 5 +- docs/core/learning/WriteAnInteractiveApp.md | 2 +- .../learning/WriteAnInteractiveDesktopApp.md | 2 +- docs/core/learning/backend/CreateElements.md | 46 +- docs/core/learning/backend/CreateModels.md | 14 +- docs/core/learning/common/GeometryStream.md | 90 + docs/core/learning/common/Logging.md | 2 +- docs/core/learning/common/fill_types.png | Bin 0 -> 23643 bytes docs/core/learning/common/geom_types.png | Bin 0 -> 136716 bytes docs/core/learning/common/index.md | 3 + docs/core/learning/common/materials.PNG | Bin 0 -> 473920 bytes docs/core/learning/common/part_refs.png | Bin 0 -> 120179 bytes docs/core/learning/common/pattern_types.png | Bin 0 -> 14024 bytes docs/core/learning/common/placement_bad.png | Bin 0 -> 100806 bytes docs/core/learning/common/placement_good.png | Bin 0 -> 108301 bytes docs/core/learning/common/stroked_ls.png | Bin 0 -> 11302 bytes docs/core/learning/frontend/PrimitiveTools.md | 75 +- docs/core/learning/frontend/accudraw.png | Bin 0 -> 219119 bytes docs/core/learning/geometry/CurvePrimitive.md | 194 +- .../core/learning/geometry/PolyfaceAuxData.md | 115 + .../geometry/figs/BCurves/BsplineExamples.dgn | Bin 0 -> 54784 bytes .../geometry/figs/BCurves/BsplineGrid.imjs | 576 ++ .../learning/geometry/figs/BCurves/order2.png | Bin 0 -> 10035 bytes .../learning/geometry/figs/BCurves/order3.png | Bin 0 -> 10238 bytes .../learning/geometry/figs/BCurves/order4.png | Bin 0 -> 9871 bytes .../learning/geometry/figs/BCurves/order5.png | Bin 0 -> 10038 bytes .../figs/PolyfaceAuxData/Cantilever.gif | Bin 0 -> 1135350 bytes .../figs/PolyfaceAuxData/RadialWaveHeight.gif | Bin 0 -> 517755 bytes .../figs/PolyfaceAuxData/RadialWaveSlope.gif | Bin 0 -> 902020 bytes docs/core/learning/geometry/index.md | 1 + docs/core/learning/iModelHub/index.md | 2 +- docs/presentation/config/docSites.json | 20 +- docs/ui/config/docSites.json | 17 +- .../getting-started/assets/imodeljs-icon.svg | 1 + .../getting-started/assets/imodeljs-logo.svg | 1 + .../ui/getting-started/assets/imodeljs_16.png | Bin 0 -> 313 bytes docs/ui/getting-started/imodeljs.ico | Bin 99750 -> 0 bytes docs/ui/reference/leftNav.md | 3 + example-code/app/package.json | 20 +- .../app/src/backend/RobotWorldEngine.ts | 2 +- .../app/src/backend/test/Bridge.test.ts | 45 +- .../app/src/backend/test/BridgeUtils.ts | 128 +- .../app/src/backend/test/RobotWorld.test.ts | 5 +- .../src/backend/test/RobotWorldRpc.test.ts | 7 +- example-code/app/src/backend/test/Utils.ts | 33 +- .../app/src/common/RobotWorldRpcInterface.ts | 12 +- example-code/snippets/package.json | 20 +- .../snippets/src/backend/ExampleCode.test.ts | 38 +- .../snippets/src/backend/ExampleOpenIModel.ts | 2 +- example-code/snippets/src/clients/Events.ts | 10 +- .../snippets/src/clients/GlobalEvents.ts | 12 +- example-code/snippets/src/clients/Versions.ts | 12 +- example-code/snippets/src/clients/iModels.ts | 2 +- .../snippets/src/frontend/ExecutingECSQL.ts | 7 +- .../snippets/src/frontend/InteractiveTools.ts | 115 +- .../snippets/src/frontend/ViewDecorations.ts | 6 +- package.json | 1 + presentation/backend/CHANGELOG.json | 54 + presentation/backend/CHANGELOG.md | 44 +- presentation/backend/package.json | 36 +- .../{tests => src/test}/IModelHostSetup.ts | 0 .../test}/NativePlatform.test.ts | 4 +- .../{tests => src/test}/Presentation.test.ts | 18 +- .../test/PresentationManager.test.snap} | 0 .../test}/PresentationManager.test.ts | 18 +- .../test}/PresentationRpcImpl.test.ts | 14 +- .../test}/RulesetManager.test.ts | 17 +- .../test}/RulesetVariablesManager.test.ts | 8 +- .../test}/TemporaryStorage.test.ts | 19 +- .../tests => backend/src/test}/tsconfig.json | 7 +- presentation/backend/src/tsconfig.json | 9 +- presentation/common/CHANGELOG.json | 63 + presentation/common/CHANGELOG.md | 47 +- presentation/common/package.json | 28 +- presentation/common/src/KeySet.ts | 129 +- .../common/{tests => src/test}/Error.test.ts | 2 +- .../test}/IRulesetManager.test.ts | 4 +- .../test/KeySet.test.snap} | 0 .../common/{tests => src/test}/KeySet.test.ts | 380 +- .../test}/PresentationRpcInterface.test.ts | 4 +- .../test}/RpcRequestsHandler.test.ts | 6 +- .../{tests => src/test}/_helpers/Mocks.ts | 10 +- .../{tests => src/test}/_helpers/Promises.ts | 0 .../{tests => src/test}/_helpers/RpcHelper.ts | 0 .../test}/_helpers/TestRpcManager.ts | 0 .../test}/_helpers/random/Content.ts | 4 +- .../{tests => src/test}/_helpers/random/EC.ts | 2 +- .../test}/_helpers/random/Hierarchy.ts | 8 +- .../test}/_helpers/random/IModelJs.ts | 0 .../test}/_helpers/random/Misc.ts | 0 .../test}/_helpers/random/Ruleset.ts | 5 +- .../test}/_helpers/random/index.ts | 0 .../test/content/Content.test.snap} | 0 .../test}/content/Content.test.ts | 4 +- .../test/content/Descriptor.test.snap} | 0 .../test}/content/Descriptor.test.ts | 4 +- .../test/content/Fields.test.snap} | 0 .../test}/content/Fields.test.ts | 4 +- .../test/content/Item.test.snap} | 0 .../{tests => src/test}/content/Item.test.ts | 4 +- .../{tests => src/test}/content/Value.test.ts | 4 +- .../test/hierarchy/Key.test.snap} | 0 .../{tests => src/test}/hierarchy/Key.test.ts | 2 +- .../test/hierarchy/Node.test.snap} | 0 .../test}/hierarchy/Node.test.ts | 4 +- .../test/hierarchy/NodePathElement.test.snap} | 0 .../test}/hierarchy/NodePathElement.test.ts | 6 +- .../tests => common/src/test}/tsconfig.json | 8 +- presentation/common/src/tsconfig.json | 3 + presentation/components/CHANGELOG.json | 87 + presentation/components/CHANGELOG.md | 55 +- presentation/components/package.json | 52 +- .../components/src/common/ContentBuilder.ts | 2 + .../src/common/ContentDataProvider.ts | 8 +- .../src/propertygrid/DataProvider.ts | 2 +- .../components/src/table/DataProvider.ts | 2 +- .../src/table/WithUnifiedSelection.tsx | 2 +- .../test}/_helpers/UiComponents.ts | 6 +- .../test/common/ContentBuilder.test.snap} | 3 + .../test}/common/ContentBuilder.test.ts | 4 +- .../test}/common/ContentDataProvider.test.ts | 42 +- .../test}/common/PageContainer.test.ts | 2 +- .../test}/common/StyleHelper.test.ts | 4 +- .../{tests => src/test}/common/Utils.test.ts | 2 +- .../{tests => src/test}/index.test.ts | 0 .../test/propertygrid/DataProvider.test.snap} | 6 + .../test}/propertygrid/DataProvider.test.ts | 21 +- .../WithUnifiedSelection.test.snap} | 0 .../WithUnifiedSelection.test.tsx | 14 +- .../test/table/DataProvider.test.snap} | 0 .../test}/table/DataProvider.test.ts | 44 +- .../table/WithUnifiedSelection.test.snap} | 0 .../test/table/WithUnifiedSelection.test.tsx | 523 ++ .../test/tree/DataProvider.test.snap} | 154 +- .../src/test/tree/DataProvider.test.ts | 258 + .../test/tree/FilteredDataProvider.test.snap} | 18 +- .../test/tree/FilteredDataProvider.test.ts | 204 + .../test/tree/Utils.test.snap} | 0 .../{tests => src/test}/tree/Utils.test.ts | 4 +- .../test/tree/WithFilteringSupport.test.snap} | 95 +- .../test/tree/WithFilteringSupport.test.tsx | 340 + .../test/tree/WithUnifiedSelection.test.snap | 22 + .../test/tree/WithUnifiedSelection.test.tsx | 461 ++ .../{tests => src/test}/tsconfig.json | 6 +- .../viewport/WithUnifiedSelection.test.snap} | 0 .../viewport/WithUnifiedSelection.test.tsx | 226 +- .../components/src/tree/DataProvider.ts | 60 +- .../src/tree/FilteredDataProvider.ts | 48 +- .../src/tree/IPresentationTreeDataProvider.ts | 4 +- presentation/components/src/tree/Utils.ts | 10 +- .../src/tree/WithFilteringSupport.scss | 1 + .../src/tree/WithFilteringSupport.tsx | 54 +- .../src/tree/WithUnifiedSelection.tsx | 26 +- presentation/components/src/tsconfig.json | 10 +- .../src/viewport/WithUnifiedSelection.tsx | 53 +- .../tests/table/WithUnifiedSelection.test.tsx | 522 -- .../tests/tree/DataProvider.test.ts | 258 - .../tests/tree/FilteredDataProvider.test.ts | 224 - .../tests/tree/WithFilteringSupport.test.tsx | 328 - .../tests/tree/WithUnifiedSelection.test.tsx | 451 -- .../tree/WithUnifiedSelection.test.tsx.snap | 10 - presentation/frontend/CHANGELOG.json | 60 + presentation/frontend/CHANGELOG.md | 46 +- presentation/frontend/package.json | 40 +- .../src/selection/SelectionHandler.ts | 4 +- .../src/selection/SelectionManager.ts | 12 +- .../test}/PersistenceHelper.test.ts | 4 +- .../{tests => src/test}/Presentation.test.ts | 27 +- .../test}/PresentationManager.test.ts | 10 +- .../test}/RulesetManager.test.ts | 47 +- .../test}/RulesetVariablesManager.test.ts | 31 +- .../test}/_helpers/MockFrontendEnvironment.ts | 0 .../test}/selection/SelectionHandler.test.ts | 2 +- .../test}/selection/SelectionManager.test.ts | 83 +- .../tests => frontend/src/test}/tsconfig.json | 6 +- presentation/frontend/src/tsconfig.json | 7 +- presentation/mocha.opts | 2 +- presentation/scripts/setup-tests.js | 9 +- rush.json | 32 +- test-apps/agent-test-app/package.json | 19 +- test-apps/agent-test-app/src/Main.ts | 2 +- .../src/OpenIdConnectTokenStore.ts | 18 +- test-apps/agent-test-app/src/QueryAgent.ts | 10 +- .../agent-test-app/src/QueryAgentWebServer.ts | 44 +- .../ChangesetGenerationHarness.ts | 30 +- .../changeSetUtility/ChangesetGenerator.ts | 2 +- .../src/changeSetUtility/HubUtility.ts | 8 +- .../IModelChangesetCLUtililty.ts | 8 +- .../src/changeSetUtility/IModelDbHandler.ts | 113 +- test-apps/analysis-importer/.gitattributes | 2 + test-apps/analysis-importer/.gitignore | 16 + .../analysis-importer/assets/Cantilever.json | 1 + test-apps/analysis-importer/package.json | 44 + .../analysis-importer/src/AnalysisImporter.ts | 226 + test-apps/analysis-importer/src/Main.ts | 18 + test-apps/analysis-importer/tsconfig.json | 16 + .../tslint.json | 0 .../mobile.backend.webpack.config.js | 102 +- .../mobile.frontend.webpack.config.js | 39 + .../package.json | 23 +- .../public/configuration.json | 0 .../public/index.html | 29 + .../src/backend/CsvWriter.ts | 154 + .../src/backend/DefaultConfig.json | 926 +++ .../src/backend/DisplayPerfRpcImpl.ts | 60 + .../src/backend/ElectronMain.ts | 4 +- .../src/backend/MobileMain.ts | 19 + .../src/backend/WebMain.ts | 106 + .../src/backend/backend.ts | 49 + .../src/common/DisplayPerfRpcInterface.ts | 23 + .../src/common/SVTConfiguration.ts | 42 +- .../src/frontend/ConnectEnv.ts | 2 +- .../src/frontend/CustomCloudEnv.ts | 29 + .../src/frontend/DefaultConfig.json | 926 +++ .../src/frontend/DisplayPerformanceTestApp.ts | 570 ++ .../src/frontend/IModelApi.ts | 46 + .../src/frontend/ProjectApi.ts | 53 + .../src/frontend/README.md | 186 + .../src/frontend/SimpleViewState.ts | 0 .../src/frontend/Utils.ts | 0 .../tsconfig.json | 0 .../display-performance-test-app/tslint.json | 3 + .../webpack.config.js | 48 + .../README.md | 22 +- .../Default.PresentationRuleSet.xml | 0 .../mobile.backend.webpack.config.js | 52 + .../mobile.frontend.webpack.config.js | 78 +- test-apps/display-test-app/package.json | 61 + .../Fonts/Bentley-Cesium-Web-Viewer-Icons.eot | Bin .../Fonts/Bentley-Cesium-Web-Viewer-Icons.svg | 0 .../Fonts/Bentley-Cesium-Web-Viewer-Icons.ttf | Bin .../Bentley-Cesium-Web-Viewer-Icons.woff | Bin .../public/Hazard_biological.svg | 0 .../public/Hazard_electric.svg | 0 .../public/Hazard_flammable.svg | 0 .../public/Hazard_toxic.svg | 0 .../public/Hazard_tripping.svg | 0 .../public/Icons/finish.png | Bin .../public/Icons/map-marker-icon.png | Bin .../public/Icons/position.png | Bin .../public/Icons/properties.png | Bin .../public/Icons/start.png | Bin .../public/Icons/view_front.png | Bin .../public/Icons/view_iso.png | Bin .../public/Icons/view_right.png | Bin .../public/Icons/view_top.png | Bin .../public/Warning_sign.svg | 0 .../public/configuration.json | 1 + .../public/connect-configuration.json | 0 .../public/favicon.ico | Bin .../public/fit-to-view.svg | 0 ...-233e1f55-561d-42a4-8e80-d6f91743863e.json | 0 .../public/index.css | 0 .../public/index.html | 12 +- .../public/map_pin.svg | 48 +- .../public/mobile-configuration.json | 0 .../public/rotate-left.svg | 0 .../public/toggle-camera.svg | 0 .../public/view-navigation.svg | 0 .../public/walk.svg | 0 .../public/window-area.svg | 0 .../public/zoom.svg | 0 .../src/backend/ElectronMain.ts | 86 + .../src/backend/MobileMain.ts | 34 +- .../src/backend/WebMain.ts | 24 +- .../src/backend/backend.ts | 0 .../src/common/SVTConfiguration.ts | 19 + .../src/frontend/ConnectEnv.ts | 42 + .../src/frontend/CustomCloudEnv.ts | 12 +- .../src/frontend/IModelApi.ts | 6 +- .../src/frontend/SimpleViewState.ts | 21 + .../src/frontend/SimpleViewTest.ts | 373 +- .../src/frontend/Tooltip.d.ts | 96 +- .../display-test-app/src/frontend/Utils.ts | 23 + test-apps/display-test-app/tsconfig.json | 15 + test-apps/display-test-app/tslint.json | 3 + .../webpack.config.js | 0 .../imodel-from-geojson/.vscode/launch.json | 25 + .../imodel-from-geojson/.vscode/settings.json | 13 + test-apps/imodel-from-geojson/LICENSE.md | 11 + test-apps/imodel-from-geojson/README.md | 12 + .../data/Neighborhoods_Philadelphia.geojson | 1 + test-apps/imodel-from-geojson/package.json | 34 + test-apps/imodel-from-geojson/src/GeoJson.ts | 30 + .../src/GeoJsonImporter.ts | 162 + test-apps/imodel-from-geojson/src/Main.ts | 30 + test-apps/imodel-from-geojson/tsconfig.json | 16 + test-apps/imodel-from-geojson/tslint.json | 3 + .../presentation-integration-tests/mocha.opts | 2 +- .../package.json | 44 +- .../src/IntegrationTests.ts | 4 +- .../src/backend/NativePlatform.test.ts | 3 + .../src/backend/RulesetsRoundtrip.test.ts | 6 +- .../src/frontend/RulesetVariables.test.ts | 2 +- .../RulesetVariables.test.ts | 4 +- .../Rulesets.test.ts | 4 +- .../providers/TableDataProvider.test.ts | 4 +- .../providers/TreeDataProvider.test.ts | 12 +- .../tsconfig.json | 14 - .../assets/sample_documents/.gitignore | 3 +- test-apps/presentation-test-app/package.json | 34 +- .../src/backend/electron/ElectronMain.ts | 2 +- .../presentation-test-app/src/backend/main.ts | 2 +- .../src/common/SampleRpcInterface.ts | 2 +- .../src/frontend/api/MyAppFrontend.ts | 2 +- .../src/frontend/components/app/App.css | 4 - .../src/frontend/components/app/App.tsx | 45 +- .../imodel-selector/IModelSelector.tsx | 4 +- .../properties-widget/PropertiesWidget.css | 1 + .../properties-widget/PropertiesWidget.tsx | 2 - .../components/tree-widget/TreeWidget.tsx | 62 +- .../src/frontend/index.tsx | 2 +- test-apps/testbed/backend/TestRpcImpl.ts | 2 +- test-apps/testbed/backend/index.ts | 39 +- test-apps/testbed/bin/testbed.js | 13 +- test-apps/testbed/bootstrap.js | 17 +- test-apps/testbed/common/TestRpcInterface.ts | 48 +- test-apps/testbed/common/TestbedConfig.ts | 2 +- test-apps/testbed/coverage.js | 2 +- test-apps/testbed/floss/electron/index.js | 10 +- test-apps/testbed/floss/electron/renderer.js | 11 +- test-apps/testbed/floss/index.js | 5 +- .../testbed/frontend/ChangeSummary.test.ts | 70 + test-apps/testbed/frontend/Disposable.test.ts | 2 +- .../testbed/frontend/FeatureOverrides.test.ts | 4 - .../testbed/frontend/FeatureSymbology.test.ts | 5 +- .../testbed/frontend/IModelConnection.test.ts | 99 +- test-apps/testbed/frontend/ModelState.test.ts | 4 +- .../testbed/frontend/RpcInterface.test.ts | 39 +- test-apps/testbed/frontend/Technique.test.ts | 6 +- test-apps/testbed/frontend/TestData.ts | 4 +- test-apps/testbed/frontend/Tool.test.ts | 54 +- .../testbed/frontend/ToolRegistry.test.ts | 6 +- test-apps/testbed/frontend/ViewState.test.ts | 4 +- .../frontend/performance/DefaultConfig.json | 264 +- .../testbed/frontend/performance/IModelApi.ts | 6 +- .../performance/PerformanceReportWriter.ts | 2 +- .../performance/PerformanceTests.test.ts | 81 +- .../performance/PerformanceWriterClient.ts | 6 +- .../testbed/frontend/performance/README.md | 4 +- .../testbed/frontend/performance/testbed.js | 1 + test-apps/testbed/package.json | 22 +- .../testbed/webpack_performance.config.js | 12 +- test-apps/ui-test-app/package.json | 52 +- .../public/locales/en/SampleApp.json | 47 +- .../src/backend/electron/ElectronMain.ts | 2 +- test-apps/ui-test-app/src/backend/main.ts | 17 +- test-apps/ui-test-app/src/common/rpcs.ts | 17 + .../src/frontend/appui/AppBackstage.tsx | 27 +- .../ui-test-app/src/frontend/appui/AppUi.tsx | 126 +- .../appui/contentviews/CubeContent.tsx | 4 +- .../appui/contentviews/IModelViewport.tsx | 8 +- .../contentviews/TableExampleContent.tsx | 36 +- .../appui/contentviews/TreeExampleContent.tsx | 52 +- .../frontend/appui/dialogs/TestMessageBox.tsx | 2 + .../appui/frontstages/Frontstage1.tsx | 37 +- .../appui/frontstages/Frontstage2.tsx | 78 +- .../appui/frontstages/Frontstage3.tsx | 87 +- .../appui/frontstages/Frontstage4.tsx | 230 +- .../appui/frontstages/ViewsFrontstage.tsx | 220 +- .../appui/tooluiproviders/MeasurePoints.tsx | 49 - .../appui/tooluiproviders/Tool1UiProvider.tsx | 2 +- .../appui/tooluiproviders/Tool2UiProvider.tsx | 2 +- .../appui/widgets/BreadcrumbDemoWidget.tsx | 69 +- .../appui/widgets/NavigationTreeWidget.tsx | 39 +- .../appui/widgets/PropertyGridDemoWidget.tsx | 110 +- .../appui/widgets/TableDemoWidget.tsx | 73 +- .../frontend/appui/widgets/TreeDemoWidget.tsx | 67 +- .../UnifiedSelectionPropertyGridWidget.tsx | 45 + .../widgets/UnifiedSelectionTableWidget.tsx | 49 + .../appui/widgets/demoTableDataProvider.ts | 228 - .../appui/widgets/demoTreeDataProvider.ts | 369 -- .../demoTableDataProvider.ts | 298 + .../demodataproviders/demoTreeDataProvider.ts | 366 ++ .../{ => draglayers}/ChildDragLayer.tsx | 2 +- .../widgets/{ => draglayers}/DragLayer.scss | 2 +- .../{ => draglayers}/ParentDragLayer.tsx | 6 +- .../widgets/{ => draglayers}/RowDragLayer.tsx | 0 test-apps/ui-test-app/src/frontend/index.tsx | 52 +- .../src/frontend/tools/MeasurePoints.ts | 4 +- .../ui-test-app/src/frontend/tools/Tool1.ts | 40 + .../ui-test-app/src/frontend/tools/Tool2.ts | 40 + .../src/frontend/tools/ToolSpecifications.tsx | 388 ++ tools/build/CHANGELOG.json | 84 + tools/build/CHANGELOG.md | 54 +- tools/build/package.json | 4 +- tools/build/scripts/docs.js | 7 +- tools/build/scripts/test-tsnode.js | 5 + tools/build/scripts/test.js | 5 + tools/build/tsconfig-base.json | 3 +- tools/build/tslint.json | 4 + tools/config-loader/CHANGELOG.json | 54 + tools/config-loader/CHANGELOG.md | 44 +- tools/config-loader/package.json | 7 +- tools/config-loader/src/index.ts | 5 + tools/dev-cors-proxy-server/.dockerignore | 4 - tools/dev-cors-proxy-server/.eslintignore | 1 - tools/dev-cors-proxy-server/.eslintrc | 28 - tools/dev-cors-proxy-server/.gitignore | 8 - tools/dev-cors-proxy-server/.travis.yml | 11 - tools/dev-cors-proxy-server/CHANGELOG.json | 17 - tools/dev-cors-proxy-server/CHANGELOG.md | 11 - tools/dev-cors-proxy-server/LICENSE.md | 22 - tools/dev-cors-proxy-server/Procfile | 1 - tools/dev-cors-proxy-server/README.md | 181 - tools/dev-cors-proxy-server/demo.html | 107 - .../lib/cors-anywhere.js | 414 -- tools/dev-cors-proxy-server/lib/help.txt | 30 - tools/dev-cors-proxy-server/lib/rate-limit.js | 74 - .../lib/regexp-top-level-domain.js | 8 - .../local_dev_server.crt | 21 - .../local_dev_server.key | 28 - tools/dev-cors-proxy-server/package.json | 56 - tools/dev-cors-proxy-server/server.js | 64 - tools/dev-cors-proxy-server/test/cert.pem | 12 - tools/dev-cors-proxy-server/test/child.js | 64 - .../test/customHelp.html | 7 - .../dev-cors-proxy-server/test/customHelp.txt | 1 - tools/dev-cors-proxy-server/test/dummy.txt | 1 - tools/dev-cors-proxy-server/test/key.pem | 15 - tools/dev-cors-proxy-server/test/setup.js | 152 - .../test/test-examples.js | 133 - .../dev-cors-proxy-server/test/test-memory.js | 111 - .../test/test-ratelimit.js | 231 - tools/dev-cors-proxy-server/test/test.js | 1011 --- tools/webpack/CHANGELOG.json | 57 + tools/webpack/CHANGELOG.md | 45 +- tools/webpack/README.md | 2 +- tools/webpack/config/env.js | 16 +- tools/webpack/package.json | 11 +- tools/webpack/scripts/start-frontend.js | 18 +- tools/webpack/scripts/utils/initialize.js | 6 +- ui/components/CHANGELOG.json | 150 + ui/components/CHANGELOG.md | 76 +- ui/components/package.json | 58 +- .../public/locales/en/UiComponents.json | 13 +- ui/components/src/UiComponents.ts | 4 +- ui/components/src/breadcrumb/Breadcrumb.scss | 275 +- ui/components/src/breadcrumb/Breadcrumb.tsx | 1122 ++-- .../src/breadcrumb/BreadcrumbPath.ts | 16 +- .../src/breadcrumb/BreadcrumbTreeUtils.ts | 176 +- .../breadcrumbdetails/BreadcrumbDetails.tsx | 258 + .../breadcrumbdetails/hoc/withDragDrop.tsx | 142 + .../DragDropBreadcrumbNode.tsx} | 15 +- .../src/breadcrumb/hoc/withDragDrop.tsx | 118 + ui/components/src/breadcrumb/index.ts | 1 + ui/components/src/common/index.ts | 1 + .../src/common/selection/SelectionHandler.ts | 4 +- .../src/common/showhide/ShowHideDialog.tsx | 105 + .../common/showhide/ShowHideItem.tsx} | 16 +- .../src/common/showhide/ShowHideMenu.tsx | 152 + .../src/common/showhide/index.tsx} | 12 +- .../converters/valuetypes/ConvertedTypes.ts | 2 + .../converters/valuetypes/PrimitiveTypes.ts | 2 + ui/components/src/dragdrop/DragDropDef.ts | 48 +- ui/components/src/dragdrop/withDragSource.tsx | 262 +- ui/components/src/dragdrop/withDropTarget.tsx | 210 +- .../src/editors/EditorContainer.scss | 7 + ui/components/src/editors/EditorContainer.tsx | 40 +- .../src/editors/PropertyEditorManager.tsx | 16 +- .../src/editors/TextEditor.scss} | 13 +- ui/components/src/editors/TextEditor.tsx | 52 +- .../src/filtering/FilteringInput.scss | 4 + .../src/filtering/FilteringInput.tsx | 14 +- ui/components/src/index.ts | 4 + ui/components/src/properties/Value.ts | 1 + .../src/properties/ValueRendererManager.tsx | 46 +- ui/components/src/properties/index.ts | 5 +- .../NonPrimitivePropertyRenderer.scss} | 16 +- .../NonPrimitivePropertyRenderer.tsx | 133 + .../renderers/PrimitivePropertyRenderer.tsx | 37 + .../properties/renderers/PropertyRenderer.tsx | 169 + .../renderers/PropertyView.scss} | 23 +- .../src/properties/renderers/PropertyView.tsx | 108 + .../src/properties/renderers/index.ts | 10 + .../NonPrimitivePropertyLabelRenderer.tsx | 44 + .../label/PrimitivePropertyLabelRenderer.tsx | 32 + .../label/PropertyLabelRenderer.scss | 71 + .../renderers/label/PropertyLabelRenderer.tsx | 46 + .../src/properties/renderers/label/index.ts | 6 + .../ArrayPropertyValueRenderer.tsx | 24 +- .../value/DoublePropertyValueRenderer.tsx | 28 + .../value/NavigationPropertyValueRenderer.tsx | 28 + .../PrimitivePropertyValueRenderer.tsx | 11 +- .../StructPropertyValueRenderer.tsx | 28 +- .../src/properties/renderers/value/index.ts | 12 + .../value/table/ArrayValueRenderer.tsx | 42 + .../table/NonPrimitiveValueRenderer.scss | 28 + .../value/table/NonPrimitiveValueRenderer.tsx | 104 + .../value/table/StructValueRenderer.tsx | 39 + .../SimplePropertyDataProvider.ts | 33 +- .../component/PropertyCategoryBlock.scss | 49 + .../component/PropertyCategoryBlock.tsx | 3 + .../propertygrid/component/PropertyGrid.scss | 21 +- .../propertygrid/component/PropertyGrid.tsx | 132 +- .../propertygrid/component/PropertyList.tsx | 26 +- .../component/PropertyRenderer.tsx | 148 - .../component/SelectablePropertyBlock.tsx | 28 +- .../src/table/SimpleTableDataProvider.ts | 4 +- .../src/table/component/ColumnDragLayer.tsx | 2 +- .../table/component/DragDropHeaderCell.tsx | 9 +- .../table/component/DragDropRowRenderer.tsx | 161 - ui/components/src/table/component/Grid.scss | 66 +- ui/components/src/table/component/Table.tsx | 453 +- .../Table.scss => hocs/DragDropRow.scss} | 9 +- ui/components/src/table/hocs/DragDropRow.tsx | 166 + ui/components/src/table/hocs/TableWrapper.tsx | 19 + ui/components/src/table/hocs/withDragDrop.tsx | 162 + ui/components/src/table/index.ts | 3 +- .../{tests => src/test}/TestUtils.ts | 11 +- ui/components/src/test/UiComponents.test.ts | 19 + .../src/test/breadcrumb/Breadcrumb.test.tsx | 287 + .../BreadcrumbDetails.test.tsx | 41 + .../test/breadcrumb/mockTreeDataProvider.ts | 254 + .../common/selection/SelectionHandler.test.ts | 4 +- .../common/showhide/ShowHideDialog.test.tsx | 76 + .../common/showhide/ShowHideMenu.test.tsx | 148 + .../converters/BooleanTypeConverter.test.ts | 2 +- .../converters/DateTimeTypeConverter.test.ts | 2 +- .../converters/EnumTypeConverter.test.ts | 2 +- .../HexadecimalTypeConverter.test.ts | 2 +- .../converters/NumericTypeConverter.test.ts | 2 +- .../converters/PointTypeConverter.test.ts | 20 +- .../converters/StringTypeConverter.test.ts | 2 +- .../test}/converters/TypeConverter.test.ts | 8 +- .../converters/TypeConverterManager.test.ts | 2 +- .../test/dragdrop/BeDragDropContext.test.snap | 112 + .../test/dragdrop/BeDragDropContext.test.tsx | 19 + .../test/dragdrop/withDragSource.test.snap} | 0 .../test}/dragdrop/withDragSource.test.tsx | 22 +- .../test/dragdrop/withDropTarget.test.snap} | 0 .../test}/dragdrop/withDropTarget.test.tsx | 8 +- .../test/editors/EditorContainer.test.snap | 31 + .../src/test/editors/EditorContainer.test.tsx | 85 + .../editors/PropertyEditorManager.test.tsx | 390 +- .../test/editors/TextEditor.test.snap} | 3 +- .../test}/editors/TextEditor.test.tsx | 105 +- .../test}/filtering/FilteringInput.test.tsx | 64 +- .../test/filtering/ResultSelector.test.snap} | 0 .../test}/filtering/ResultSelector.test.tsx | 2 +- .../properties/ValueRendererManager.test.tsx | 2 +- .../NonPrimitivePropertyRenderer.test.tsx | 119 + .../PrimitivePropertyRenderer.test.tsx | 33 + .../renderers/PropertyRenderer.test.tsx | 218 + .../renderers/PropertyView.test.tsx | 181 + .../label/PropertyLabelRenderer.test.tsx | 93 + .../ArrayPropertyValueRenderer.test.tsx | 32 +- .../DoublePropertyValueRenderer.test.tsx | 58 + .../NavigationPropertyValueRenderer.test.tsx | 60 + .../PrimitivePropertyValueRenderer.test.tsx | 6 +- .../StructPropertyValueRenderer.test.tsx | 44 +- .../value/table/ArrayValueRenderer.test.tsx | 44 + .../table/NonPrimitiveValueRenderer.test.tsx | 106 + .../value/table/StructValueRenderer.test.tsx | 28 + .../SimplePropertyDataProvider.test.ts | 96 + .../component/PropertyCategoryBlock.test.tsx | 4 +- .../component/PropertyGrid.test.tsx | 193 +- .../SelectablePropertyBlock.test.tsx | 17 +- .../table/SimpleTableDataProvider.test.ts | 930 +-- .../test/table/component/Grid.test.snap} | 0 .../test}/table/component/Grid.test.tsx | 120 +- .../test}/table/component/Table.test.tsx | 148 +- .../{tests => src/test}/test-helpers/misc.ts | 19 + .../src/test/tree/HighlightingEngine.test.tsx | 59 + .../test/tree/SimpleTreeDataProvider.test.ts | 151 + .../src/test/tree/TreeDataProvider.test.ts | 65 + .../tree/component/BeInspireTree.test.snap | 3030 +++++++++ .../test/tree/component/BeInspireTree.test.ts | 997 +++ .../src/test/tree/component/Tree.test.tsx | 1055 +++ .../test/viewport/ViewportComponent.test.tsx | 38 + ui/components/src/tree/HighlightingEngine.tsx | 99 +- .../src/tree/SimpleTreeDataProvider.ts | 29 +- ui/components/src/tree/TreeDataProvider.ts | 109 +- .../src/tree/component/BeInspireTree.ts | 881 ++- ui/components/src/tree/component/DataTree.tsx | 207 - ui/components/src/tree/component/Tree.scss | 45 +- ui/components/src/tree/component/Tree.tsx | 905 ++- .../src/tree/hocs/DragDropTreeNode.scss | 34 + .../DragDropTreeNode.tsx} | 20 +- ui/components/src/tree/hocs/withDragDrop.tsx | 185 + ui/components/src/tree/index.ts | 3 +- .../src/viewport/ViewRotationCube.ts | 31 +- .../src/viewport/ViewportComponent.tsx | 10 +- .../tests/breadcrumb/Breadcrumb.test.tsx | 72 - .../tests/breadcrumb/mockTreeDataProvider.ts | 83 - .../tests/editors/EditorContainer.test.tsx | 67 - .../editors/EditorContainer.test.tsx.snap | 11 - .../tests/propertygrid/PropertyTestHelpers.ts | 26 - .../SimplePropertyDataProvider.test.ts | 51 - .../component/PropertyRenderer.test.tsx | 127 - .../tests/tree/HighlightingEngine.test.tsx | 133 - .../tree/HighlightingEngine.test.tsx.snap | 7 - .../tests/tree/SimpleTreeDataProvider.test.ts | 154 - .../tests/tree/component/Tree.test.tsx | 379 -- ui/components/tests/tsconfig.json | 19 - ui/core/CHANGELOG.json | 132 + ui/core/CHANGELOG.md | 70 +- ui/core/package.json | 29 +- ui/core/src/UiCore.ts | 4 +- ui/core/src/base/Div.tsx | 1 + ui/core/src/base/WebFontIcon.tsx | 2 +- ui/core/src/checklistbox/CheckListBox.tsx | 16 +- ui/core/src/contextmenu/ContextMenu.scss | 64 +- ui/core/src/contextmenu/ContextMenu.tsx | 338 +- ui/core/src/dialog/Dialog.scss | 101 +- ui/core/src/dialog/Dialog.tsx | 341 +- .../elementseparator/ElementSeparator.scss | 32 +- .../src/elementseparator/ElementSeparator.tsx | 28 +- ui/core/src/expandable/ExpandableBlock.scss | 2 +- ui/core/src/expandable/ExpandableBlock.tsx | 4 +- ui/core/src/hocs/withIsPressed.tsx | 2 +- ui/core/src/hocs/withOnOutsideClick.tsx | 8 +- ui/core/src/hocs/withTimeout.tsx | 2 +- ui/core/src/messagebox/MessageBox.tsx | 14 +- ui/core/src/popup/Popup.tsx | 198 +- ui/core/src/radialmenu/RadialMenu.tsx | 4 +- ui/core/src/searchbox/SearchBox.tsx | 31 +- ui/core/src/splitbutton/SplitButton.tsx | 6 +- ui/core/{tests => src/test}/TestUtils.ts | 7 +- ui/core/src/test/UiCore.test.ts | 19 + ui/core/src/test/base/Div.test.snap | 3 + ui/core/src/test/base/Div.test.tsx | 19 + ui/core/src/test/base/UiEvent.test.ts | 36 + ui/core/src/test/base/WaitSpinner.test.snap | 18 + .../src/test/base/WaitSpinner.test.tsx} | 9 +- ui/core/src/test/base/WebFontIcon.test.snap | 7 + ui/core/src/test/base/WebFontIcon.test.tsx | 19 + .../test/checklistbox/CheckListBox.test.snap} | 0 .../test}/checklistbox/CheckListBox.test.tsx | 92 +- .../test/contextmenu/ContextMenu.test.snap | 356 ++ .../test}/contextmenu/ContextMenu.test.tsx | 5 +- .../test/cube/Cube.test.snap} | 0 .../{tests => src/test}/cube/Cube.test.tsx | 2 +- ui/core/src/test/dialog/Dialog.test.snap | 65 + ui/core/src/test/dialog/Dialog.test.tsx | 82 + .../ElementSeparator.test.tsx | 4 +- .../expandable/ExpandableBlock.test.snap} | 4 +- .../test}/expandable/ExpandableBlock.test.tsx | 92 +- .../test/expandable/ExpandableList.test.snap} | 6 + .../test/expandable/ExpandableList.test.tsx | 62 + ui/core/src/test/hocs/withIsPressed.test.snap | 14 + ui/core/src/test/hocs/withIsPressed.test.tsx | 60 + .../test/hocs/withOnOutsideClick.test.snap | 7 + .../src/test/hocs/withOnOutsideClick.test.tsx | 38 + ui/core/src/test/hocs/withTimeout.test.snap | 3 + ui/core/src/test/hocs/withTimeout.test.tsx | 25 + .../test/messagebox/MessageBox.test.snap} | 3 +- .../src/test/messagebox/MessageBox.test.tsx | 58 + ui/core/src/test/popup/Popup.test.snap | 31 + ui/core/src/test/popup/Popup.test.tsx | 181 + .../test/radialmenu/RadialMenu.test.snap} | 16 +- .../test}/radialmenu/RadialMenu.test.tsx | 8 +- .../test/searchbox/SearchBox.test.snap} | 5 +- ui/core/src/test/searchbox/SearchBox.test.tsx | 100 + .../test/splitbutton/SplitButton.test.snap} | 8 +- .../test}/splitbutton/SplitButton.test.tsx | 10 +- .../test/toggle/Toggle.test.snap} | 0 .../test}/toggle/Toggle.test.tsx | 88 +- .../test/tree/Branch.test.snap} | 0 .../{tests => src/test}/tree/Branch.test.tsx | 2 +- .../test/tree/ExpansionToggle.test.snap} | 0 .../test}/tree/ExpansionToggle.test.tsx | 2 +- .../test/tree/Node.test.snap} | 113 +- .../{tests => src/test}/tree/Node.test.tsx | 43 +- ui/core/src/test/tree/Placeholder.test.tsx | 33 + .../test/tree/Tree.test.snap} | 0 ui/core/src/test/tree/Tree.test.tsx | 140 + .../test}/uisettings/LocalUiSettings.test.ts | 2 +- .../{tests => src/test}/utils/Timer.test.ts | 2 +- ui/core/src/tree/Branch.tsx | 2 +- ui/core/src/tree/ExpansionToggle.tsx | 2 + ui/core/src/tree/Node.scss | 35 +- ui/core/src/tree/Node.tsx | 62 +- ui/core/src/tree/Placeholder.scss | 20 + ui/core/src/tree/Placeholder.tsx | 44 + ui/core/src/tree/Tree.scss | 2 - ui/core/src/tree/Tree.tsx | 80 +- ui/core/src/tree/index.ts | 3 +- ui/core/src/utils/getDisplayName.ts | 16 + ui/core/src/utils/index.ts | 3 + ui/core/src/utils/shallowDiffers.ts | 22 + .../src/utils/typeUtils.ts} | 11 +- .../contextmenu/ContextMenu.test.tsx.snap | 324 - ui/core/tests/dialog/Dialog.test.tsx | 38 - ui/core/tests/dialog/Dialog.test.tsx.snap | 61 - .../tests/expandable/ExpandableList.test.tsx | 25 - ui/core/tests/messagebox/MessageBox.test.tsx | 29 - ui/core/tests/popup/Popup.test.tsx | 24 - ui/core/tests/popup/Popup.test.tsx.snap | 15 - ui/core/tests/searchbox/SearchBox.test.tsx | 26 - ui/core/tests/tree/Tree.test.tsx | 77 - ui/core/tests/tsconfig.json | 18 - ui/framework/.npmignore | 1 + ui/framework/CHANGELOG.json | 117 + ui/framework/CHANGELOG.md | 65 +- ui/framework/package.json | 64 +- .../public/locales/en/UiFramework.json | 27 + ui/framework/rulesets/Categories.json | 53 + ui/framework/rulesets/Models.json | 29 + ui/framework/rulesets/ModelsCategories.json | 25 + ui/framework/src/CoreToolDefinitions.ts | 131 + ui/framework/src/FrameworkState.ts | 2 - ui/framework/src/SyncUiEventDispatcher.ts | 98 +- ui/framework/src/UiFramework.ts | 55 +- .../clientservices/DefaultIModelServices.ts | 19 +- .../clientservices/DefaultLoginServices.ts | 36 - .../clientservices/DefaultProjectServices.ts | 1 + .../src/clientservices/LoginServices.ts | 15 - .../clientservices/index.ts} | 10 +- .../src/configurableui/ActionItemButton.tsx | 94 + .../configurableui/AppNotificationManager.tsx | 2 +- ui/framework/src/configurableui/Backstage.tsx | 67 +- .../configurableui/ConfigurableUiControl.tsx | 2 +- .../configurableui/ConfigurableUiManager.tsx | 118 +- .../src/configurableui/ContentGroup.tsx | 1 + .../src/configurableui/ContentLayout.scss | 6 +- .../src/configurableui/ContentLayout.tsx | 183 +- .../src/configurableui/ContentViewManager.tsx | 11 +- .../configurableui/DragDropLayerManager.tsx | 7 +- .../src/configurableui/ElementTooltip.tsx | 6 +- .../configurableui/FrameworkFrontstage.tsx | 114 - .../src/configurableui/FrameworkZone.tsx | 4 +- .../src/configurableui/Frontstage.tsx | 11 +- .../src/configurableui/FrontstageComposer.tsx | 114 +- .../src/configurableui/FrontstageDef.tsx | 155 +- .../src/configurableui/FrontstageManager.tsx | 71 +- .../src/configurableui/FrontstageZone.tsx | 93 - ui/framework/src/configurableui/GroupItem.tsx | 70 +- .../src/configurableui/IconComponent.tsx | 29 + .../src/configurableui/IconLabelSupport.tsx | 84 - ui/framework/src/configurableui/Item.tsx | 183 +- .../src/configurableui/ItemDefBase.tsx | 80 +- .../src/configurableui/ItemFactory.tsx | 71 - ui/framework/src/configurableui/ItemMap.tsx | 29 + ui/framework/src/configurableui/ItemProps.tsx | 63 +- .../src/configurableui/MessageManager.tsx | 32 +- .../src/configurableui/ModalDialogManager.tsx | 6 +- .../src/configurableui/ModalFrontstage.tsx | 2 +- .../src/configurableui/NavigationWidget.tsx | 11 +- .../src/configurableui/SelectionScope.ts | 15 + .../src/configurableui/StackedWidget.tsx | 6 +- .../src/configurableui/StandardMessageBox.tsx | 2 +- ui/framework/src/configurableui/StatusBar.tsx | 2 +- ui/framework/src/configurableui/Task.tsx | 2 +- .../src/configurableui/ToolButton.tsx | 103 + .../src/configurableui/ToolInformation.tsx | 1 + .../src/configurableui/ToolSettingsZone.tsx | 48 +- .../src/configurableui/ToolUiProvider.tsx | 4 +- .../src/configurableui/ToolWidget.tsx | 29 +- .../src/configurableui/ToolbarWidgetBase.tsx | 38 +- .../configurableui/ViewportContentControl.tsx | 2 +- ui/framework/src/configurableui/Widget.tsx | 14 +- .../src/configurableui/WidgetControl.tsx | 6 - ui/framework/src/configurableui/WidgetDef.tsx | 75 +- .../src/configurableui/WidgetFactory.tsx | 2 +- ui/framework/src/configurableui/Workflow.tsx | 21 +- ui/framework/src/configurableui/Zone.tsx | 31 +- ui/framework/src/configurableui/ZoneDef.tsx | 62 +- .../src/configurableui/configurableui.scss | 4 - ui/framework/src/configurableui/index.ts | 9 +- .../navigationaids/CubeNavigationAid.tsx | 62 +- .../navigationaids/SheetNavigationAid.tsx | 6 +- .../navigationaids/SheetsModalFrontstage.scss | 101 +- .../navigationaids/SheetsModalFrontstage.tsx | 43 +- .../StandardRotationNavigationAid.tsx | 47 +- ui/framework/src/configurableui/state.ts | 2 - .../statusbarfields/ActivityCenter.tsx | 4 +- ui/framework/src/feedback/index.ts | 6 + ui/framework/src/index.ts | 32 +- ui/framework/src/messages/InputField.tsx | 8 +- ui/framework/src/messages/Pointer.tsx | 25 +- ui/framework/src/messages/index.ts | 7 + ui/framework/src/oidc/Callback.tsx | 64 - ui/framework/src/oidc/OidcBrowserClient.ts | 205 + ui/framework/src/oidc/OidcClientWrapper.ts | 36 + ui/framework/src/oidc/SignIn.tsx | 5 +- ui/framework/src/oidc/SignOut.tsx | 32 +- ui/framework/src/oidc/index.ts | 6 + ui/framework/src/openimodel/IModelCard.tsx | 2 +- ui/framework/src/openimodel/IModelOpen.tsx | 4 +- .../src/openimodel/IModelViewPicker.tsx | 2 +- ui/framework/src/openimodel/ProjectDialog.tsx | 4 +- .../src/openimodel/index.ts} | 27 +- .../src/overallcontent/OverallContent.tsx | 55 +- ui/framework/src/overallcontent/index.ts | 7 + ui/framework/src/pickers/ListPicker.tsx | 15 +- ui/framework/src/pickers/ModelSelector.scss | 77 +- ui/framework/src/pickers/ModelSelector.tsx | 416 +- ui/framework/src/pickers/ViewSelector.tsx | 37 +- ui/framework/src/pickers/index.ts | 8 + .../src/test/CoreToolDefinitions.test.snap | 269 + .../src/test/CoreToolDefinitions.test.tsx | 63 + ui/framework/{tests => src/test}/TestUtils.ts | 12 +- ui/framework/src/test/UiFramework.test.ts | 39 + .../AnalysisAnimationToolSettings.test.snap | 228 + .../AppNotificationManager.test.tsx | 4 +- .../test/configurableui/Backstage.test.snap} | 41 +- .../test}/configurableui/Backstage.test.tsx | 82 +- .../ConfigurableUiContent.test.snap} | 0 .../ConfigurableUiContent.test.tsx | 68 +- .../ConfigurableUiManager.test.tsx | 95 +- .../configurableui/ContentControl.test.tsx | 118 +- .../configurableui/ContentLayout.test.snap | 222 + .../configurableui/ContentLayout.test.tsx | 175 + .../ContentViewManager.test.tsx | 0 .../DragDropLayerManager.test.snap | 114 + .../DragDropLayerManager.test.tsx | 22 +- .../configurableui/ElementTooltip.test.tsx | 2 +- .../test/configurableui/Frontstage.test.snap} | 0 .../test/configurableui/Frontstage.test.tsx | 185 + .../FrontstageComposer.test.tsx | 4 +- .../configurableui/FrontstageDef.test.tsx | 0 .../configurableui/FrontstageManager.test.tsx | 0 .../test/configurableui/GroupItem.test.snap | 156 + .../test}/configurableui/GroupItem.test.tsx | 83 +- .../configurableui/IconLabelSupport.test.tsx | 2 +- .../src/test/configurableui/Item.test.snap | 48 + .../src/test/configurableui/Item.test.tsx | 115 + .../configurableui/MessageManager.test.tsx | 2 +- .../ModalDialogManager.test.tsx | 2 +- .../configurableui/ModalFrontstage.test.tsx | 2 +- .../NavigationAidControl.test.tsx | 0 .../NavigationWidget.test.snap} | 30 +- .../configurableui/NavigationWidget.test.tsx | 51 +- .../configurableui/StackedWidget.test.tsx | 113 + .../StandardMessageBox.test.snap} | 15 +- .../StandardMessageBox.test.tsx | 12 +- .../test}/configurableui/StatusBar.test.tsx | 2 +- .../StatusBarWidgetControl.test.tsx | 2 +- .../test}/configurableui/Task.test.tsx | 4 +- .../configurableui/ToolSettingsZone.test.tsx | 119 + .../configurableui/ToolUiProvider.test.tsx | 79 +- .../test/configurableui/ToolWidget.test.snap | 200 + .../test/configurableui/ToolWidget.test.tsx | 156 + .../ViewportContentControl.test.tsx | 214 +- .../test/configurableui/Widget.test.snap} | 0 .../test}/configurableui/Widget.test.tsx | 48 +- .../configurableui/WidgetControl.test.tsx | 20 +- .../test}/configurableui/WidgetDef.test.tsx | 6 +- .../configurableui/WidgetFactory.test.tsx | 9 +- .../test}/configurableui/Workflow.test.tsx | 22 +- .../test/configurableui/Zone.test.snap} | 0 .../test}/configurableui/Zone.test.tsx | 48 +- .../src/test/configurableui/ZoneDef.test.tsx | 72 + .../test/configurableui/ZoneTargets.test.snap | 18 + .../test/configurableui/ZoneTargets.test.tsx | 70 + .../CubeNavigationAid.test.snap} | 0 .../navigationaids/CubeNavigationAid.test.tsx | 2 +- .../SheetNavigationAid.test.snap} | 0 .../SheetNavigationAid.test.tsx | 3 +- .../SheetsModalFrontstage.test.tsx | 183 +- .../StandardRotationNavigationAid.test.snap} | 0 .../StandardRotationNavigationAid.test.tsx | 25 +- .../statusbarfields/ActivityCenter.test.tsx | 99 + .../statusbarfields/MessageCenter.test.tsx | 2 +- .../statusbarfields/PromptField.test.tsx | 14 +- .../statusbarfields/SnapMode.test.tsx | 2 +- .../feedback/ValidationTextbox.test.snap} | 0 .../test}/feedback/ValidationTextbox.test.tsx | 174 +- .../test/messages/InputField.test.snap} | 0 .../test}/messages/InputField.test.tsx | 70 +- .../test/messages/Pointer.test.snap} | 0 .../test}/messages/Pointer.test.tsx | 156 +- .../test/pickers/ListPicker.test.snap} | 2 +- .../test}/pickers/ListPicker.test.tsx | 398 +- .../src/test/pickers/ModelSelector.test.snap | 115 + .../src/test/pickers/ModelSelector.test.tsx | 25 + .../test/pickers/ViewSelector.test.snap} | 2 +- .../test}/pickers/ViewSelector.test.tsx | 89 +- .../AnalysisAnimationToolSettings.test.snap | 228 + .../AnalysisAnimationToolSettings.test.tsx | 91 + ui/framework/src/test/tools/Tool1.ts | 40 + .../test}/utils/SyncUiEventDispatcher.test.ts | 2 +- .../test}/utils/ViewUtilities.test.tsx | 70 +- ui/framework/src/tools/AnalysisAnimation.ts | 57 + .../tools/AnalysisAnimationToolSettings.scss | 30 + .../tools/AnalysisAnimationToolSettings.tsx | 215 + ui/framework/src/tools/index.ts | 7 + .../configurableui/ContentLayout.test.tsx | 92 - .../ContentLayout.test.tsx.snap | 66 - .../tests/configurableui/Frontstage.test.tsx | 148 - .../configurableui/GroupItem.test.tsx.snap | 179 - .../tests/configurableui/Item.test.tsx | 151 - .../tests/configurableui/Item.test.tsx.snap | 41 - .../configurableui/StackedWidget.test.tsx | 102 - .../configurableui/ToolSettingsZone.test.tsx | 133 - .../tests/configurableui/ToolWidget.test.tsx | 148 - .../configurableui/ToolWidget.test.tsx.snap | 114 - .../tests/configurableui/ZoneDef.test.tsx | 115 - ui/framework/tests/tsconfig.json | 18 - ui/framework/tsconfig.json | 2 +- ui/mocha.opts | 5 +- ui/ninezone/CHANGELOG.json | 84 + ui/ninezone/CHANGELOG.md | 54 +- ui/ninezone/demo/src/pages/Tools.tsx | 10 +- ui/ninezone/demo/src/pages/Zones.tsx | 9 +- ui/ninezone/package.json | 23 +- ui/ninezone/src/backstage/Item.tsx | 4 +- ui/ninezone/src/backstage/UserProfile.tsx | 2 +- ui/ninezone/src/index.ts | 4 +- .../test/appbutton/App.test.snap} | 0 .../test/appbutton}/App.test.tsx | 2 +- .../test/appbutton/Back.test.snap} | 0 .../test/appbutton}/Back.test.tsx | 2 +- .../test/backstage/Backstage.test.snap} | 0 .../test}/backstage/Backstage.test.tsx | 2 +- .../test/backstage/Item.test.snap} | 0 .../test}/backstage/Item.test.tsx | 2 +- .../test/backstage/Separator.test.snap} | 0 .../test}/backstage/Separator.test.tsx | 2 +- .../test/base/PointerCaptor.test.snap} | 0 .../test}/base/PointerCaptor.test.tsx | 2 +- .../test/base/SvgPath.test.snap} | 0 .../{tests => src/test}/base/SvgPath.test.tsx | 60 +- .../test/base/SvgSprite.test.snap} | 0 .../test}/base/SvgSprite.test.tsx | 2 +- .../test/context/MouseTracker.test.snap} | 0 .../test}/context/MouseTracker.test.tsx | 2 +- .../test/footer/Footer.test.snap} | 0 .../test}/footer/Footer.test.tsx | 2 +- .../footer/message-center/Content.test.snap} | 0 .../footer/message-center/Content.test.tsx | 2 +- .../message-center/Indicator.test.snap} | 0 .../footer/message-center/Indicator.test.tsx | 2 +- .../footer/message-center/Message.test.snap} | 0 .../footer/message-center/Message.test.tsx | 2 +- .../message-center/MessageCenter.test.snap} | 0 .../message-center/MessageCenter.test.tsx | 2 +- .../test/footer/message-center/Tab.test.snap} | 0 .../test}/footer/message-center/Tab.test.tsx | 2 +- .../test/footer/message/Activity.test.snap} | 0 .../test}/footer/message/Activity.test.tsx | 2 +- .../test/footer/message/Modal.test.snap} | 0 .../test}/footer/message/Modal.test.tsx | 2 +- .../test/footer/message/Sticky.test.snap} | 0 .../test}/footer/message/Sticky.test.tsx | 2 +- .../test/footer/message/Temporary.test.snap} | 0 .../test}/footer/message/Temporary.test.tsx | 2 +- .../test/footer/message/Toast.test.snap} | 0 .../test}/footer/message/Toast.test.tsx | 2 +- .../test}/footer/snap-mode/Dialog.tsx | 2 +- .../test}/footer/snap-mode/Icon.tsx | 2 +- .../test}/footer/snap-mode/Indicator.tsx | 2 +- .../test}/footer/snap-mode/Snap.tsx | 2 +- .../footer/tool-assistance/Content.test.snap} | 0 .../footer/tool-assistance/Content.test.tsx | 2 +- .../footer/tool-assistance/Dialog.test.snap} | 0 .../footer/tool-assistance/Dialog.test.tsx | 2 +- .../tool-assistance/Indicator.test.snap} | 0 .../footer/tool-assistance/Indicator.test.tsx | 2 +- .../footer/tool-assistance/Item.test.snap} | 0 .../footer/tool-assistance}/Item.test.tsx | 2 +- .../tool-assistance/Separator.test.snap} | 0 .../tool-assistance}/Separator.test.tsx | 2 +- .../test/popup/popover/Popover.test.snap} | 0 .../test}/popup/popover/Popover.test.tsx | 4 +- .../test/popup/popover/Triangle.test.snap} | 0 .../test}/popup/popover/Triangle.test.tsx | 4 +- .../test/popup/tooltip/Tooltip.test.snap} | 0 .../test}/popup/tooltip/Tooltip.test.tsx | 2 +- .../test/toolbar/Scrollable.test.snap} | 0 .../test}/toolbar/Scrollable.test.tsx | 8 +- .../test/toolbar/Toolbar.test.snap} | 0 .../test}/toolbar/Toolbar.test.tsx | 4 +- .../src/test/toolbar/button/App.test.snap | 21 + .../src/test/toolbar/button/Back.test.snap | 7 + .../test/toolbar/item/Icon.test.snap} | 5 +- .../test}/toolbar/item/Icon.test.tsx | 2 +- .../test/toolbar/item/Item.test.snap} | 10 +- .../test/toolbar/item/Overflow.test.snap} | 2 +- .../test}/toolbar/item/Overflow.test.tsx | 2 +- .../item/expandable/Expandable.test.snap} | 0 .../item/expandable/Expandable.test.tsx | 2 +- .../expandable/group/BackArrow.test.snap} | 0 .../item/expandable/group/BackArrow.test.tsx | 2 +- .../item/expandable/group/Column.test.snap} | 0 .../item/expandable/group/Column.test.tsx | 2 +- .../item/expandable/group/Columns.test.snap} | 0 .../item/expandable/group/Columns.test.tsx | 2 +- .../item/expandable/group/Group.test.snap} | 0 .../item/expandable/group/Group.test.tsx | 2 +- .../item/expandable/group/Nested.test.snap} | 0 .../item/expandable/group/Nested.test.tsx | 2 +- .../item/expandable/group/Panel.test.snap} | 0 .../item/expandable/group/Panel.test.tsx | 2 +- .../item/expandable/group/Title.test.snap} | 0 .../item/expandable/group/Title.test.tsx | 2 +- .../expandable/group/tool/Expander.test.snap} | 0 .../expandable/group/tool/Expander.test.tsx | 2 +- .../expandable/group/tool/Tool.test.snap} | 0 .../item/expandable/group/tool/Tool.test.tsx | 2 +- .../item/expandable/history/Icon.test.snap} | 0 .../item/expandable/history/Icon.test.tsx | 2 +- .../item/expandable/history/Item.test.snap} | 0 .../item/expandable/history/Item.test.tsx | 2 +- .../item/expandable/history/Tray.test.snap} | 0 .../item/expandable/history/Tray.test.tsx | 2 +- .../test/toolbar/scroll/Arrow.test.snap} | 0 .../test}/toolbar/scroll/Arrow.test.tsx | 4 +- .../test/toolbar/scroll/Indicator.test.snap} | 0 .../test}/toolbar/scroll/Indicator.test.tsx | 4 +- .../test}/utilities/Cell.test.ts | 2 +- .../{tests => src/test}/utilities/Css.test.ts | 2 +- .../test}/utilities/Direction.test.ts | 2 +- .../test}/utilities/Point.test.ts | 2 +- .../test}/utilities/Rectangle.test.ts | 2 +- .../test}/utilities/Size.test.ts | 2 +- .../test/widget/Stacked.test.snap} | 0 .../test}/widget/Stacked.test.tsx | 2 +- .../test/widget/Tools.test.snap} | 0 .../{tests => src/test}/widget/Tools.test.tsx | 2 +- .../widget/rectangular/Content.test.snap} | 0 .../test}/widget/rectangular/Content.test.tsx | 4 +- .../widget/rectangular/ResizeGrip.test.snap} | 0 .../widget/rectangular/ResizeGrip.test.tsx | 2 +- .../rectangular/tab/Draggable.test.snap} | 0 .../widget/rectangular/tab/Draggable.test.tsx | 6 +- .../rectangular/tab/Separator.test.snap} | 0 .../rectangular/tab}/Separator.test.tsx | 2 +- .../widget/rectangular/tab/Tab.test.snap} | 0 .../test}/widget/rectangular/tab/Tab.test.tsx | 4 +- .../widget/tool-settings/Nested.test.snap} | 0 .../widget/tool-settings/Nested.test.tsx | 2 +- .../tool-settings/ScrollableArea.test.snap} | 0 .../tool-settings/ScrollableArea.test.tsx | 2 +- .../widget/tool-settings/Settings.test.snap} | 0 .../widget/tool-settings/Settings.test.tsx | 2 +- .../widget/tool-settings/Toggle.test.snap} | 0 .../widget/tool-settings/Toggle.test.tsx | 2 +- .../test/zones/Footer.test.snap} | 0 .../{tests => src/test}/zones/Footer.test.tsx | 2 +- .../test/zones/GhostOutline.test.snap} | 0 .../test}/zones/GhostOutline.test.tsx | 2 +- .../test/zones/Zone.test.snap} | 0 .../{tests => src/test}/zones/Zone.test.tsx | 2 +- .../test/zones/Zones.test.snap} | 0 .../{tests => src/test}/zones/Zones.test.tsx | 2 +- .../test/zones/state/Manager.test.snap} | 0 .../test}/zones/state/Manager.test.ts | 10 +- .../test}/zones/state/NineZone.test.ts | 2 +- .../test}/zones/state/TestProps.ts | 4 +- .../test}/zones/state/Widget.test.ts | 2 +- .../test}/zones/state/Zone.test.ts | 6 +- .../test}/zones/state/layout/Layout.test.ts | 8 +- .../test}/zones/state/layout/Layouts.test.ts | 12 +- .../test}/zones/state/layout/Root.test.ts | 2 +- .../test/zones/target/Back.test.snap} | 0 .../test}/zones/target/Back.test.tsx | 2 +- .../test/zones/target/Container.test.snap} | 0 .../test}/zones/target/Container.test.tsx | 2 +- .../test/zones/target/Merge.test.snap} | 0 .../test}/zones/target/Merge.test.tsx | 2 +- .../test/zones/target/Target.test.snap} | 0 .../test}/zones/target/Target.test.tsx | 2 +- ui/ninezone/src/theme/WithTheme.tsx | 2 +- ui/ninezone/src/toolbar/item/Icon.scss | 36 + ui/ninezone/src/toolbar/item/Icon.tsx | 39 +- ui/ninezone/src/toolbar/item/Item.scss | 58 - ui/ninezone/src/toolbar/item/Item.tsx | 54 - ui/ninezone/src/toolbar/item/_variables.scss | 3 +- .../item/expandable/group/tool/Expander.tsx | 3 +- .../item/expandable/group/tool/Tool.scss | 1 + ui/ninezone/src/utilities/Props.ts | 4 +- ui/ninezone/src/widget/TabIcon.scss | 51 + ui/ninezone/src/widget/TabIcon.tsx | 46 + ui/ninezone/src/widget/ToolSettings.scss | 4 +- ui/ninezone/src/widget/_variables.scss | 9 + .../src/widget/rectangular/Content.scss | 5 +- ui/ninezone/src/widget/tool-settings/Tab.scss | 9 +- ui/ninezone/src/widget/tool-settings/Tab.tsx | 4 +- ui/ninezone/src/widget/{ => tools}/Tools.scss | 20 +- ui/ninezone/src/widget/{ => tools}/Tools.tsx | 2 +- .../{toolbar => widget/tools}/button/App.scss | 0 .../{toolbar => widget/tools}/button/App.tsx | 2 +- .../tools}/button/Back.scss | 0 .../{toolbar => widget/tools}/button/Back.tsx | 2 +- .../tools}/button/Button.scss | 0 .../tools}/button/Button.tsx | 2 +- .../tools}/button/Expandable.scss | 12 +- .../tools}/button/Expandable.tsx | 2 +- .../tools}/button/Icon.scss | 0 .../{toolbar => widget/tools}/button/Icon.tsx | 0 .../tools}/button/_variables.scss | 0 ui/ninezone/tests/tsconfig.json | 18 - ui/scripts/copy-test-setup.js | 2 +- ui/scripts/setup-tests.js | 36 +- 1504 files changed, 59946 insertions(+), 37373 deletions(-) create mode 100644 common/scripts/mocha-reporter-tweaks.js create mode 100644 common/scripts/utils.js delete mode 100644 core/backend/src/LinkTableRelationship.ts create mode 100644 core/backend/src/NavigationRelationship.ts create mode 100644 core/backend/src/Relationship.ts create mode 100644 core/backend/src/test/standalone/IModelImporter.test.ts create mode 100644 core/backend/src/test/standalone/TxnManager.test.ts create mode 100644 core/bentley/src/FluentdBunyanLoggerConfig.ts create mode 100644 core/bentley/src/FluentdLoggerStream.ts delete mode 100644 core/bentley/src/PromiseUtil.ts rename core/clients-backend/src/{ => oidc}/OidcAgentClient.ts (100%) rename core/clients-backend/src/{ => oidc}/OidcBackendClient.ts (83%) rename core/clients-backend/src/{ => oidc}/OidcDelegationClient.ts (100%) create mode 100644 core/clients-backend/src/oidc/OidcDeviceClient.ts create mode 100644 core/clients-backend/src/oidc/index.ts create mode 100644 core/clients/src/UserInfo.ts delete mode 100644 core/clients/src/UserProfile.ts delete mode 100644 core/clients/src/test/UrlValidator.test.ts create mode 100644 core/clients/src/test/imodelhub/UrlValidator.test.ts create mode 100644 core/common/src/RenderSchedule.ts rename {test-apps/testbed/frontend => core/common/src/test}/FeatureIndex.test.ts (98%) rename {test-apps/testbed/frontend => core/common/src/test}/OctEncodedNormal.test.ts (96%) rename {test-apps/testbed/frontend => core/common/src/test}/QPoint.test.ts (96%) rename {test-apps/testbed/frontend => core/common/src/test}/Render.test.ts (97%) create mode 100644 core/ecschema-metadata/test/Deserialization/JsonParser.test.ts create mode 100644 core/frontend/src/ContextRealityModelState.ts rename core/geometry/src/curve/{PathWithDistanceIndex.ts => CurveChainWithDistanceIndex.ts} (57%) create mode 100644 core/geometry/src/geometry3d/GrowableBlockedArray.ts create mode 100644 core/geometry/src/geometry3d/GrowableFloat64Array.ts rename core/geometry/src/geometry3d/{GrowableArray.ts => GrowableXYZArray.ts} (55%) create mode 100644 core/geometry/src/polyface/PolyfaceData.ts create mode 100644 core/geometry/src/test/PolyfaceAuxData.test.ts create mode 100644 core/geometry/src/test/PolyfaceData.test.ts create mode 100644 docs/core/bis/ec/differences-between-ec2-and-ec3.md create mode 100644 docs/core/bis/intro/modeling-perspectives.md create mode 100644 docs/core/bis/intro/top-of-the-world.md rename docs/core/{delta/NextVersion.md => changehistory/0.163.0.md} (56%) create mode 100644 docs/core/changehistory/0.164.0.md create mode 100644 docs/core/changehistory/0.165.0.md create mode 100644 docs/core/changehistory/0.166.0.md create mode 100644 docs/core/changehistory/0.167.0.md create mode 100644 docs/core/changehistory/0.168.0.md create mode 100644 docs/core/changehistory/0.169.0.md create mode 100644 docs/core/changehistory/0.170.0.md create mode 100644 docs/core/changehistory/0.171.0.md create mode 100644 docs/core/changehistory/NextVersion.md create mode 100644 docs/core/changehistory/index.md create mode 100644 docs/core/changehistory/leftNav.md create mode 100644 docs/core/learning/common/GeometryStream.md create mode 100644 docs/core/learning/common/fill_types.png create mode 100644 docs/core/learning/common/geom_types.png create mode 100644 docs/core/learning/common/materials.PNG create mode 100644 docs/core/learning/common/part_refs.png create mode 100644 docs/core/learning/common/pattern_types.png create mode 100644 docs/core/learning/common/placement_bad.png create mode 100644 docs/core/learning/common/placement_good.png create mode 100644 docs/core/learning/common/stroked_ls.png create mode 100644 docs/core/learning/frontend/accudraw.png create mode 100644 docs/core/learning/geometry/PolyfaceAuxData.md create mode 100644 docs/core/learning/geometry/figs/BCurves/BsplineExamples.dgn create mode 100644 docs/core/learning/geometry/figs/BCurves/BsplineGrid.imjs create mode 100644 docs/core/learning/geometry/figs/BCurves/order2.png create mode 100644 docs/core/learning/geometry/figs/BCurves/order3.png create mode 100644 docs/core/learning/geometry/figs/BCurves/order4.png create mode 100644 docs/core/learning/geometry/figs/BCurves/order5.png create mode 100644 docs/core/learning/geometry/figs/PolyfaceAuxData/Cantilever.gif create mode 100644 docs/core/learning/geometry/figs/PolyfaceAuxData/RadialWaveHeight.gif create mode 100644 docs/core/learning/geometry/figs/PolyfaceAuxData/RadialWaveSlope.gif create mode 100644 docs/ui/getting-started/assets/imodeljs-icon.svg create mode 100644 docs/ui/getting-started/assets/imodeljs-logo.svg create mode 100644 docs/ui/getting-started/assets/imodeljs_16.png delete mode 100644 docs/ui/getting-started/imodeljs.ico rename presentation/backend/{tests => src/test}/IModelHostSetup.ts (100%) rename presentation/backend/{tests => src/test}/NativePlatform.test.ts (98%) rename presentation/backend/{tests => src/test}/Presentation.test.ts (80%) rename presentation/backend/{tests/PresentationManager.test.ts.snap => src/test/PresentationManager.test.snap} (100%) rename presentation/backend/{tests => src/test}/PresentationManager.test.ts (98%) rename presentation/backend/{tests => src/test}/PresentationRpcImpl.test.ts (97%) rename presentation/backend/{tests => src/test}/RulesetManager.test.ts (89%) rename presentation/backend/{tests => src/test}/RulesetVariablesManager.test.ts (97%) rename presentation/backend/{tests => src/test}/TemporaryStorage.test.ts (91%) rename presentation/{common/tests => backend/src/test}/tsconfig.json (55%) rename presentation/common/{tests => src/test}/Error.test.ts (92%) rename presentation/common/{tests => src/test}/IRulesetManager.test.ts (95%) rename presentation/common/{tests/KeySet.test.ts.snap => src/test/KeySet.test.snap} (100%) rename presentation/common/{tests => src/test}/KeySet.test.ts (54%) rename presentation/common/{tests => src/test}/PresentationRpcInterface.test.ts (99%) rename presentation/common/{tests => src/test}/RpcRequestsHandler.test.ts (99%) rename presentation/common/{tests => src/test}/_helpers/Mocks.ts (65%) rename presentation/common/{tests => src/test}/_helpers/Promises.ts (100%) rename presentation/common/{tests => src/test}/_helpers/RpcHelper.ts (100%) rename presentation/common/{tests => src/test}/_helpers/TestRpcManager.ts (100%) rename presentation/common/{tests => src/test}/_helpers/random/Content.ts (96%) rename presentation/common/{tests => src/test}/_helpers/random/EC.ts (98%) rename presentation/common/{tests => src/test}/_helpers/random/Hierarchy.ts (93%) rename presentation/common/{tests => src/test}/_helpers/random/IModelJs.ts (100%) rename presentation/common/{tests => src/test}/_helpers/random/Misc.ts (100%) rename presentation/common/{tests => src/test}/_helpers/random/Ruleset.ts (90%) rename presentation/common/{tests => src/test}/_helpers/random/index.ts (100%) rename presentation/common/{tests/content/Content.test.ts.snap => src/test/content/Content.test.snap} (100%) rename presentation/common/{tests => src/test}/content/Content.test.ts (94%) rename presentation/common/{tests/content/Descriptor.test.ts.snap => src/test/content/Descriptor.test.snap} (100%) rename presentation/common/{tests => src/test}/content/Descriptor.test.ts (97%) rename presentation/common/{tests/content/Fields.test.ts.snap => src/test/content/Fields.test.snap} (100%) rename presentation/common/{tests => src/test}/content/Fields.test.ts (98%) rename presentation/common/{tests/content/Item.test.ts.snap => src/test/content/Item.test.snap} (100%) rename presentation/common/{tests => src/test}/content/Item.test.ts (96%) rename presentation/common/{tests => src/test}/content/Value.test.ts (98%) rename presentation/common/{tests/hierarchy/Key.test.ts.snap => src/test/hierarchy/Key.test.snap} (100%) rename presentation/common/{tests => src/test}/hierarchy/Key.test.ts (98%) rename presentation/common/{tests/hierarchy/Node.test.ts.snap => src/test/hierarchy/Node.test.snap} (100%) rename presentation/common/{tests => src/test}/hierarchy/Node.test.ts (92%) rename presentation/common/{tests/hierarchy/NodePathElement.test.ts.snap => src/test/hierarchy/NodePathElement.test.snap} (100%) rename presentation/common/{tests => src/test}/hierarchy/NodePathElement.test.ts (93%) rename presentation/{frontend/tests => common/src/test}/tsconfig.json (50%) rename presentation/components/{tests => src/test}/_helpers/UiComponents.ts (84%) rename presentation/components/{tests/common/ContentBuilder.test.ts.snap => src/test/common/ContentBuilder.test.snap} (99%) rename presentation/components/{tests => src/test}/common/ContentBuilder.test.ts (99%) rename presentation/components/{tests => src/test}/common/ContentDataProvider.test.ts (92%) rename presentation/components/{tests => src/test}/common/PageContainer.test.ts (99%) rename presentation/components/{tests => src/test}/common/StyleHelper.test.ts (96%) rename presentation/components/{tests => src/test}/common/Utils.test.ts (97%) rename presentation/components/{tests => src/test}/index.test.ts (100%) rename presentation/components/{tests/propertygrid/DataProvider.test.ts.snap => src/test/propertygrid/DataProvider.test.snap} (99%) rename presentation/components/{tests => src/test}/propertygrid/DataProvider.test.ts (97%) rename presentation/components/{tests/propertygrid/WithUnifiedSelection.test.tsx.snap => src/test/propertygrid/WithUnifiedSelection.test.snap} (100%) rename presentation/components/{tests => src/test}/propertygrid/WithUnifiedSelection.test.tsx (95%) rename presentation/components/{tests/table/DataProvider.test.ts.snap => src/test/table/DataProvider.test.snap} (100%) rename presentation/components/{tests => src/test}/table/DataProvider.test.ts (90%) rename presentation/components/{tests/table/WithUnifiedSelection.test.tsx.snap => src/test/table/WithUnifiedSelection.test.snap} (100%) create mode 100644 presentation/components/src/test/table/WithUnifiedSelection.test.tsx rename presentation/components/{tests/tree/DataProvider.test.ts.snap => src/test/tree/DataProvider.test.snap} (63%) create mode 100644 presentation/components/src/test/tree/DataProvider.test.ts rename presentation/components/{tests/tree/FilteredDataProvider.test.ts.snap => src/test/tree/FilteredDataProvider.test.snap} (82%) create mode 100644 presentation/components/src/test/tree/FilteredDataProvider.test.ts rename presentation/components/{tests/tree/Utils.test.ts.snap => src/test/tree/Utils.test.snap} (100%) rename presentation/components/{tests => src/test}/tree/Utils.test.ts (97%) rename presentation/components/{tests/tree/WithFilteringSupport.test.tsx.snap => src/test/tree/WithFilteringSupport.test.snap} (50%) create mode 100644 presentation/components/src/test/tree/WithFilteringSupport.test.tsx create mode 100644 presentation/components/src/test/tree/WithUnifiedSelection.test.snap create mode 100644 presentation/components/src/test/tree/WithUnifiedSelection.test.tsx rename presentation/components/{tests => src/test}/tsconfig.json (64%) rename presentation/components/{tests/viewport/WithUnifiedSelection.test.tsx.snap => src/test/viewport/WithUnifiedSelection.test.snap} (100%) rename presentation/components/{tests => src/test}/viewport/WithUnifiedSelection.test.tsx (75%) delete mode 100644 presentation/components/tests/table/WithUnifiedSelection.test.tsx delete mode 100644 presentation/components/tests/tree/DataProvider.test.ts delete mode 100644 presentation/components/tests/tree/FilteredDataProvider.test.ts delete mode 100644 presentation/components/tests/tree/WithFilteringSupport.test.tsx delete mode 100644 presentation/components/tests/tree/WithUnifiedSelection.test.tsx delete mode 100644 presentation/components/tests/tree/WithUnifiedSelection.test.tsx.snap rename presentation/frontend/{tests => src/test}/PersistenceHelper.test.ts (97%) rename presentation/frontend/{tests => src/test}/Presentation.test.ts (87%) rename presentation/frontend/{tests => src/test}/PresentationManager.test.ts (97%) rename presentation/frontend/{tests => src/test}/RulesetManager.test.ts (75%) rename presentation/frontend/{tests => src/test}/RulesetVariablesManager.test.ts (87%) rename presentation/frontend/{tests => src/test}/_helpers/MockFrontendEnvironment.ts (100%) rename presentation/frontend/{tests => src/test}/selection/SelectionHandler.test.ts (99%) rename presentation/frontend/{tests => src/test}/selection/SelectionManager.test.ts (78%) rename presentation/{backend/tests => frontend/src/test}/tsconfig.json (59%) create mode 100644 test-apps/analysis-importer/.gitattributes create mode 100644 test-apps/analysis-importer/.gitignore create mode 100644 test-apps/analysis-importer/assets/Cantilever.json create mode 100644 test-apps/analysis-importer/package.json create mode 100644 test-apps/analysis-importer/src/AnalysisImporter.ts create mode 100644 test-apps/analysis-importer/src/Main.ts create mode 100644 test-apps/analysis-importer/tsconfig.json rename test-apps/{simpleviewtest => analysis-importer}/tslint.json (100%) rename test-apps/{simpleviewtest => display-performance-test-app}/mobile.backend.webpack.config.js (96%) create mode 100644 test-apps/display-performance-test-app/mobile.frontend.webpack.config.js rename test-apps/{simpleviewtest => display-performance-test-app}/package.json (77%) rename test-apps/{simpleviewtest => display-performance-test-app}/public/configuration.json (100%) create mode 100644 test-apps/display-performance-test-app/public/index.html create mode 100644 test-apps/display-performance-test-app/src/backend/CsvWriter.ts create mode 100644 test-apps/display-performance-test-app/src/backend/DefaultConfig.json create mode 100644 test-apps/display-performance-test-app/src/backend/DisplayPerfRpcImpl.ts rename test-apps/{simpleviewtest => display-performance-test-app}/src/backend/ElectronMain.ts (96%) create mode 100644 test-apps/display-performance-test-app/src/backend/MobileMain.ts create mode 100644 test-apps/display-performance-test-app/src/backend/WebMain.ts create mode 100644 test-apps/display-performance-test-app/src/backend/backend.ts create mode 100644 test-apps/display-performance-test-app/src/common/DisplayPerfRpcInterface.ts rename test-apps/{simpleviewtest => display-performance-test-app}/src/common/SVTConfiguration.ts (88%) rename test-apps/{simpleviewtest => display-performance-test-app}/src/frontend/ConnectEnv.ts (96%) create mode 100644 test-apps/display-performance-test-app/src/frontend/CustomCloudEnv.ts create mode 100644 test-apps/display-performance-test-app/src/frontend/DefaultConfig.json create mode 100644 test-apps/display-performance-test-app/src/frontend/DisplayPerformanceTestApp.ts create mode 100644 test-apps/display-performance-test-app/src/frontend/IModelApi.ts create mode 100644 test-apps/display-performance-test-app/src/frontend/ProjectApi.ts create mode 100644 test-apps/display-performance-test-app/src/frontend/README.md rename test-apps/{simpleviewtest => display-performance-test-app}/src/frontend/SimpleViewState.ts (100%) rename test-apps/{simpleviewtest => display-performance-test-app}/src/frontend/Utils.ts (100%) rename test-apps/{simpleviewtest => display-performance-test-app}/tsconfig.json (100%) create mode 100644 test-apps/display-performance-test-app/tslint.json create mode 100644 test-apps/display-performance-test-app/webpack.config.js rename test-apps/{simpleviewtest => display-test-app}/README.md (63%) rename test-apps/{simpleviewtest => display-test-app}/assets/presentation_rules/Default.PresentationRuleSet.xml (100%) create mode 100644 test-apps/display-test-app/mobile.backend.webpack.config.js rename test-apps/{simpleviewtest => display-test-app}/mobile.frontend.webpack.config.js (96%) create mode 100644 test-apps/display-test-app/package.json rename test-apps/{simpleviewtest => display-test-app}/public/Fonts/Bentley-Cesium-Web-Viewer-Icons.eot (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Fonts/Bentley-Cesium-Web-Viewer-Icons.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Fonts/Bentley-Cesium-Web-Viewer-Icons.ttf (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Fonts/Bentley-Cesium-Web-Viewer-Icons.woff (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Hazard_biological.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Hazard_electric.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Hazard_flammable.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Hazard_toxic.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Hazard_tripping.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/finish.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/map-marker-icon.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/position.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/properties.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/start.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/view_front.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/view_iso.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/view_right.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Icons/view_top.png (100%) rename test-apps/{simpleviewtest => display-test-app}/public/Warning_sign.svg (100%) create mode 100644 test-apps/display-test-app/public/configuration.json rename test-apps/{simpleviewtest => display-test-app}/public/connect-configuration.json (100%) rename test-apps/{simpleviewtest => display-test-app}/public/favicon.ico (100%) rename test-apps/{simpleviewtest => display-test-app}/public/fit-to-view.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/imodelbank-233e1f55-561d-42a4-8e80-d6f91743863e.json (100%) rename test-apps/{simpleviewtest => display-test-app}/public/index.css (100%) rename test-apps/{simpleviewtest => display-test-app}/public/index.html (94%) rename test-apps/{simpleviewtest => display-test-app}/public/map_pin.svg (98%) rename test-apps/{simpleviewtest => display-test-app}/public/mobile-configuration.json (100%) rename test-apps/{simpleviewtest => display-test-app}/public/rotate-left.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/toggle-camera.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/view-navigation.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/walk.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/window-area.svg (100%) rename test-apps/{simpleviewtest => display-test-app}/public/zoom.svg (100%) create mode 100644 test-apps/display-test-app/src/backend/ElectronMain.ts rename test-apps/{simpleviewtest => display-test-app}/src/backend/MobileMain.ts (98%) rename test-apps/{simpleviewtest => display-test-app}/src/backend/WebMain.ts (80%) rename test-apps/{simpleviewtest => display-test-app}/src/backend/backend.ts (100%) create mode 100644 test-apps/display-test-app/src/common/SVTConfiguration.ts create mode 100644 test-apps/display-test-app/src/frontend/ConnectEnv.ts rename test-apps/{simpleviewtest => display-test-app}/src/frontend/CustomCloudEnv.ts (74%) rename test-apps/{simpleviewtest => display-test-app}/src/frontend/IModelApi.ts (81%) create mode 100644 test-apps/display-test-app/src/frontend/SimpleViewState.ts rename test-apps/{simpleviewtest => display-test-app}/src/frontend/SimpleViewTest.ts (85%) rename test-apps/{simpleviewtest => display-test-app}/src/frontend/Tooltip.d.ts (96%) create mode 100644 test-apps/display-test-app/src/frontend/Utils.ts create mode 100644 test-apps/display-test-app/tsconfig.json create mode 100644 test-apps/display-test-app/tslint.json rename test-apps/{simpleviewtest => display-test-app}/webpack.config.js (100%) create mode 100644 test-apps/imodel-from-geojson/.vscode/launch.json create mode 100644 test-apps/imodel-from-geojson/.vscode/settings.json create mode 100644 test-apps/imodel-from-geojson/LICENSE.md create mode 100644 test-apps/imodel-from-geojson/README.md create mode 100644 test-apps/imodel-from-geojson/data/Neighborhoods_Philadelphia.geojson create mode 100644 test-apps/imodel-from-geojson/package.json create mode 100644 test-apps/imodel-from-geojson/src/GeoJson.ts create mode 100644 test-apps/imodel-from-geojson/src/GeoJsonImporter.ts create mode 100644 test-apps/imodel-from-geojson/src/Main.ts create mode 100644 test-apps/imodel-from-geojson/tsconfig.json create mode 100644 test-apps/imodel-from-geojson/tslint.json create mode 100644 test-apps/testbed/frontend/ChangeSummary.test.ts create mode 100644 test-apps/ui-test-app/src/common/rpcs.ts delete mode 100644 test-apps/ui-test-app/src/frontend/appui/tooluiproviders/MeasurePoints.tsx create mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/UnifiedSelectionPropertyGridWidget.tsx create mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/UnifiedSelectionTableWidget.tsx delete mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/demoTableDataProvider.ts delete mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/demoTreeDataProvider.ts create mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/demodataproviders/demoTableDataProvider.ts create mode 100644 test-apps/ui-test-app/src/frontend/appui/widgets/demodataproviders/demoTreeDataProvider.ts rename test-apps/ui-test-app/src/frontend/appui/widgets/{ => draglayers}/ChildDragLayer.tsx (95%) rename test-apps/ui-test-app/src/frontend/appui/widgets/{ => draglayers}/DragLayer.scss (99%) rename test-apps/ui-test-app/src/frontend/appui/widgets/{ => draglayers}/ParentDragLayer.tsx (85%) rename test-apps/ui-test-app/src/frontend/appui/widgets/{ => draglayers}/RowDragLayer.tsx (100%) create mode 100644 test-apps/ui-test-app/src/frontend/tools/Tool1.ts create mode 100644 test-apps/ui-test-app/src/frontend/tools/Tool2.ts create mode 100644 test-apps/ui-test-app/src/frontend/tools/ToolSpecifications.tsx create mode 100644 tools/config-loader/src/index.ts delete mode 100644 tools/dev-cors-proxy-server/.dockerignore delete mode 100644 tools/dev-cors-proxy-server/.eslintignore delete mode 100644 tools/dev-cors-proxy-server/.eslintrc delete mode 100644 tools/dev-cors-proxy-server/.gitignore delete mode 100644 tools/dev-cors-proxy-server/.travis.yml delete mode 100644 tools/dev-cors-proxy-server/CHANGELOG.json delete mode 100644 tools/dev-cors-proxy-server/CHANGELOG.md delete mode 100644 tools/dev-cors-proxy-server/LICENSE.md delete mode 100644 tools/dev-cors-proxy-server/Procfile delete mode 100644 tools/dev-cors-proxy-server/README.md delete mode 100644 tools/dev-cors-proxy-server/demo.html delete mode 100644 tools/dev-cors-proxy-server/lib/cors-anywhere.js delete mode 100644 tools/dev-cors-proxy-server/lib/help.txt delete mode 100644 tools/dev-cors-proxy-server/lib/rate-limit.js delete mode 100644 tools/dev-cors-proxy-server/lib/regexp-top-level-domain.js delete mode 100644 tools/dev-cors-proxy-server/local_dev_server.crt delete mode 100644 tools/dev-cors-proxy-server/local_dev_server.key delete mode 100644 tools/dev-cors-proxy-server/package.json delete mode 100644 tools/dev-cors-proxy-server/server.js delete mode 100644 tools/dev-cors-proxy-server/test/cert.pem delete mode 100644 tools/dev-cors-proxy-server/test/child.js delete mode 100644 tools/dev-cors-proxy-server/test/customHelp.html delete mode 100644 tools/dev-cors-proxy-server/test/customHelp.txt delete mode 100644 tools/dev-cors-proxy-server/test/dummy.txt delete mode 100644 tools/dev-cors-proxy-server/test/key.pem delete mode 100644 tools/dev-cors-proxy-server/test/setup.js delete mode 100644 tools/dev-cors-proxy-server/test/test-examples.js delete mode 100644 tools/dev-cors-proxy-server/test/test-memory.js delete mode 100644 tools/dev-cors-proxy-server/test/test-ratelimit.js delete mode 100644 tools/dev-cors-proxy-server/test/test.js create mode 100644 ui/components/src/breadcrumb/breadcrumbdetails/BreadcrumbDetails.tsx create mode 100644 ui/components/src/breadcrumb/breadcrumbdetails/hoc/withDragDrop.tsx rename ui/components/src/breadcrumb/{DragDropBreadcrumbButton.tsx => hoc/DragDropBreadcrumbNode.tsx} (58%) create mode 100644 ui/components/src/breadcrumb/hoc/withDragDrop.tsx create mode 100644 ui/components/src/common/showhide/ShowHideDialog.tsx rename ui/components/{tests/tree/component/BeInspireTree.test.ts => src/common/showhide/ShowHideItem.tsx} (59%) create mode 100644 ui/components/src/common/showhide/ShowHideMenu.tsx rename ui/{framework/tests/configurableui/ZoneTargets.test.tsx => components/src/common/showhide/index.tsx} (70%) create mode 100644 ui/components/src/editors/EditorContainer.scss rename ui/{framework/tests/configurableui/StatusBarZone.test.tsx => components/src/editors/TextEditor.scss} (69%) rename ui/{framework/tests/configurableui/FrameworkFrontstage.test.tsx => components/src/properties/renderers/NonPrimitivePropertyRenderer.scss} (62%) create mode 100644 ui/components/src/properties/renderers/NonPrimitivePropertyRenderer.tsx create mode 100644 ui/components/src/properties/renderers/PrimitivePropertyRenderer.tsx create mode 100644 ui/components/src/properties/renderers/PropertyRenderer.tsx rename ui/components/src/{propertygrid/component/PropertyRenderer.scss => properties/renderers/PropertyView.scss} (82%) create mode 100644 ui/components/src/properties/renderers/PropertyView.tsx create mode 100644 ui/components/src/properties/renderers/index.ts create mode 100644 ui/components/src/properties/renderers/label/NonPrimitivePropertyLabelRenderer.tsx create mode 100644 ui/components/src/properties/renderers/label/PrimitivePropertyLabelRenderer.tsx create mode 100644 ui/components/src/properties/renderers/label/PropertyLabelRenderer.scss create mode 100644 ui/components/src/properties/renderers/label/PropertyLabelRenderer.tsx create mode 100644 ui/components/src/properties/renderers/label/index.ts rename ui/components/src/properties/renderers/{ => value}/ArrayPropertyValueRenderer.tsx (54%) create mode 100644 ui/components/src/properties/renderers/value/DoublePropertyValueRenderer.tsx create mode 100644 ui/components/src/properties/renderers/value/NavigationPropertyValueRenderer.tsx rename ui/components/src/properties/renderers/{ => value}/PrimitivePropertyValueRenderer.tsx (64%) rename ui/components/src/properties/renderers/{ => value}/StructPropertyValueRenderer.tsx (55%) create mode 100644 ui/components/src/properties/renderers/value/index.ts create mode 100644 ui/components/src/properties/renderers/value/table/ArrayValueRenderer.tsx create mode 100644 ui/components/src/properties/renderers/value/table/NonPrimitiveValueRenderer.scss create mode 100644 ui/components/src/properties/renderers/value/table/NonPrimitiveValueRenderer.tsx create mode 100644 ui/components/src/properties/renderers/value/table/StructValueRenderer.tsx create mode 100644 ui/components/src/propertygrid/component/PropertyCategoryBlock.scss delete mode 100644 ui/components/src/propertygrid/component/PropertyRenderer.tsx delete mode 100644 ui/components/src/table/component/DragDropRowRenderer.tsx rename ui/components/src/table/{component/Table.scss => hocs/DragDropRow.scss} (89%) create mode 100644 ui/components/src/table/hocs/DragDropRow.tsx create mode 100644 ui/components/src/table/hocs/TableWrapper.tsx create mode 100644 ui/components/src/table/hocs/withDragDrop.tsx rename ui/components/{tests => src/test}/TestUtils.ts (89%) create mode 100644 ui/components/src/test/UiComponents.test.ts create mode 100644 ui/components/src/test/breadcrumb/Breadcrumb.test.tsx create mode 100644 ui/components/src/test/breadcrumb/breadcrumbdetails/BreadcrumbDetails.test.tsx create mode 100644 ui/components/src/test/breadcrumb/mockTreeDataProvider.ts rename ui/components/{tests => src/test}/common/selection/SelectionHandler.test.ts (99%) create mode 100644 ui/components/src/test/common/showhide/ShowHideDialog.test.tsx create mode 100644 ui/components/src/test/common/showhide/ShowHideMenu.test.tsx rename ui/components/{tests => src/test}/converters/BooleanTypeConverter.test.ts (98%) rename ui/components/{tests => src/test}/converters/DateTimeTypeConverter.test.ts (99%) rename ui/components/{tests => src/test}/converters/EnumTypeConverter.test.ts (97%) rename ui/components/{tests => src/test}/converters/HexadecimalTypeConverter.test.ts (97%) rename ui/components/{tests => src/test}/converters/NumericTypeConverter.test.ts (98%) rename ui/components/{tests => src/test}/converters/PointTypeConverter.test.ts (79%) rename ui/components/{tests => src/test}/converters/StringTypeConverter.test.ts (98%) rename ui/components/{tests => src/test}/converters/TypeConverter.test.ts (90%) rename ui/components/{tests => src/test}/converters/TypeConverterManager.test.ts (98%) create mode 100644 ui/components/src/test/dragdrop/BeDragDropContext.test.snap create mode 100644 ui/components/src/test/dragdrop/BeDragDropContext.test.tsx rename ui/components/{tests/dragdrop/withDragSource.test.tsx.snap => src/test/dragdrop/withDragSource.test.snap} (100%) rename ui/components/{tests => src/test}/dragdrop/withDragSource.test.tsx (82%) rename ui/components/{tests/dragdrop/withDropTarget.test.tsx.snap => src/test/dragdrop/withDropTarget.test.snap} (100%) rename ui/components/{tests => src/test}/dragdrop/withDropTarget.test.tsx (86%) create mode 100644 ui/components/src/test/editors/EditorContainer.test.snap create mode 100644 ui/components/src/test/editors/EditorContainer.test.tsx rename ui/components/{tests => src/test}/editors/PropertyEditorManager.test.tsx (92%) rename ui/components/{tests/editors/TextEditor.test.tsx.snap => src/test/editors/TextEditor.test.snap} (61%) rename ui/components/{tests => src/test}/editors/TextEditor.test.tsx (81%) rename ui/components/{tests => src/test}/filtering/FilteringInput.test.tsx (72%) rename ui/components/{tests/filtering/ResultSelector.test.tsx.snap => src/test/filtering/ResultSelector.test.snap} (100%) rename ui/components/{tests => src/test}/filtering/ResultSelector.test.tsx (98%) rename ui/components/{tests => src/test}/properties/ValueRendererManager.test.tsx (98%) create mode 100644 ui/components/src/test/properties/renderers/NonPrimitivePropertyRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/PrimitivePropertyRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/PropertyRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/PropertyView.test.tsx create mode 100644 ui/components/src/test/properties/renderers/label/PropertyLabelRenderer.test.tsx rename ui/components/{tests/properties/renderers => src/test/properties/renderers/value}/ArrayPropertyValueRenderer.test.tsx (72%) create mode 100644 ui/components/src/test/properties/renderers/value/DoublePropertyValueRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/value/NavigationPropertyValueRenderer.test.tsx rename ui/components/{tests/properties/renderers => src/test/properties/renderers/value}/PrimitivePropertyValueRenderer.test.tsx (90%) rename ui/components/{tests/properties/renderers => src/test/properties/renderers/value}/StructPropertyValueRenderer.test.tsx (64%) create mode 100644 ui/components/src/test/properties/renderers/value/table/ArrayValueRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/value/table/NonPrimitiveValueRenderer.test.tsx create mode 100644 ui/components/src/test/properties/renderers/value/table/StructValueRenderer.test.tsx create mode 100644 ui/components/src/test/propertygrid/SimplePropertyDataProvider.test.ts rename ui/components/{tests => src/test}/propertygrid/component/PropertyCategoryBlock.test.tsx (93%) rename ui/components/{tests => src/test}/propertygrid/component/PropertyGrid.test.tsx (51%) rename ui/components/{tests => src/test}/propertygrid/component/SelectablePropertyBlock.test.tsx (91%) rename ui/components/{tests => src/test}/table/SimpleTableDataProvider.test.ts (96%) rename ui/components/{tests/table/component/Grid.test.tsx.snap => src/test/table/component/Grid.test.snap} (100%) rename ui/components/{tests => src/test}/table/component/Grid.test.tsx (94%) rename ui/components/{tests => src/test}/table/component/Table.test.tsx (80%) rename ui/components/{tests => src/test}/test-helpers/misc.ts (53%) create mode 100644 ui/components/src/test/tree/HighlightingEngine.test.tsx create mode 100644 ui/components/src/test/tree/SimpleTreeDataProvider.test.ts create mode 100644 ui/components/src/test/tree/TreeDataProvider.test.ts create mode 100644 ui/components/src/test/tree/component/BeInspireTree.test.snap create mode 100644 ui/components/src/test/tree/component/BeInspireTree.test.ts create mode 100644 ui/components/src/test/tree/component/Tree.test.tsx create mode 100644 ui/components/src/test/viewport/ViewportComponent.test.tsx delete mode 100644 ui/components/src/tree/component/DataTree.tsx create mode 100644 ui/components/src/tree/hocs/DragDropTreeNode.scss rename ui/components/src/tree/{component/DragDropNodeWrapper.tsx => hocs/DragDropTreeNode.tsx} (77%) create mode 100644 ui/components/src/tree/hocs/withDragDrop.tsx delete mode 100644 ui/components/tests/breadcrumb/Breadcrumb.test.tsx delete mode 100644 ui/components/tests/breadcrumb/mockTreeDataProvider.ts delete mode 100644 ui/components/tests/editors/EditorContainer.test.tsx delete mode 100644 ui/components/tests/editors/EditorContainer.test.tsx.snap delete mode 100644 ui/components/tests/propertygrid/PropertyTestHelpers.ts delete mode 100644 ui/components/tests/propertygrid/SimplePropertyDataProvider.test.ts delete mode 100644 ui/components/tests/propertygrid/component/PropertyRenderer.test.tsx delete mode 100644 ui/components/tests/tree/HighlightingEngine.test.tsx delete mode 100644 ui/components/tests/tree/HighlightingEngine.test.tsx.snap delete mode 100644 ui/components/tests/tree/SimpleTreeDataProvider.test.ts delete mode 100644 ui/components/tests/tree/component/Tree.test.tsx delete mode 100644 ui/components/tests/tsconfig.json rename ui/core/{tests => src/test}/TestUtils.ts (89%) create mode 100644 ui/core/src/test/UiCore.test.ts create mode 100644 ui/core/src/test/base/Div.test.snap create mode 100644 ui/core/src/test/base/Div.test.tsx create mode 100644 ui/core/src/test/base/UiEvent.test.ts create mode 100644 ui/core/src/test/base/WaitSpinner.test.snap rename ui/{ninezone/tests/footer/tool-assistance/Item.test.tsx => core/src/test/base/WaitSpinner.test.tsx} (76%) create mode 100644 ui/core/src/test/base/WebFontIcon.test.snap create mode 100644 ui/core/src/test/base/WebFontIcon.test.tsx rename ui/core/{tests/checklistbox/CheckListBox.test.tsx.snap => src/test/checklistbox/CheckListBox.test.snap} (100%) rename ui/core/{tests => src/test}/checklistbox/CheckListBox.test.tsx (90%) create mode 100644 ui/core/src/test/contextmenu/ContextMenu.test.snap rename ui/core/{tests => src/test}/contextmenu/ContextMenu.test.tsx (96%) rename ui/core/{tests/cube/Cube.test.tsx.snap => src/test/cube/Cube.test.snap} (100%) rename ui/core/{tests => src/test}/cube/Cube.test.tsx (97%) create mode 100644 ui/core/src/test/dialog/Dialog.test.snap create mode 100644 ui/core/src/test/dialog/Dialog.test.tsx rename ui/core/{tests => src/test}/elementseparator/ElementSeparator.test.tsx (96%) rename ui/core/{tests/expandable/ExpandableBlock.test.tsx.snap => src/test/expandable/ExpandableBlock.test.snap} (86%) rename ui/core/{tests => src/test}/expandable/ExpandableBlock.test.tsx (93%) rename ui/core/{tests/expandable/ExpandableList.test.tsx.snap => src/test/expandable/ExpandableList.test.snap} (61%) create mode 100644 ui/core/src/test/expandable/ExpandableList.test.tsx create mode 100644 ui/core/src/test/hocs/withIsPressed.test.snap create mode 100644 ui/core/src/test/hocs/withIsPressed.test.tsx create mode 100644 ui/core/src/test/hocs/withOnOutsideClick.test.snap create mode 100644 ui/core/src/test/hocs/withOnOutsideClick.test.tsx create mode 100644 ui/core/src/test/hocs/withTimeout.test.snap create mode 100644 ui/core/src/test/hocs/withTimeout.test.tsx rename ui/core/{tests/messagebox/MessageBox.test.tsx.snap => src/test/messagebox/MessageBox.test.snap} (85%) create mode 100644 ui/core/src/test/messagebox/MessageBox.test.tsx create mode 100644 ui/core/src/test/popup/Popup.test.snap create mode 100644 ui/core/src/test/popup/Popup.test.tsx rename ui/core/{tests/radialmenu/RadialMenu.test.tsx.snap => src/test/radialmenu/RadialMenu.test.snap} (99%) rename ui/core/{tests => src/test}/radialmenu/RadialMenu.test.tsx (89%) rename ui/core/{tests/searchbox/SearchBox.test.tsx.snap => src/test/searchbox/SearchBox.test.snap} (73%) create mode 100644 ui/core/src/test/searchbox/SearchBox.test.tsx rename ui/core/{tests/splitbutton/SplitButton.test.tsx.snap => src/test/splitbutton/SplitButton.test.snap} (91%) rename ui/core/{tests => src/test}/splitbutton/SplitButton.test.tsx (88%) rename ui/core/{tests/toggle/Toggle.test.tsx.snap => src/test/toggle/Toggle.test.snap} (100%) rename ui/core/{tests => src/test}/toggle/Toggle.test.tsx (93%) rename ui/core/{tests/tree/Branch.test.tsx.snap => src/test/tree/Branch.test.snap} (100%) rename ui/core/{tests => src/test}/tree/Branch.test.tsx (94%) rename ui/core/{tests/tree/ExpansionToggle.test.tsx.snap => src/test/tree/ExpansionToggle.test.snap} (100%) rename ui/core/{tests => src/test}/tree/ExpansionToggle.test.tsx (94%) rename ui/core/{tests/tree/Node.test.tsx.snap => src/test/tree/Node.test.snap} (76%) rename ui/core/{tests => src/test}/tree/Node.test.tsx (56%) create mode 100644 ui/core/src/test/tree/Placeholder.test.tsx rename ui/core/{tests/tree/Tree.test.tsx.snap => src/test/tree/Tree.test.snap} (100%) create mode 100644 ui/core/src/test/tree/Tree.test.tsx rename ui/core/{tests => src/test}/uisettings/LocalUiSettings.test.ts (97%) rename ui/core/{tests => src/test}/utils/Timer.test.ts (98%) create mode 100644 ui/core/src/tree/Placeholder.scss create mode 100644 ui/core/src/tree/Placeholder.tsx create mode 100644 ui/core/src/utils/getDisplayName.ts create mode 100644 ui/core/src/utils/shallowDiffers.ts rename ui/{framework/tests/configurableui/FrameworkZone.test.tsx => core/src/utils/typeUtils.ts} (69%) delete mode 100644 ui/core/tests/contextmenu/ContextMenu.test.tsx.snap delete mode 100644 ui/core/tests/dialog/Dialog.test.tsx delete mode 100644 ui/core/tests/dialog/Dialog.test.tsx.snap delete mode 100644 ui/core/tests/expandable/ExpandableList.test.tsx delete mode 100644 ui/core/tests/messagebox/MessageBox.test.tsx delete mode 100644 ui/core/tests/popup/Popup.test.tsx delete mode 100644 ui/core/tests/popup/Popup.test.tsx.snap delete mode 100644 ui/core/tests/searchbox/SearchBox.test.tsx delete mode 100644 ui/core/tests/tree/Tree.test.tsx delete mode 100644 ui/core/tests/tsconfig.json create mode 100644 ui/framework/rulesets/Categories.json create mode 100644 ui/framework/rulesets/Models.json create mode 100644 ui/framework/rulesets/ModelsCategories.json create mode 100644 ui/framework/src/CoreToolDefinitions.ts delete mode 100644 ui/framework/src/clientservices/DefaultLoginServices.ts delete mode 100644 ui/framework/src/clientservices/LoginServices.ts rename ui/framework/{tests/configurableui/FrontstageZone.test.tsx => src/clientservices/index.ts} (69%) create mode 100644 ui/framework/src/configurableui/ActionItemButton.tsx delete mode 100644 ui/framework/src/configurableui/FrameworkFrontstage.tsx delete mode 100644 ui/framework/src/configurableui/FrontstageZone.tsx create mode 100644 ui/framework/src/configurableui/IconComponent.tsx delete mode 100644 ui/framework/src/configurableui/IconLabelSupport.tsx delete mode 100644 ui/framework/src/configurableui/ItemFactory.tsx create mode 100644 ui/framework/src/configurableui/ItemMap.tsx create mode 100644 ui/framework/src/configurableui/SelectionScope.ts create mode 100644 ui/framework/src/configurableui/ToolButton.tsx create mode 100644 ui/framework/src/feedback/index.ts create mode 100644 ui/framework/src/messages/index.ts delete mode 100644 ui/framework/src/oidc/Callback.tsx create mode 100644 ui/framework/src/oidc/OidcBrowserClient.ts create mode 100644 ui/framework/src/oidc/OidcClientWrapper.ts create mode 100644 ui/framework/src/oidc/index.ts rename ui/{components/src/tree/component/DragDropNodeWrapper.scss => framework/src/openimodel/index.ts} (52%) create mode 100644 ui/framework/src/overallcontent/index.ts create mode 100644 ui/framework/src/pickers/index.ts create mode 100644 ui/framework/src/test/CoreToolDefinitions.test.snap create mode 100644 ui/framework/src/test/CoreToolDefinitions.test.tsx rename ui/framework/{tests => src/test}/TestUtils.ts (91%) create mode 100644 ui/framework/src/test/UiFramework.test.ts create mode 100644 ui/framework/src/test/configurableui/AnalysisAnimationToolSettings.test.snap rename ui/framework/{tests => src/test}/configurableui/AppNotificationManager.test.tsx (96%) rename ui/framework/{tests/configurableui/Backstage.test.tsx.snap => src/test/configurableui/Backstage.test.snap} (73%) rename ui/framework/{tests => src/test}/configurableui/Backstage.test.tsx (70%) rename ui/framework/{tests/configurableui/ConfigurableUiContent.test.tsx.snap => src/test/configurableui/ConfigurableUiContent.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/ConfigurableUiContent.test.tsx (91%) rename ui/framework/{tests => src/test}/configurableui/ConfigurableUiManager.test.tsx (67%) rename ui/framework/{tests => src/test}/configurableui/ContentControl.test.tsx (55%) create mode 100644 ui/framework/src/test/configurableui/ContentLayout.test.snap create mode 100644 ui/framework/src/test/configurableui/ContentLayout.test.tsx rename ui/framework/{tests => src/test}/configurableui/ContentViewManager.test.tsx (100%) create mode 100644 ui/framework/src/test/configurableui/DragDropLayerManager.test.snap rename ui/framework/{tests => src/test}/configurableui/DragDropLayerManager.test.tsx (59%) rename ui/framework/{tests => src/test}/configurableui/ElementTooltip.test.tsx (95%) rename ui/framework/{tests/configurableui/Frontstage.test.tsx.snap => src/test/configurableui/Frontstage.test.snap} (100%) create mode 100644 ui/framework/src/test/configurableui/Frontstage.test.tsx rename ui/framework/{tests => src/test}/configurableui/FrontstageComposer.test.tsx (92%) rename ui/framework/{tests => src/test}/configurableui/FrontstageDef.test.tsx (100%) rename ui/framework/{tests => src/test}/configurableui/FrontstageManager.test.tsx (100%) create mode 100644 ui/framework/src/test/configurableui/GroupItem.test.snap rename ui/framework/{tests => src/test}/configurableui/GroupItem.test.tsx (62%) rename ui/framework/{tests => src/test}/configurableui/IconLabelSupport.test.tsx (92%) create mode 100644 ui/framework/src/test/configurableui/Item.test.snap create mode 100644 ui/framework/src/test/configurableui/Item.test.tsx rename ui/framework/{tests => src/test}/configurableui/MessageManager.test.tsx (96%) rename ui/framework/{tests => src/test}/configurableui/ModalDialogManager.test.tsx (97%) rename ui/framework/{tests => src/test}/configurableui/ModalFrontstage.test.tsx (99%) rename ui/framework/{tests => src/test}/configurableui/NavigationAidControl.test.tsx (100%) rename ui/framework/{tests/configurableui/NavigationWidget.test.tsx.snap => src/test/configurableui/NavigationWidget.test.snap} (75%) rename ui/framework/{tests => src/test}/configurableui/NavigationWidget.test.tsx (63%) create mode 100644 ui/framework/src/test/configurableui/StackedWidget.test.tsx rename ui/framework/{tests/configurableui/StandardMessageBox.test.tsx.snap => src/test/configurableui/StandardMessageBox.test.snap} (93%) rename ui/framework/{tests => src/test}/configurableui/StandardMessageBox.test.tsx (95%) rename ui/framework/{tests => src/test}/configurableui/StatusBar.test.tsx (99%) rename ui/framework/{tests => src/test}/configurableui/StatusBarWidgetControl.test.tsx (98%) rename ui/framework/{tests => src/test}/configurableui/Task.test.tsx (92%) create mode 100644 ui/framework/src/test/configurableui/ToolSettingsZone.test.tsx rename ui/framework/{tests => src/test}/configurableui/ToolUiProvider.test.tsx (62%) create mode 100644 ui/framework/src/test/configurableui/ToolWidget.test.snap create mode 100644 ui/framework/src/test/configurableui/ToolWidget.test.tsx rename ui/framework/{tests => src/test}/configurableui/ViewportContentControl.test.tsx (51%) rename ui/framework/{tests/configurableui/Widget.test.tsx.snap => src/test/configurableui/Widget.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/Widget.test.tsx (91%) rename ui/framework/{tests => src/test}/configurableui/WidgetControl.test.tsx (70%) rename ui/framework/{tests => src/test}/configurableui/WidgetDef.test.tsx (96%) rename ui/framework/{tests => src/test}/configurableui/WidgetFactory.test.tsx (79%) rename ui/framework/{tests => src/test}/configurableui/Workflow.test.tsx (90%) rename ui/framework/{tests/configurableui/Zone.test.tsx.snap => src/test/configurableui/Zone.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/Zone.test.tsx (91%) create mode 100644 ui/framework/src/test/configurableui/ZoneDef.test.tsx create mode 100644 ui/framework/src/test/configurableui/ZoneTargets.test.snap create mode 100644 ui/framework/src/test/configurableui/ZoneTargets.test.tsx rename ui/framework/{tests/configurableui/navigationaids/CubeNavigationAid.test.tsx.snap => src/test/configurableui/navigationaids/CubeNavigationAid.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/navigationaids/CubeNavigationAid.test.tsx (99%) rename ui/framework/{tests/configurableui/navigationaids/SheetNavigationAid.test.tsx.snap => src/test/configurableui/navigationaids/SheetNavigationAid.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/navigationaids/SheetNavigationAid.test.tsx (98%) rename ui/framework/{tests => src/test}/configurableui/navigationaids/SheetsModalFrontstage.test.tsx (53%) rename ui/framework/{tests/configurableui/navigationaids/StandardRotationNavigationAid.test.tsx.snap => src/test/configurableui/navigationaids/StandardRotationNavigationAid.test.snap} (100%) rename ui/framework/{tests => src/test}/configurableui/navigationaids/StandardRotationNavigationAid.test.tsx (50%) create mode 100644 ui/framework/src/test/configurableui/statusbarfields/ActivityCenter.test.tsx rename ui/framework/{tests => src/test}/configurableui/statusbarfields/MessageCenter.test.tsx (99%) rename ui/framework/{tests => src/test}/configurableui/statusbarfields/PromptField.test.tsx (84%) rename ui/framework/{tests => src/test}/configurableui/statusbarfields/SnapMode.test.tsx (99%) rename ui/framework/{tests/feedback/ValidationTextbox.test.tsx.snap => src/test/feedback/ValidationTextbox.test.snap} (100%) rename ui/framework/{tests => src/test}/feedback/ValidationTextbox.test.tsx (94%) rename ui/framework/{tests/messages/InputField.test.tsx.snap => src/test/messages/InputField.test.snap} (100%) rename ui/framework/{tests => src/test}/messages/InputField.test.tsx (91%) rename ui/framework/{tests/messages/Pointer.test.tsx.snap => src/test/messages/Pointer.test.snap} (100%) rename ui/framework/{tests => src/test}/messages/Pointer.test.tsx (92%) rename ui/framework/{tests/pickers/ListPicker.test.tsx.snap => src/test/pickers/ListPicker.test.snap} (98%) rename ui/framework/{tests => src/test}/pickers/ListPicker.test.tsx (95%) create mode 100644 ui/framework/src/test/pickers/ModelSelector.test.snap create mode 100644 ui/framework/src/test/pickers/ModelSelector.test.tsx rename ui/framework/{tests/pickers/ViewSelector.test.tsx.snap => src/test/pickers/ViewSelector.test.snap} (87%) rename ui/framework/{tests => src/test}/pickers/ViewSelector.test.tsx (74%) create mode 100644 ui/framework/src/test/tools/AnalysisAnimationToolSettings.test.snap create mode 100644 ui/framework/src/test/tools/AnalysisAnimationToolSettings.test.tsx create mode 100644 ui/framework/src/test/tools/Tool1.ts rename ui/framework/{tests => src/test}/utils/SyncUiEventDispatcher.test.ts (98%) rename ui/framework/{tests => src/test}/utils/ViewUtilities.test.tsx (94%) create mode 100644 ui/framework/src/tools/AnalysisAnimation.ts create mode 100644 ui/framework/src/tools/AnalysisAnimationToolSettings.scss create mode 100644 ui/framework/src/tools/AnalysisAnimationToolSettings.tsx create mode 100644 ui/framework/src/tools/index.ts delete mode 100644 ui/framework/tests/configurableui/ContentLayout.test.tsx delete mode 100644 ui/framework/tests/configurableui/ContentLayout.test.tsx.snap delete mode 100644 ui/framework/tests/configurableui/Frontstage.test.tsx delete mode 100644 ui/framework/tests/configurableui/GroupItem.test.tsx.snap delete mode 100644 ui/framework/tests/configurableui/Item.test.tsx delete mode 100644 ui/framework/tests/configurableui/Item.test.tsx.snap delete mode 100644 ui/framework/tests/configurableui/StackedWidget.test.tsx delete mode 100644 ui/framework/tests/configurableui/ToolSettingsZone.test.tsx delete mode 100644 ui/framework/tests/configurableui/ToolWidget.test.tsx delete mode 100644 ui/framework/tests/configurableui/ToolWidget.test.tsx.snap delete mode 100644 ui/framework/tests/configurableui/ZoneDef.test.tsx delete mode 100644 ui/framework/tests/tsconfig.json rename ui/ninezone/{tests/toolbar/button/App.test.tsx.snap => src/test/appbutton/App.test.snap} (100%) rename ui/ninezone/{tests/toolbar/button => src/test/appbutton}/App.test.tsx (91%) rename ui/ninezone/{tests/toolbar/button/Back.test.tsx.snap => src/test/appbutton/Back.test.snap} (100%) rename ui/ninezone/{tests/toolbar/button => src/test/appbutton}/Back.test.tsx (91%) rename ui/ninezone/{tests/backstage/Backstage.test.tsx.snap => src/test/backstage/Backstage.test.snap} (100%) rename ui/ninezone/{tests => src/test}/backstage/Backstage.test.tsx (96%) rename ui/ninezone/{tests/backstage/Item.test.tsx.snap => src/test/backstage/Item.test.snap} (100%) rename ui/ninezone/{tests => src/test}/backstage/Item.test.tsx (94%) rename ui/ninezone/{tests/backstage/Separator.test.tsx.snap => src/test/backstage/Separator.test.snap} (100%) rename ui/ninezone/{tests => src/test}/backstage/Separator.test.tsx (91%) rename ui/ninezone/{tests/base/PointerCaptor.test.tsx.snap => src/test/base/PointerCaptor.test.snap} (100%) rename ui/ninezone/{tests => src/test}/base/PointerCaptor.test.tsx (95%) rename ui/ninezone/{tests/base/SvgPath.test.tsx.snap => src/test/base/SvgPath.test.snap} (100%) rename ui/ninezone/{tests => src/test}/base/SvgPath.test.tsx (98%) rename ui/ninezone/{tests/base/SvgSprite.test.tsx.snap => src/test/base/SvgSprite.test.snap} (100%) rename ui/ninezone/{tests => src/test}/base/SvgSprite.test.tsx (92%) rename ui/ninezone/{tests/context/MouseTracker.test.tsx.snap => src/test/context/MouseTracker.test.snap} (100%) rename ui/ninezone/{tests => src/test}/context/MouseTracker.test.tsx (91%) rename ui/ninezone/{tests/footer/Footer.test.tsx.snap => src/test/footer/Footer.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/Footer.test.tsx (92%) rename ui/ninezone/{tests/footer/message-center/Content.test.tsx.snap => src/test/footer/message-center/Content.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message-center/Content.test.tsx (90%) rename ui/ninezone/{tests/footer/message-center/Indicator.test.tsx.snap => src/test/footer/message-center/Indicator.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message-center/Indicator.test.tsx (89%) rename ui/ninezone/{tests/footer/message-center/Message.test.tsx.snap => src/test/footer/message-center/Message.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message-center/Message.test.tsx (90%) rename ui/ninezone/{tests/footer/message-center/MessageCenter.test.tsx.snap => src/test/footer/message-center/MessageCenter.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message-center/MessageCenter.test.tsx (89%) rename ui/ninezone/{tests/footer/message-center/Tab.test.tsx.snap => src/test/footer/message-center/Tab.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message-center/Tab.test.tsx (91%) rename ui/ninezone/{tests/footer/message/Activity.test.tsx.snap => src/test/footer/message/Activity.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message/Activity.test.tsx (91%) rename ui/ninezone/{tests/footer/message/Modal.test.tsx.snap => src/test/footer/message/Modal.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message/Modal.test.tsx (91%) rename ui/ninezone/{tests/footer/message/Sticky.test.tsx.snap => src/test/footer/message/Sticky.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message/Sticky.test.tsx (91%) rename ui/ninezone/{tests/footer/message/Temporary.test.tsx.snap => src/test/footer/message/Temporary.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message/Temporary.test.tsx (90%) rename ui/ninezone/{tests/footer/message/Toast.test.tsx.snap => src/test/footer/message/Toast.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/message/Toast.test.tsx (90%) rename ui/ninezone/{tests => src/test}/footer/snap-mode/Dialog.tsx (91%) rename ui/ninezone/{tests => src/test}/footer/snap-mode/Icon.tsx (91%) rename ui/ninezone/{tests => src/test}/footer/snap-mode/Indicator.tsx (90%) rename ui/ninezone/{tests => src/test}/footer/snap-mode/Snap.tsx (91%) rename ui/ninezone/{tests/footer/tool-assistance/Content.test.tsx.snap => src/test/footer/tool-assistance/Content.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/tool-assistance/Content.test.tsx (89%) rename ui/ninezone/{tests/footer/tool-assistance/Dialog.test.tsx.snap => src/test/footer/tool-assistance/Dialog.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/tool-assistance/Dialog.test.tsx (90%) rename ui/ninezone/{tests/footer/tool-assistance/Indicator.test.tsx.snap => src/test/footer/tool-assistance/Indicator.test.snap} (100%) rename ui/ninezone/{tests => src/test}/footer/tool-assistance/Indicator.test.tsx (89%) rename ui/ninezone/{tests/footer/tool-assistance/Item.test.tsx.snap => src/test/footer/tool-assistance/Item.test.snap} (100%) rename ui/ninezone/{tests/toolbar/item => src/test/footer/tool-assistance}/Item.test.tsx (91%) rename ui/ninezone/{tests/footer/tool-assistance/Separator.test.tsx.snap => src/test/footer/tool-assistance/Separator.test.snap} (100%) rename ui/ninezone/{tests/widget/rectangular/tab => src/test/footer/tool-assistance}/Separator.test.tsx (89%) rename ui/ninezone/{tests/popup/popover/Popover.test.tsx.snap => src/test/popup/popover/Popover.test.snap} (100%) rename ui/ninezone/{tests => src/test}/popup/popover/Popover.test.tsx (86%) rename ui/ninezone/{tests/popup/popover/Triangle.test.tsx.snap => src/test/popup/popover/Triangle.test.snap} (100%) rename ui/ninezone/{tests => src/test}/popup/popover/Triangle.test.tsx (86%) rename ui/ninezone/{tests/popup/tooltip/Tooltip.test.tsx.snap => src/test/popup/tooltip/Tooltip.test.snap} (100%) rename ui/ninezone/{tests => src/test}/popup/tooltip/Tooltip.test.tsx (91%) rename ui/ninezone/{tests/toolbar/Scrollable.test.tsx.snap => src/test/toolbar/Scrollable.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/Scrollable.test.tsx (92%) rename ui/ninezone/{tests/toolbar/Toolbar.test.tsx.snap => src/test/toolbar/Toolbar.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/Toolbar.test.tsx (86%) create mode 100644 ui/ninezone/src/test/toolbar/button/App.test.snap create mode 100644 ui/ninezone/src/test/toolbar/button/Back.test.snap rename ui/ninezone/{tests/toolbar/item/Icon.test.tsx.snap => src/test/toolbar/item/Icon.test.snap} (80%) rename ui/ninezone/{tests => src/test}/toolbar/item/Icon.test.tsx (92%) rename ui/ninezone/{tests/toolbar/item/Item.test.tsx.snap => src/test/toolbar/item/Item.test.snap} (56%) rename ui/ninezone/{tests/toolbar/item/Overflow.test.tsx.snap => src/test/toolbar/item/Overflow.test.snap} (97%) rename ui/ninezone/{tests => src/test}/toolbar/item/Overflow.test.tsx (91%) rename ui/ninezone/{tests/toolbar/item/expandable/Expandable.test.tsx.snap => src/test/toolbar/item/expandable/Expandable.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/Expandable.test.tsx (89%) rename ui/ninezone/{tests/toolbar/item/expandable/group/BackArrow.test.tsx.snap => src/test/toolbar/item/expandable/group/BackArrow.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/BackArrow.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Column.test.tsx.snap => src/test/toolbar/item/expandable/group/Column.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Column.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Columns.test.tsx.snap => src/test/toolbar/item/expandable/group/Columns.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Columns.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Group.test.tsx.snap => src/test/toolbar/item/expandable/group/Group.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Group.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Nested.test.tsx.snap => src/test/toolbar/item/expandable/group/Nested.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Nested.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Panel.test.tsx.snap => src/test/toolbar/item/expandable/group/Panel.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Panel.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/Title.test.tsx.snap => src/test/toolbar/item/expandable/group/Title.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/Title.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/group/tool/Expander.test.tsx.snap => src/test/toolbar/item/expandable/group/tool/Expander.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/tool/Expander.test.tsx (87%) rename ui/ninezone/{tests/toolbar/item/expandable/group/tool/Tool.test.tsx.snap => src/test/toolbar/item/expandable/group/tool/Tool.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/group/tool/Tool.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/history/Icon.test.tsx.snap => src/test/toolbar/item/expandable/history/Icon.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/history/Icon.test.tsx (88%) rename ui/ninezone/{tests/toolbar/item/expandable/history/Item.test.tsx.snap => src/test/toolbar/item/expandable/history/Item.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/history/Item.test.tsx (90%) rename ui/ninezone/{tests/toolbar/item/expandable/history/Tray.test.tsx.snap => src/test/toolbar/item/expandable/history/Tray.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/item/expandable/history/Tray.test.tsx (95%) rename ui/ninezone/{tests/toolbar/scroll/Arrow.test.tsx.snap => src/test/toolbar/scroll/Arrow.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/scroll/Arrow.test.tsx (84%) rename ui/ninezone/{tests/toolbar/scroll/Indicator.test.tsx.snap => src/test/toolbar/scroll/Indicator.test.snap} (100%) rename ui/ninezone/{tests => src/test}/toolbar/scroll/Indicator.test.tsx (84%) rename ui/ninezone/{tests => src/test}/utilities/Cell.test.ts (97%) rename ui/ninezone/{tests => src/test}/utilities/Css.test.ts (91%) rename ui/ninezone/{tests => src/test}/utilities/Direction.test.ts (95%) rename ui/ninezone/{tests => src/test}/utilities/Point.test.ts (98%) rename ui/ninezone/{tests => src/test}/utilities/Rectangle.test.ts (98%) rename ui/ninezone/{tests => src/test}/utilities/Size.test.ts (93%) rename ui/ninezone/{tests/widget/Stacked.test.tsx.snap => src/test/widget/Stacked.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/Stacked.test.tsx (90%) rename ui/ninezone/{tests/widget/Tools.test.tsx.snap => src/test/widget/Tools.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/Tools.test.tsx (92%) rename ui/ninezone/{tests/widget/rectangular/Content.test.tsx.snap => src/test/widget/rectangular/Content.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/rectangular/Content.test.tsx (84%) rename ui/ninezone/{tests/widget/rectangular/ResizeGrip.test.tsx.snap => src/test/widget/rectangular/ResizeGrip.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/rectangular/ResizeGrip.test.tsx (88%) rename ui/ninezone/{tests/widget/rectangular/tab/Draggable.test.tsx.snap => src/test/widget/rectangular/tab/Draggable.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/rectangular/tab/Draggable.test.tsx (78%) rename ui/ninezone/{tests/widget/rectangular/tab/Separator.test.tsx.snap => src/test/widget/rectangular/tab/Separator.test.snap} (100%) rename ui/ninezone/{tests/footer/tool-assistance => src/test/widget/rectangular/tab}/Separator.test.tsx (90%) rename ui/ninezone/{tests/widget/rectangular/tab/Tab.test.tsx.snap => src/test/widget/rectangular/tab/Tab.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/rectangular/tab/Tab.test.tsx (83%) rename ui/ninezone/{tests/widget/tool-settings/Nested.test.tsx.snap => src/test/widget/tool-settings/Nested.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/tool-settings/Nested.test.tsx (90%) rename ui/ninezone/{tests/widget/tool-settings/ScrollableArea.test.tsx.snap => src/test/widget/tool-settings/ScrollableArea.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/tool-settings/ScrollableArea.test.tsx (88%) rename ui/ninezone/{tests/widget/tool-settings/Settings.test.tsx.snap => src/test/widget/tool-settings/Settings.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/tool-settings/Settings.test.tsx (90%) rename ui/ninezone/{tests/widget/tool-settings/Toggle.test.tsx.snap => src/test/widget/tool-settings/Toggle.test.snap} (100%) rename ui/ninezone/{tests => src/test}/widget/tool-settings/Toggle.test.tsx (90%) rename ui/ninezone/{tests/zones/Footer.test.tsx.snap => src/test/zones/Footer.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/Footer.test.tsx (94%) rename ui/ninezone/{tests/zones/GhostOutline.test.tsx.snap => src/test/zones/GhostOutline.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/GhostOutline.test.tsx (91%) rename ui/ninezone/{tests/zones/Zone.test.tsx.snap => src/test/zones/Zone.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/Zone.test.tsx (94%) rename ui/ninezone/{tests/zones/Zones.test.tsx.snap => src/test/zones/Zones.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/Zones.test.tsx (93%) rename ui/ninezone/{tests/zones/state/Manager.test.ts.snap => src/test/zones/state/Manager.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/state/Manager.test.ts (98%) rename ui/ninezone/{tests => src/test}/zones/state/NineZone.test.ts (86%) rename ui/ninezone/{tests => src/test}/zones/state/TestProps.ts (98%) rename ui/ninezone/{tests => src/test}/zones/state/Widget.test.ts (93%) rename ui/ninezone/{tests => src/test}/zones/state/Zone.test.ts (98%) rename ui/ninezone/{tests => src/test}/zones/state/layout/Layout.test.ts (98%) rename ui/ninezone/{tests => src/test}/zones/state/layout/Layouts.test.ts (96%) rename ui/ninezone/{tests => src/test}/zones/state/layout/Root.test.ts (88%) rename ui/ninezone/{tests/zones/target/Back.test.tsx.snap => src/test/zones/target/Back.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/target/Back.test.tsx (92%) rename ui/ninezone/{tests/zones/target/Container.test.tsx.snap => src/test/zones/target/Container.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/target/Container.test.tsx (91%) rename ui/ninezone/{tests/zones/target/Merge.test.tsx.snap => src/test/zones/target/Merge.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/target/Merge.test.tsx (91%) rename ui/ninezone/{tests/zones/target/Target.test.tsx.snap => src/test/zones/target/Target.test.snap} (100%) rename ui/ninezone/{tests => src/test}/zones/target/Target.test.tsx (91%) delete mode 100644 ui/ninezone/src/toolbar/item/Item.scss delete mode 100644 ui/ninezone/src/toolbar/item/Item.tsx create mode 100644 ui/ninezone/src/widget/TabIcon.scss create mode 100644 ui/ninezone/src/widget/TabIcon.tsx rename ui/ninezone/src/widget/{ => tools}/Tools.scss (78%) rename ui/ninezone/src/widget/{ => tools}/Tools.tsx (97%) rename ui/ninezone/src/{toolbar => widget/tools}/button/App.scss (100%) rename ui/ninezone/src/{toolbar => widget/tools}/button/App.tsx (94%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Back.scss (100%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Back.tsx (93%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Button.scss (100%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Button.tsx (95%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Expandable.scss (91%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Expandable.tsx (96%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Icon.scss (100%) rename ui/ninezone/src/{toolbar => widget/tools}/button/Icon.tsx (100%) rename ui/ninezone/src/{toolbar => widget/tools}/button/_variables.scss (100%) delete mode 100644 ui/ninezone/tests/tsconfig.json diff --git a/README.md b/README.md index e8ce86f..8de8479 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Copyright © 2018 Bentley Systems, Incorporated. All rights reserved. -The [iModel.js](http://imodeljs.org) library is an open source library for creating and accessing iModels. +The [iModel.js](http://imodeljs.org) library is an open source library for creating, querying, modifying, and displaying iModels. -If you have questions, or want to contribute to iModel.js, see our [Contributing guide](./CONTRIBUTING.md). +If you have questions, or wish to contribute to iModel.js, see our [Contributing guide](./CONTRIBUTING.md). ## About this Repository @@ -34,7 +34,7 @@ See [rush.json](./rush.json) for the list of packages. These packages are descri * Comprehensive ECSchema library * `example-code` * Example code snippets are extracted from these packages -* `test-apps/simpleviewtest/package.json` +* `test-apps/display-test-app/package.json` * Private, not published * Test application for graphics visualization * `test-apps/testbed/package.json` @@ -89,7 +89,7 @@ Note that it is a good idea to `rush install` after each `git pull` as dependenc 6. Follow prompts to enter a change description or press ENTER if the change does not warrant a changelog entry. If multiple packages have changed, multiple sets of prompts will be presented. If the changes are only to non-published packages (like **testbed**), then `rush change` will indicate that a changelog entry is not needed. 7. Completing the `rush change` prompts will cause new changelog entry JSON files to be created. 8. To keep the Git history clean, amend the prior commit using the **Commit Staged (Amend)** menu item in Visual Studio Code or use the command line: `git commit --amend --no-edit` -10. Push changes +9. Push changes If using the command line, steps 5 through 9 above can be completed in one step by running `rushchange.bat` from the imodeljs-core root directory. > Note: The CI build will break if changes are pushed without running `rush change`. The fix will be to run `rush change` (as above) and push those changes as a separate commit. diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json index a37895b..92bbe4b 100644 --- a/common/config/rush/browser-approved-packages.json +++ b/common/config/rush/browser-approved-packages.json @@ -6,10 +6,6 @@ "name": "@bentley/bentleyjs-core", "allowedCategories": [ "backend", "common", "frontend", "internal" ] }, - { - "name": "@bentley/bentleyjs-tools", - "allowedCategories": [ "backend", "common", "frontend", "internal" ] - }, { "name": "@bentley/build-tools", "allowedCategories": [ "backend", "common", "frontend", "internal", "tools" ] @@ -42,6 +38,10 @@ "name": "@bentley/imodeljs-clients", "allowedCategories": [ "backend", "common", "frontend", "internal" ] }, + { + "name": "@bentley/imodeljs-clients-backend", + "allowedCategories": [ "frontend", "internal" ] + }, { "name": "@bentley/imodeljs-common", "allowedCategories": [ "backend", "common", "frontend", "internal" ] @@ -72,7 +72,7 @@ }, { "name": "@bentley/presentation-components", - "allowedCategories": [ "internal" ] + "allowedCategories": [ "frontend", "internal" ] }, { "name": "@bentley/presentation-frontend", @@ -98,6 +98,10 @@ "name": "@bentley/webpack-tools", "allowedCategories": [ "internal" ] }, + { + "name": "@openid/appauth", + "allowedCategories": [ "common" ] + }, { "name": "app-root-path", "allowedCategories": [ "tools" ] @@ -122,6 +126,10 @@ "name": "cache-require-paths", "allowedCategories": [ "internal", "tools" ] }, + { + "name": "callable-instance2", + "allowedCategories": [ "frontend" ] + }, { "name": "case-sensitive-paths-webpack-plugin", "allowedCategories": [ "tools" ] @@ -142,6 +150,10 @@ "name": "chai-spies", "allowedCategories": [ "backend", "common", "frontend", "internal" ] }, + { + "name": "chai-string", + "allowedCategories": [ "frontend" ] + }, { "name": "chalk", "allowedCategories": [ "tools" ] @@ -198,6 +210,10 @@ "name": "cross-spawn", "allowedCategories": [ "tools" ] }, + { + "name": "css-element-queries", + "allowedCategories": [ "frontend" ] + }, { "name": "css-loader", "allowedCategories": [ "frontend", "tools" ] @@ -230,10 +246,18 @@ "name": "electron", "allowedCategories": [ "backend", "internal" ] }, + { + "name": "electron-builder", + "allowedCategories": [ "internal" ] + }, { "name": "electron-chromedriver", "allowedCategories": [ "internal", "tools" ] }, + { + "name": "electron-devtools-installer", + "allowedCategories": [ "internal" ] + }, { "name": "enzyme", "allowedCategories": [ "frontend", "internal", "tools" ] @@ -550,6 +574,10 @@ "name": "react-redux", "allowedCategories": [ "frontend", "internal" ] }, + { + "name": "react-resize-detector", + "allowedCategories": [ "frontend" ] + }, { "name": "react-router-dom", "allowedCategories": [ "frontend" ] @@ -566,6 +594,10 @@ "name": "react-testing-library", "allowedCategories": [ "frontend" ] }, + { + "name": "react-virtualized", + "allowedCategories": [ "frontend" ] + }, { "name": "readline", "allowedCategories": [ "tools" ] @@ -582,6 +614,10 @@ "name": "redux-oidc", "allowedCategories": [ "frontend", "internal" ] }, + { + "name": "resize-observer-polyfill", + "allowedCategories": [ "frontend" ] + }, { "name": "resolve", "allowedCategories": [ "internal" ] @@ -604,11 +640,11 @@ }, { "name": "sinon", - "allowedCategories": [ "common", "frontend", "tools" ] + "allowedCategories": [ "backend", "common", "frontend", "internal", "tools" ] }, { "name": "sinon-chai", - "allowedCategories": [ "frontend" ] + "allowedCategories": [ "backend", "common", "frontend", "internal" ] }, { "name": "source-map-loader", @@ -650,6 +686,10 @@ "name": "tree-kill", "allowedCategories": [ "tools" ] }, + { + "name": "ts-key-enum", + "allowedCategories": [ "frontend" ] + }, { "name": "ts-loader", "allowedCategories": [ "frontend", "tools" ] @@ -702,6 +742,10 @@ "name": "url-loader", "allowedCategories": [ "frontend", "tools" ] }, + { + "name": "url-search-params", + "allowedCategories": [ "common" ] + }, { "name": "wdio-chromedriver-service", "allowedCategories": [ "tools" ] @@ -780,7 +824,7 @@ }, { "name": "yargs", - "allowedCategories": [ "common", "tools" ] + "allowedCategories": [ "common", "internal", "tools" ] } ] } diff --git a/common/config/rush/command-line.json b/common/config/rush/command-line.json index ce1337b..39088fa 100644 --- a/common/config/rush/command-line.json +++ b/common/config/rush/command-line.json @@ -45,7 +45,7 @@ "commandKind": "bulk", "summary": "Run test script for each package", "description": "Iterates through each package in the monorepo and runs the 'test' script", - "enableParallelism": false, + "enableParallelism": true, "ignoreMissingScript": false } ] diff --git a/common/config/rush/common-versions.json b/common/config/rush/common-versions.json index cd97c3c..4a92a29 100644 --- a/common/config/rush/common-versions.json +++ b/common/config/rush/common-versions.json @@ -1,6 +1,6 @@ { "preferredVersions": { "@types/react": "^16.4.14", - "typescript": "~3.0.0" + "typescript": "~3.1.0" } } \ No newline at end of file diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index b070974..446a4d1 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -13,12 +13,12 @@ } }, "@babel/generator": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.3.tgz", - "integrity": "sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.6.tgz", + "integrity": "sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ==", "requires": { - "@babel/types": "7.1.3", - "jsesc": "2.5.1", + "@babel/types": "7.1.6", + "jsesc": "2.5.2", "lodash": "4.17.11", "source-map": "0.5.7", "trim-right": "1.0.1" @@ -31,7 +31,7 @@ "requires": { "@babel/helper-get-function-arity": "7.0.0", "@babel/template": "7.1.2", - "@babel/types": "7.1.3" + "@babel/types": "7.1.6" } }, "@babel/helper-get-function-arity": { @@ -39,7 +39,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", "requires": { - "@babel/types": "7.1.3" + "@babel/types": "7.1.6" } }, "@babel/helper-split-export-declaration": { @@ -47,7 +47,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", "requires": { - "@babel/types": "7.1.3" + "@babel/types": "7.1.6" } }, "@babel/highlight": { @@ -61,14 +61,14 @@ } }, "@babel/parser": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.3.tgz", - "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==" + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.6.tgz", + "integrity": "sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==" }, "@babel/runtime": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", - "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz", + "integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==", "requires": { "regenerator-runtime": "0.12.1" }, @@ -86,30 +86,30 @@ "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", "requires": { "@babel/code-frame": "7.0.0", - "@babel/parser": "7.1.3", - "@babel/types": "7.1.3" + "@babel/parser": "7.1.6", + "@babel/types": "7.1.6" } }, "@babel/traverse": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.4.tgz", - "integrity": "sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", + "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", "requires": { "@babel/code-frame": "7.0.0", - "@babel/generator": "7.1.3", + "@babel/generator": "7.1.6", "@babel/helper-function-name": "7.1.0", "@babel/helper-split-export-declaration": "7.0.0", - "@babel/parser": "7.1.3", - "@babel/types": "7.1.3", - "debug": "3.2.6", - "globals": "11.8.0", + "@babel/parser": "7.1.6", + "@babel/types": "7.1.6", + "debug": "4.1.0", + "globals": "11.9.0", "lodash": "4.17.11" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", "requires": { "ms": "2.1.1" } @@ -122,9 +122,9 @@ } }, "@babel/types": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", - "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.6.tgz", + "integrity": "sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w==", "requires": { "esutils": "2.0.2", "lodash": "4.17.11", @@ -132,61 +132,117 @@ } }, "@bentley/bwc": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@bentley/bwc/-/bwc-7.0.1.tgz", - "integrity": "sha1-Zp94VolxKcWdx6rKFSmr7aVaL9c=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@bentley/bwc/-/bwc-7.1.0.tgz", + "integrity": "sha1-/swac26E+xBn94QxuFJH69S3m9I=", "requires": { "classnames": "2.2.6", "highlight.js": "9.13.1", "js-beautify": "1.8.8", - "react": "16.6.0", - "react-dom": "16.6.0", + "react": "16.6.3", + "react-dom": "16.6.3", "react-syntax-highlighter": "3.0.2" } }, "@bentley/icons-generic-webfont": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@bentley/icons-generic-webfont/-/icons-generic-webfont-0.0.5.tgz", - "integrity": "sha1-O0KPBOcSPHOVHnP4uyBhEhBgbNQ=" + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@bentley/icons-generic-webfont/-/icons-generic-webfont-0.0.6.tgz", + "integrity": "sha1-vqSw5td04IBnOy4HvLWX+EVcAbI=" }, "@bentley/imodeljs-native-platform-api": { - "version": "0.66.0", - "resolved": "https://registry.npmjs.org/@bentley/imodeljs-native-platform-api/-/imodeljs-native-platform-api-0.66.0.tgz", - "integrity": "sha1-klrPlb7AXYfoFf3LM7z2hXcxXZg=" + "version": "0.71.0", + "resolved": "https://registry.npmjs.org/@bentley/imodeljs-native-platform-api/-/imodeljs-native-platform-api-0.71.0.tgz", + "integrity": "sha1-ttBUbXfnG0PWLys7KVwp3i8oCfg=" }, "@fimbul/bifrost": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.11.0.tgz", - "integrity": "sha512-GspMaQafpaUoXWWOUgNLQ4vsV52tIHUt0zpKPeJUYEyMvOSp7FIcZ1eQa7SK3GTusrEiksjMrDX/fwanigC3nQ==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.15.0.tgz", + "integrity": "sha512-sHTwnwA9YhxcVEJkBlfKH1KLmGQGnNYPxk+09w5NnkXelYiiP8a5f351weYfxG0CUPLt1Fgkha20Y/9+jhjn/Q==", "requires": { - "@fimbul/ymir": "0.11.0", - "get-caller-file": "1.0.3", + "@fimbul/ymir": "0.15.0", + "get-caller-file": "2.0.0", "tslib": "1.9.3", - "tsutils": "2.29.0" + "tsutils": "3.5.2" + }, + "dependencies": { + "tsutils": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.2.tgz", + "integrity": "sha512-qIlklNuI/1Dzfm+G+kJV5gg3gimZIX5haYtIVQe7qGyKd7eu8T1t1DY6pz4Sc2CGXAj9s1izycctm9Zfl9sRuQ==", + "requires": { + "tslib": "1.9.3" + } + } } }, "@fimbul/ymir": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.11.0.tgz", - "integrity": "sha512-aIYQMCWbBXe7DIofgu+4DLCPDCfqbKhPjBg4ajskJdq6CAJgySz6KyhGLNnKiDYZMF93ZsaEB/y3SafyMi98Mg==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.15.0.tgz", + "integrity": "sha512-Ow0TfxxQ65vIktHcZyXHeDsGKuzJ9Vt6y77R/aOrXQXLMdYHG+XdbiUWzQbtaGOmNzYVkQfINiFnIdvn5Bn24g==", "requires": { - "inversify": "4.14.0", + "inversify": "5.0.1", "reflect-metadata": "0.1.12", "tslib": "1.9.3" } }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "requires": { + "call-me-maybe": "1.0.1", + "glob-to-regexp": "0.3.0" + }, + "dependencies": { + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + } + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + }, + "@openid/appauth": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.1.2.tgz", + "integrity": "sha512-9gPaEiXltrl3vcyYtEYJGKPIAAZz5UVpLIoJq0QULeG0w4Im/omf65YaDw8qCvB6bXTu3qUl1ZrKR4aTPFGoJA==", + "requires": { + "@types/base64-js": "1.2.5", + "@types/jquery": "3.3.22", + "base64-js": "1.3.0", + "follow-redirects": "1.5.10", + "form-data": "2.3.3", + "opener": "1.5.1" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.7", + "mime-types": "2.1.21" + } + } + } + }, "@rush-temp/agent-test-app": { "version": "file:projects/agent-test-app.tgz", - "integrity": "sha1-qrTo/dHQcyVLQ41i8OaHGKGRa4M=", + "integrity": "sha1-xjxiNzMdbiwcm4koc6w/rRHO5nY=", "requires": { "@types/body-parser": "1.17.0", - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/express": "4.16.0", "@types/express-session": "1.15.11", "@types/minimist": "1.2.0", "@types/mocha": "5.2.5", "@types/node": "10.10.3", - "@types/passport": "0.4.6", + "@types/passport": "0.4.7", "body-parser": "1.18.3", "chai": "4.2.0", "cookie-parser": "1.4.3", @@ -197,22 +253,22 @@ "express-session": "1.15.6", "minimist": "1.2.0", "mocha": "5.2.0", - "npm-run-all": "4.1.3", + "npm-run-all": "4.1.5", "nyc": "13.1.0", - "openid-client": "2.4.4", + "openid-client": "2.4.5", "passport": "0.4.0", "path": "0.12.7", - "react": "16.6.0", + "react": "16.6.3", "tslint": "5.11.0", "typemoq": "2.1.0", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/bentleyjs-core": { "version": "file:projects/bentleyjs-core.tgz", - "integrity": "sha1-QMet937dNe3Pw+2ux5euOK12mOM=", + "integrity": "sha1-0u6D8G3eJUfJbeZNkpqt7tsrT90=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/mocha": "5.2.5", "@types/node": "10.10.3", "chai": "4.2.0", @@ -224,12 +280,12 @@ "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/build-tools": { "version": "file:projects/build-tools.tgz", - "integrity": "sha1-OtyYJrAVdZmAPp3JNgL+w/YI/fw=", + "integrity": "sha1-nJipiCVMl8Elp8krE7/4o76t5Ro=", "requires": { "cache-require-paths": "0.3.0", "chai": "4.2.0", @@ -239,7 +295,7 @@ "comment-json": "1.1.3", "cpx": "1.5.0", "cross-spawn": "6.0.5", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "fs-extra": "6.0.1", "glob": "7.1.3", @@ -254,17 +310,17 @@ "recursive-readdir": "2.2.2", "rimraf": "2.6.2", "ts-node": "7.0.1", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", - "tslint-consistent-codestyle": "1.13.3", + "tslint-consistent-codestyle": "1.14.1", "typedoc": "0.11.1", - "typescript": "3.0.3", - "yargs": "12.0.2" + "typescript": "3.1.6", + "yargs": "12.0.5" } }, "@rush-temp/config-loader": { "version": "file:projects/config-loader.tgz", - "integrity": "sha1-OPCvnAOzOeoThfmekqUDVoDbpPQ=", + "integrity": "sha1-NqekFoVKGkUtQilii9nHKODx6d8=", "requires": { "@types/json5": "0.0.30", "@types/node": "10.10.3", @@ -272,54 +328,82 @@ "json5": "2.1.0", "rimraf": "2.6.2", "tslint": "5.11.0", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, - "@rush-temp/dev-cors-proxy-server": { - "version": "file:projects/dev-cors-proxy-server.tgz", - "integrity": "sha1-6WL8rgrZq2xFj/7tu/NKirk71k4=", + "@rush-temp/display-performance-test-app": { + "version": "file:projects/display-performance-test-app.tgz", + "integrity": "sha1-aWG1NjubIF0R0Tv4B8EAk7wHsMo=", "requires": { - "coveralls": "2.13.3", - "cross-env": "5.2.0", - "eslint": "2.13.1", - "http-proxy": "1.11.1", - "https-proxy-agent": "2.2.1", - "istanbul": "0.4.5", - "lolex": "2.7.5", - "mocha": "5.2.0", - "nock": "9.6.1", - "proxy-from-env": "0.0.1", + "@types/body-parser": "1.17.0", + "@types/express": "4.16.0", + "@types/node": "10.10.3", + "body-parser": "1.18.3", + "child_process": "1.0.2", + "cpx": "1.5.0", + "electron": "2.0.14", + "express": "4.16.4", + "node-glob": "1.2.0", + "null-loader": "0.1.1", + "popper.js": "1.14.6", "rimraf": "2.6.2", - "supertest": "2.0.1" + "source-map-loader": "0.2.4", + "tooltip.js": "1.3.1", + "tslint": "5.11.0", + "typescript": "3.1.6", + "webpack": "4.26.1" + } + }, + "@rush-temp/display-test-app": { + "version": "file:projects/display-test-app.tgz", + "integrity": "sha1-sXtGSFpDbdtiwYp3itZ3TzMO58M=", + "requires": { + "@types/body-parser": "1.17.0", + "@types/express": "4.16.0", + "@types/node": "10.10.3", + "body-parser": "1.18.3", + "child_process": "1.0.2", + "cpx": "1.5.0", + "electron": "2.0.14", + "express": "4.16.4", + "node-glob": "1.2.0", + "null-loader": "0.1.1", + "popper.js": "1.14.6", + "rimraf": "2.6.2", + "source-map-loader": "0.2.4", + "tooltip.js": "1.3.1", + "tslint": "5.11.0", + "typescript": "3.1.6", + "webpack": "4.26.1" } }, "@rush-temp/ecschema-metadata": { "version": "file:projects/ecschema-metadata.tgz", - "integrity": "sha1-jZanV2BZ+PZxjg/H5tJ7sUyopsU=", + "integrity": "sha1-XNyV5gA2xAJoLEFOwphgFHhac94=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/glob": "5.0.36", "@types/mocha": "5.2.5", - "@types/sinon": "5.0.5", + "@types/sinon": "5.0.7", "chai": "4.2.0", "chai-as-promised": "7.1.1", "glob": "7.1.3", "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", - "sinon": "6.3.5", + "sinon": "7.1.1", "ts-node": "7.0.1", "typedoc": "0.11.1", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/example-code-app": { "version": "file:projects/example-code-app.tgz", - "integrity": "sha1-gDT4o52/5DjNURV+TgyxWoU3iXo=", + "integrity": "sha1-ZMKE/sO7fPQUvvtRYGUAg9fJxUI=", "requires": { "@types/body-parser": "1.17.0", - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/express": "4.16.0", "@types/fs-extra": "4.0.8", "@types/i18next": "8.4.6", @@ -333,29 +417,29 @@ "chai": "4.2.0", "commander": "2.19.0", "cpx": "1.5.0", - "electron": "2.0.12", + "electron": "2.0.14", "express": "4.16.4", "fs-extra": "6.0.1", "fuse.js": "3.3.0", "i18next": "10.6.0", - "i18next-browser-languagedetector": "2.2.3", + "i18next-browser-languagedetector": "2.2.4", "i18next-xhr-backend": "1.5.1", "js-base64": "2.4.9", "mocha": "5.2.0", "rimraf": "2.6.2", - "save": "2.3.2", + "save": "2.3.3", "tslint": "5.11.0", - "typescript": "3.0.3", - "webpack": "4.23.1", + "typescript": "3.1.6", + "webpack": "4.26.1", "xmlhttprequest": "1.8.0" } }, "@rush-temp/example-code-snippets": { "version": "file:projects/example-code-snippets.tgz", - "integrity": "sha1-5XaKwH5Q4RAesPwTf74Ebx1DK0Y=", + "integrity": "sha1-MQ678yGLHmio1ql4RI3mO6Fk1PY=", "requires": { "@types/body-parser": "1.17.0", - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/express": "4.16.0", "@types/fs-extra": "4.0.8", "@types/i18next": "8.4.6", @@ -369,27 +453,27 @@ "chai": "4.2.0", "commander": "2.19.0", "cpx": "1.5.0", - "electron": "2.0.12", + "electron": "2.0.14", "express": "4.16.4", "fs-extra": "6.0.1", "fuse.js": "3.3.0", "i18next": "10.6.0", - "i18next-browser-languagedetector": "2.2.3", + "i18next-browser-languagedetector": "2.2.4", "i18next-xhr-backend": "1.5.1", "js-base64": "2.4.9", "mocha": "5.2.0", "rimraf": "2.6.2", - "save": "2.3.2", + "save": "2.3.3", "tslint": "5.11.0", - "typescript": "3.0.3", - "webpack": "4.23.1" + "typescript": "3.1.6", + "webpack": "4.26.1" } }, "@rush-temp/geometry-core": { "version": "file:projects/geometry-core.tgz", - "integrity": "sha1-z4RlAb/P7Kmj9M+VkEE8bPtGhPQ=", + "integrity": "sha1-aCZXLWzkrfTp/YmGZKAc5WYC8uI=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/mocha": "5.2.5", "@types/node": "10.10.3", "chai": "4.2.0", @@ -404,15 +488,30 @@ "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3" + "typescript": "3.1.6" + } + }, + "@rush-temp/imodel-from-geojson": { + "version": "file:projects/imodel-from-geojson.tgz", + "integrity": "sha1-gvc96UP8P+TMuqILOYNWXcHwQ+4=", + "requires": { + "@types/fs-extra": "4.0.8", + "@types/lodash": "4.14.118", + "@types/node": "10.10.3", + "@types/yargs": "12.0.1", + "fs-extra": "6.0.1", + "rimraf": "2.6.2", + "tslint": "5.11.0", + "typescript": "3.1.6", + "yargs": "12.0.5" } }, "@rush-temp/imodeljs-backend": { "version": "file:projects/imodeljs-backend.tgz", - "integrity": "sha1-awOa6aQy0eXXAZ1XImZG0Wksbh8=", + "integrity": "sha1-9LudamItV06D7IDify7SdqghqcY=", "requires": { - "@bentley/imodeljs-native-platform-api": "0.66.0", - "@types/chai": "4.1.6", + "@bentley/imodeljs-native-platform-api": "0.71.0", + "@types/chai": "4.1.7", "@types/express": "4.16.0", "@types/form-data": "2.2.1", "@types/fs-extra": "4.0.8", @@ -426,7 +525,7 @@ "body-parser": "1.18.3", "chai": "4.2.0", "cpx": "1.5.0", - "electron": "2.0.12", + "electron": "2.0.14", "form-data": "2.3.2", "fs-extra": "6.0.1", "glob": "7.1.3", @@ -442,15 +541,15 @@ "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", - "webpack": "4.23.1" + "typescript": "3.1.6", + "webpack": "4.26.1" } }, "@rush-temp/imodeljs-clients": { "version": "file:projects/imodeljs-clients.tgz", - "integrity": "sha1-PDFLq/GvboK5pRy3kRWiFAtiSvg=", + "integrity": "sha1-47PNCOa/EPooBFPd9hna85tmpVM=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/deep-assign": "0.1.1", "@types/fs-extra": "4.0.8", "@types/js-base64": "2.3.1", @@ -468,25 +567,26 @@ "mocha": "5.2.0", "nock": "9.6.1", "nyc": "13.1.0", - "oidc-client": "1.5.3", - "qs": "6.5.2", + "oidc-client": "1.5.4", + "qs": "6.6.0", "source-map-support": "0.5.9", "superagent": "3.8.3", "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3", - "webpack": "4.23.1", + "typescript": "3.1.6", + "webpack": "4.26.1", "xmldom": "0.1.27", "xpath": "0.0.27" } }, "@rush-temp/imodeljs-clients-backend": { "version": "file:projects/imodeljs-clients-backend.tgz", - "integrity": "sha1-J1GcbvBKM5K2Zfo3mXnixnbL/UU=", + "integrity": "sha1-TZfBh8HW8SIOYtWsVlqQeN/D+RU=", "requires": { - "@types/chai": "4.1.6", + "@openid/appauth": "1.1.2", + "@types/chai": "4.1.7", "@types/mocha": "5.2.5", "@types/nock": "9.3.0", "@types/node": "10.10.3", @@ -495,33 +595,43 @@ "mocha": "5.2.0", "nock": "9.6.1", "nyc": "13.1.0", - "openid-client": "2.4.4", + "openid-client": "2.4.5", "source-map-support": "0.5.9", "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3", - "webpack": "4.23.1" + "typescript": "3.1.6", + "webpack": "4.26.1" } }, "@rush-temp/imodeljs-common": { "version": "file:projects/imodeljs-common.tgz", - "integrity": "sha1-kNfvTn31JMHBlm3V3PgW/xsaIY0=", + "integrity": "sha1-AkAFHRmfvJlugxe/9LP8k7xZi4Q=", "requires": { + "@types/chai": "4.1.7", + "@types/mocha": "5.2.5", + "@types/node": "10.10.3", "@types/semver": "5.5.0", + "@types/url-search-params": "0.10.2", + "chai": "4.2.0", "cpx": "1.5.0", + "mocha": "5.2.0", + "nyc": "13.1.0", "rimraf": "2.6.2", "semver": "5.6.0", + "source-map-support": "0.5.9", + "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3" + "typescript": "3.1.6", + "url-search-params": "1.1.0" } }, "@rush-temp/imodeljs-frontend": { "version": "file:projects/imodeljs-frontend.tgz", - "integrity": "sha1-1m9eakdn0dVgF9OlUkDuFtljZo8=", + "integrity": "sha1-eiiIWdfrvA868f9J4sNYeYtRvgc=", "requires": { "@types/i18next": "8.4.6", "@types/i18next-browser-languagedetector": "2.0.1", @@ -531,70 +641,70 @@ "cpx": "1.5.0", "fuse.js": "3.3.0", "i18next": "10.6.0", - "i18next-browser-languagedetector": "2.2.3", + "i18next-browser-languagedetector": "2.2.4", "i18next-xhr-backend": "1.5.1", "js-base64": "2.4.9", "rimraf": "2.6.2", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/imodeljs-i18n": { "version": "file:projects/imodeljs-i18n.tgz", - "integrity": "sha1-yeQOuypXTZVwqShoNIU/OvCX804=", + "integrity": "sha1-bj6BIIss17HyPWaU8PXVH14VSPs=", "requires": { "@types/i18next": "8.4.6", "@types/i18next-browser-languagedetector": "2.0.1", "@types/i18next-xhr-backend": "1.4.1", "@types/node": "10.10.3", "i18next": "10.6.0", - "i18next-browser-languagedetector": "2.2.3", + "i18next-browser-languagedetector": "2.2.4", "i18next-xhr-backend": "1.5.1", "rimraf": "2.6.2", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/imodeljs-quantity": { "version": "file:projects/imodeljs-quantity.tgz", - "integrity": "sha1-uubLS3dSpz80xV7qHDf2XTdFbFk=", + "integrity": "sha1-/88yms0iDCdZp7b54KFSdxruP0w=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/glob": "5.0.36", "@types/mocha": "5.2.5", - "@types/sinon": "5.0.5", + "@types/sinon": "5.0.7", "chai": "4.2.0", "chai-as-promised": "7.1.1", "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", - "sinon": "6.3.5", + "sinon": "7.1.1", "ts-node": "7.0.1", "typedoc": "0.11.1", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/presentation-backend": { "version": "file:projects/presentation-backend.tgz", - "integrity": "sha1-oVUx4MNgUtQDiOtiUMnK10vE3aE=", + "integrity": "sha1-7Nj7js2mEgQPc0l7AcxvYKutNco=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", - "@types/chai-spies": "1.0.0", "@types/deep-equal": "1.0.1", "@types/faker": "4.1.4", "@types/lolex": "2.1.3", "@types/mocha": "5.2.5", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", - "chai-spies": "1.0.0", "cpx": "1.5.0", "cross-env": "5.2.0", "deep-equal": "1.0.1", @@ -603,30 +713,32 @@ "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/presentation-common": { "version": "file:projects/presentation-common.tgz", - "integrity": "sha1-cyDyBWb6MM8tKXSK7whv29S1G1M=", + "integrity": "sha1-OeEIuyQ1tR8dWMuLHAxVtFQaYvw=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", - "@types/chai-spies": "1.0.0", "@types/deep-equal": "1.0.1", "@types/faker": "4.1.4", "@types/mocha": "5.2.5", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "@types/source-map-support": "0.4.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", - "chai-spies": "1.0.0", "cpx": "1.5.0", "cross-env": "5.2.0", "deep-equal": "1.0.1", @@ -635,71 +747,75 @@ "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", "tslint": "5.11.0", - "tslint-consistent-codestyle": "1.13.3", + "tslint-consistent-codestyle": "1.14.1", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "typescript-json-schema": "0.28.0", - "yargs": "12.0.2" + "yargs": "12.0.5" } }, "@rush-temp/presentation-components": { "version": "file:projects/presentation-components.tgz", - "integrity": "sha1-P9GypTv3yghbDcOMsfbkHEA+HZA=", + "integrity": "sha1-Au4WKZAjdsW2e1Mm4vECHIxYo6s=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", - "@types/chai-spies": "1.0.0", - "@types/enzyme": "3.1.14", + "@types/enzyme": "3.1.15", "@types/faker": "4.1.4", - "@types/lodash": "4.14.117", + "@types/lodash": "4.14.118", "@types/mocha": "5.2.5", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", - "chai-spies": "1.0.0", "cpx": "1.5.0", "cross-env": "5.2.0", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "faker": "4.1.0", "lodash": "4.17.11", "mocha": "5.2.0", "nyc": "13.1.0", - "react": "16.6.0", - "react-dom": "16.6.0", + "react": "16.6.3", + "react-dom": "16.6.3", "rimraf": "2.6.2", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/presentation-frontend": { "version": "file:projects/presentation-frontend.tgz", - "integrity": "sha1-Lw/M3P+QU+cqJvhnlZD9dVFMkkg=", + "integrity": "sha1-ch9H+92Rxkfc87hgtk39GacEN9g=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", - "@types/chai-spies": "1.0.0", "@types/deep-equal": "1.0.1", "@types/faker": "4.1.4", "@types/mocha": "5.2.5", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", - "chai-spies": "1.0.0", "cpx": "1.5.0", "cross-env": "5.2.0", "deep-equal": "1.0.1", @@ -707,33 +823,35 @@ "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/presentation-integration-tests": { "version": "file:projects/presentation-integration-tests.tgz", - "integrity": "sha1-ygnLcntA2wv/8CjN7wgI1hgiQoY=", + "integrity": "sha1-tX8W4n+EJdE6dhGIYFmc1M2yeBA=", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", - "@types/chai-spies": "1.0.0", "@types/cpx": "1.5.0", "@types/deep-equal": "1.0.1", "@types/faker": "4.1.4", "@types/mocha": "5.2.5", "@types/rimraf": "2.0.2", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "cache-require-paths": "0.3.0", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", - "chai-spies": "1.0.0", "cpx": "1.5.0", "cross-env": "5.2.0", "deep-equal": "1.0.1", @@ -741,38 +859,40 @@ "mocha": "5.2.0", "nyc": "13.1.0", "rimraf": "2.6.2", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", "tslint": "5.11.0", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/presentation-test-app": { "version": "file:projects/presentation-test-app.tgz", - "integrity": "sha1-nnD+FhoJfF2+J4OE+KXKas/utdA=", + "integrity": "sha1-HS6R1gY1is3JDEHo51cUtr+eCwU=", "requires": { - "@bentley/bwc": "7.0.1", + "@bentley/bwc": "7.1.0", "@types/body-parser": "1.17.0", "@types/bunyan": "1.8.5", "@types/express": "4.16.0", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", "body-parser": "1.18.3", "bunyan": "1.8.12", - "electron": "2.0.12", + "electron": "2.0.14", "express": "4.16.4", - "react": "16.6.0", - "react-dom": "16.6.0", + "react": "16.6.3", + "react-dom": "16.6.3", "rimraf": "2.6.2", "tslint": "5.11.0", - "typescript": "3.0.3", - "webpack": "4.23.1" + "typescript": "3.1.6", + "webpack": "4.26.1" } }, - "@rush-temp/simpleviewtest": { - "version": "file:projects/simpleviewtest.tgz", - "integrity": "sha1-VkCi6PcyW6w3acpSQhTEiS0t524=", + "@rush-temp/test-apps-analysis-importer": { + "version": "file:projects/test-apps-analysis-importer.tgz", + "integrity": "sha1-pKMIXE/em7GV/5sorHNVLcMKXG8=", "requires": { "@types/body-parser": "1.17.0", "@types/express": "4.16.0", @@ -780,25 +900,23 @@ "body-parser": "1.18.3", "child_process": "1.0.2", "cpx": "1.5.0", - "electron": "2.0.12", "express": "4.16.4", "node-glob": "1.2.0", "null-loader": "0.1.1", - "popper.js": "1.14.4", + "popper.js": "1.14.6", "rimraf": "2.6.2", "source-map-loader": "0.2.4", - "tooltip.js": "1.3.0", "tslint": "5.11.0", - "typescript": "3.0.3", - "webpack": "4.23.1" + "typescript": "3.1.6", + "webpack": "4.26.1" } }, "@rush-temp/testbed": { "version": "file:projects/testbed.tgz", - "integrity": "sha1-OSRgk3ob3BNFjaUwAh+Cy6gTuHI=", + "integrity": "sha1-kJmHOcCTiM673rAHotXPse+68Z4=", "requires": { "@types/body-parser": "1.17.0", - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/exceljs": "0.5.2", "@types/express": "4.16.0", "@types/fs-extra": "4.0.8", @@ -815,14 +933,14 @@ "chai": "4.2.0", "commander": "2.19.0", "cpx": "1.5.0", - "electron": "2.0.12", - "exceljs": "1.6.2", + "electron": "2.0.14", + "exceljs": "1.6.3", "express": "4.16.4", "find-root": "1.1.0", "fs-extra": "6.0.1", "fuse.js": "3.3.0", "i18next": "10.6.0", - "i18next-browser-languagedetector": "2.2.3", + "i18next-browser-languagedetector": "2.2.4", "i18next-xhr-backend": "1.5.1", "istanbul-lib-hook": "1.2.2", "istanbul-lib-instrument": "2.3.2", @@ -834,45 +952,50 @@ "object-assign": "4.1.1", "resolve": "1.8.1", "rimraf": "2.6.2", - "save": "2.3.2", + "save": "2.3.3", "semver": "5.6.0", "source-map-loader": "0.2.4", "tslint": "5.11.0", - "typescript": "3.0.3", - "webpack": "4.23.1", - "ws": "6.1.0" + "typescript": "3.1.6", + "webpack": "4.26.1", + "ws": "6.1.2" } }, "@rush-temp/ui-components": { "version": "file:projects/ui-components.tgz", - "integrity": "sha1-YzYEuHcypDWFgOQkvkppLB52bOc=", + "integrity": "sha1-BWdWcHhBb95S0yM5AV4HZeAt7UA=", "requires": { - "@bentley/bwc": "7.0.1", - "@bentley/icons-generic-webfont": "0.0.5", - "@types/chai": "4.1.6", + "@bentley/bwc": "7.1.0", + "@bentley/icons-generic-webfont": "0.0.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", "@types/chai-spies": "1.0.0", + "@types/chai-string": "1.4.1", "@types/classnames": "2.2.6", - "@types/enzyme": "3.1.14", + "@types/enzyme": "3.1.15", "@types/faker": "4.1.4", - "@types/lodash": "4.14.117", + "@types/lodash": "4.14.118", "@types/mocha": "5.2.5", - "@types/react": "16.4.18", - "@types/react-data-grid": "2.0.14", + "@types/react": "16.7.11", + "@types/react-data-grid": "4.0.0", "@types/react-dom": "16.0.7", "@types/react-highlight-words": "0.11.1", - "@types/sinon": "5.0.5", - "@types/sinon-chai": "3.2.0", + "@types/react-resize-detector": "3.1.0", + "@types/react-virtualized": "9.18.11", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", + "callable-instance2": "1.0.0", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", "chai-spies": "1.0.0", + "chai-string": "1.5.0", "classnames": "2.2.6", "cpx": "1.5.0", "cross-env": "5.2.0", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "eventemitter2": "5.0.1", "faker": "4.1.0", @@ -883,45 +1006,49 @@ "lodash": "4.17.11", "mocha": "5.2.0", "nyc": "13.1.0", - "raf": "3.4.0", - "react": "16.6.0", - "react-data-grid": "3.0.11", + "raf": "3.4.1", + "react": "16.6.3", + "react-data-grid": "4.0.9", "react-dnd": "5.0.0", "react-dnd-html5-backend": "5.0.1", "react-dnd-test-backend": "5.0.1", - "react-dom": "16.6.0", + "react-dom": "16.6.3", "react-highlight-words": "0.14.0", - "react-testing-library": "5.2.3", + "react-resize-detector": "3.2.1", + "react-testing-library": "5.3.1", + "react-virtualized": "9.21.0", + "resize-observer-polyfill": "1.5.0", "rimraf": "2.6.2", - "sinon": "6.3.5", - "sinon-chai": "3.2.0", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", + "ts-key-enum": "2.0.0", "ts-node": "7.0.1", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/ui-core": { "version": "file:projects/ui-core.tgz", - "integrity": "sha1-DAplSG9LmHsLDMaNBzEDMD6y2Iw=", + "integrity": "sha1-zxwPqxImWIE8fsizLloxcPhLkbo=", "requires": { - "@bentley/bwc": "7.0.1", - "@bentley/icons-generic-webfont": "0.0.5", - "@types/chai": "4.1.6", + "@bentley/bwc": "7.1.0", + "@bentley/icons-generic-webfont": "0.0.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", "@types/chai-spies": "1.0.0", "@types/classnames": "2.2.6", - "@types/enzyme": "3.1.14", + "@types/enzyme": "3.1.15", "@types/mocha": "5.2.5", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", - "@types/sinon": "5.0.5", - "@types/sinon-chai": "3.2.0", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", @@ -931,46 +1058,46 @@ "cross-env": "5.2.0", "csstype": "2.5.7", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "ignore-styles": "5.0.1", "jsdom": "11.12.0", "jsdom-global": "3.0.2", "mocha": "5.2.0", "nyc": "13.1.0", - "raf": "3.4.0", - "react": "16.6.0", - "react-dom": "16.6.0", + "raf": "3.4.1", + "react": "16.6.3", + "react-dom": "16.6.3", "rimraf": "2.6.2", - "sinon": "6.3.5", - "sinon-chai": "3.2.0", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/ui-framework": { "version": "file:projects/ui-framework.tgz", - "integrity": "sha1-titaBnHjcFo62ydaYF61Azr+hRY=", + "integrity": "sha1-Og4OyVbgJ1si4UD28rMeD+R4xhU=", "requires": { - "@bentley/bwc": "7.0.1", - "@bentley/icons-generic-webfont": "0.0.5", - "@types/chai": "4.1.6", + "@bentley/bwc": "7.1.0", + "@bentley/icons-generic-webfont": "0.0.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", "@types/chai-spies": "1.0.0", "@types/classnames": "2.2.6", - "@types/enzyme": "3.1.14", + "@types/enzyme": "3.1.15", "@types/mocha": "5.2.5", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", "@types/react-redux": "5.0.21", - "@types/sinon": "5.0.5", - "@types/sinon-chai": "3.2.0", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", @@ -979,7 +1106,7 @@ "cpx": "1.5.0", "cross-env": "5.2.0", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "ignore-styles": "5.0.1", "immutable": "3.8.2", @@ -987,46 +1114,45 @@ "jsdom-global": "3.0.2", "mocha": "5.2.0", "nyc": "13.1.0", - "oidc-client": "1.5.3", - "raf": "3.4.0", - "react": "16.6.0", + "oidc-client": "1.5.4", + "raf": "3.4.1", + "react": "16.6.3", "react-dnd": "5.0.0", "react-dnd-html5-backend": "5.0.1", - "react-dom": "16.6.0", - "react-redux": "5.1.0", - "react-split-pane": "0.1.84", + "react-dom": "16.6.3", + "react-redux": "5.1.1", + "react-split-pane": "0.1.77", "redux": "4.0.1", - "redux-oidc": "3.1.0", "rimraf": "2.6.2", - "sinon": "6.3.5", - "sinon-chai": "3.2.0", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "ts-node": "7.0.1", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "xmlhttprequest": "1.8.0" } }, "@rush-temp/ui-ninezone": { "version": "file:projects/ui-ninezone.tgz", - "integrity": "sha1-5Wm9uYmor7dCobd04uno5Gaxuec=", + "integrity": "sha1-5A/zUPLR9azU45KceiW2UcKf3Mo=", "requires": { - "@bentley/bwc": "7.0.1", - "@bentley/icons-generic-webfont": "0.0.5", - "@types/chai": "4.1.6", + "@bentley/bwc": "7.1.0", + "@bentley/icons-generic-webfont": "0.0.6", + "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-jest-snapshot": "1.3.4", "@types/chai-spies": "1.0.0", "@types/classnames": "2.2.6", - "@types/enzyme": "3.1.14", + "@types/enzyme": "3.1.15", "@types/mocha": "5.2.5", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", "@types/react-router-dom": "4.3.1", - "@types/sinon": "5.0.5", + "@types/sinon": "5.0.7", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-jest-snapshot": "2.0.0", @@ -1036,7 +1162,7 @@ "cross-env": "5.2.0", "css-loader": "0.28.11", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "html-webpack-plugin": "4.0.0-alpha.2", "ignore-styles": "5.0.1", @@ -1044,75 +1170,75 @@ "jsdom-global": "3.0.2", "mocha": "5.2.0", "nyc": "13.1.0", - "raf": "3.4.0", + "raf": "3.4.1", "raw-loader": "0.5.1", - "react": "16.6.0", - "react-dom": "16.6.0", + "react": "16.6.3", + "react-dom": "16.6.3", "react-markdown": "3.6.0", "react-router-dom": "4.3.1", "rimraf": "2.6.2", "sass-loader": "7.1.0", - "sinon": "6.3.5", - "sinon-chai": "3.2.0", + "sinon": "7.1.1", + "sinon-chai": "3.3.0", "style-loader": "0.21.0", "svg-react-loader": "0.4.6", "ts-loader": "4.5.0", "ts-node": "7.0.1", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", "tslint-loader": "3.6.0", "typedoc": "0.11.1", "typedoc-plugin-external-module-name": "1.1.3", "typemoq": "2.1.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "url-loader": "1.1.2", - "webpack": "4.23.1", + "webpack": "4.26.1", "webpack-cli": "3.1.2", "webpack-dev-server": "3.1.10" } }, "@rush-temp/ui-test-app": { "version": "file:projects/ui-test-app.tgz", - "integrity": "sha1-8erbZH8aEADxXJluOMVpElJftMU=", + "integrity": "sha1-6y9hPOl1rHtZqQFjLJ+npo/Dz8k=", "requires": { - "@bentley/bwc": "7.0.1", - "@bentley/icons-generic-webfont": "0.0.5", + "@bentley/bwc": "7.1.0", + "@bentley/icons-generic-webfont": "0.0.6", "@types/body-parser": "1.17.0", "@types/express": "4.16.0", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", "@types/react-redux": "5.0.21", "@types/semver": "5.5.0", "body-parser": "1.18.3", "cpx": "1.5.0", - "electron": "2.0.12", + "electron": "2.0.14", "electron-chromedriver": "2.0.0", "express": "4.16.4", "immutable": "3.8.2", - "react": "16.6.0", - "react-dom": "16.6.0", - "react-redux": "5.1.0", - "react-test-renderer": "16.6.0", + "react": "16.6.3", + "react-dom": "16.6.3", + "react-redux": "5.1.1", + "react-test-renderer": "16.6.3", "redux": "4.0.1", "rimraf": "2.6.2", "semver": "5.6.0", "svg-sprite-loader": "3.9.2", - "tsconfig-paths": "3.6.0", + "tsconfig-paths": "3.7.0", "tslint": "5.11.0", - "typescript": "3.0.3" + "typescript": "3.1.6" } }, "@rush-temp/webpack-tools": { "version": "file:projects/webpack-tools.tgz", - "integrity": "sha1-T2ioT1a0gpfMtE8VFNfHqHgQS6U=", + "integrity": "sha1-TBo3zB7EZQVDuB8DRYjdSyuav4o=", "requires": { - "@types/chai": "4.1.6", - "@types/enzyme": "3.1.14", + "@types/chai": "4.1.7", + "@types/enzyme": "3.1.15", "@types/mocha": "5.2.5", "@types/node": "10.10.3", - "@types/react": "16.4.18", + "@types/react": "16.7.11", "@types/react-dom": "16.0.7", - "@types/sinon": "5.0.5", + "@types/sinon": "5.0.7", "@types/webdriverio": "4.13.0", "app-root-path": "2.1.0", "autoprefixer": "8.6.5", @@ -1120,19 +1246,19 @@ "chai": "4.2.0", "chai-jest-snapshot": "2.0.0", "chalk": "2.4.1", - "chromedriver": "2.43.1", + "chromedriver": "2.44.1", "cli-highlight": "2.0.0", "concurrently": "3.6.1", "css-loader": "0.28.11", "dotenv": "6.1.0", "electron-chromedriver": "2.0.0", "enzyme": "3.7.0", - "enzyme-adapter-react-16": "1.6.0", + "enzyme-adapter-react-16": "1.7.0", "enzyme-to-json": "3.3.4", "escape-string-regexp": "1.0.5", "exports-loader": "0.7.0", "file-loader": "1.1.11", - "fork-ts-checker-webpack-plugin": "0.4.12", + "fork-ts-checker-webpack-plugin": "0.4.15", "fs-extra": "6.0.1", "glob": "7.1.3", "html-webpack-plugin": "4.0.0-alpha.2", @@ -1141,98 +1267,65 @@ "jsdom": "11.12.0", "license-webpack-plugin": "1.5.0", "lodash": "4.17.11", - "mini-css-extract-plugin": "0.4.4", + "mini-css-extract-plugin": "0.4.5", "mocha": "5.2.0", "mocha-junit-reporter": "1.18.0", "mocha-webpack": "2.0.0-beta.0", "node-gyp": "3.6.2", - "node-sass": "4.9.4", - "nodemon": "1.18.5", + "node-sass": "4.10.0", + "nodemon": "1.18.7", "null-loader": "0.1.1", "nyc": "13.1.0", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.3.1", "postcss-loader": "2.1.6", "promise": "8.0.2", - "react": "16.6.0", - "react-dev-utils": "6.0.5", - "react-dom": "16.6.0", - "react-test-renderer": "16.6.0", + "react": "16.6.3", + "react-dev-utils": "6.1.1", + "react-dom": "16.6.3", + "react-test-renderer": "16.6.3", "readline": "1.3.0", "sass-loader": "7.1.0", - "sinon": "6.3.5", + "sinon": "7.1.1", "source-map-loader": "0.2.4", "style-loader": "0.21.0", "svg-sprite-loader": "3.9.2", "sw-precache-webpack-plugin": "0.11.5", - "tree-kill": "1.2.0", + "tree-kill": "1.2.1", "ts-loader": "4.5.0", "tslint": "5.11.0", "tslint-loader": "3.6.0", - "typescript": "3.0.3", + "typescript": "3.1.6", "uglifyjs-webpack-plugin": "1.3.0", "url-loader": "1.1.2", "wdio-chromedriver-service": "0.1.3", "wdio-junit-reporter": "0.4.4", - "wdio-mocha-framework": "0.6.3", + "wdio-mocha-framework": "0.6.4", "wdio-screenshot": "0.6.0", "wdio-spec-reporter": "0.1.5", "wdio-visual-regression-service": "0.9.0", - "webdriverio": "4.14.0", - "webpack": "4.23.1", + "webdriverio": "4.14.1", + "webpack": "4.26.1", "webpack-dev-server": "3.1.10", "webpack-manifest-plugin": "2.0.3", "webpack-merge": "4.1.4", "webpack-node-externals": "1.7.2", "whatwg-fetch": "2.0.4", "yargonaut": "1.1.4", - "yargs": "12.0.2" + "yargs": "12.0.5" }, "dependencies": { "chromedriver": { - "version": "2.43.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.43.1.tgz", - "integrity": "sha512-AqUEHpS+IzYm7HkBwElzVGe26Rs7hQwBoUSA4l7ju6Uymr1l0EtYpYfA3mDBqvvFif80yYh7blR0gLsc7h6Qpw==", + "version": "2.44.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.44.1.tgz", + "integrity": "sha512-IPM8XQzQYVNJ9Rfec5cy0aGbrZno5xUlDNLLuph9bjkTJVoi14zqjvtmRd8Dc1P5vTw0MwNQ5JD89zibXp/W5A==", "requires": { "del": "3.0.0", "extract-zip": "1.6.7", - "kew": "0.7.0", "mkdirp": "0.5.1", "request": "2.88.0", "tcp-port-used": "1.0.1" } - }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "1.0.2", - "glob": "7.1.3", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } } } }, @@ -1247,9 +1340,9 @@ "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" }, "@sinonjs/commons": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.0.2.tgz", - "integrity": "sha512-WR3dlgqJP4QNrLC4iXN/5/2WaLQQ0VijOOkmflqFGVJ6wLEpbSjo7c0ZeGIdtY8Crk7xBBp87sM6+Mkerz7alw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", "requires": { "type-detect": "4.0.8" } @@ -1273,9 +1366,14 @@ } }, "@sinonjs/samsam": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.2.tgz", - "integrity": "sha512-ZwTHAlC9akprWDinwEPD4kOuwaYZlyMwVJIANsKNC3QVp0AHB04m7RnB4eqeWfgmxw8MGTzS9uMaw93Z3QcZbw==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-2.1.3.tgz", + "integrity": "sha512-8zNeBkSKhU9a5cRNbpCKau2WWPfan+Q2zDlcXvXyhn9EsMqgYs4qzo0XHNVlXC6ABQL8fT6nV+zzo5RTHJzyXw==" + }, + "@types/base64-js": { + "version": "1.2.5", + "resolved": "http://registry.npmjs.org/@types/base64-js/-/base64-js-1.2.5.tgz", + "integrity": "sha1-WCskdhaabLpGCiFNR2x0REHYc9U=" }, "@types/body-parser": { "version": "1.17.0", @@ -1295,16 +1393,16 @@ } }, "@types/chai": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.6.tgz", - "integrity": "sha512-CBk7KTZt3FhPsEkYioG6kuCIpWISw+YI8o+3op4+NXwTpvAPxE1ES8+PY8zfaK2L98b1z5oq03UHa4VYpeUxnw==" + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==" }, "@types/chai-as-promised": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", "requires": { - "@types/chai": "4.1.6" + "@types/chai": "4.1.7" } }, "@types/chai-jest-snapshot": { @@ -1312,7 +1410,7 @@ "resolved": "https://registry.npmjs.org/@types/chai-jest-snapshot/-/chai-jest-snapshot-1.3.4.tgz", "integrity": "sha512-l048YOiFs/6L4kZnvSTZ6Pdus024LGlYlF8cqQvU/BYL7TEJZKKnvEkQUHnbrLDMavYSu6bblHNz+ocYbdGBcw==", "requires": { - "@types/chai": "4.1.6", + "@types/chai": "4.1.7", "@types/mocha": "5.2.5" } }, @@ -1321,13 +1419,21 @@ "resolved": "https://registry.npmjs.org/@types/chai-spies/-/chai-spies-1.0.0.tgz", "integrity": "sha512-Bj/froHomMnlAPEYEeqhmSuNSjTWW/VuSvCVdhLdcb67+dy4ffjTR6fC0YYw9tHP6KR3U8fkF1mgzmzlChHc5Q==", "requires": { - "@types/chai": "4.1.6" + "@types/chai": "4.1.7" + } + }, + "@types/chai-string": { + "version": "1.4.1", + "resolved": "http://registry.npmjs.org/@types/chai-string/-/chai-string-1.4.1.tgz", + "integrity": "sha512-aRNMs6TKgjgPlCHwDfq/YNy5VtRR2hJ4AUWByddrT0TRVVD8eX4MiHW6/iHvmQHRlVuuPZcwnTUE7b4yFt7bEA==", + "requires": { + "@types/chai": "4.1.7" } }, "@types/cheerio": { - "version": "0.22.9", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.9.tgz", - "integrity": "sha512-q6LuBI0t5u04f0Q4/R+cGBqIbZMtJkVvCSF+nTfFBBdQqQvJR/mNHeWjRkszyLl7oyf2rDoKUYMEjTw5AV0hiw==" + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.10.tgz", + "integrity": "sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==" }, "@types/classnames": { "version": "2.2.6", @@ -1366,12 +1472,12 @@ "integrity": "sha512-mMUu4nWHLBlHtxXY17Fg6+ucS/MnndyOWyOe7MmwkoMYxvfQU2ajtRaEvqSUv+aVkMqH/C0NCI8UoVfRNQ10yg==" }, "@types/enzyme": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.1.14.tgz", - "integrity": "sha512-jvAbagrpoSNAXeZw2kRpP10eTsSIH8vW1IBLCXbN0pbZsYZU8FvTPMMd5OzSWUKWTQfrbXFUY8e6un/W4NpqIA==", + "version": "3.1.15", + "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.1.15.tgz", + "integrity": "sha512-6b4JWgV+FNec1c4+8HauGbXg5gRc1oQK93t2+4W+bHjG/PzO+iPvagY6d6bXAZ+t+ps51Zb2F9LQ4vl0S0Epog==", "requires": { - "@types/cheerio": "0.22.9", - "@types/react": "16.4.18" + "@types/cheerio": "0.22.10", + "@types/react": "16.7.11" } }, "@types/events": { @@ -1478,6 +1584,14 @@ "resolved": "https://registry.npmjs.org/@types/i18next-xhr-backend/-/i18next-xhr-backend-1.4.1.tgz", "integrity": "sha512-k8YOdiXidKc3Dc9LRQoCceAwPoj34U+PXrUdrbxjz/RttN9SyYeU0YJWmrp1mhp23/TF9q5mJl25G3a4xV/YzA==" }, + "@types/jquery": { + "version": "3.3.22", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.22.tgz", + "integrity": "sha512-a4JDcIhJhHYnoWCkG3xT2CZxXZeA92JeREESorg0DMQ3ZsjuKF48h7XK4l5Gl2GRa/ItGRpKMT0pyK88yRgqXQ==", + "requires": { + "@types/sizzle": "2.3.2" + } + }, "@types/js-base64": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@types/js-base64/-/js-base64-2.3.1.tgz", @@ -1489,9 +1603,9 @@ "integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA==" }, "@types/lodash": { - "version": "4.14.117", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.117.tgz", - "integrity": "sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw==" + "version": "4.14.118", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.118.tgz", + "integrity": "sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw==" }, "@types/lolex": { "version": "2.1.3", @@ -1545,9 +1659,9 @@ "integrity": "sha512-dWk7F3b0m6uDLHero7tsnpAi9r2RGPQHGbb0/VCv7DPJRMFtk3RonY1/29vfJKnheRMBa7+uF+vunlg/mBGlxg==" }, "@types/passport": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-0.4.6.tgz", - "integrity": "sha512-P7TxrdpAze3nvHghYPeLlHkYcFDiIkRBbp7xYz2ehX9zmi1yr/qWQMTpXsMxN5w3ESJpMzn917inK4giASaDcQ==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-0.4.7.tgz", + "integrity": "sha512-EePlxNYx5tf3n0yjdPXX0/zDOv0UCwjMyQo4UkWGlhHteNDItAj7TfDdLttSThVMKQz3uCW7lsGzMuml0f8g9Q==", "requires": { "@types/express": "4.16.0" } @@ -1568,20 +1682,20 @@ "integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==" }, "@types/react": { - "version": "16.4.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.18.tgz", - "integrity": "sha512-eFzJKEg6pdeaukVLVZ8Xb79CTl/ysX+ExmOfAAqcFlCCK5TgFDD9kWR0S18sglQ3EmM8U+80enjUqbfnUyqpdA==", + "version": "16.7.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.7.11.tgz", + "integrity": "sha512-KZvX2XjzBPz+Dh7cjgDzzLxUmg4k8CnCSsB1V2Vtt1YtSIGe1sfHp2+Htl3S5DzeKrHnioaDHTMN/fjAvnEsSg==", "requires": { "@types/prop-types": "15.5.6", "csstype": "2.5.7" } }, "@types/react-data-grid": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@types/react-data-grid/-/react-data-grid-2.0.14.tgz", - "integrity": "sha512-Kq9paywR9wB+vesA4V0sfF4Edo7Xt8Asa2D+cHz4v1nunUjF/oKGIQ7waL5BOuKPg6kVOZQKAS9c6UesMAfCOg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/react-data-grid/-/react-data-grid-4.0.0.tgz", + "integrity": "sha512-55NM6G+EwjBWD9AFVm/JWskpwWozGfkB5zGiDRIv6o6843eCDm4TNmqgcldTF+6mP5JspWkhNVl2huwo5Q7OaA==", "requires": { - "@types/react": "16.4.18" + "@types/react": "16.7.11" } }, "@types/react-dom": { @@ -1590,7 +1704,7 @@ "integrity": "sha512-vaq4vMaJOaNgFff1t3LnHYr6vRa09vRspMkmLdXtFZmO1fwDI2snP+dpOkwrtlU8UC8qsqemCu4RmVM2OLq/fA==", "requires": { "@types/node": "10.10.3", - "@types/react": "16.4.18" + "@types/react": "16.7.11" } }, "@types/react-highlight-words": { @@ -1598,7 +1712,7 @@ "resolved": "https://registry.npmjs.org/@types/react-highlight-words/-/react-highlight-words-0.11.1.tgz", "integrity": "sha512-zBrtCb0qhn6XqmkIPZhDzLjZ41k4ppeGoC5iaZotAwBEThGO0BrcpRTLWX69oWplnuBsbiNFvHj0IcT6gl9mdg==", "requires": { - "@types/react": "16.4.18" + "@types/react": "16.7.11" } }, "@types/react-redux": { @@ -1606,7 +1720,7 @@ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-5.0.21.tgz", "integrity": "sha512-ewkOW4GjnyXq5L++T31utI8yRmwj8iCIahZohYi1Ef7Xkrw0V/q92ao7x20rm38FKgImDaCCsaRGWfCJmF/Ukg==", "requires": { - "@types/react": "16.4.18", + "@types/react": "16.7.11", "redux": "3.7.2" }, "dependencies": { @@ -1623,13 +1737,21 @@ } } }, + "@types/react-resize-detector": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/react-resize-detector/-/react-resize-detector-3.1.0.tgz", + "integrity": "sha512-q/kuav2WHLqCOypvNMWR7S3UKSphE0urlvgkiaKpnGXOPsy6/3BCrr+HzcoaMOvuZW7bFngbheS2gITRl4B1xQ==", + "requires": { + "@types/react": "16.7.11" + } + }, "@types/react-router": { - "version": "4.0.32", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.0.32.tgz", - "integrity": "sha512-VLQSifCIKCTpfMFrJN/nO5a45LduB6qSMkO9ASbcGdCHiDwJnrLNzk91Q895yG0qWY7RqT2jR16giBRpRG1HQw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.4.1.tgz", + "integrity": "sha512-CtQfdcXyMye3vflnQQ2sHU832iDJRoAr4P+7f964KlLYupXU1I5crP1+d/WnCMo6mmtjBjqQvxrtbAbodqerMA==", "requires": { "@types/history": "4.7.2", - "@types/react": "16.4.18" + "@types/react": "16.7.11" } }, "@types/react-router-dom": { @@ -1638,8 +1760,17 @@ "integrity": "sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A==", "requires": { "@types/history": "4.7.2", - "@types/react": "16.4.18", - "@types/react-router": "4.0.32" + "@types/react": "16.7.11", + "@types/react-router": "4.4.1" + } + }, + "@types/react-virtualized": { + "version": "9.18.11", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.18.11.tgz", + "integrity": "sha512-KT2FzDtV9YT2uN+5g9HPaME4Dtdlh7tEHEqTsOExWYzJGZjGKqHgBHTZC0vnHkzU1rXF9rVDxpi5MZuApoO7rA==", + "requires": { + "@types/prop-types": "15.5.6", + "@types/react": "16.7.11" } }, "@types/rimraf": { @@ -1675,19 +1806,24 @@ } }, "@types/sinon": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.5.tgz", - "integrity": "sha512-Wnuv66VhvAD2LEJfZkq8jowXGxe+gjVibeLCYcVBp7QLdw0BFx2sRkKzoiiDkYEPGg5VyqO805Rcj0stVjQwCQ==" + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.7.tgz", + "integrity": "sha512-opwMHufhUwkn/UUDk35LDbKJpA2VBsZT8WLU8NjayvRLGPxQkN+8XmfC2Xl35MAscBE8469koLLBjaI3XLEIww==" }, "@types/sinon-chai": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.0.tgz", - "integrity": "sha512-VpsC3L8/ynicaLJB/aVvLCFn+c2tjo5jJ5MGiy7keoN431LSAKv9xhLO9dhx9vSVJHG8cjfshdqU4+W8Occf7w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.1.tgz", + "integrity": "sha512-6iO999tZP+1cixtz4jGaVS/aKRq2GYvFUhJzHWejYVUfTP0O98mGb0WrCaC9ARZVKfkm2FZrn3c0p3iaHfK+6A==", "requires": { - "@types/chai": "4.1.6", - "@types/sinon": "5.0.5" + "@types/chai": "4.1.7", + "@types/sinon": "5.0.7" } }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" + }, "@types/source-map-support": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.4.1.tgz", @@ -1725,6 +1861,11 @@ } } }, + "@types/url-search-params": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@types/url-search-params/-/url-search-params-0.10.2.tgz", + "integrity": "sha512-AaM3cTaHIbIJecuqf15ystbsUL3rtiJHZHhv6hTu1hBeKbvGwUNdHXfqNiY2LMWIT8ip6SqA+M9wYWIsQiMH2g==" + }, "@types/webdriverio": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/@types/webdriverio/-/webdriverio-4.13.0.tgz", @@ -1765,157 +1906,162 @@ "resolved": "https://registry.npmjs.org/@types/xmldom/-/xmldom-0.1.29.tgz", "integrity": "sha1-xEKLDKhtO4gUdXJv2UmAs4onw4E=" }, + "@types/yargs": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.1.tgz", + "integrity": "sha512-UVjo2oH79aRNcsDlFlnQ/iJ67Jd7j6uSg7jUJP/RZ/nUjAh5ElmnwlD5K/6eGgETJUgCHkiWn91B8JjXQ6ubAw==" + }, "@webassemblyjs/ast": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.10.tgz", - "integrity": "sha512-wTUeaByYN2EA6qVqhbgavtGc7fLTOx0glG2IBsFlrFG51uXIGlYBTyIZMf4SPLo3v1bgV/7lBN3l7Z0R6Hswew==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", + "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==", "requires": { - "@webassemblyjs/helper-module-context": "1.7.10", - "@webassemblyjs/helper-wasm-bytecode": "1.7.10", - "@webassemblyjs/wast-parser": "1.7.10" + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.10.tgz", - "integrity": "sha512-gMsGbI6I3p/P1xL2UxqhNh1ga2HCsx5VBB2i5VvJFAaqAjd2PBTRULc3BpTydabUQEGlaZCzEUQhLoLG7TvEYQ==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz", + "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==" }, "@webassemblyjs/helper-api-error": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.10.tgz", - "integrity": "sha512-DoYRlPWtuw3yd5BOr9XhtrmB6X1enYF0/54yNvQWGXZEPDF5PJVNI7zQ7gkcKfTESzp8bIBWailaFXEK/jjCsw==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz", + "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==" }, "@webassemblyjs/helper-buffer": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.10.tgz", - "integrity": "sha512-+RMU3dt/dPh4EpVX4u5jxsOlw22tp3zjqE0m3ftU2tsYxnPULb4cyHlgaNd2KoWuwasCQqn8Mhr+TTdbtj3LlA==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz", + "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==" }, "@webassemblyjs/helper-code-frame": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.10.tgz", - "integrity": "sha512-UiytbpKAULOEab2hUZK2ywXen4gWJVrgxtwY3Kn+eZaaSWaRM8z/7dAXRSoamhKFiBh1uaqxzE/XD9BLlug3gw==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz", + "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==", "requires": { - "@webassemblyjs/wast-printer": "1.7.10" + "@webassemblyjs/wast-printer": "1.7.11" } }, "@webassemblyjs/helper-fsm": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.10.tgz", - "integrity": "sha512-w2vDtUK9xeSRtt5+RnnlRCI7wHEvLjF0XdnxJpgx+LJOvklTZPqWkuy/NhwHSLP19sm9H8dWxKeReMR7sCkGZA==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz", + "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==" }, "@webassemblyjs/helper-module-context": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.10.tgz", - "integrity": "sha512-yE5x/LzZ3XdPdREmJijxzfrf+BDRewvO0zl8kvORgSWmxpRrkqY39KZSq6TSgIWBxkK4SrzlS3BsMCv2s1FpsQ==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz", + "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==" }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.10.tgz", - "integrity": "sha512-u5qy4SJ/OrxKxZqJ9N3qH4ZQgHaAzsopsYwLvoWJY6Q33r8PhT3VPyNMaJ7ZFoqzBnZlCcS/0f4Sp8WBxylXfg==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz", + "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.10.tgz", - "integrity": "sha512-Ecvww6sCkcjatcyctUrn22neSJHLN/TTzolMGG/N7S9rpbsTZ8c6Bl98GpSpV77EvzNijiNRHBG0+JO99qKz6g==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz", + "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-buffer": "1.7.10", - "@webassemblyjs/helper-wasm-bytecode": "1.7.10", - "@webassemblyjs/wasm-gen": "1.7.10" + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11" } }, "@webassemblyjs/ieee754": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.10.tgz", - "integrity": "sha512-HRcWcY+YWt4+s/CvQn+vnSPfRaD4KkuzQFt5MNaELXXHSjelHlSEA8ZcqT69q0GTIuLWZ6JaoKar4yWHVpZHsQ==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz", + "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==", "requires": { "@xtuc/ieee754": "1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.10.tgz", - "integrity": "sha512-og8MciYlA8hvzCLR71hCuZKPbVBfLQeHv7ImKZ4nlyxrYbG7uJHYtHiHu6OV9SqrGuD03H/HtXC4Bgdjfm9FHw==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz", + "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==", "requires": { "@xtuc/long": "4.2.1" } }, "@webassemblyjs/utf8": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.10.tgz", - "integrity": "sha512-Ng6Pxv6siyZp635xCSnH3mKmIFgqWPCcGdoo0GBYgyGdxu7cUj4agV7Uu1a8REP66UYUFXJLudeGgd4RvuJAnQ==" + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz", + "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==" }, "@webassemblyjs/wasm-edit": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.10.tgz", - "integrity": "sha512-e9RZFQlb+ZuYcKRcW9yl+mqX/Ycj9+3/+ppDI8nEE/NCY6FoK8f3dKBcfubYV/HZn44b+ND4hjh+4BYBt+sDnA==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz", + "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-buffer": "1.7.10", - "@webassemblyjs/helper-wasm-bytecode": "1.7.10", - "@webassemblyjs/helper-wasm-section": "1.7.10", - "@webassemblyjs/wasm-gen": "1.7.10", - "@webassemblyjs/wasm-opt": "1.7.10", - "@webassemblyjs/wasm-parser": "1.7.10", - "@webassemblyjs/wast-printer": "1.7.10" + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/helper-wasm-section": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-opt": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", + "@webassemblyjs/wast-printer": "1.7.11" } }, "@webassemblyjs/wasm-gen": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.10.tgz", - "integrity": "sha512-M0lb6cO2Y0PzDye/L39PqwV+jvO+2YxEG5ax+7dgq7EwXdAlpOMx1jxyXJTScQoeTpzOPIb+fLgX/IkLF8h2yw==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz", + "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-wasm-bytecode": "1.7.10", - "@webassemblyjs/ieee754": "1.7.10", - "@webassemblyjs/leb128": "1.7.10", - "@webassemblyjs/utf8": "1.7.10" + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" } }, "@webassemblyjs/wasm-opt": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.10.tgz", - "integrity": "sha512-R66IHGCdicgF5ZliN10yn5HaC7vwYAqrSVJGjtJJQp5+QNPBye6heWdVH/at40uh0uoaDN/UVUfXK0gvuUqtVg==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz", + "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-buffer": "1.7.10", - "@webassemblyjs/wasm-gen": "1.7.10", - "@webassemblyjs/wasm-parser": "1.7.10" + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-buffer": "1.7.11", + "@webassemblyjs/wasm-gen": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11" } }, "@webassemblyjs/wasm-parser": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.10.tgz", - "integrity": "sha512-AEv8mkXVK63n/iDR3T693EzoGPnNAwKwT3iHmKJNBrrALAhhEjuPzo/lTE4U7LquEwyvg5nneSNdTdgrBaGJcA==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz", + "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-api-error": "1.7.10", - "@webassemblyjs/helper-wasm-bytecode": "1.7.10", - "@webassemblyjs/ieee754": "1.7.10", - "@webassemblyjs/leb128": "1.7.10", - "@webassemblyjs/utf8": "1.7.10" + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-wasm-bytecode": "1.7.11", + "@webassemblyjs/ieee754": "1.7.11", + "@webassemblyjs/leb128": "1.7.11", + "@webassemblyjs/utf8": "1.7.11" } }, "@webassemblyjs/wast-parser": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.10.tgz", - "integrity": "sha512-YTPEtOBljkCL0VjDp4sHe22dAYSm3ZwdJ9+2NTGdtC7ayNvuip1wAhaAS8Zt9Q6SW9E5Jf5PX7YE3XWlrzR9cw==", - "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/floating-point-hex-parser": "1.7.10", - "@webassemblyjs/helper-api-error": "1.7.10", - "@webassemblyjs/helper-code-frame": "1.7.10", - "@webassemblyjs/helper-fsm": "1.7.10", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz", + "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/floating-point-hex-parser": "1.7.11", + "@webassemblyjs/helper-api-error": "1.7.11", + "@webassemblyjs/helper-code-frame": "1.7.11", + "@webassemblyjs/helper-fsm": "1.7.11", "@xtuc/long": "4.2.1" } }, "@webassemblyjs/wast-printer": { - "version": "1.7.10", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.10.tgz", - "integrity": "sha512-mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA==", + "version": "1.7.11", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz", + "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==", "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/wast-parser": "1.7.10", + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/wast-parser": "1.7.11", "@xtuc/long": "4.2.1" } }, @@ -1971,50 +2117,27 @@ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", "requires": { - "acorn": "6.0.2", - "acorn-walk": "6.1.0" - }, - "dependencies": { - "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==" - } - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "requires": { - "acorn": "3.3.0" + "acorn": "6.0.4", + "acorn-walk": "6.1.1" }, "dependencies": { "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==" } } }, "acorn-walk": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", - "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==" + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" }, "address": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==" }, - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "requires": { - "es6-promisify": "5.0.0" - } - }, "aggregate-error": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", @@ -2025,14 +2148,14 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", + "fast-deep-equal": "2.0.1", "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" } }, "ajv-errors": { @@ -2041,9 +2164,9 @@ "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=" }, "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" }, "alphanum-sort": { "version": "1.0.2", @@ -2069,9 +2192,9 @@ "integrity": "sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==" }, "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" + "version": "3.1.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" }, "ansi-html": { "version": "0.0.7", @@ -2125,7 +2248,7 @@ }, "archiver": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", "requires": { "archiver-utils": "1.3.0", @@ -2145,7 +2268,7 @@ "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", "requires": { "glob": "7.1.3", - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "lazystream": "1.0.0", "lodash": "4.17.11", "normalize-path": "2.1.1", @@ -2198,7 +2321,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" }, "array-extended": { @@ -2223,7 +2346,7 @@ }, "array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-from": { @@ -2319,7 +2442,7 @@ }, "util": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "requires": { "inherits": "2.0.1" @@ -2376,9 +2499,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autobind-decorator": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.1.0.tgz", - "integrity": "sha512-bgyxeRi1R2Q8kWpHsb1c+lXCulbIAHsyZRddaS+agAUX3hFUVZMociwvRgeZi1zWvfqEEjybSv4zxWvFV8ydQQ==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==" }, "autoprefixer": { "version": "8.6.5", @@ -2386,7 +2509,7 @@ "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==", "requires": { "browserslist": "3.2.8", - "caniuse-lite": "1.0.30000899", + "caniuse-lite": "1.0.30000912", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "6.0.23", @@ -2398,8 +2521,8 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", "requires": { - "caniuse-lite": "1.0.30000899", - "electron-to-chromium": "1.3.82" + "caniuse-lite": "1.0.30000912", + "electron-to-chromium": "1.3.87" } }, "postcss": { @@ -2485,7 +2608,7 @@ "dependencies": { "jsesc": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" } } @@ -2660,9 +2783,9 @@ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "base64url": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.0.tgz", - "integrity": "sha512-LIVmqIrIWuiqTvn4RzcrwCOuHo2DD6tKmKBPXXlr4p4n4l6BZBkwFTIa3zu1XkX5MbZgro4a6BvPi+n2Mns5Gg==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" }, "batch": { "version": "0.6.1", @@ -2684,7 +2807,7 @@ }, "bignumber.js": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" }, "binary": { @@ -2719,9 +2842,9 @@ } }, "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" }, "bmp-js": { "version": "0.0.3", @@ -2748,6 +2871,13 @@ "qs": "6.5.2", "raw-body": "2.3.3", "type-is": "1.6.16" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } } }, "bonjour": { @@ -2755,7 +2885,7 @@ "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "requires": { - "array-flatten": "2.1.1", + "array-flatten": "2.1.2", "deep-equal": "1.0.1", "dns-equal": "1.0.0", "dns-txt": "2.0.2", @@ -2764,9 +2894,9 @@ }, "dependencies": { "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" } } }, @@ -2775,14 +2905,6 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, "bowser": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", @@ -2898,7 +3020,7 @@ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "requires": { - "pako": "1.0.6" + "pako": "1.0.7" } }, "browserslist": { @@ -2906,8 +3028,8 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "requires": { - "caniuse-db": "1.0.30000899", - "electron-to-chromium": "1.3.82" + "caniuse-db": "1.0.30000912", + "electron-to-chromium": "1.3.87" } }, "buffer": { @@ -2996,21 +3118,22 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", + "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", "requires": { - "bluebird": "3.5.2", + "bluebird": "3.5.3", "chownr": "1.1.1", + "figgy-pudding": "3.5.1", "glob": "7.1.3", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.3", - "mississippi": "2.0.0", + "graceful-fs": "4.1.15", + "lru-cache": "4.1.5", + "mississippi": "3.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", "promise-inflight": "1.0.1", "rimraf": "2.6.2", - "ssri": "5.3.0", + "ssri": "6.0.1", "unique-filename": "1.1.1", "y18n": "4.0.0" } @@ -3069,18 +3192,10 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "requires": { - "callsites": "0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + "callable-instance2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callable-instance2/-/callable-instance2-1.0.0.tgz", + "integrity": "sha512-zHat10GllE4v8+9AmUz6SKHgrrHkaYV7VlsxJiA9bsQwXKhFMbQHZns1ubDhIPyEo2zjyvrum9lc6KLNcLKdxg==" }, "camel-case": { "version": "3.0.0", @@ -3112,20 +3227,20 @@ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000899", + "caniuse-db": "1.0.30000912", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" } }, "caniuse-db": { - "version": "1.0.30000899", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000899.tgz", - "integrity": "sha512-MSCUohyoLU4/PGapapw/PLQkmQ+sFgzX6e3tM6ue8HX9HW9rBD5gRiAYKhC8r0QkvUE0pWTA8Ze6f3jrzBizVg==" + "version": "1.0.30000912", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000912.tgz", + "integrity": "sha512-uiepPdHcJ06Na9t15L5l+pp3NWQU4IETbmleghD6tqCqbIYqhHSu7nVfbK2gqPjfy+9jl/wHF1UQlyTszh9tJQ==" }, "caniuse-lite": { - "version": "1.0.30000899", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000899.tgz", - "integrity": "sha512-enC3zKfUCJxxwvUIsBkbHd54CtJw1KtIWvrK0JZxWD/fEN2knHaai45lndJ4xXAkyRAPyk60J3yagkKDWhfeMA==" + "version": "1.0.30000912", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000912.tgz", + "integrity": "sha512-M3zAtV36U+xw5mMROlTXpAHClmPAor6GPKAMD5Yi7glCB5sbMPFtnQ3rGpk4XqPdUrrTIaVYSJZxREZWNy8QJg==" }, "capture-stack-trace": { "version": "1.0.1", @@ -3177,6 +3292,11 @@ "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.0.0.tgz", "integrity": "sha512-elF2ZUczBsFoP07qCfMO/zeggs8pqCf3fZGyK5+2X4AndS8jycZYID91ztD9oQ7d/0tnS963dPkd0frQEThDsg==" }, + "chai-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz", + "integrity": "sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==" + }, "chainsaw": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", @@ -3694,11 +3814,11 @@ "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "requires": { - "restore-cursor": "1.0.1" + "restore-cursor": "2.0.0" } }, "cli-highlight": { @@ -3718,7 +3838,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "shebang-command": "1.2.0", "which": "1.3.1" } @@ -3737,6 +3857,11 @@ "strip-eof": "1.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -3928,7 +4053,7 @@ }, "color-string": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", "requires": { "color-name": "1.1.3" @@ -3946,7 +4071,7 @@ }, "colors": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz", "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" }, "combined-stream": { @@ -4042,7 +4167,7 @@ "rx": "2.3.24", "spawn-command": "0.0.2-1", "supports-color": "3.2.3", - "tree-kill": "1.2.0" + "tree-kill": "1.2.1" }, "dependencies": { "commander": { @@ -4085,7 +4210,7 @@ "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "requires": { "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "make-dir": "1.3.0", "unique-string": "1.0.0", "write-file-atomic": "2.3.0", @@ -4196,188 +4321,37 @@ "require-from-string": "2.0.2" } }, - "coveralls": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", - "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", + "cpx": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cpx/-/cpx-1.5.0.tgz", + "integrity": "sha1-GFvgGFEdhycN7czCkxceN2VauI8=", "requires": { - "js-yaml": "3.6.1", - "lcov-parse": "0.0.10", - "log-driver": "1.2.5", - "minimist": "1.2.0", - "request": "2.79.0" + "babel-runtime": "6.26.0", + "chokidar": "1.7.0", + "duplexer": "0.1.1", + "glob": "7.1.3", + "glob2base": "0.0.12", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "resolve": "1.8.1", + "safe-buffer": "5.1.2", + "shell-quote": "1.6.1", + "subarg": "1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.7", - "mime-types": "2.1.21" - } - }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "requires": { - "chalk": "1.1.3", - "commander": "2.19.0", - "is-my-json-valid": "2.19.0", - "pinkie-promise": "2.0.1" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.15.1" - } - }, - "js-yaml": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", - "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" - }, - "request": { - "version": "2.79.0", - "resolved": "http://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.8.0", - "caseless": "0.11.0", - "combined-stream": "1.0.7", - "extend": "3.0.2", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.21", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3", - "uuid": "3.3.2" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" - } - } - }, - "cpx": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/cpx/-/cpx-1.5.0.tgz", - "integrity": "sha1-GFvgGFEdhycN7czCkxceN2VauI8=", - "requires": { - "babel-runtime": "6.26.0", - "chokidar": "1.7.0", - "duplexer": "0.1.1", - "glob": "7.1.3", - "glob2base": "0.0.12", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "resolve": "1.8.1", - "safe-buffer": "5.1.2", - "shell-quote": "1.6.1", - "subarg": "1.0.0" - }, - "dependencies": { - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.2.1" + "anymatch": "1.3.2", + "async-each": "1.0.1", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.2.1" } } } @@ -4464,14 +4438,6 @@ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" - } - }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -4540,14 +4506,14 @@ "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", "requires": { "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", + "css-selector-tokenizer": "0.7.1", "cssnano": "3.10.0", "icss-utils": "2.1.0", "loader-utils": "1.1.0", "lodash.camelcase": "4.3.0", "object-assign": "4.1.1", "postcss": "5.2.18", - "postcss-modules-extract-imports": "1.2.0", + "postcss-modules-extract-imports": "1.2.1", "postcss-modules-local-by-default": "1.2.0", "postcss-modules-scope": "1.1.0", "postcss-modules-values": "1.3.0", @@ -4575,12 +4541,12 @@ } }, "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", "requires": { "cssesc": "0.1.0", - "fastparse": "1.1.1", + "fastparse": "1.1.2", "regexpu-core": "1.0.0" } }, @@ -4644,7 +4610,7 @@ "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000899", + "caniuse-db": "1.0.30000912", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "5.2.18", @@ -4693,14 +4659,6 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "requires": { - "es5-ext": "0.10.46" - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -4715,7 +4673,7 @@ "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", "requires": { "abab": "2.0.0", - "whatwg-mimetype": "2.2.0", + "whatwg-mimetype": "2.3.0", "whatwg-url": "7.0.0" }, "dependencies": { @@ -4748,7 +4706,7 @@ }, "date-format": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz", + "resolved": "http://registry.npmjs.org/date-format/-/date-format-0.0.2.tgz", "integrity": "sha1-+v1Ej3IRXvHitzkVWukvK+bCjdE=" }, "date-now": { @@ -4805,7 +4763,7 @@ }, "deep-assign": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/deep-assign/-/deep-assign-2.0.0.tgz", "integrity": "sha1-6+BrHwfwja5ZdiDj3RYi83GhxXI=", "requires": { "is-obj": "1.0.1" @@ -4927,24 +4885,16 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "requires": { - "globby": "5.0.0", + "globby": "6.1.0", "is-path-cwd": "1.0.0", "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", + "p-map": "1.2.0", + "pify": "3.0.0", "rimraf": "2.6.2" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } } }, "delay-cli": { @@ -4992,6 +4942,11 @@ "repeating": "2.0.1" } }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-node": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", @@ -5068,15 +5023,6 @@ "buffer-indexof": "1.1.1" } }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" - } - }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -5085,6 +5031,14 @@ "utila": "0.4.0" } }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "7.1.5" + } + }, "dom-serializer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", @@ -5102,13 +5056,14 @@ } }, "dom-testing-library": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/dom-testing-library/-/dom-testing-library-3.12.0.tgz", - "integrity": "sha512-eWykEDuKmffXOUKvv4IK00krvC4m+V2/y137M1YccWanUlfUzqS1+3eqm3GMC9qMqCJugfHWn6OpIaQd9rwqcw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/dom-testing-library/-/dom-testing-library-3.13.0.tgz", + "integrity": "sha512-ImIZQrsEPQkmXNFzYmOsCJBjaBcZJe4vRJfP55DhYySD2LL56ACPaJATbXphLGred5efqGC1Q4H3UuqWCZ9Bqg==", "requires": { + "@babel/runtime": "7.1.5", "@sheerun/mutationobserver-shim": "0.3.2", "pretty-format": "23.6.0", - "wait-for-expect": "1.0.1" + "wait-for-expect": "1.1.0" }, "dependencies": { "ansi-regex": { @@ -5247,18 +5202,18 @@ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.2.tgz", "integrity": "sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==", "requires": { - "@types/node": "10.12.0", + "@types/node": "10.12.11", "@types/semver": "5.5.0", "commander": "2.19.0", - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "semver": "5.6.0", "sigmund": "1.0.1" }, "dependencies": { "@types/node": { - "version": "10.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.0.tgz", - "integrity": "sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==" + "version": "10.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.11.tgz", + "integrity": "sha512-3iIOhNiPGTdcUNVCv9e5G7GotfvJJe2pc9w2UgDXlUwnxSZ3RgcUocIU+xYm+rTU54jIKih998QE4dMOyMN1NQ==" } } }, @@ -5273,19 +5228,19 @@ "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" }, "electron": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/electron/-/electron-2.0.12.tgz", - "integrity": "sha512-mw8hoM/GPtFPP8FGiJcVNe8Rx63YJ7O8bf7McQj21HAvrXGAwReGFrpIe5xN6ec10fDXNSNyfzRucjFXtOtLcg==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/electron/-/electron-2.0.14.tgz", + "integrity": "sha512-8HLVZuscZxVhoMUL6RlF5kMcwGUAMWw5HNwrEmRgzZyBIBbdCO4aMo9z0qknnPTUDROz8xXZFNhFvBXDu61g5Q==", "requires": { - "@types/node": "8.10.36", + "@types/node": "8.10.38", "electron-download": "3.3.0", "extract-zip": "1.6.7" }, "dependencies": { "@types/node": { - "version": "8.10.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.36.tgz", - "integrity": "sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw==" + "version": "8.10.38", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.38.tgz", + "integrity": "sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A==" } } }, @@ -5327,7 +5282,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "4.0.0", "universalify": "0.1.2" } @@ -5380,10 +5335,10 @@ "dependencies": { "fs-extra": { "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "2.4.0", "klaw": "1.3.1", "path-is-absolute": "1.0.1", @@ -5395,7 +5350,7 @@ "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } }, "path-exists": { @@ -5409,9 +5364,9 @@ } }, "electron-to-chromium": { - "version": "1.3.82", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz", - "integrity": "sha512-NI4nB2IWGcU4JVT1AE8kBb/dFor4zjLHMLsOROPahppeHrR0FG5uslxMmkp/thO1MvPjM2xhlKoY29/I60s0ew==" + "version": "1.3.87", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.87.tgz", + "integrity": "sha512-EV5FZ68Hu+n9fHVhOc9AcG3Lvf+E1YqR36ulJUpwaQTkf4LwdvBqmGIazaIrt4kt6J8Gw3Kv7r9F+PQjAkjWeA==" }, "elliptic": { "version": "6.4.1", @@ -5420,7 +5375,7 @@ "requires": { "bn.js": "4.11.8", "brorand": "1.1.0", - "hash.js": "1.1.5", + "hash.js": "1.1.7", "hmac-drbg": "1.0.1", "inherits": "2.0.3", "minimalistic-assert": "1.0.1", @@ -5458,9 +5413,9 @@ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "memory-fs": "0.4.1", - "tapable": "1.1.0" + "tapable": "1.1.1" } }, "entities": { @@ -5494,33 +5449,34 @@ "object.assign": "4.1.0", "object.entries": "1.0.4", "object.values": "1.0.4", - "raf": "3.4.0", + "raf": "3.4.1", "rst-selector-parser": "2.2.3", "string.prototype.trim": "1.1.2" } }, "enzyme-adapter-react-16": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.6.0.tgz", - "integrity": "sha512-ay9eGFpChyUDnjTFMMJHzrb681LF3hPWJLEA7RoLFG9jSWAdAm2V50pGmFV9dYGJgh5HfdiqM+MNvle41Yf/PA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.7.0.tgz", + "integrity": "sha512-rDr0xlnnFPffAPYrvG97QYJaRl9unVDslKee33wTStsBEwZTkESX1H7VHGT5eUc6ifNzPgOJGvSh2zpHT4gXjA==", "requires": { - "enzyme-adapter-utils": "1.8.1", + "enzyme-adapter-utils": "1.9.0", "function.prototype.name": "1.1.0", "object.assign": "4.1.0", "object.values": "1.0.4", "prop-types": "15.6.2", - "react-is": "16.6.0", - "react-test-renderer": "16.6.0" + "react-is": "16.6.3", + "react-test-renderer": "16.6.3" } }, "enzyme-adapter-utils": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.8.1.tgz", - "integrity": "sha512-s3QB3xQAowaDS2sHhmEqrT13GJC4+n5bG015ZkLv60n9k5vhxxHTQRIneZmQ4hmdCZEBrvUJ89PG6fRI5OEeuQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.9.0.tgz", + "integrity": "sha512-uMe4xw4l/Iloh2Fz+EO23XUYMEQXj5k/5ioLUXCNOUCI8Dml5XQMO9+QwUq962hBsY5qftfHHns+d990byWHvg==", "requires": { "function.prototype.name": "1.1.0", "object.assign": "4.1.0", - "prop-types": "15.6.2" + "prop-types": "15.6.2", + "semver": "5.6.0" } }, "enzyme-to-json": { @@ -5569,84 +5525,11 @@ "is-symbol": "1.0.2" } }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, "es6-promise": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==" }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "4.2.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -5682,96 +5565,6 @@ } } }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "eslint": { - "version": "2.13.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", - "integrity": "sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE=", - "requires": { - "chalk": "1.1.3", - "concat-stream": "1.6.2", - "debug": "2.6.9", - "doctrine": "1.5.0", - "es6-map": "0.1.5", - "escope": "3.6.0", - "espree": "3.5.4", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "file-entry-cache": "1.3.1", - "glob": "7.1.3", - "globals": "9.18.0", - "ignore": "3.3.10", - "imurmurhash": "0.1.4", - "inquirer": "0.12.0", - "is-my-json-valid": "2.19.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.12.0", - "json-stable-stringify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.11", - "mkdirp": "0.5.1", - "optionator": "0.8.2", - "path-is-absolute": "1.0.1", - "path-is-inside": "1.0.2", - "pluralize": "1.2.1", - "progress": "1.1.8", - "require-uncached": "1.0.3", - "shelljs": "0.6.1", - "strip-json-comments": "1.0.4", - "table": "3.8.3", - "text-table": "0.2.0", - "user-home": "2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" - }, - "progress": { - "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" - }, - "shelljs": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", - "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=" - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", @@ -5781,15 +5574,6 @@ "estraverse": "4.2.0" } }, - "espree": { - "version": "3.5.4", - "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "requires": { - "acorn": "5.7.3", - "acorn-jsx": "3.0.1" - } - }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -5818,15 +5602,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.46" - } - }, "event-stream": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", @@ -5847,9 +5622,9 @@ "integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=" }, "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" }, "events": { "version": "1.1.1", @@ -5874,17 +5649,19 @@ } }, "exceljs": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-1.6.2.tgz", - "integrity": "sha512-y4jT8QNrgBsM2MMIwoUZ3ealLG8uWf/YqJMYHsjiTAnqH7aO8asKEnFhV3e3sHbXfTp3xkMrfTNCDl9BSgy6Nw==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-1.6.3.tgz", + "integrity": "sha512-GUAGrh9eWN75Nh+qubb+oP2gltoWM+hfExdY8ItRfpOCZraThjBA+yeZh2HpGN+MJnhpjV1jQoFqe6EajuyVBA==", "requires": { + "@types/node": "10.10.3", "archiver": "1.3.0", "fast-csv": "2.4.1", "jszip": "3.1.3", "moment": "2.22.2", "node-unzip-2": "0.2.7", "promish": "5.1.1", - "sax": "1.2.4" + "sax": "1.2.4", + "temp": "0.8.3" } }, "execa": { @@ -5906,11 +5683,6 @@ "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" - }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", @@ -5921,7 +5693,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "requires": { "fill-range": "2.2.4" @@ -5946,7 +5718,7 @@ "dependencies": { "source-map": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", "integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=" } } @@ -5988,6 +5760,11 @@ "vary": "1.1.2" }, "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -6112,277 +5889,807 @@ } }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=" - }, - "fault": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz", - "integrity": "sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==", - "requires": { - "format": "0.2.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "fast-glob": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.4.tgz", + "integrity": "sha512-FjK2nCGI/McyzgNtTESqaWP3trPvHyRyoyY70hxjc3oKPNmDe8taohLZpoVKoUjW85tbU5txaYUZCNtVzygl1g==", "requires": { - "websocket-driver": "0.7.0" - } - }, - "fbjs": { + "@mrmlnc/readdir-enhanced": "2.2.1", + "@nodelib/fs.stat": "1.1.3", + "glob-parent": "3.1.0", + "is-glob": "4.0.0", + "merge2": "1.2.3", + "micromatch": "3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "2.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==" + }, + "fault": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz", + "integrity": "sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==", + "requires": { + "format": "0.2.2" + } + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "requires": { + "websocket-driver": "0.7.0" + } + }, + "fbjs": { "version": "0.8.17", "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.4.0", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.19" + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.19" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "2.0.6" + } + } + } + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "requires": { + "pend": "1.2.0" + } + }, + "fibers": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fibers/-/fibers-3.1.1.tgz", + "integrity": "sha512-dl3Ukt08rHVQfY8xGD0ODwyjwrRALtaghuqGH2jByYX1wpY+nAnRQjJ6Dbqq0DnVgNVQ9yibObzbF4IlPyiwPw==", + "requires": { + "detect-libc": "1.0.3" + } + }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + }, + "figlet": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.2.1.tgz", + "integrity": "sha512-qc8gycfnnfOmfvPl7Fi3JeTbcvdmbZkckyUVGGAM02je7Ookvu+bBfKy1I4FKqTsQHCs3ARJ76ip/k98r+OQuQ==" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "http://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "requires": { + "loader-utils": "1.1.0", + "schema-utils": "0.4.7" + } + }, + "file-type": { + "version": "3.9.0", + "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.1", + "repeat-element": "1.1.3", + "repeat-string": "1.6.1" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.4.0", + "unpipe": "1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "requires": { + "commondir": "1.0.1", + "make-dir": "1.3.0", + "pkg-dir": "3.0.0" + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "2.0.0" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "1.1.4" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "1.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "fork-ts-checker-webpack-plugin": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.4.15.tgz", + "integrity": "sha512-qNYuygh2GxXehBvQZ5rI5YlQFn+7ZV6kmkyD9Sgs33dWl73NZdUOB5aCp8v0EXJn176AhPrZP8YCMT3h01fs+g==", + "requires": { + "babel-code-frame": "6.26.0", + "chalk": "2.4.1", + "chokidar": "2.0.4", + "lodash": "4.17.11", + "micromatch": "3.1.10", + "minimatch": "3.0.4", + "resolve": "1.8.1", + "tapable": "1.1.1" }, "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { - "asap": "2.0.6" + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "1.1.6" + } + } } - } - } - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "requires": { - "pend": "1.2.0" - } - }, - "fibers": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-2.0.2.tgz", - "integrity": "sha512-HfVRxhYG7C8Jl9FqtrlElMR2z/8YiLQVDKf67MLY25Ic+ILx3ecmklfT1v3u+7P5/4vEFjuxaAFXhr2/Afwk5g==" - }, - "figlet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.2.1.tgz", - "integrity": "sha512-qc8gycfnnfOmfvPl7Fi3JeTbcvdmbZkckyUVGGAM02je7Ookvu+bBfKy1I4FKqTsQHCs3ARJ76ip/k98r+OQuQ==" - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "file-entry-cache": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", - "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", - "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" - } - }, - "file-loader": { - "version": "1.1.11", - "resolved": "http://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.7" - } - }, - "file-type": { - "version": "3.9.0", - "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=" - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "3.1.1", - "repeat-element": "1.1.3", - "repeat-string": "1.6.1" - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "requires": { - "commondir": "1.0.1", - "make-dir": "1.3.0", - "pkg-dir": "2.0.0" - } - }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "2.0.0" - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "follow-redirects": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", - "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", - "requires": { - "debug": "3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "requires": { - "ms": "2.0.0" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } } } }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "requires": { - "is-callable": "1.1.4" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "1.0.2" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "fork-ts-checker-webpack-plugin": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.4.12.tgz", - "integrity": "sha512-XoeNFO2Blc+6pkiIct74w+6gWa6VRcBmBsWKMDZ94XVLt8AnrcZcRSUTeEggIkhNa6sN3KDEwYbGXQ4Z3RBbKg==", - "requires": { - "babel-code-frame": "6.26.0", - "chalk": "2.4.1", - "chokidar": "2.0.4", - "lodash.endswith": "4.2.1", - "lodash.isfunction": "3.0.9", - "lodash.isstring": "4.0.1", - "lodash.startswith": "4.2.1", - "minimatch": "3.0.4", - "resolve": "1.8.1", - "tapable": "1.1.0" - } - }, "form-data": { "version": "2.3.2", "resolved": "http://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", @@ -6460,7 +6767,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "4.0.0", "universalify": "0.1.2" } @@ -6470,7 +6777,7 @@ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "iferr": "0.1.5", "imurmurhash": "0.1.4", "readable-stream": "2.3.6" @@ -6486,7 +6793,7 @@ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "inherits": "2.0.3", "mkdirp": "0.5.1", "rimraf": "2.6.2" @@ -6555,26 +6862,10 @@ "globule": "1.2.1" } }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "requires": { - "is-property": "1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "requires": { - "is-property": "1.0.2" - } - }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.0.tgz", + "integrity": "sha512-cF41L/f/7nXpSwMMHMY0FIurpTPZq/eHwJdeh2+0kKYhL9eD7RqsgMujd3qdqvWdjGIHjwvd/iEMTNECl2EhzA==" }, "get-func-name": { "version": "2.0.0", @@ -6682,9 +6973,9 @@ } }, "global-modules-path": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.0.tgz", - "integrity": "sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.1.tgz", + "integrity": "sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==" }, "global-prefix": { "version": "1.0.2", @@ -6699,17 +6990,16 @@ } }, "globals": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==" + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==" }, "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "version": "6.1.0", + "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "requires": { "array-union": "1.0.2", - "arrify": "1.0.1", "glob": "7.1.3", "object-assign": "4.1.1", "pify": "2.3.0", @@ -6749,7 +7039,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "which": "1.3.1" } }, @@ -6793,9 +7083,9 @@ } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "grapheme-splitter": { "version": "1.0.4", @@ -6818,7 +7108,7 @@ }, "handle-thing": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", + "resolved": "http://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=" }, "handlebars": { @@ -6845,11 +7135,11 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "5.5.2", + "ajv": "6.6.1", "har-schema": "2.0.0" } }, @@ -6961,34 +7251,23 @@ } }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "2.0.3", "minimalistic-assert": "1.0.1" } }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, "highlight-words-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.0.tgz", - "integrity": "sha512-nu5bMsWIgpsrlXEMNKSvbJMeUPhFxCOVT28DnI8UCVfhm3e98LC8oeyMNrc7E18+QQ4l/PvbeN7ojyN4XsmBdA==" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/highlight-words-core/-/highlight-words-core-1.2.2.tgz", + "integrity": "sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg==" }, "highlight.js": { "version": "9.13.1", @@ -7022,16 +7301,11 @@ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "requires": { - "hash.js": "1.1.5", + "hash.js": "1.1.7", "minimalistic-assert": "1.0.1", "minimalistic-crypto-utils": "1.0.1" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "hoist-non-react-statics": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", @@ -7120,7 +7394,7 @@ "loader-utils": "1.1.0", "lodash": "4.17.11", "pretty-error": "2.1.1", - "tapable": "1.1.0", + "tapable": "1.1.1", "util.promisify": "1.0.0" }, "dependencies": { @@ -7188,12 +7462,13 @@ "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==" }, "http-proxy": { - "version": "1.11.1", - "resolved": "http://registry.npmjs.org/http-proxy/-/http-proxy-1.11.1.tgz", - "integrity": "sha1-cd9VdX6ALVjqgQ3yJEAZ3aBa6F0=", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "requires": { - "eventemitter3": "1.2.0", - "requires-port": "0.0.1" + "eventemitter3": "3.1.0", + "follow-redirects": "1.5.10", + "requires-port": "1.0.0" } }, "http-proxy-middleware": { @@ -7244,11 +7519,6 @@ } } }, - "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==" - }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -7386,16 +7656,6 @@ } } }, - "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", - "requires": { - "eventemitter3": "3.1.0", - "follow-redirects": "1.5.9", - "requires-port": "1.0.0" - } - }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", @@ -7481,12 +7741,7 @@ "regex-not": "1.0.2", "snapdragon": "0.8.2", "to-regex": "3.0.2" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + } } } }, @@ -7497,7 +7752,7 @@ "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", - "sshpk": "1.15.1" + "sshpk": "1.15.2" } }, "https-browserify": { @@ -7505,30 +7760,6 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "requires": { - "agent-base": "4.2.1", - "debug": "3.2.6" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, "humanize-duration": { "version": "3.15.3", "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz", @@ -7545,9 +7776,9 @@ "integrity": "sha1-kP/Z+bxhfzS5oS4DcmD1JERfdoQ=" }, "i18next-browser-languagedetector": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.3.tgz", - "integrity": "sha512-sJZ2n9Vgax0vGer23hJMwyO3FRO7P0dq2DXZPXWE329g3snfJUcw+S24Mp3lqJaxL/0McDu4BD75ds6pzIfhhw==" + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.4.tgz", + "integrity": "sha512-wPbtH18FdOuB245I8Bhma5/XSDdN/HpYlX+wga1eMy+slhaFQSnrWX6fp+aYSL2eEuj0RlfHeEVz6Fo/lxAj6A==" }, "i18next-xhr-backend": { "version": "1.5.1", @@ -7627,6 +7858,11 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, + "immer": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz", + "integrity": "sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA==" + }, "immutable": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", @@ -7646,13 +7882,6 @@ "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", "requires": { "resolve-from": "3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } } }, "import-lazy": { @@ -7667,54 +7896,6 @@ "requires": { "pkg-dir": "3.0.0", "resolve-cwd": "2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "3.0.0", - "path-exists": "3.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "requires": { - "p-try": "2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "3.0.0" - } - } } }, "imports-loader": { @@ -7787,64 +7968,37 @@ } }, "inquirer": { - "version": "0.12.0", - "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "requires": { - "ansi-escapes": "1.4.0", - "ansi-regex": "2.1.1", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "cli-cursor": "2.1.0", "cli-width": "2.2.0", - "figures": "1.7.0", + "external-editor": "3.0.3", + "figures": "2.0.0", "lodash": "4.17.11", - "readline2": "1.0.1", - "run-async": "0.1.0", - "rx-lite": "3.1.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rxjs": "6.3.3", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", "through": "2.3.8" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "ansi-regex": "3.0.0" } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } }, @@ -7891,9 +8045,9 @@ } }, "inversify": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.14.0.tgz", - "integrity": "sha512-DQLg2u2tWaiHo6V5lGr47a/M9YBX3g72c8Y58+JPH0Lx9fXugEsnXRc08mwsTvDg6gGWBKSkIgtBS/eJCQmDVg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" }, "invert-kv": { "version": "2.0.0", @@ -8097,23 +8251,6 @@ "is-path-inside": "1.0.1" } }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==" - }, - "is-my-json-valid": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", - "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", - "requires": { - "generate-function": "2.3.1", - "generate-object-property": "1.2.0", - "is-my-ip-valid": "1.0.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" - } - }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -8198,11 +8335,6 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" - }, "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", @@ -8216,11 +8348,6 @@ "has": "1.0.3" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -8339,113 +8466,6 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.12", - "js-yaml": "3.12.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.1", - "wordwrap": "1.0.0" - }, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" - }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1.0.9" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "optional": true, - "requires": { - "amdefine": "1.0.1" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "requires": { - "has-flag": "1.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - } - } - }, "istanbul-instrumenter-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", @@ -8457,6 +8477,22 @@ "schema-utils": "0.3.0" }, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, "istanbul-lib-coverage": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", @@ -8476,6 +8512,11 @@ "semver": "5.6.0" } }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, "schema-utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", @@ -8527,7 +8568,7 @@ "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", "requires": { "@babel/types": "7.0.0-beta.51", - "jsesc": "2.5.1", + "jsesc": "2.5.2", "lodash": "4.17.11", "source-map": "0.5.7", "trim-right": "1.0.1" @@ -8597,7 +8638,7 @@ "@babel/parser": "7.0.0-beta.51", "@babel/types": "7.0.0-beta.51", "debug": "3.2.6", - "globals": "11.8.0", + "globals": "11.9.0", "invariant": "2.2.4", "lodash": "4.17.11" } @@ -8819,11 +8860,11 @@ "request-promise-native": "1.0.5", "sax": "1.2.4", "symbol-tree": "3.2.2", - "tough-cookie": "2.4.3", + "tough-cookie": "2.5.0", "w3c-hr-time": "1.0.1", "webidl-conversions": "4.0.2", "whatwg-encoding": "1.0.5", - "whatwg-mimetype": "2.2.0", + "whatwg-mimetype": "2.3.0", "whatwg-url": "6.5.0", "ws": "5.2.2", "xml-name-validator": "3.0.0" @@ -8845,9 +8886,9 @@ "integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=" }, "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-buffer": { "version": "3.0.0", @@ -8916,9 +8957,9 @@ } }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify": { "version": "1.0.1", @@ -8951,7 +8992,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } }, "jsonify": { @@ -8977,11 +9018,6 @@ } } }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -9006,7 +9042,7 @@ "core-js": "2.3.0", "es6-promise": "3.0.2", "lie": "3.1.1", - "pako": "1.0.6", + "pako": "1.0.7", "readable-stream": "2.0.6" }, "dependencies": { @@ -9040,7 +9076,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -9068,11 +9104,6 @@ "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-3.0.0.tgz", "integrity": "sha512-Fu3T6pKBuxjWT/p4DkqGHFRsysc8OauWr4ZRTY9dIx07Y9O0RkoR5jcv28aeD1vuAwhm3nLkDurwLXoALp4DpQ==" }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=" - }, "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -9099,7 +9130,7 @@ "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } }, "latest-version": { @@ -9126,11 +9157,6 @@ "invert-kv": "2.0.0" } }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=" - }, "left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", @@ -9179,6 +9205,13 @@ "phin": "2.9.3", "xhr": "2.5.0", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "load-json-file": { @@ -9186,7 +9219,7 @@ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "parse-json": "4.0.0", "pify": "3.0.0", "strip-bom": "3.0.0" @@ -9268,11 +9301,6 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, - "lodash.endswith": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.endswith/-/lodash.endswith-4.2.1.tgz", - "integrity": "sha1-/tWawXOO0+I27dcGTsRWRIs3vAk=" - }, "lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -9308,16 +9336,6 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "lodash.isfunction": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", - "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9353,11 +9371,6 @@ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, - "lodash.startswith": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", - "integrity": "sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=" - }, "lodash.tail": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", @@ -9390,11 +9403,6 @@ "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-4.3.0.tgz", "integrity": "sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=" }, - "log-driver": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", - "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=" - }, "loglevel": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", @@ -9438,25 +9446,18 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lowlight": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.10.0.tgz", - "integrity": "sha512-ABxJUAYNftRU6b2vC3HlVf2pEln9ViUpfZWDOtsraVhGrcYUSzIe8cldF+mK6FQTaK6ba7dNDPoGOQTFVfiqxg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.11.0.tgz", + "integrity": "sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A==", "requires": { - "fault": "1.0.2", - "highlight.js": "9.12.0" - }, - "dependencies": { - "highlight.js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", - "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=" - } + "fault": "1.0.2", + "highlight.js": "9.13.1" } }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" @@ -9476,9 +9477,9 @@ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" }, "map-age-cleaner": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", - "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", "requires": { "p-defer": "1.0.0" } @@ -9543,7 +9544,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -9596,15 +9597,15 @@ "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", "requires": { - "map-age-cleaner": "0.1.2", + "map-age-cleaner": "0.1.3", "mimic-fn": "1.2.0", "p-is-promise": "1.1.0" } }, "memoize-one": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.2.tgz", - "integrity": "sha512-ucx2DmXTeZTsS4GPPUZCbULAN7kdPT1G+H49Y34JjbQ5ESc6OGhVxKvb1iKhr9v19ZB9OtnHwNnhUnNR/7Wteg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", + "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" }, "memory-fs": { "version": "0.4.1", @@ -9647,13 +9648,18 @@ "integrity": "sha1-RI4RhCTHdSryrcqD+7BJdYWWpoA=" }, "merge-options": { - "version": "0.0.64", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-0.0.64.tgz", - "integrity": "sha1-y+BPWUppheryf3+PCyo6z2+dVi0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-1.0.1.tgz", + "integrity": "sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==", "requires": { "is-plain-obj": "1.1.0" } }, + "merge2": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", + "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -9730,47 +9736,21 @@ "integrity": "sha1-aSLE0Ufvx3GgFCWixMj3eER4xUY=" }, "mini-css-extract-plugin": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz", - "integrity": "sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.5.tgz", + "integrity": "sha512-dqBanNfktnp2hwL2YguV9Jh91PFX7gu7nRLs4TGsbAfAG6WOtlynFRYzwDwmmeSb5uIwHo9nx1ta0f7vAZVp2w==", "requires": { "loader-utils": "1.1.0", "schema-utils": "1.0.0", "webpack-sources": "1.3.0" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.5.4", + "ajv": "6.6.1", "ajv-errors": "1.0.0", "ajv-keywords": "3.2.0" } @@ -9810,9 +9790,9 @@ } }, "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "1.6.2", "duplexify": "3.6.1", @@ -9820,20 +9800,25 @@ "flush-write-stream": "1.0.3", "from2": "2.3.0", "parallel-transform": "1.1.0", - "pump": "2.0.1", + "pump": "3.0.0", "pumpify": "1.5.1", "stream-each": "1.2.3", - "through2": "2.0.3" + "through2": "2.0.5" }, "dependencies": { "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "requires": { "readable-stream": "2.3.6", "xtend": "4.0.1" } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } }, @@ -9999,7 +9984,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "shebang-command": "1.2.0", "which": "1.3.1" } @@ -10018,6 +10003,11 @@ "strip-eof": "1.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -10212,9 +10202,9 @@ } }, "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "mv": { "version": "2.1.1", @@ -10359,11 +10349,6 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -10416,7 +10401,7 @@ "lodash": "4.17.11", "mkdirp": "0.5.1", "propagate": "1.0.0", - "qs": "6.5.2", + "qs": "6.6.0", "semver": "5.6.0" }, "dependencies": { @@ -10451,7 +10436,7 @@ }, "node-glob": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/node-glob/-/node-glob-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/node-glob/-/node-glob-1.2.0.tgz", "integrity": "sha1-UkD/7e/G1mPOhRXleWpNR6dQwNU=", "requires": { "async": "1.5.2", @@ -10472,7 +10457,7 @@ "requires": { "fstream": "1.0.11", "glob": "7.1.3", - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "minimatch": "3.0.4", "mkdirp": "0.5.1", "nopt": "3.0.6", @@ -10501,11 +10486,11 @@ } }, "node-jose": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.0.0.tgz", - "integrity": "sha512-RE3P8l60Rj9ELrpPmvw6sOQ1hSyYfmQdNUMCa4EN7nCE1ux5JVX+GfXv+mfUTEMhZwNMwxBtI0+X1CKKeukSVQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.0.tgz", + "integrity": "sha512-Ux5MDElyiAlBQyOdFcwznK2TWMJbG8ZUfIZ28UtBvTB/VUz5HA/1WJV7s+YCah5NilPhkMkNi6xjnFRI+MQAVg==", "requires": { - "base64url": "3.0.0", + "base64url": "3.0.1", "es6-promise": "4.2.5", "lodash.assign": "4.2.0", "lodash.clone": "4.5.0", @@ -10560,9 +10545,9 @@ } }, "node-releases": { - "version": "1.0.0-alpha.15", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.15.tgz", - "integrity": "sha512-hKG6hd/g6a9OV/ARt2qrxbRhe/4WEMFohTLOB9PNyTYvvI59gICZFzt9/mMgpYUTts06qXlN8H6UjfbIRdnW8A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.5.tgz", + "integrity": "sha512-Ky7q0BO1BBkG/rQz6PkEZ59rwo+aSfhczHP1wwq8IowoVdN/FpiP7qp0XW0P2+BVCWe5fQUBozdbVd54q1RbCQ==", "requires": { "semver": "5.6.0" } @@ -10583,9 +10568,9 @@ } }, "node-sass": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.4.tgz", - "integrity": "sha512-MXyurANsUoE4/6KmfMkwGcBzAnJQ5xJBGW7Ei6ea8KnUKuzHr/SguVBIi3uaUAHtZCPUYkvlJ3Ef5T5VAwVpaA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.10.0.tgz", + "integrity": "sha512-fDQJfXszw6vek63Fe/ldkYXmRYK/QS6NbvM3i5oEo9ntPDy4XX7BcKZyTKv+/kSSxRtXXc7l+MSwEmYc0CSy6Q==", "requires": { "async-foreach": "0.1.3", "chalk": "1.1.3", @@ -10644,7 +10629,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "which": "1.3.1" } }, @@ -10670,7 +10655,7 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "parse-json": "2.2.0", "pify": "2.3.0", "pinkie-promise": "2.0.1", @@ -10706,7 +10691,7 @@ "requires": { "fstream": "1.0.11", "glob": "7.1.3", - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "mkdirp": "0.5.1", "nopt": "3.0.6", "npmlog": "4.1.2", @@ -10747,7 +10732,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "pify": "2.3.0", "pinkie-promise": "2.0.1" } @@ -10849,7 +10834,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -10864,15 +10849,15 @@ } }, "nodemon": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.5.tgz", - "integrity": "sha512-8806dC8dfBlbxQmqNOSEeay/qlbddKvFzxIGNxnPtxUlTtH77xsrC66RnA3M47HCSgMgE5bj+U586o50RowXBg==", + "version": "1.18.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.7.tgz", + "integrity": "sha512-xuC1V0F5EcEyKQ1VhHYD13owznQbUw29JKvZ8bVH7TmuvVNHvvbp9pLgE4PjTMRJVe0pJ8fGRvwR2nMiosIsPQ==", "requires": { "chokidar": "2.0.4", "debug": "3.2.6", "ignore-by-default": "1.0.1", "minimatch": "3.0.4", - "pstree.remy": "1.1.0", + "pstree.remy": "1.1.2", "semver": "5.6.0", "supports-color": "5.4.0", "touch": "3.1.0", @@ -10965,16 +10950,16 @@ "integrity": "sha1-1+/jz816sAYUuJbqUxGdyaslkSU=" }, "npm-run-all": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.3.tgz", - "integrity": "sha512-aOG0N3Eo/WW+q6sUIdzcV2COS8VnTZCmdji0VQIAZF3b+a3YWb0AD0vFIyjKec18A7beLGbaQ5jFTNI2bPt9Cg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "requires": { "ansi-styles": "3.2.1", "chalk": "2.4.1", "cross-spawn": "6.0.5", "memorystream": "0.3.1", "minimatch": "3.0.4", - "ps-tree": "1.1.0", + "pidtree": "0.3.0", "read-pkg": "3.0.0", "shell-quote": "1.6.1", "string.prototype.padend": "3.0.0" @@ -11075,8 +11060,7 @@ "dependencies": { "align-text": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "bundled": true, "requires": { "kind-of": "3.2.2", "longest": "1.0.1", @@ -11085,46 +11069,38 @@ }, "amdefine": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "bundled": true }, "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "bundled": true }, "append-transform": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "bundled": true, "requires": { "default-require-extensions": "2.0.0" } }, "archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + "bundled": true }, "arrify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "bundled": true }, "async": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "bundled": true }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "bundled": true }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -11132,13 +11108,11 @@ }, "builtin-modules": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + "bundled": true }, "caching-transform": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-2.0.0.tgz", - "integrity": "sha512-tTfemGmFWe7KZ3KN6VsSgQZbd9Bgo7A40wlp4PTsJJvFu4YAnEC5YnfdiKq6Vh2i9XJLnA9n8OXD46orVpnPMw==", + "bundled": true, "requires": { "make-dir": "1.3.0", "md5-hex": "2.0.0", @@ -11148,14 +11122,12 @@ }, "camelcase": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "bundled": true, "optional": true }, "center-align": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "bundled": true, "optional": true, "requires": { "align-text": "0.1.4", @@ -11164,8 +11136,7 @@ }, "cliui": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "bundled": true, "optional": true, "requires": { "center-align": "0.1.3", @@ -11175,39 +11146,33 @@ "dependencies": { "wordwrap": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "bundled": true, "optional": true } } }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "bundled": true }, "commondir": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + "bundled": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "bundled": true }, "convert-source-map": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "bundled": true, "requires": { "safe-buffer": "5.1.2" } }, "cross-spawn": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "bundled": true, "requires": { "lru-cache": "4.1.3", "which": "1.3.1" @@ -11215,47 +11180,40 @@ }, "debug": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "bundled": true, "requires": { "ms": "2.0.0" } }, "debug-log": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=" + "bundled": true }, "decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "bundled": true }, "default-require-extensions": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "bundled": true, "requires": { "strip-bom": "3.0.0" } }, "error-ex": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "bundled": true, "requires": { "is-arrayish": "0.2.1" } }, "es6-error": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + "bundled": true }, "execa": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "bundled": true, "requires": { "cross-spawn": "5.1.0", "get-stream": "3.0.0", @@ -11268,8 +11226,7 @@ "dependencies": { "cross-spawn": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "bundled": true, "requires": { "lru-cache": "4.1.3", "shebang-command": "1.2.0", @@ -11280,8 +11237,7 @@ }, "find-cache-dir": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", - "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "bundled": true, "requires": { "commondir": "1.0.1", "make-dir": "1.3.0", @@ -11290,16 +11246,14 @@ }, "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "bundled": true, "requires": { "locate-path": "3.0.0" } }, "foreground-child": { "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "bundled": true, "requires": { "cross-spawn": "4.0.2", "signal-exit": "3.0.2" @@ -11307,23 +11261,19 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "bundled": true }, "get-caller-file": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "bundled": true }, "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "bundled": true }, "glob": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "bundled": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -11335,13 +11285,11 @@ }, "graceful-fs": { "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "bundled": true }, "handlebars": { "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "bundled": true, "requires": { "async": "1.5.2", "optimist": "0.6.1", @@ -11351,8 +11299,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "bundled": true, "requires": { "amdefine": "1.0.1" } @@ -11361,23 +11308,19 @@ }, "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "bundled": true }, "hosted-git-info": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "bundled": true }, "imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "bundled": true }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -11385,56 +11328,46 @@ }, "inherits": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true }, "invert-kv": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + "bundled": true }, "is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "bundled": true }, "is-buffer": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "bundled": true }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "bundled": true, "requires": { "builtin-modules": "1.1.1" } }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "bundled": true }, "is-stream": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "bundled": true }, "isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "bundled": true }, "istanbul-lib-coverage": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==" + "bundled": true }, "istanbul-lib-hook": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.1.tgz", - "integrity": "sha512-ufiZoiJ8CxY577JJWEeFuxXZoMqiKpq/RqZtOAYuQLvlkbJWscq9n3gc4xrCGH9n4pW0qnTxOz1oyMmVtk8E1w==", + "bundled": true, "requires": { "append-transform": "1.0.0" } @@ -11444,19 +11377,18 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz", "integrity": "sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==", "requires": { - "@babel/generator": "7.1.3", - "@babel/parser": "7.1.3", + "@babel/generator": "7.1.6", + "@babel/parser": "7.1.6", "@babel/template": "7.1.2", - "@babel/traverse": "7.1.4", - "@babel/types": "7.1.3", + "@babel/traverse": "7.1.6", + "@babel/types": "7.1.6", "istanbul-lib-coverage": "2.0.1", "semver": "5.5.0" } }, "istanbul-lib-report": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.2.tgz", - "integrity": "sha512-rJ8uR3peeIrwAxoDEbK4dJ7cqqtxBisZKCuwkMtMv0xYzaAnsAi3AHrHPAAtNXzG/bcCgZZ3OJVqm1DTi9ap2Q==", + "bundled": true, "requires": { "istanbul-lib-coverage": "2.0.1", "make-dir": "1.3.0", @@ -11465,8 +11397,7 @@ }, "istanbul-lib-source-maps": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.1.tgz", - "integrity": "sha512-30l40ySg+gvBLcxTrLzR4Z2XTRj3HgRCA/p2rnbs/3OiTaoj054gAbuP5DcLOtwqmy4XW8qXBHzrmP2/bQ9i3A==", + "bundled": true, "requires": { "debug": "3.1.0", "istanbul-lib-coverage": "2.0.1", @@ -11477,50 +11408,43 @@ "dependencies": { "source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "bundled": true } } }, "istanbul-reports": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.0.1.tgz", - "integrity": "sha512-CT0QgMBJqs6NJLF678ZHcquUAZIoBIUNzdJrRJfpkI9OnzG6MkUfHxbJC3ln981dMswC7/B1mfX3LNkhgJxsuw==", + "bundled": true, "requires": { "handlebars": "4.0.11" } }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "bundled": true }, "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "bundled": true, "requires": { "is-buffer": "1.1.6" } }, "lazy-cache": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "bundled": true, "optional": true }, "lcid": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "bundled": true, "requires": { "invert-kv": "1.0.0" } }, "load-json-file": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "bundled": true, "requires": { "graceful-fs": "4.1.11", "parse-json": "4.0.0", @@ -11530,8 +11454,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "bundled": true, "requires": { "p-locate": "3.0.0", "path-exists": "3.0.0" @@ -11539,18 +11462,15 @@ }, "lodash.flattendeep": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + "bundled": true }, "longest": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + "bundled": true }, "lru-cache": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "bundled": true, "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" @@ -11558,90 +11478,77 @@ }, "make-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "bundled": true, "requires": { "pify": "3.0.0" } }, "md5-hex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-2.0.0.tgz", - "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", + "bundled": true, "requires": { "md5-o-matic": "0.1.1" } }, "md5-o-matic": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", - "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=" + "bundled": true }, "mem": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "bundled": true, "requires": { "mimic-fn": "1.2.0" } }, "merge-source-map": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "bundled": true, "requires": { "source-map": "0.6.1" }, "dependencies": { "source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "bundled": true } } }, "mimic-fn": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "bundled": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "requires": { "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + "bundled": true }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "requires": { "minimist": "0.0.8" }, "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "bundled": true } } }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "bundled": true }, "normalize-package-data": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "bundled": true, "requires": { "hosted-git-info": "2.7.1", "is-builtin-module": "1.0.0", @@ -11651,29 +11558,25 @@ }, "npm-run-path": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "bundled": true, "requires": { "path-key": "2.0.1" } }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "bundled": true }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "requires": { "wrappy": "1.0.2" } }, "optimist": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "bundled": true, "requires": { "minimist": "0.0.10", "wordwrap": "0.0.3" @@ -11681,13 +11584,11 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "bundled": true }, "os-locale": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "bundled": true, "requires": { "execa": "0.7.0", "lcid": "1.0.0", @@ -11696,34 +11597,29 @@ }, "p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "bundled": true }, "p-limit": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "bundled": true, "requires": { "p-try": "2.0.0" } }, "p-locate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "bundled": true, "requires": { "p-limit": "2.0.0" } }, "p-try": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + "bundled": true }, "package-hash": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-2.0.0.tgz", - "integrity": "sha1-eK4ybIngWk2BO2hgGXevBcANKg0=", + "bundled": true, "requires": { "graceful-fs": "4.1.11", "lodash.flattendeep": "4.4.0", @@ -11733,8 +11629,7 @@ }, "parse-json": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "bundled": true, "requires": { "error-ex": "1.3.2", "json-parse-better-errors": "1.0.2" @@ -11742,49 +11637,41 @@ }, "path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "bundled": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "bundled": true }, "path-key": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "bundled": true }, "path-type": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "bundled": true, "requires": { "pify": "3.0.0" } }, "pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "bundled": true }, "pkg-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "bundled": true, "requires": { "find-up": "3.0.0" } }, "pseudomap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "bundled": true }, "read-pkg": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "bundled": true, "requires": { "load-json-file": "4.0.0", "normalize-package-data": "2.4.0", @@ -11793,8 +11680,7 @@ }, "read-pkg-up": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "bundled": true, "requires": { "find-up": "3.0.0", "read-pkg": "3.0.0" @@ -11802,36 +11688,30 @@ }, "release-zalgo": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "bundled": true, "requires": { "es6-error": "4.1.1" } }, "repeat-string": { "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "bundled": true }, "require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "bundled": true }, "require-main-filename": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "bundled": true }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "bundled": true }, "right-align": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "bundled": true, "optional": true, "requires": { "align-text": "0.1.4" @@ -11839,55 +11719,46 @@ }, "rimraf": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "bundled": true, "requires": { "glob": "7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "bundled": true }, "semver": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "bundled": true }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "bundled": true }, "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "bundled": true, "requires": { "shebang-regex": "1.0.0" } }, "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "bundled": true }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "bundled": true }, "source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "bundled": true, "optional": true }, "spawn-wrap": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", + "bundled": true, "requires": { "foreground-child": "1.5.6", "mkdirp": "0.5.1", @@ -11899,8 +11770,7 @@ }, "spdx-correct": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "bundled": true, "requires": { "spdx-expression-parse": "3.0.0", "spdx-license-ids": "3.0.0" @@ -11908,13 +11778,11 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "bundled": true }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "bundled": true, "requires": { "spdx-exceptions": "2.1.0", "spdx-license-ids": "3.0.0" @@ -11922,13 +11790,11 @@ }, "spdx-license-ids": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + "bundled": true }, "string-width": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "bundled": true, "requires": { "is-fullwidth-code-point": "2.0.0", "strip-ansi": "4.0.0" @@ -11936,34 +11802,29 @@ }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "bundled": true, "requires": { "ansi-regex": "3.0.0" } }, "strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "bundled": true }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "bundled": true }, "supports-color": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "bundled": true, "requires": { "has-flag": "3.0.0" } }, "test-exclude": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.0.0.tgz", - "integrity": "sha512-bO3Lj5+qFa9YLfYW2ZcXMOV1pmQvw+KS/DpjqhyX6Y6UZ8zstpZJ+mA2ERkXfpOqhxsJlQiLeVXD3Smsrs6oLw==", + "bundled": true, "requires": { "arrify": "1.0.1", "minimatch": "3.0.4", @@ -11973,8 +11834,7 @@ }, "uglify-js": { "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "bundled": true, "optional": true, "requires": { "source-map": "0.5.7", @@ -11984,8 +11844,7 @@ "dependencies": { "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "bundled": true, "optional": true, "requires": { "camelcase": "1.2.1", @@ -11998,19 +11857,16 @@ }, "uglify-to-browserify": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "bundled": true, "optional": true }, "uuid": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "bundled": true }, "validate-npm-package-license": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "bundled": true, "requires": { "spdx-correct": "3.0.0", "spdx-expression-parse": "3.0.0" @@ -12018,32 +11874,27 @@ }, "which": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "bundled": true, "requires": { "isexe": "2.0.0" } }, "which-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "bundled": true }, "window-size": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "bundled": true, "optional": true }, "wordwrap": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "bundled": true }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "bundled": true, "requires": { "string-width": "1.0.2", "strip-ansi": "3.0.1" @@ -12051,21 +11902,18 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "bundled": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "requires": { "number-is-nan": "1.0.1" } }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -12074,8 +11922,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "requires": { "ansi-regex": "2.1.1" } @@ -12084,13 +11931,11 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "bundled": true }, "write-file-atomic": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "bundled": true, "requires": { "graceful-fs": "4.1.11", "imurmurhash": "0.1.4", @@ -12099,18 +11944,15 @@ }, "y18n": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "bundled": true }, "yallist": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "bundled": true }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "bundled": true, "requires": { "cliui": "4.1.0", "decamelize": "1.2.0", @@ -12128,8 +11970,7 @@ "dependencies": { "cliui": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "bundled": true, "requires": { "string-width": "2.1.1", "strip-ansi": "4.0.0", @@ -12138,16 +11979,14 @@ }, "find-up": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "bundled": true, "requires": { "locate-path": "2.0.0" } }, "locate-path": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "bundled": true, "requires": { "p-locate": "2.0.0", "path-exists": "3.0.0" @@ -12155,39 +11994,34 @@ }, "p-limit": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "bundled": true, "requires": { "p-try": "1.0.0" } }, "p-locate": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "bundled": true, "requires": { "p-limit": "1.3.0" } }, "p-try": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "bundled": true } } }, "yargs-parser": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "bundled": true, "requires": { "camelcase": "4.1.0" }, "dependencies": { "camelcase": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + "bundled": true } } } @@ -12343,9 +12177,9 @@ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, "oidc-client": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.5.3.tgz", - "integrity": "sha512-TvONI7d9ZxlpRNrqkUiFfEhu9dyX7UoN/9mbaoa96r10a8SaekDOJev3ZieHCGsGRvTBFYuIEFefH1D/Zzljxg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.5.4.tgz", + "integrity": "sha512-ddom2rLs7xc6kAOOD7LMXQJTcoJxGLfIM/Up19/LiDYQQrSERGCtyuqjyDoR8T2XQtFhU5Y2HWs1O3WdvyYckA==", "requires": { "babel-polyfill": "6.26.0", "jsrsasign": "8.0.12" @@ -12356,7 +12190,7 @@ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-3.0.1.tgz", "integrity": "sha512-oLnVSEcNZkw01sB5aFR+2iJmW4oyC1PIMJmd3FMBGDuPTy5ZtEuX5WNhKMRarJIMOq8NiOwIB6eJB9AhgYwBTg==", "requires": { - "base64url": "3.0.0" + "base64url": "3.0.1" } }, "on-finished": { @@ -12381,9 +12215,12 @@ } }, "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "1.2.0" + } }, "ono": { "version": "4.0.10", @@ -12393,16 +12230,21 @@ "format-util": "1.0.3" } }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==" + }, "openid-client": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-2.4.4.tgz", - "integrity": "sha512-jyiIXip2ONPC0sSJkLrP7fPh+al1IAUTGNGzhB4bWWZaxAdcTzRXKHSx4RUMe6W9DTHX9MMbqv22lNi+qnWuuQ==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-2.4.5.tgz", + "integrity": "sha512-TmQQF7mOiYZsxX4kaQbrN06SXZ31UR5m4ur6sjM+VhaIuF79lqHe7uLmmt/NGVBIQojox6a4eilQ8DQDEQv31Q==", "requires": { - "base64url": "3.0.0", + "base64url": "3.0.1", "got": "8.3.2", "lodash": "4.17.11", - "lru-cache": "4.1.3", - "node-jose": "1.0.0", + "lru-cache": "4.1.5", + "node-jose": "1.1.0", "oidc-token-hash": "3.0.1", "p-any": "1.1.0" } @@ -12456,7 +12298,7 @@ "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "requires": { - "url-parse": "1.4.3" + "url-parse": "1.4.4" } }, "os-browserify": { @@ -12466,7 +12308,7 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { @@ -12481,7 +12323,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { @@ -12613,9 +12455,9 @@ } }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==" }, "parallel-transform": { "version": "1.1.0", @@ -12773,7 +12615,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { @@ -12849,6 +12691,11 @@ "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, + "pidtree": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz", + "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==" + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -12876,11 +12723,51 @@ } }, "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "requires": { - "find-up": "2.1.0" + "find-up": "3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + } } }, "pkg-up": { @@ -12896,11 +12783,6 @@ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=" - }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -12912,14 +12794,14 @@ "integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==" }, "popper.js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz", - "integrity": "sha1-juwdj/AqWjoVLdQ0FKFce3n9abY=" + "version": "1.14.6", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", + "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" }, "portfinder": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", - "integrity": "sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", "requires": { "async": "1.5.2", "debug": "2.6.9", @@ -13205,9 +13087,9 @@ } }, "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", "requires": { "postcss": "6.0.23" }, @@ -13234,7 +13116,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", "requires": { - "css-selector-tokenizer": "0.7.0", + "css-selector-tokenizer": "0.7.1", "postcss": "6.0.23" }, "dependencies": { @@ -13260,7 +13142,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", "requires": { - "css-selector-tokenizer": "0.7.0", + "css-selector-tokenizer": "0.7.1", "postcss": "6.0.23" }, "dependencies": { @@ -13470,9 +13352,9 @@ } }, "posthtml-rename-id": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/posthtml-rename-id/-/posthtml-rename-id-1.0.8.tgz", - "integrity": "sha512-xloDnCx/PFUCxEnXbUC79p3I5/ik3x2ffsx3ihX9HBhsbffXWIVl9O8+tplGk9d13IVhp202VvH++Hht7dGJTQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/posthtml-rename-id/-/posthtml-rename-id-1.0.10.tgz", + "integrity": "sha512-/nEE3mIriQvp8GgoZwRopTm/7mvmBZeSB2K9NXIeCbI0BV1+112fKuNxUM+RDC7TyKr9YRSmLkQaRekgq9vRvA==", "requires": { "escape-string-regexp": "1.0.5" } @@ -13483,11 +13365,11 @@ "integrity": "sha512-jL6eFIzoN3xUEvbo33OAkSDE2VIKU4JQ1wENOows1DpfnrdapR/K3Q1/fB43Mq7wQlcSgRm23nFrvoioufM7eA==" }, "posthtml-svg-mode": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/posthtml-svg-mode/-/posthtml-svg-mode-1.0.2.tgz", - "integrity": "sha512-yH4w0CULTg6v3YW5hVUY2z14R+11XWvtxMRqk30FDgxg0JWv+BhS9FLtKNIu858/1mrO1NcIC4heb6IezLAfHw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/posthtml-svg-mode/-/posthtml-svg-mode-1.0.3.tgz", + "integrity": "sha512-hEqw9NHZ9YgJ2/0G7CECOeuLQKZi8HjWLkBaSVtOWjygQ9ZD8P7tqeowYs7WrFdKsWEKG7o+IlsPY8jrr0CJpQ==", "requires": { - "merge-options": "0.0.64", + "merge-options": "1.0.1", "posthtml": "0.9.2", "posthtml-parser": "0.2.1", "posthtml-render": "1.1.4" @@ -13515,7 +13397,7 @@ }, "pretty-bytes": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", "requires": { "get-stdin": "4.0.1", @@ -13558,7 +13440,7 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "parse-json": "2.2.0", "pify": "2.3.0", "pinkie-promise": "2.0.1", @@ -13608,7 +13490,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "pify": "2.3.0", "pinkie-promise": "2.0.1" } @@ -13774,24 +13656,11 @@ "ipaddr.js": "1.8.0" } }, - "proxy-from-env": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-0.0.1.tgz", - "integrity": "sha1-snxJRunm1dutt1mKZDXTAUxM/Uk=" - }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, - "ps-tree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", - "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", - "requires": { - "event-stream": "3.3.6" - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -13803,12 +13672,9 @@ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "pstree.remy": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", - "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", - "requires": { - "ps-tree": "1.1.0" - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.2.tgz", + "integrity": "sha512-vL6NLxNHzkNTjGJUpMm5PLC+94/0tTlC1vkP9bdU0pOHih+EujMjgMTwfZopZvHWRFbqJ5Y73OMoau50PewDDA==" }, "public-encrypt": { "version": "4.0.3", @@ -13852,15 +13718,15 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "1.4.1", "once": "1.4.0" @@ -13874,6 +13740,17 @@ "duplexify": "3.6.1", "inherits": "2.0.3", "pump": "2.0.1" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + } } }, "punycode": { @@ -13887,9 +13764,9 @@ "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", + "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==" }, "query-string": { "version": "5.1.1", @@ -13922,9 +13799,9 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" }, "raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", "requires": { "performance-now": "2.1.0" } @@ -13936,7 +13813,7 @@ }, "ramda": { "version": "0.21.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz", + "resolved": "http://registry.npmjs.org/ramda/-/ramda-0.21.0.tgz", "integrity": "sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=" }, "randexp": { @@ -14029,35 +13906,28 @@ "ini": "1.3.5", "minimist": "1.2.0", "strip-json-comments": "2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - } } }, "react": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.0.tgz", - "integrity": "sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", + "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", "requires": { "loose-envify": "1.4.0", "object-assign": "4.1.1", "prop-types": "15.6.2", - "scheduler": "0.10.0" + "scheduler": "0.11.2" } }, "react-data-grid": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-3.0.11.tgz", - "integrity": "sha1-BjKdFUiineQ7MU3FEBb5cso0dQo=" + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-4.0.9.tgz", + "integrity": "sha512-OEzsGRlLPzxfw2XiFNPmfOPZSAfGHqOQcN4GkDJDVfDtUcsgrvUhV9uKQGyGfehswvwEEuDhN3K1Ob7Vrquj5Q==" }, "react-dev-utils": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-6.0.5.tgz", - "integrity": "sha512-X3/q2y8GHvcn6qzqlFhFNoIQgU4TfyerYpBTc5BrMtrSO0q1ctIheAaxDQawNeRgsm2W7L3QSQ9po+YLAGcYFQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-6.1.1.tgz", + "integrity": "sha512-ThbJ86coVd6wV/QiTo8klDTvdAJ1WsFCGQN07+UkN+QN9CtCSsl/+YuDJToKGeG8X4j9HMGXNKbk2QhPAZr43w==", "requires": { "@babel/code-frame": "7.0.0", "address": "1.0.3", @@ -14069,13 +13939,15 @@ "filesize": "3.6.1", "find-up": "3.0.0", "global-modules": "1.0.0", + "globby": "8.0.1", "gzip-size": "5.0.0", + "immer": "1.7.2", "inquirer": "6.2.0", "is-root": "2.0.0", "loader-utils": "1.1.0", "opn": "5.4.0", "pkg-up": "2.0.0", - "react-error-overlay": "5.0.5", + "react-error-overlay": "5.1.0", "recursive-readdir": "2.2.2", "shell-quote": "1.6.1", "sockjs-client": "1.1.5", @@ -14083,11 +13955,6 @@ "text-table": "0.2.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -14098,17 +13965,9 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.1.1.tgz", "integrity": "sha512-VBorw+tgpOtZ1BYhrVSVTzTt/3+vSE3eFUh0N2GCFK1HffceOaf32YS/bs6WiFhjDAblAFrx85jMy3BG9fBK2Q==", "requires": { - "caniuse-lite": "1.0.30000899", - "electron-to-chromium": "1.3.82", - "node-releases": "1.0.0-alpha.15" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "requires": { - "restore-cursor": "2.0.0" + "caniuse-lite": "1.0.30000912", + "electron-to-chromium": "1.3.87", + "node-releases": "1.0.5" } }, "eventsource": { @@ -14127,14 +13986,6 @@ "websocket-driver": "0.7.0" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "requires": { - "escape-string-regexp": "1.0.5" - } - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -14143,24 +13994,18 @@ "locate-path": "3.0.0" } }, - "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "globby": { + "version": "8.0.1", + "resolved": "http://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.4.1", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "3.0.3", - "figures": "2.0.0", - "lodash": "4.17.11", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "6.3.3", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "array-union": "1.0.2", + "dir-glob": "2.0.0", + "fast-glob": "2.2.4", + "glob": "7.1.3", + "ignore": "3.3.10", + "pify": "3.0.0", + "slash": "1.0.0" } }, "locate-path": { @@ -14172,19 +14017,6 @@ "path-exists": "3.0.0" } }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "1.2.0" - } - }, "p-limit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", @@ -14206,23 +14038,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "2.1.0" - } - }, "sockjs-client": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", @@ -14233,7 +14048,7 @@ "faye-websocket": "0.11.1", "inherits": "2.0.3", "json3": "3.3.2", - "url-parse": "1.4.3" + "url-parse": "1.4.4" } }, "strip-ansi": { @@ -14264,7 +14079,7 @@ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-5.0.1.tgz", "integrity": "sha1-C1eNecXAExfHBBTI1xf2MrkZ1PE=", "requires": { - "autobind-decorator": "2.1.0", + "autobind-decorator": "2.4.0", "dnd-core": "4.0.5", "lodash": "4.17.11", "shallowequal": "1.1.0" @@ -14280,35 +14095,35 @@ } }, "react-dom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0.tgz", - "integrity": "sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", + "integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==", "requires": { "loose-envify": "1.4.0", "object-assign": "4.1.1", "prop-types": "15.6.2", - "scheduler": "0.10.0" + "scheduler": "0.11.2" } }, "react-error-overlay": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.0.5.tgz", - "integrity": "sha512-ab0HWBgxdIsngHtMGU8+8gYFdTBXpUGd4AE4lN2VZvOIlBmWx9dtaWEViihuGSIGosCKPeHCnzFoRWB42UtnLg==" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.0.tgz", + "integrity": "sha512-akMy/BQT5m1J3iJIHkSb4qycq2wzllWsmmolaaFVnb+LPV9cIJ/nTud40ZsiiT0H3P+/wXIdbjx2fzF61OaeOQ==" }, "react-highlight-words": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-highlight-words/-/react-highlight-words-0.14.0.tgz", "integrity": "sha512-DNp1zAIiNqs1nunFv5K67ca0KVSKihbmpZLNwqQj61yMK6zf8YHoGrpLlEt7WEqj1227FOm41FuXiv9KZgcwdA==", "requires": { - "highlight-words-core": "1.2.0", - "memoize-one": "4.0.2", + "highlight-words-core": "1.2.2", + "memoize-one": "4.0.3", "prop-types": "15.6.2" } }, "react-is": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", - "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==" + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -14326,32 +14141,50 @@ "unified": "6.2.0", "unist-util-visit": "1.4.0", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "react-redux": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.0.tgz", - "integrity": "sha512-CRMpEx8+ccpoVxQrQDkG1obExNYpj6dZ1Ni7lUNFB9wcxOhy02tIqpFo4IUXc0kYvCGMu6ABj9A4imEX2VGZJQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.1.tgz", + "integrity": "sha512-LE7Ned+cv5qe7tMV5BPYkGQ5Lpg8gzgItK07c67yHvJ8t0iaD9kPFPAli/mYkiyJYrs2pJgExR2ZgsGqlrOApg==", "requires": { - "@babel/runtime": "7.1.2", - "hoist-non-react-statics": "3.0.1", + "@babel/runtime": "7.1.5", + "hoist-non-react-statics": "3.2.0", "invariant": "2.2.4", "loose-envify": "1.4.0", "prop-types": "15.6.2", - "react-is": "16.6.0", + "react-is": "16.6.3", "react-lifecycles-compat": "3.0.4" }, "dependencies": { "hoist-non-react-statics": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz", - "integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.0.tgz", + "integrity": "sha512-3IascCRfaEkbmHjJnUxWSspIUE1okLPjGTMVXW8zraUo1t3yg1BadKAxAGILHwgoBzmMnzrgeeaDGBvpuPz6dA==", "requires": { - "react-is": "16.6.0" + "react-is": "16.6.3" } } } }, + "react-resize-detector": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-3.2.1.tgz", + "integrity": "sha512-w3DSXcsv73ZxGoLb2tD8VZNcZXoyCOVKDt137Em7xuGo+UQzCzKdWYOqNBI3I6RV0wB5g8FnwUyOoa0M6B7dqQ==", + "requires": { + "lodash": "4.17.11", + "lodash-es": "4.17.11", + "prop-types": "15.6.2", + "resize-observer-polyfill": "1.5.0" + } + }, "react-router": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", @@ -14395,13 +14228,12 @@ } }, "react-split-pane": { - "version": "0.1.84", - "resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.84.tgz", - "integrity": "sha512-rso1dRAXX/WETyqF5C0fomIYzpF71Nothfr1R7pFkrJCPVJ20ok2e6wqF+JvUTyE/meiBvsbNPT1loZjyU+53w==", + "version": "0.1.77", + "resolved": "http://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.77.tgz", + "integrity": "sha512-xq0PPsbkNI9xEd6yTrGPr7hzf6XfIgnsxuUEdRJELq+kLPHMsO3ymFCjhiYP35wlDPn9W46+rHDsJd7LFYteMw==", "requires": { "inline-style-prefixer": "3.0.8", "prop-types": "15.6.2", - "react-lifecycles-compat": "3.0.4", "react-style-proptype": "3.2.2" } }, @@ -14420,7 +14252,7 @@ "requires": { "babel-runtime": "6.26.0", "highlight.js": "9.8.0", - "lowlight": "1.10.0" + "lowlight": "1.11.0" }, "dependencies": { "highlight.js": { @@ -14431,22 +14263,35 @@ } }, "react-test-renderer": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.0.tgz", - "integrity": "sha512-w+Y3YT7OX1LP5KO7HCd0YR34Ol1qmISHaooPNMRYa6QzmwtcWhEGuZPr34wO8UCBIokswuhyLQUq7rjPDcEtJA==", + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", + "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", "requires": { "object-assign": "4.1.1", "prop-types": "15.6.2", - "react-is": "16.6.0", - "scheduler": "0.10.0" + "react-is": "16.6.3", + "scheduler": "0.11.2" } }, "react-testing-library": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-5.2.3.tgz", - "integrity": "sha512-Bw52++7uORuIQnL55lK/WQfppqAc9+8yFG4lWUp/kmSOvYDnt8J9oI5fNCfAGSQi9iIhAv9aNsI2G5rtid0nrA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-testing-library/-/react-testing-library-5.3.1.tgz", + "integrity": "sha512-lYhbNhlDRzOkqo+QjDGmG7BpFL8nBNLmCi0npW+VsF8pvhwtu78D4rZ7V3WimRaXFQqOt8VQ0+3CwjGkqV9VUA==", + "requires": { + "dom-testing-library": "3.13.0" + } + }, + "react-virtualized": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.0.tgz", + "integrity": "sha512-duKD2HvO33mqld4EtQKm9H9H0p+xce1c++2D5xn59Ma7P8VT7CprfAe5hwjd1OGkyhqzOZiTMlTal7LxjH5yBQ==", "requires": { - "dom-testing-library": "3.12.0" + "babel-runtime": "6.26.0", + "classnames": "2.2.6", + "dom-helpers": "3.4.0", + "loose-envify": "1.4.0", + "prop-types": "15.6.2", + "react-lifecycles-compat": "3.0.4" } }, "read-chunk": { @@ -14492,7 +14337,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "micromatch": "3.1.10", "readable-stream": "2.3.6" }, @@ -14752,26 +14597,6 @@ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "mute-stream": "0.0.5" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - } - } - }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -14851,14 +14676,6 @@ "symbol-observable": "1.2.0" } }, - "redux-oidc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-oidc/-/redux-oidc-3.1.0.tgz", - "integrity": "sha512-VygfGdkuUI8kRn+R1LQLcGAmm0fJUPHKqaRB3l2BL8D9s3sIFFanuVf7Q05QS6DlRVIz9pmcgInyBHlAVZE8og==", - "requires": { - "immutable": "3.8.2" - } - }, "reflect-metadata": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", @@ -14920,12 +14737,12 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=" }, "regjsparser": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "resolved": "http://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", "requires": { "jsesc": "0.5.0" @@ -14933,7 +14750,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" } } @@ -14961,8 +14778,15 @@ "trim-trailing-lines": "1.1.1", "unherit": "1.1.1", "unist-util-remove-position": "1.1.2", - "vfile-location": "2.0.3", + "vfile-location": "2.0.4", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "remove-trailing-separator": { @@ -15027,7 +14851,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -15067,7 +14891,7 @@ "extend": "3.0.2", "forever-agent": "0.6.1", "form-data": "2.3.2", - "har-validator": "5.1.0", + "har-validator": "5.1.3", "http-signature": "1.2.0", "is-typedarray": "1.0.0", "isstream": "0.1.2", @@ -15080,6 +14904,27 @@ "tough-cookie": "2.4.3", "tunnel-agent": "0.6.0", "uuid": "3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.1.29", + "punycode": "1.4.1" + } + } } }, "request-promise-core": { @@ -15097,7 +14942,7 @@ "requires": { "request-promise-core": "1.1.1", "stealthy-require": "1.1.1", - "tough-cookie": "2.4.3" + "tough-cookie": "2.5.0" } }, "require-directory": { @@ -15115,19 +14960,15 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - } - }, "requires-port": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-0.0.1.tgz", - "integrity": "sha1-S0QUQR2d98hVmV3YmajHiilRwW0=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resize-observer-polyfill": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz", + "integrity": "sha512-M2AelyJDVR/oLnToJLtuDJRBBWUGUvvGigj1411hXhAdyFWqMaqHp7TixW3FpiLuVaikIcR1QL+zqoJoZlOgpg==" }, "resolve": { "version": "1.8.1", @@ -15143,13 +14984,6 @@ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "requires": { "resolve-from": "3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } } }, "resolve-dir": { @@ -15162,9 +14996,9 @@ } }, "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "resolve-pathname": { "version": "2.2.0", @@ -15185,12 +15019,12 @@ } }, "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "onetime": "2.0.1", + "signal-exit": "3.0.2" } }, "ret": { @@ -15230,11 +15064,11 @@ } }, "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "requires": { - "once": "1.4.0" + "is-promise": "2.1.0" } }, "run-queue": { @@ -15251,16 +15085,16 @@ "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" }, "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=" + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" }, "rx-lite-aggregates": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", "requires": { - "rx-lite": "3.1.2" + "rx-lite": "4.0.8" } }, "rxjs": { @@ -15284,7 +15118,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "0.1.15" @@ -15330,6 +15164,11 @@ "pinkie-promise": "2.0.1" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -15356,7 +15195,7 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "parse-json": "2.2.0", "pify": "2.3.0", "pinkie-promise": "2.0.1", @@ -15392,7 +15231,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "pify": "2.3.0", "pinkie-promise": "2.0.1" } @@ -15509,9 +15348,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.10.0.tgz", - "integrity": "sha512-+TSTVTCBAA3h8Anei3haDc1IRwMeDmtI/y/o3iBe3Mjl2vwYF9DtPDt929HyRmV/e7au7CLu8sc4C4W0VOs29w==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", + "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", "requires": { "loose-envify": "1.4.0", "object-assign": "4.1.1" @@ -15522,36 +15361,8 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", "requires": { - "ajv": "6.5.4", + "ajv": "6.6.1", "ajv-keywords": "3.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - } } }, "scss-tokenizer": { @@ -15565,7 +15376,7 @@ "dependencies": { "source-map": { "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "requires": { "amdefine": "1.0.1" @@ -15760,9 +15571,9 @@ } }, "shelljs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", - "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", "requires": { "glob": "7.1.3", "interpret": "1.1.0", @@ -15808,21 +15619,26 @@ } }, "sinon": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.3.5.tgz", - "integrity": "sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.1.1.tgz", + "integrity": "sha512-iYagtjLVt1vN3zZY7D8oH7dkjNJEjLjyuzy8daX5+3bbQl8gaohrheB9VfH1O3L6LKuue5WTJvFluHiuZ9y3nQ==", "requires": { - "@sinonjs/commons": "1.0.2", + "@sinonjs/commons": "1.3.0", "@sinonjs/formatio": "3.0.0", - "@sinonjs/samsam": "2.1.2", + "@sinonjs/samsam": "2.1.3", "diff": "3.5.0", "lodash.get": "4.4.2", - "lolex": "2.7.5", + "lolex": "3.0.0", "nise": "1.4.6", "supports-color": "5.5.0", "type-detect": "4.0.8" }, "dependencies": { + "lolex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -15834,20 +15650,15 @@ } }, "sinon-chai": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.2.0.tgz", - "integrity": "sha512-Z72B4a0l0IQe5uWi9yzcqX/Ml6K9e1Hp03NmkjJnRG3gDsKTX7KvLFZsVUmCaz0eqeXLLK089mwTsP1P1W+DUQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==" }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" - }, "slice-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz", @@ -15874,7 +15685,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } @@ -15976,14 +15787,6 @@ "kind-of": "3.2.2" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", @@ -16003,7 +15806,7 @@ "faye-websocket": "0.11.1", "inherits": "2.0.3", "json3": "3.3.2", - "url-parse": "1.4.3" + "url-parse": "1.4.4" }, "dependencies": { "debug": { @@ -16100,7 +15903,7 @@ "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", "requires": { "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.1" + "spdx-license-ids": "3.0.2" } }, "spdx-exceptions": { @@ -16114,13 +15917,13 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { "spdx-exceptions": "2.2.0", - "spdx-license-ids": "3.0.1" + "spdx-license-ids": "3.0.2" } }, "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", + "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==" }, "spdy": { "version": "3.4.7", @@ -16132,13 +15935,13 @@ "http-deceiver": "1.2.7", "safe-buffer": "5.1.2", "select-hose": "2.0.0", - "spdy-transport": "2.1.0" + "spdy-transport": "2.1.1" } }, "spdy-transport": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", - "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.1.tgz", + "integrity": "sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==", "requires": { "debug": "2.6.9", "detect-node": "2.0.4", @@ -16176,9 +15979,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz", - "integrity": "sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -16192,11 +15995,11 @@ } }, "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "requires": { - "safe-buffer": "5.1.2" + "figgy-pudding": "3.5.1" } }, "state-toggle": { @@ -16251,7 +16054,7 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "requires": { "inherits": "2.0.3", @@ -16286,6 +16089,13 @@ "readable-stream": "2.3.6", "to-arraybuffer": "1.0.1", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "stream-shift": { @@ -16368,17 +16178,12 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "5.1.2" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, "strip-ansi": { "version": "3.0.1", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -16394,7 +16199,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { @@ -16403,9 +16208,9 @@ "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "style-loader": { "version": "0.21.0", @@ -16446,7 +16251,7 @@ "formidable": "1.2.1", "methods": "1.1.2", "mime": "1.4.1", - "qs": "6.5.2", + "qs": "6.6.0", "readable-stream": "2.3.6" }, "dependencies": { @@ -16465,49 +16270,6 @@ } } }, - "supertest": { - "version": "2.0.1", - "resolved": "http://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz", - "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=", - "requires": { - "methods": "1.1.2", - "superagent": "2.3.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "form-data": { - "version": "1.0.0-rc4", - "resolved": "http://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", - "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", - "requires": { - "async": "1.5.2", - "combined-stream": "1.0.7", - "mime-types": "2.1.21" - } - }, - "superagent": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz", - "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=", - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.2", - "debug": "2.6.9", - "extend": "3.0.2", - "form-data": "1.0.0-rc4", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.2", - "readable-stream": "2.3.6" - } - } - } - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -16517,21 +16279,21 @@ } }, "svg-baker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/svg-baker/-/svg-baker-1.3.0.tgz", - "integrity": "sha512-C5mevzNJ9IGZa4MFP/wTxFGopeGtUxK9m2Ta2xmG26fRnuGR8TdTEkkeW31hnJfyrim2wBjf/kleMPAtWBRZgg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/svg-baker/-/svg-baker-1.4.0.tgz", + "integrity": "sha512-VDI530erEOb1E8kbl2fMOCWBMnk9kz7RyjE0JtcvcAyhofSyqd0Oj7mL9MGvECexyZJn+rX6Isk6Hk21ApRcJg==", "requires": { - "bluebird": "3.5.2", + "bluebird": "3.5.3", "clone": "2.1.2", "he": "1.1.1", "image-size": "0.5.5", "loader-utils": "1.1.0", - "merge-options": "0.0.64", + "merge-options": "1.0.1", "micromatch": "3.1.0", "postcss": "5.2.18", "postcss-prefix-selector": "1.6.0", - "posthtml-rename-id": "1.0.8", - "posthtml-svg-mode": "1.0.2", + "posthtml-rename-id": "1.0.10", + "posthtml-svg-mode": "1.0.3", "query-string": "4.3.4", "traverse": "0.6.6" }, @@ -16792,13 +16554,13 @@ } }, "svg-baker-runtime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/svg-baker-runtime/-/svg-baker-runtime-1.3.5.tgz", - "integrity": "sha512-BKxJT/Zz9M+K043zXbZf7CA3c10NKWByxobAukO30VLv71OvmpagjG32Z0UIay6ctMaOUmywOKHuceiSDqwUOA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/svg-baker-runtime/-/svg-baker-runtime-1.4.0.tgz", + "integrity": "sha512-wrv8ivS2MlkKKl9CLULEevxOSLXtCkQuwZaF9stFnvSQRp/Xexh2R5MOWpMQr+px0JMOy4I1dAXm50hq75Asrg==", "requires": { "deepmerge": "1.3.2", "mitt": "1.1.2", - "svg-baker": "1.3.0" + "svg-baker": "1.4.0" }, "dependencies": { "deepmerge": { @@ -16833,13 +16595,13 @@ "resolved": "https://registry.npmjs.org/svg-sprite-loader/-/svg-sprite-loader-3.9.2.tgz", "integrity": "sha512-tnL7qj5ArgSYjXePzx+pZpDDzz2rMhjYdzaTjiuBz6nbPPgf2uOvO8mWj8wf/0Iv6Szd406fUYMDIyT3XnwEww==", "requires": { - "bluebird": "3.5.2", + "bluebird": "3.5.3", "deepmerge": "1.3.2", "domready": "1.0.8", "escape-string-regexp": "1.0.5", "loader-utils": "1.1.0", - "svg-baker": "1.3.0", - "svg-baker-runtime": "1.3.5", + "svg-baker": "1.4.0", + "svg-baker-runtime": "1.4.0", "url-slug": "2.0.0" }, "dependencies": { @@ -16866,7 +16628,7 @@ "dependencies": { "colors": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=" }, "esprima": { @@ -16938,7 +16700,7 @@ "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "parse-json": "2.2.0", "pify": "2.3.0", "pinkie-promise": "2.0.1", @@ -16988,7 +16750,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "pify": "2.3.0", "pinkie-promise": "2.0.1" } @@ -17000,7 +16762,7 @@ }, "pretty-bytes": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" }, "read-pkg": { @@ -17069,38 +16831,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "1.0.2", - "glob": "7.1.3", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -17151,60 +16881,14 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" }, - "table": { - "version": "3.8.3", - "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.11", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, "tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", - "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", + "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==" }, "tar": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "requires": { "block-stream": "0.0.9", @@ -17224,6 +16908,13 @@ "readable-stream": "2.3.6", "to-buffer": "1.1.1", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "tcp-port-used": { @@ -17250,6 +16941,22 @@ } } }, + "temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "requires": { + "os-tmpdir": "1.0.2", + "rimraf": "2.2.8" + }, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "resolved": "http://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" + } + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -17263,7 +16970,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "shebang-command": "1.2.0", "which": "1.3.1" } @@ -17284,6 +16991,60 @@ } } }, + "terser": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.11.0.tgz", + "integrity": "sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==", + "requires": { + "commander": "2.17.1", + "source-map": "0.6.1", + "source-map-support": "0.5.9" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "terser-webpack-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz", + "integrity": "sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA==", + "requires": { + "cacache": "11.3.1", + "find-cache-dir": "2.0.0", + "schema-utils": "1.0.0", + "serialize-javascript": "1.5.0", + "source-map": "0.6.1", + "terser": "3.11.0", + "webpack-sources": "1.3.0", + "worker-farm": "1.6.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "6.6.1", + "ajv-errors": "1.0.0", + "ajv-keywords": "3.2.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, "text-encoding": { "version": "0.6.4", "resolved": "http://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", @@ -17322,7 +17083,7 @@ }, "through2": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "resolved": "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz", "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", "requires": { "readable-stream": "1.1.14", @@ -17334,11 +17095,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" - }, "readable-stream": { "version": "1.1.14", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -17352,16 +17108,8 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", - "requires": { - "object-keys": "0.4.0" - } } } }, @@ -17455,11 +17203,11 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tooltip.js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tooltip.js/-/tooltip.js-1.3.0.tgz", - "integrity": "sha512-5aj0jSQ2J8OOKRPTricY45HNUPRVbVRJZpZm2Wy9pd5BknGEc6epHwJ1eFvEmhXc3hpocDyRpZq31IiWcmcLVg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tooltip.js/-/tooltip.js-1.3.1.tgz", + "integrity": "sha512-nZQGUM3YfjUlCVw3+RqYcCubIDj5NmtPsQ6Xj1NZvii1KSnPxuQ9d6xvNgsNmsmilwhNcBOmHLhwuSDVYINm0g==", "requires": { - "popper.js": "1.14.4" + "popper.js": "1.14.6" } }, "toposort": { @@ -17486,19 +17234,12 @@ } }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { "psl": "1.1.29", - "punycode": "1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "punycode": "2.1.1" } }, "tr46": { @@ -17515,9 +17256,9 @@ "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, "tree-kill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", - "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==" }, "trim": { "version": "0.0.1", @@ -17552,6 +17293,11 @@ "glob": "7.1.3" } }, + "ts-key-enum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.0.tgz", + "integrity": "sha1-NcmPC8LHM//+QiXE/r4DmwXxxoY=" + }, "ts-loader": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-4.5.0.tgz", @@ -17830,9 +17576,9 @@ } }, "tsconfig-paths": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.6.0.tgz", - "integrity": "sha512-mrqQIP2F4e03aMTCiPdedCIT300//+q0ET53o5WqqtQjmEICxP9yfz/sHTpPqXpssuJEzODsEzJaLRaf5J2X1g==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.7.0.tgz", + "integrity": "sha512-7iE+Q/2E1lgvxD+c0Ot+GFFmgmfIjt/zCayyruXkXQ84BLT85gHXy0WSoQSiuFX9+d+keE/jiON7notV74ZY+A==", "requires": { "@types/json5": "0.0.29", "deepmerge": "2.2.1", @@ -17881,11 +17627,11 @@ } }, "tslint-consistent-codestyle": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.13.3.tgz", - "integrity": "sha512-+ocXSNGHqUCUyTJsPhS7xqcC3qf6FyP4vd1jEaXaWaJ5NNN36gKZhqNt3nAWH/YgSV0tYaapjSWMbJQJmn/5MQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.14.1.tgz", + "integrity": "sha512-UxGRX2fF5LpZtqYpuPFaIva+2D7ASX3pTVw41yis6Hmw7PPA3cBnFEX1jqRsnyxGrca6mHxz7xDnwCHtOjWJMQ==", "requires": { - "@fimbul/bifrost": "0.11.0", + "@fimbul/bifrost": "0.15.0", "tslib": "1.9.3", "tsutils": "2.29.0" } @@ -17974,7 +17720,7 @@ "marked": "0.3.19", "minimatch": "3.0.4", "progress": "2.0.1", - "shelljs": "0.8.2", + "shelljs": "0.8.3", "typedoc-default-themes": "0.5.0", "typescript": "2.7.2" }, @@ -17997,7 +17743,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "4.0.0", "universalify": "0.1.2" } @@ -18030,9 +17776,9 @@ } }, "typescript": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.3.tgz", - "integrity": "sha512-kk80vLW9iGtjMnIv11qyxLqZm20UklzuR2tL0QAnDIygIUIemcZMxlMWudl9OOt76H3ntVzcTiddQ1/pAAJMYg==" + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==" }, "typescript-json-schema": { "version": "0.28.0", @@ -18050,7 +17796,7 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "4.1.3", + "lru-cache": "4.1.5", "shebang-command": "1.2.0", "which": "1.3.1" } @@ -18069,6 +17815,11 @@ "strip-eof": "1.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -18180,16 +17931,97 @@ "worker-farm": "1.6.0" }, "dependencies": { + "cacache": { + "version": "10.0.4", + "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "requires": { + "bluebird": "3.5.3", + "chownr": "1.1.1", + "glob": "7.1.3", + "graceful-fs": "4.1.15", + "lru-cache": "4.1.5", + "mississippi": "2.0.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.2", + "ssri": "5.3.0", + "unique-filename": "1.1.1", + "y18n": "4.0.0" + } + }, "commander": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "requires": { + "commondir": "1.0.1", + "make-dir": "1.3.0", + "pkg-dir": "2.0.0" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "requires": { + "concat-stream": "1.6.2", + "duplexify": "3.6.1", + "end-of-stream": "1.4.1", + "flush-write-stream": "1.0.3", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "2.0.1", + "pumpify": "1.5.1", + "stream-each": "1.2.3", + "through2": "2.0.5" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "2.1.0" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "1.4.1", + "once": "1.4.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "2.3.6", + "xtend": "4.0.1" + } + }, "uglify-es": { "version": "3.3.9", "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", @@ -18198,6 +18030,11 @@ "commander": "2.13.0", "source-map": "0.6.1" } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } }, @@ -18229,6 +18066,13 @@ "requires": { "inherits": "2.0.3", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "unidecode": { @@ -18479,47 +18323,21 @@ "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "requires": { "loader-utils": "1.1.0", - "mime": "2.3.1", + "mime": "2.4.0", "schema-utils": "1.0.0" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.5.4", + "ajv": "6.6.1", "ajv-errors": "1.0.0", "ajv-keywords": "3.2.0" } @@ -18527,19 +18345,12 @@ } }, "url-parse": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", - "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", + "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", "requires": { "querystringify": "2.1.0", "requires-port": "1.0.0" - }, - "dependencies": { - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - } } }, "url-parse-lax": { @@ -18565,6 +18376,11 @@ } } }, + "url-search-params": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/url-search-params/-/url-search-params-1.1.0.tgz", + "integrity": "sha512-XiO5GLAxmlZgdLob/RmKZRc2INHrssMbpwD6O46JkB1aEJO4fkV3x3mR6+CDX01ijfEUwvfwCiUQZrKqfm1ILw==" + }, "url-slug": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/url-slug/-/url-slug-2.0.0.tgz", @@ -18583,14 +18399,6 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "requires": { - "os-homedir": "1.0.2" - } - }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -18675,18 +18483,18 @@ "is-buffer": "1.1.6", "replace-ext": "1.0.0", "unist-util-stringify-position": "1.1.2", - "vfile-message": "1.0.1" + "vfile-message": "1.0.2" } }, "vfile-location": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.3.tgz", - "integrity": "sha512-zM5/l4lfw1CBoPx3Jimxoc5RNDAHHpk6AM6LM0pTIkm5SUSsx8ZekZ0PVdf0WEZ7kjlhSt7ZlqbRL6Cd6dBs6A==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.4.tgz", + "integrity": "sha512-KRL5uXQPoUKu+NGvQVL4XLORw45W62v4U4gxJ3vRlDfI9QsT4ZN1PNXn/zQpKUulqGDpYuT0XDfp5q9O87/y/w==" }, "vfile-message": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.0.1.tgz", - "integrity": "sha512-vSGCkhNvJzO6VcWC6AlJW4NtYOVtS+RgCaqFIYUjoGIlHnFL+i0LbtYvonDWOMcB97uTPT4PRsyYY7REWC9vug==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.0.2.tgz", + "integrity": "sha512-dNdEXHfPCvzyOlEaaQ+DcXpcxEz+pFvdrebKLiAMjobjaBC2bMeWoHOKPwJ+I8A4jQOEUDH7uoVcLWDLF1qhVQ==", "requires": { "unist-util-stringify-position": "1.1.2" } @@ -18708,9 +18516,9 @@ } }, "wait-for-expect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.0.1.tgz", - "integrity": "sha512-TPZMSxGWUl2DWmqdspLDEy97/S1Mqq0pzbh2A7jTq0WbJurUb5GKli+bai6ayeYdeWTF0rQNWZmUvCVZ9gkrfA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.1.0.tgz", + "integrity": "sha512-vQDokqxyMyknfX3luCDn16bSaRcOyH6gGuUXMIbxBLeTo6nWuEWYqMTT9a+44FmW8c2m6TRWBdNvBBjA1hwEKg==" }, "walkdir": { "version": "0.0.11", @@ -18731,7 +18539,7 @@ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", "requires": { "chokidar": "2.0.4", - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "neo-async": "2.6.0" } }, @@ -18753,10 +18561,10 @@ "dependencies": { "fs-extra": { "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "2.4.0", "klaw": "1.3.1", "path-is-absolute": "1.0.1", @@ -18768,7 +18576,7 @@ "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } } } @@ -18789,13 +18597,13 @@ } }, "wdio-mocha-framework": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/wdio-mocha-framework/-/wdio-mocha-framework-0.6.3.tgz", - "integrity": "sha512-oul0hN6Z2PALt6GhX9zEE2zSVPslrTqj7TQooFWlTOAOG7DjlTHK3XptiRNCUlzslpNnR5F6k/EUut22rxGkRQ==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/wdio-mocha-framework/-/wdio-mocha-framework-0.6.4.tgz", + "integrity": "sha512-GZsXwoW60/fkkfqZJR/ZAdiALaM+hW+BbnTT9x214qPR4Pe5XeyYxhJNEdyf0dNI9625cMdkyZYaWoFHN5zDcA==", "requires": { "babel-runtime": "6.26.0", "mocha": "5.2.0", - "wdio-sync": "0.7.2" + "wdio-sync": "0.7.3" } }, "wdio-screenshot": { @@ -18819,7 +18627,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "3.0.1", "universalify": "0.1.2" } @@ -18829,7 +18637,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } } } @@ -18845,12 +18653,12 @@ } }, "wdio-sync": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.2.tgz", - "integrity": "sha512-JAOzXvQYYjC8PeeA2/95srVYKHK8aymhxgkEdEYJj7HLyamHy7kbCB0IZzhcXRdl9bnOj2XW6LmN7TbsAIV+aA==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz", + "integrity": "sha512-ukASSHOQmOxaz5HTILR0jykqlHBtAPsBpMtwhpiG0aW9uc7SO7PF+E5LhVvTG4ypAh+UGmY3rTjohOsqDr39jw==", "requires": { "babel-runtime": "6.26.0", - "fibers": "2.0.2", + "fibers": "3.1.1", "object.assign": "4.1.0" } }, @@ -18874,7 +18682,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "3.0.1", "universalify": "0.1.2" } @@ -18884,15 +18692,15 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } } } }, "webdriverio": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.0.tgz", - "integrity": "sha512-642Iqp9en2hvuVINkTfQvWoQCaLb6zJyLHgQFUFLx7s+8l8GnrHzMjkv5DbecZHwnBkhybpphbTW7k0B2ARH5A==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.14.1.tgz", + "integrity": "sha512-Gjb5ft6JtO7WdoZifedeM6U941UZi03IlG0t3Xq9M9SxSm6FuyqMEmNZ4HI3UcBRkSbWxdOWGAvpFShYxVr7iA==", "requires": { "archiver": "2.1.1", "babel-runtime": "6.26.0", @@ -18918,11 +18726,6 @@ "wgxpath": "1.0.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -18948,14 +18751,6 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "requires": { - "restore-cursor": "2.0.0" - } - }, "deepmerge": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.0.1.tgz", @@ -18976,14 +18771,6 @@ "tmp": "0.0.33" } }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "requires": { - "escape-string-regexp": "1.0.5" - } - }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", @@ -19010,41 +18797,6 @@ "through": "2.3.8" } }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "1.2.0" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "2.1.0" - } - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -19069,17 +18821,17 @@ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "webpack": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz", - "integrity": "sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==", - "requires": { - "@webassemblyjs/ast": "1.7.10", - "@webassemblyjs/helper-module-context": "1.7.10", - "@webassemblyjs/wasm-edit": "1.7.10", - "@webassemblyjs/wasm-parser": "1.7.10", + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.26.1.tgz", + "integrity": "sha512-i2oOvEvuvLLSuSCkdVrknaxAhtUZ9g+nLSoHCWV0gDzqGX2DXaCrMmMUpbRsTSSLrUqAI56PoEiyMUZIZ1msug==", + "requires": { + "@webassemblyjs/ast": "1.7.11", + "@webassemblyjs/helper-module-context": "1.7.11", + "@webassemblyjs/wasm-edit": "1.7.11", + "@webassemblyjs/wasm-parser": "1.7.11", "acorn": "5.7.3", "acorn-dynamic-import": "3.0.0", - "ajv": "6.5.4", + "ajv": "6.6.1", "ajv-keywords": "3.2.0", "chrome-trace-event": "1.0.0", "enhanced-resolve": "4.1.0", @@ -19093,28 +18845,12 @@ "neo-async": "2.6.0", "node-libs-browser": "2.1.0", "schema-utils": "0.4.7", - "tapable": "1.1.0", - "uglifyjs-webpack-plugin": "1.3.0", + "tapable": "1.1.1", + "terser-webpack-plugin": "1.1.0", "watchpack": "1.6.0", "webpack-sources": "1.3.0" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -19268,11 +19004,6 @@ } } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -19343,11 +19074,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -19383,13 +19109,13 @@ "chalk": "2.4.1", "cross-spawn": "6.0.5", "enhanced-resolve": "4.1.0", - "global-modules-path": "2.3.0", + "global-modules-path": "2.3.1", "import-local": "2.0.0", "interpret": "1.1.0", "loader-utils": "1.1.0", "supports-color": "5.5.0", "v8-compile-cache": "2.0.2", - "yargs": "12.0.2" + "yargs": "12.0.5" }, "dependencies": { "supports-color": { @@ -19408,15 +19134,15 @@ "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", "requires": { "memory-fs": "0.4.1", - "mime": "2.3.1", + "mime": "2.4.0", "range-parser": "1.2.0", "webpack-log": "2.0.0" }, "dependencies": { "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" } } }, @@ -19441,7 +19167,7 @@ "killable": "1.0.1", "loglevel": "1.6.1", "opn": "5.4.0", - "portfinder": "1.0.19", + "portfinder": "1.0.20", "schema-utils": "1.0.0", "selfsigned": "1.10.4", "serve-index": "1.9.1", @@ -19455,22 +19181,6 @@ "yargs": "12.0.2" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=" - }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -19479,62 +19189,98 @@ "ms": "2.1.1" } }, - "del": { + "decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "requires": { + "xregexp": "4.0.0" + } + }, + "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" + "locate-path": "3.0.0" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "array-union": "1.0.2", - "glob": "7.1.3", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, + "p-limit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", + "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "requires": { - "ajv": "6.5.4", + "ajv": "6.6.1", "ajv-errors": "1.0.0", "ajv-keywords": "3.2.0" } + }, + "yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "requires": { + "cliui": "4.1.0", + "decamelize": "2.0.0", + "find-up": "3.0.0", + "get-caller-file": "1.0.3", + "os-locale": "3.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "4.0.0", + "yargs-parser": "10.1.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "requires": { + "camelcase": "4.1.0" + } } } }, @@ -19554,15 +19300,15 @@ "requires": { "fs-extra": "0.30.0", "lodash": "4.17.11", - "tapable": "1.1.0" + "tapable": "1.1.1" }, "dependencies": { "fs-extra": { "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "jsonfile": "2.4.0", "klaw": "1.3.1", "path-is-absolute": "1.0.1", @@ -19574,7 +19320,7 @@ "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "4.1.15" } } } @@ -19651,9 +19397,9 @@ "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "whatwg-mimetype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", - "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" }, "whatwg-url": { "version": "6.5.0", @@ -19746,28 +19492,20 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "requires": { - "mkdirp": "0.5.1" - } - }, "write-file-atomic": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", "requires": { - "graceful-fs": "4.1.11", + "graceful-fs": "4.1.15", "imurmurhash": "0.1.4", "signal-exit": "3.0.2" } }, "ws": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz", - "integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", + "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", "requires": { "async-limiter": "1.0.0" } @@ -19791,6 +19529,13 @@ "is-function": "1.0.1", "parse-headers": "2.0.1", "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } } }, "xml": { @@ -19846,9 +19591,19 @@ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "requires": { + "object-keys": "0.4.0" + }, + "dependencies": { + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + } + } }, "y18n": { "version": "4.0.0", @@ -19895,12 +19650,12 @@ } }, "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "requires": { "cliui": "4.1.0", - "decamelize": "2.0.0", + "decamelize": "1.2.0", "find-up": "3.0.0", "get-caller-file": "1.0.3", "os-locale": "3.0.1", @@ -19910,17 +19665,9 @@ "string-width": "2.1.1", "which-module": "2.0.0", "y18n": "4.0.0", - "yargs-parser": "10.1.0" + "yargs-parser": "11.1.1" }, "dependencies": { - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "requires": { - "xregexp": "4.0.0" - } - }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -19929,6 +19676,11 @@ "locate-path": "3.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -19962,11 +19714,19 @@ } }, "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "requires": { - "camelcase": "4.1.0" + "camelcase": "5.0.0", + "decamelize": "1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + } } }, "yauzl": { @@ -19994,4 +19754,4 @@ } } } -} \ No newline at end of file +} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index e803d24..80c05ab 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -2,7 +2,7 @@ { "policyName": "prerelease-monorepo-lockStep", "definitionName": "lockStepVersion", - "version": "0.163.0", + "version": "0.171.0", "nextBump": "minor" } ] diff --git a/common/scripts/audit.js b/common/scripts/audit.js index 3445080..488889f 100644 --- a/common/scripts/audit.js +++ b/common/scripts/audit.js @@ -6,10 +6,11 @@ const path = require("path"); const fs = require("fs"); const { spawnSync } = require("child_process"); +const { requireFromTempNodeModules, logBuildError, logBuildWarning, failBuild } = require("./utils"); // NEEDSWORK: This really isn't the safest way to import the ssri package - I don't actually know how it gets into common/temp/node_modules, // or if npm is going to move it somewhere else someday, but this seems a lot easier for now than having to setup an entire package just for this script. -const ssri = require(path.join(__dirname, "..", "temp", "node_modules", "ssri")); +const ssri = requireFromTempNodeModules("ssri"); // There seems to be an issue with the shrinkwrap file rush creates in common/temp. // Basically, the shrinkwrap contains @rush-temp packages with undefined versions. @@ -43,9 +44,8 @@ for (const k of Object.keys(shrinkwrap.dependencies)) { if (jsonOut.error) { console.error(jsonOut.error.summary); - console.log("##vso[task.logissue type=error;]Rush audit failed. This may be caused by a problem with the npm-shrinkwrap.json."); - console.log("##vso[task.complete result=Failed;]DONE") - process.exit(0); + logBuildError("Rush audit failed. This may be caused by a problem with the npm-shrinkwrap.json."); + failBuild(); } for (const action of jsonOut.actions) { @@ -55,12 +55,16 @@ for (const action of jsonOut.actions) { // Map "scrubbed" packages hashes back to readable package names const mpath = issue.path.replace(/[\da-f]{64}/, (match) => hashes[match]) - // For now, we'll only treat HIGH and CRITICAL vulnerabilities as errors in CI builds. const severity = advisory.severity.toUpperCase(); - const prefix = `##vso[task.logissue type=${(severity === "HIGH" || severity === "CRITICAL") ? "error" : "warning"};]` - console.log(`${prefix}${severity} Security Vulnerability: ${advisory.title} in ${advisory.module_name} (from ${mpath}). See ${advisory.url} for more info.`); + const message = `${severity} Security Vulnerability: ${advisory.title} in ${advisory.module_name} (from ${mpath}). See ${advisory.url} for more info.`; + + // For now, we'll only treat HIGH and CRITICAL vulnerabilities as errors in CI builds. + if (severity === "HIGH" || severity === "CRITICAL") + logBuildError(message); + else + logBuildWarning(message); } } if (jsonOut.metadata.vulnerabilities.high || jsonOut.metadata.vulnerabilities.critical) - console.log("##vso[task.complete result=Failed;]DONE") + failBuild(); diff --git a/common/scripts/install-run-rush.js b/common/scripts/install-run-rush.js index e127a20..a877aef 100644 --- a/common/scripts/install-run-rush.js +++ b/common/scripts/install-run-rush.js @@ -33,9 +33,10 @@ function getRushVersion() { } } function run() { - const [nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable - scriptPath, /* /repo/common/scripts/install-run-rush.js */ // tslint:disable-line:no-unused-variable - ...packageBinArgs /* [build, --to, myproject] */] = process.argv; + const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ ...packageBinArgs /* [build, --to, myproject] */] = process.argv; + if (!nodePath || !scriptPath) { + throw new Error('Unexpected exception: could not detect node path or script path'); + } if (process.argv.length < 3) { console.log('Usage: install-run-rush.js [args...]'); console.log('Example: install-run-rush.js build --to myproject'); diff --git a/common/scripts/install-run.js b/common/scripts/install-run.js index c215093..ca6ece7 100644 --- a/common/scripts/install-run.js +++ b/common/scripts/install-run.js @@ -9,7 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); // version of the specified tool (if not already installed), and then pass a command-line to it. // An example usage would be: // -// node common/scripts/install-run.js rimraf@2.6.2 rimraf -f project1/lib +// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io // // For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ const childProcess = require("child_process"); @@ -369,8 +369,10 @@ function runWithErrorAndStatusCode(fn) { } exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode; function run() { - const [nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable - scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* rimraf@^2.0.0 */ packageBinName, /* rimraf */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; + const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* qrcode@^1.2.0 */ packageBinName, /* qrcode */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; + if (!nodePath) { + throw new Error('Unexpected exception: could not detect node path'); + } if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') { // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control // to the script that (presumably) imported this file @@ -378,7 +380,7 @@ function run() { } if (process.argv.length < 4) { console.log('Usage: install-run.js @ [args...]'); - console.log('Example: install-run.js rimraf@2.6.2 rimraf -f project1/lib'); + console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io'); process.exit(1); } runWithErrorAndStatusCode(() => { diff --git a/common/scripts/mocha-reporter-tweaks.js b/common/scripts/mocha-reporter-tweaks.js new file mode 100644 index 0000000..9c2fecd --- /dev/null +++ b/common/scripts/mocha-reporter-tweaks.js @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +const path = require("path"); +const inherits = require("util").inherits; +const { requireFromTempNodeModules, monkeyPatch, logBuildWarning } = require("./utils"); + +const Mocha = requireFromTempNodeModules("mocha/lib/mocha"); +const Base = requireFromTempNodeModules("mocha/lib/reporters/base"); +const MochaJUnitReporter = requireFromTempNodeModules("mocha-junit-reporter"); + +const forceConsoleError = () => monkeyPatch(console, "log", (s, ...args) => console.error(...args)); +const restoreConsoleLog = () => console.log.reset(); + +// This is necessary to enable colored output when running in rush test: +Object.defineProperty(Base, "useColors", { + get: () => !process.env.TF_BUILD && (process.env.FORCE_COLOR !== "false" && process.env.FORCE_COLOR !== "0"), + set: () => { } +}); + +// Monkey patch the base mocha reporter to force test errors to be printed to stderr instead of stdout. +// This will allow rush to correctly summarize test failure when running rush test. +monkeyPatch(Base.prototype, "epilogue", function (_super) { + if (this.stats.failures) { + forceConsoleError(); + _super(); + restoreConsoleLog(); + } else { + _super(); + } + + // Also log warnings in CI builds when tests have been skipped. + if (this.stats.pending) { + const currentPackage = require(path.join(process.cwd(), "package.json")).name; + if (this.stats.pending === 1) + logBuildWarning(`1 test skipped in ${currentPackage}`); + else + logBuildWarning(`${this.stats.pending} tests skipped in ${currentPackage}`); + } +}); + +// Monkey patch mocha-junit-reporter to log errors when tests fail and warnings when tests are skipped. +monkeyPatch(MochaJUnitReporter.prototype, "flush", function (_super) { + _super(); + this.epilogue(); +}); + +inherits(MochaJUnitReporter, Base); + +// Also force rush test to fail CI builds if describe.only or it.only is used. +// These should only be used for debugging and must not be committed, otherwise we may be accidentally skipping lots of tests. +if (process.env.CI || process.env.TF_BUILD) { + monkeyPatch(Mocha.prototype, "run", function (_super) { + this.forbidOnly(); + _super(); + }); +} \ No newline at end of file diff --git a/common/scripts/utils.js b/common/scripts/utils.js new file mode 100644 index 0000000..09eec00 --- /dev/null +++ b/common/scripts/utils.js @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +const path = require("path"); +const Module = require("module"); + +function requireFromTempNodeModules(request) { + const tempNodeModules = path.join(__dirname, "..", "temp", "node_modules"); + + Module.globalPaths.push(tempNodeModules); + const requirePath = require.resolve(request, { paths: [tempNodeModules] }); + Module.globalPaths.pop(); + + return require(requirePath); +} + +function monkeyPatch(object, name, cb) { + const method = object[name]; + object[name] = function () { + const _super = method.bind(this, ...arguments); + cb.call(this, _super, ...arguments); + } + + object[name].reset = () => { + object[name] = method; + } +} + +function logBuildWarning(msg) { + // Since we run both a windows and linux build, only printing warnings with the "#vso..." prefix should avoid duplicates in build summaries + if (process.env.TF_BUILD && process.platform === "win32") + console.log("##vso[task.logissue type=warning;]%s", msg); + else + console.error("WARNING: %s", msg); +} + +function logBuildError(msg) { + if (process.env.TF_BUILD) + console.log("##vso[task.logissue type=error;]%s", msg); + else + console.error("ERROR: %s", msg); +} + +function failBuild() { + if (process.env.TF_BUILD) { + console.log("##vso[task.complete result=Failed;]DONE") + process.exit(0); + } else { + process.exit(1); + } +} + +module.exports = { + requireFromTempNodeModules, + monkeyPatch, + logBuildWarning, + logBuildError, + failBuild +} \ No newline at end of file diff --git a/core/backend/CHANGELOG.json b/core/backend/CHANGELOG.json index 6513f2f..d1f20d9 100644 --- a/core/backend/CHANGELOG.json +++ b/core/backend/CHANGELOG.json @@ -1,6 +1,180 @@ { "name": "@bentley/imodeljs-backend", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-backend_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "More information logged from BriefcaseManager.\\nFixed deletion/cleanup of invalid briefcases.\\nAdded OIDC support for simpleviewtest application. " + }, + { + "comment": "Add ElementRefersToElements.insert" + }, + { + "comment": "Fixed front end integration tests. " + }, + { + "comment": "Document the intended purpose of IModelJsExpressServer within a deployment environment." + }, + { + "comment": "Fixed integration tests. " + }, + { + "comment": "added tests for ElementDrivesElement handlers" + }, + { + "comment": "Fixes to integration tests. " + }, + { + "comment": "Add OrthographicViewDefinition.setRange" + }, + { + "comment": "Cleaned up use of mocks in core tests. " + }, + { + "comment": "Enable test now that addon was updated." + }, + { + "comment": "Fix Subject.insert to set parent" + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-backend_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Add DrawingViewDefinition.insert" + }, + { + "comment": "Fix GeometryParams constructor. Added test to ensure subcategory id set correctly." + }, + { + "comment": "rename LinkTableRelationship to just Relationship. Work on adding callbacks for dependency propagation." + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-backend_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-backend_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-backend_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Add IModelDb.CodeSpecs.insert overload" + }, + { + "comment": "Add SubCategory.insert" + }, + { + "comment": "Add missing createCode methods" + }, + { + "comment": "Changes to debug utilities. " + }, + { + "comment": "Added IModelHubClient.IModel, removed IModelQuery.primary(), use IModelHubClient.IModel.Get instead" + }, + { + "comment": "Add IModelDb.Views.setDefaultViewId" + }, + { + "comment": "Add OrthographicViewDefinition.insert" + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-backend_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": { + "none": [ + { + "comment": "Hydrated briefcases for ReadOnly cases from the latest checkpoint, rather than the seed files. This significantly improves performance of IModelDb/IModelConnection.open() for typical cases." + } + ] + } + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-backend_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": { + "none": [ + { + "comment": "clean up IModelImporter" + }, + { + "comment": "Add static insert methods to many classes to simplify iModel creation." + }, + { + "comment": "Add more TypeScript wrapper classes for BisCore relationships" + }, + { + "comment": "Add Subject.createCode and Subject.insert methods" + }, + { + "comment": "Add FunctionalModel.insert method" + } + ] + } + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-backend_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Fix JSON representation of DisplayStyle." + }, + { + "comment": "Add IModelImporter as a base class for utility methods needed by all importers" + }, + { + "comment": "Removed assertion when deleting a memoized open call. " + }, + { + "comment": "Add more methods to IModelImporter" + }, + { + "comment": "Fix snapping test" + }, + { + "comment": "OIDC related enhancments (WIP)." + }, + { + "comment": "Re-enabled several backend integration tests. " + }, + { + "comment": "Refactor analysis-importer to use IModelImporter" + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-backend_v0.163.0", diff --git a/core/backend/CHANGELOG.md b/core/backend/CHANGELOG.md index 469ef77..dad9ead 100644 --- a/core/backend/CHANGELOG.md +++ b/core/backend/CHANGELOG.md @@ -1,6 +1,88 @@ # Change Log - @bentley/imodeljs-backend -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- More information logged from BriefcaseManager.\nFixed deletion/cleanup of invalid briefcases.\nAdded OIDC support for simpleviewtest application. +- Add ElementRefersToElements.insert +- Fixed front end integration tests. +- Document the intended purpose of IModelJsExpressServer within a deployment environment. +- Fixed integration tests. +- added tests for ElementDrivesElement handlers +- Fixes to integration tests. +- Add OrthographicViewDefinition.setRange +- Cleaned up use of mocks in core tests. +- Enable test now that addon was updated. +- Fix Subject.insert to set parent + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Add DrawingViewDefinition.insert +- Fix GeometryParams constructor. Added test to ensure subcategory id set correctly. +- rename LinkTableRelationship to just Relationship. Work on adding callbacks for dependency propagation. + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Add IModelDb.CodeSpecs.insert overload +- Add SubCategory.insert +- Add missing createCode methods +- Changes to debug utilities. +- Added IModelHubClient.IModel, removed IModelQuery.primary(), use IModelHubClient.IModel.Get instead +- Add IModelDb.Views.setDefaultViewId +- Add OrthographicViewDefinition.insert + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +### Updates + +- Hydrated briefcases for ReadOnly cases from the latest checkpoint, rather than the seed files. This significantly improves performance of IModelDb/IModelConnection.open() for typical cases. + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +### Updates + +- clean up IModelImporter +- Add static insert methods to many classes to simplify iModel creation. +- Add more TypeScript wrapper classes for BisCore relationships +- Add Subject.createCode and Subject.insert methods +- Add FunctionalModel.insert method + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Fix JSON representation of DisplayStyle. +- Add IModelImporter as a base class for utility methods needed by all importers +- Removed assertion when deleting a memoized open call. +- Add more methods to IModelImporter +- Fix snapping test +- OIDC related enhancments (WIP). +- Re-enabled several backend integration tests. +- Refactor analysis-importer to use IModelImporter +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/backend/package.json b/core/backend/package.json index 18a8753..b2d5320 100644 --- a/core/backend/package.json +++ b/core/backend/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-backend", - "version": "0.163.0", + "version": "0.171.0", "description": "iModel.js backend components", "main": "lib/backend.js", "typings": "lib/backend", @@ -37,22 +37,22 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0", - "@bentley/imodeljs-common": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0", + "@bentley/imodeljs-common": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/config-loader": "0.163.0", - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0", - "@bentley/imodeljs-common": "0.163.0", + "@bentley/config-loader": "0.171.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0", + "@bentley/imodeljs-common": "0.171.0", "@types/chai": "^4.1.4", "@types/express": "^4.11.1", "@types/fs-extra": "^4.0.7", @@ -75,12 +75,12 @@ "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", "typemoq": "^2.1.0", - "typescript": "~3.0.0", + "typescript": "~3.1.0", "webpack": "^4.16.4", "asn1": "0.2.3" }, "dependencies": { - "@bentley/imodeljs-native-platform-api": "~0.66.0", + "@bentley/imodeljs-native-platform-api": "~0.71.0", "body-parser": "^1.18.2", "fs-extra": "^6.0.1", "glob": "^7.1.2", diff --git a/core/backend/src/AutoPush.ts b/core/backend/src/AutoPush.ts index e735e38..691b079 100644 --- a/core/backend/src/AutoPush.ts +++ b/core/backend/src/AutoPush.ts @@ -200,7 +200,7 @@ export class AutoPush { Logger.logTrace(loggingCategory, "AutoPush - next push in " + (intervalMillis / 1000) + " seconds..."); } - public reserveCodes(): Promise { + public async reserveCodes(): Promise { return this._iModel.concurrencyControl.request(this._activityContext, this.getAccessToken()); } @@ -248,7 +248,7 @@ export class AutoPush { // Push changes, if there are changes and only if the backend is idle. private doAutoPush() { // Nothing to push? - if (!this.iModel.txns.findLocalChanges()) { + if (!this.iModel.txns.hasLocalChanges) { this.cancel(); this.scheduleNextPush(); return; diff --git a/core/backend/src/BisCore.ts b/core/backend/src/BisCore.ts index 019a732..1f68a0b 100644 --- a/core/backend/src/BisCore.ts +++ b/core/backend/src/BisCore.ts @@ -12,7 +12,7 @@ import * as aspectMod from "./ElementAspect"; import * as modelMod from "./Model"; import * as categoryMod from "./Category"; import * as viewMod from "./ViewDefinition"; -import * as linkMod from "./LinkTableRelationship"; +import * as linkMod from "./Relationship"; /** * The [BisCore]($docs/bis/intro/schemas-domains.md) schema is the lowest level Schema in an iModel. diff --git a/core/backend/src/BriefcaseManager.ts b/core/backend/src/BriefcaseManager.ts index 174e7ae..fb3afe2 100644 --- a/core/backend/src/BriefcaseManager.ts +++ b/core/backend/src/BriefcaseManager.ts @@ -29,15 +29,11 @@ export class BriefcaseId { public static get Illegal(): number { return 0xffffffff; } public static get Master(): number { return 0; } public static get Standalone(): number { return 1; } - constructor(value?: number) { - if (value === undefined) - this._value = BriefcaseId.Illegal; - else this._value = value; - } + constructor(value?: number) { this._value = value === undefined ? BriefcaseId.Illegal : value; } public get isValid(): boolean { return this._value !== BriefcaseId.Illegal; } public get isMaster(): boolean { return this._value !== BriefcaseId.Master; } public get isStandaloneId(): boolean { return this._value !== BriefcaseId.Standalone; } - public getValue(): number { return this._value; } + public get value(): number { return this._value; } public toString(): string { return this._value.toString(); } } @@ -55,18 +51,18 @@ export class ChangeSetToken { /** Entry in the briefcase cache */ export class BriefcaseEntry { - /** Id of the iModel - set to the DbGuid field in the BIM, it corresponds to the Guid used to track the iModel in iModelHub */ + /** Id of the iModel - set to the DbGuid field in the briefcase, it corresponds to the Guid used to track the iModel in iModelHub */ public iModelId!: GuidString; /** Absolute path where the briefcase is cached/stored */ public pathname!: string; - /** Id of the last change set that was applied to the BIM. + /** Id of the last change set that was applied to the briefcase. * Set to an empty string if it is the initial version, or a standalone briefcase */ public changeSetId!: string; - /** Index of the last change set that was applied to the BI. + /** Index of the last change set that was applied to the briefcase. * Only specified if the briefcase was acquired from the Hub. * Set to 0 if it is the initial version. */ @@ -87,13 +83,13 @@ export class BriefcaseEntry { /** Params used to open the briefcase */ public openParams?: OpenParams; - /** Id of the last change set that was applied to the BIM after it was reversed. + /** Id of the last change set that was applied to the briefcase after it was reversed. * Undefined if no change sets have been reversed. * Set to empty string if reversed to the first version. */ public reversedChangeSetId?: string; - /** Index of the last change set that was applied to the BIM after it was reversed. + /** Index of the last change set that was applied to the briefcase after it was reversed. * Undefined if no change sets have been reversed * Set to 0 if the briefcase has been reversed to the first version */ @@ -102,8 +98,13 @@ export class BriefcaseEntry { /** Id of the user that acquired the briefcase. This is not set if it is a standalone briefcase */ public userId?: string; - /** In-memory handle fo the IModelDb that corresponds with this briefcase. This is only set if an IModelDb wrapper has been created for this briefcase */ - public iModelDb?: IModelDb; + /** The IModelDb for this briefcase. */ + private _iModelDb?: IModelDb; + public get iModelDb(): IModelDb | undefined { return this._iModelDb; } + public set iModelDb(iModelDb: IModelDb | undefined) { + this.nativeDb.setIModelDb(iModelDb); // store a pointer to this IModelDb on the native object so we can send it callbacks + this._iModelDb = iModelDb; + } /** File Id used to upload change sets for this briefcase (only setup in Read-Write cases) */ public fileId?: string; @@ -133,12 +134,33 @@ export class BriefcaseEntry { // Standalone (FixedVersion, PullOnly) if (this.briefcaseId === BriefcaseId.Standalone) { const uniqueId = path.basename(path.dirname(this.pathname)).substr(1); - return `${this.iModelId}:${this.changeSetId}:${uniqueId}`; + return `${this.iModelId}:${this.currentChangeSetId}:${uniqueId}`; } // Acquired (PullPush) return `${this.iModelId}:${this.briefcaseId}`; } + + /** + * Gets the current changeSetId of the briefcase + * Note that this may not be the changeSetId if the briefcase has reversed changes + */ + public get currentChangeSetId(): string { + return (typeof this.reversedChangeSetId !== "undefined") ? this.reversedChangeSetId : this.changeSetId; + } + + /** + * Gets the current changeSetIndex of the briefcase + * Note that this may not be the changeSetIndex if the briefcase has reversed changes + */ + public get currentChangeSetIndex(): number { + return (typeof this.reversedChangeSetIndex !== "undefined") ? this.reversedChangeSetIndex! : this.changeSetIndex!; + } + + /** Returns true if the briefcase has reversed changes */ + public get hasReversedChanges(): boolean { + return typeof this.reversedChangeSetId !== "undefined"; + } } /** In-memory cache of briefcases */ @@ -166,19 +188,16 @@ class BriefcaseCache { const key = briefcase.getKey(); if (this._briefcases.get(key)) { - const msg = `Briefcase ${key} already exists in the cache.`; - Logger.logError(loggingCategory, msg); - throw new IModelError(DbResult.BE_SQLITE_ERROR, msg); + Logger.logError(loggingCategory, "Briefcase already exists in the cache.", () => ({ key, ...briefcase })); + throw new IModelError(DbResult.BE_SQLITE_ERROR, `Briefcase ${key} already exists in the cache.`); } - Logger.logTrace(loggingCategory, `Added briefcase ${key} (${briefcase.pathname}) to the cache`); + Logger.logTrace(loggingCategory, "Added briefcase to the cache", () => ({ key, ...briefcase })); this._briefcases.set(key, briefcase); } /** Delete a briefcase from the cache */ - public deleteBriefcase(briefcase: BriefcaseEntry) { - this.deleteBriefcaseByKey(briefcase.getKey()); - } + public deleteBriefcase(briefcase: BriefcaseEntry) { this.deleteBriefcaseByKey(briefcase.getKey()); } /** Delete a briefcase from the cache by key */ public deleteBriefcaseByKey(key: string) { @@ -189,7 +208,7 @@ class BriefcaseCache { throw new IModelError(DbResult.BE_SQLITE_ERROR, msg); } - Logger.logTrace(loggingCategory, `Removed briefcase ${key} (${briefcase.pathname}) from the cache`); + Logger.logTrace(loggingCategory, "Removed briefcase from the cache", () => ({ key, ...briefcase })); this._briefcases.delete(key); } @@ -220,14 +239,12 @@ export class BriefcaseManager { public static get imodelClient(): IModelClient { if (!this._imodelClient) { // The server handler defaults to iModelHub handler and the file handler defaults to AzureFileHandler - this._imodelClient = new IModelHubClient(new AzureFileHandler(false)); + this._imodelClient = new IModelHubClient(new AzureFileHandler()); } return this._imodelClient; } - public static set imodelClient(cli: IModelClient) { - this._imodelClient = cli; - } + public static set imodelClient(cli: IModelClient) { this._imodelClient = cli; } private static _connectClient?: ConnectClient; @@ -308,38 +325,40 @@ export class BriefcaseManager { throw new IModelError(DbResult.BE_SQLITE_ERROR, `Briefcase directory ${briefcaseDir} must contain exactly one briefcase`); const pathname = path.join(briefcaseDir, fileNames[0]); - // Open the briefcase (for now as ReadWrite to allow reinstating reversed briefcases) - const briefcase = BriefcaseManager.openBriefcase(iModelId, pathname, new OpenParams(OpenMode.ReadWrite)); + // Open the briefcase to populate the briefcase entry with briefcaseid changesetid and reversedchangesetid + const briefcase = new BriefcaseEntry(); + briefcase.iModelId = iModelId; + briefcase.pathname = pathname; + briefcase.isStandalone = false; + + const nativeDb: NativeDgnDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); + const res: DbResult = nativeDb.openIModelFile(briefcase.pathname, OpenMode.Readonly); + if (DbResult.BE_SQLITE_OK !== res) + throw new IModelError(DbResult.BE_SQLITE_ERROR, `Unable to open briefcase at ${briefcase.pathname}`); + briefcase.briefcaseId = nativeDb.getBriefcaseId(); + briefcase.changeSetId = nativeDb.getParentChangeSetId(); + briefcase.reversedChangeSetId = nativeDb.getReversedChangeSetId(); + nativeDb.closeIModelFile(); + + // Now populate changesetIndex, reversedChangesetIndex, userId, fileId from hub try { - // Append information from the IModelServer briefcase.changeSetIndex = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, briefcase.iModelId, briefcase.changeSetId); actx.enter(); - if (briefcase.reversedChangeSetId) + if (typeof briefcase.reversedChangeSetId !== "undefined") briefcase.reversedChangeSetIndex = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, briefcase.iModelId, briefcase.reversedChangeSetId); actx.enter(); if (briefcase.briefcaseId !== BriefcaseId.Standalone) { - const hubBriefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.Briefcases().get(actx, accessToken, iModelId, new BriefcaseQuery().byId(briefcase.briefcaseId)); + const hubBriefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, iModelId, new BriefcaseQuery().byId(briefcase.briefcaseId)); if (hubBriefcases.length === 0) throw new IModelError(DbResult.BE_SQLITE_ERROR, `Unable to find briefcase ${briefcase.briefcaseId}:${briefcase.pathname} on the Hub (for the current user)`); briefcase.userId = hubBriefcases[0].userId; briefcase.fileId = hubBriefcases[0].fileId!.toString(); } - - // Reinstate any reversed changes - these can happen with exclusive briefcases - // Note: We currently allow reversal only in Exclusive briefcases. Ideally we should just keep these briefcases reversed in case they - // are exclusive, but at the moment we don't have a way of differentiating exclusive and shared briefcases when opening them the - // first time. We could consider caching that information also. see findCachedBriefcaseToOpen() - if (briefcase.reversedChangeSetId) { - await BriefcaseManager.reinstateChanges(actx, accessToken, briefcase); - assert(!briefcase.reversedChangeSetId && !briefcase.nativeDb.getReversedChangeSetId(), "Error with reinstating reversed changes"); - } } catch (error) { throw error; } finally { actx.enter(); - if (briefcase && briefcase.isOpen) - BriefcaseManager.closeBriefcase(briefcase); } BriefcaseManager._cache.addBriefcase(briefcase); @@ -460,7 +479,7 @@ export class BriefcaseManager { if (changeSetId === "") return 0; // the first version try { - const changeSet: ChangeSet = (await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, iModelId, new ChangeSetQuery().byId(changeSetId)))[0]; + const changeSet: ChangeSet = (await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, iModelId, new ChangeSetQuery().byId(changeSetId)))[0]; return +changeSet.index!; } catch (err) { assert(false, "Could not determine index of change set"); @@ -471,79 +490,77 @@ export class BriefcaseManager { /** Open a briefcase */ public static async open(actx: ActivityLoggingContext, accessToken: AccessToken, contextId: string, iModelId: GuidString, openParams: OpenParams, version: IModelVersion): Promise { await BriefcaseManager.memoizedInitCache(actx, accessToken); - actx.enter(); - assert(!!BriefcaseManager.imodelClient); - let perfLogger = new PerfLogger("IModelDb.open, evaluating change set"); + let perfLogger = new PerfLogger("BriefcaseManager.open -> version.evaluateChangeSet + BriefcaseManager.getChangeSetIndexFromId"); const changeSetId: string = await version.evaluateChangeSet(actx, accessToken, iModelId, BriefcaseManager.imodelClient); actx.enter(); - let changeSetIndex: number; - if (changeSetId === "") { - changeSetIndex = 0; // First version - } else { - const changeSet: ChangeSet = await BriefcaseManager.getChangeSetFromId(actx, accessToken, iModelId, changeSetId); - actx.enter(); - changeSetIndex = changeSet ? +changeSet.index! : 0; - } + const changeSetIndex: number = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, iModelId, changeSetId); + actx.enter(); perfLogger.dispose(); - perfLogger = new PerfLogger("IModelDb.open, find cached briefcase"); - let briefcase = BriefcaseManager.findCachedBriefcaseToOpen(accessToken, iModelId, changeSetIndex, openParams); - if (briefcase && briefcase.isOpen && briefcase.changeSetIndex === changeSetIndex) { - Logger.logTrace(loggingCategory, `Reused briefcase ${briefcase.pathname} without changes`); - return briefcase; - } - + // Find a cached briefcase if possible + perfLogger = new PerfLogger("BriefcaseManager.open -> BriefcaseManager.findCachedBriefcaseToOpen"); + let briefcase: BriefcaseEntry | undefined = BriefcaseManager.findCachedBriefcaseToOpen(accessToken, iModelId, changeSetIndex, openParams); perfLogger.dispose(); - perfLogger = new PerfLogger("IModelDb.open, preparing briefcase"); - let isNewBriefcase: boolean = false; - const tempOpenParams = new OpenParams(OpenMode.ReadWrite, openParams.accessMode, openParams.syncMode, openParams.exclusiveAccessOption); // Merge needs the Db to be opened ReadWrite - if (briefcase) { - Logger.logTrace(loggingCategory, `Reused briefcase ${briefcase.pathname} after upgrades (if necessary)`); - BriefcaseManager.reopenBriefcase(accessToken, briefcase, tempOpenParams); - } else { - briefcase = await BriefcaseManager.createBriefcase(actx, accessToken, contextId, iModelId, tempOpenParams); // Merge needs the Db to be opened ReadWrite - actx.enter(); - isNewBriefcase = true; - } + const createNewBriefcase: boolean = !briefcase; + const readWriteOpenParams: OpenParams = openParams.openMode === OpenMode.ReadWrite ? openParams : new OpenParams(OpenMode.ReadWrite, openParams.accessMode, openParams.syncMode, openParams.exclusiveAccessOption); // Open ReadWrite to allow applying changes + if (!!briefcase) { + // briefcase already exists. If open and has a different version than requested, close first + if (briefcase.isOpen) { + if (briefcase.currentChangeSetIndex === changeSetIndex) { + Logger.logTrace(loggingCategory, `Reused briefcase ${briefcase.pathname} without changes`); + return briefcase; + } - let changeSetApplyOption: ChangeSetApplyOption | undefined; - if (changeSetIndex > briefcase.changeSetIndex!) { - changeSetApplyOption = ChangeSetApplyOption.Merge; - } else if (changeSetIndex < briefcase.changeSetIndex!) { - if (openParams.syncMode === SyncMode.PullAndPush) { - Logger.logWarning(loggingCategory, `No support to open an older version when opening an IModel to push changes (SyncMode.PullAndPush). Cannot open briefcase ${briefcase.iModelId}:${briefcase.briefcaseId}.`); - await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); - actx.enter(); - return Promise.reject(new IModelError(BriefcaseStatus.CannotApplyChanges, "Cannot merge when there are reversed changes")); + BriefcaseManager.closeBriefcase(briefcase); } - changeSetApplyOption = ChangeSetApplyOption.Reverse; - } - if (changeSetApplyOption) { - assert(briefcase.isOpen && briefcase.openParams!.openMode === OpenMode.ReadWrite); // Briefcase must be opened ReadWrite first to allow applying change sets + perfLogger = new PerfLogger("BriefcaseManager.open -> BriefcaseManager.openBriefcase"); + BriefcaseManager.openBriefcase(accessToken, contextId, briefcase, readWriteOpenParams); + perfLogger.dispose(); + } else { + perfLogger = new PerfLogger("BriefcaseManager.open -> BriefcaseManager.createBriefcase"); try { - await BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, IModelVersion.asOfChangeSet(changeSetId), changeSetApplyOption); + briefcase = await BriefcaseManager.createBriefcase(actx, accessToken, contextId, iModelId, readWriteOpenParams); + } finally { actx.enter(); - } catch (error) { - actx.enter(); - Logger.logWarning(loggingCategory, `Error applying changes to briefcase ${briefcase.iModelId}:${briefcase.briefcaseId}. Deleting it so that it can be re-fetched again.`); - await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); - return Promise.reject(error); + perfLogger.dispose(); } } - // Reopen the briefcase if the briefcase hasn't been opened with the required OpenMode - if (briefcase.openParams!.openMode !== openParams.openMode) - BriefcaseManager.reopenBriefcase(accessToken, briefcase, openParams); + assert(!!briefcase); + if (changeSetIndex < briefcase.changeSetIndex! && openParams.syncMode === SyncMode.PullAndPush) { + Logger.logError(loggingCategory, "No support to open an older version when opening an IModel to push changes (SyncMode.PullAndPush). Cannot open briefcase", () => ({ ...briefcase })); + await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); + actx.enter(); + throw new IModelError(BriefcaseStatus.CannotApplyChanges, "Cannot merge when there are reversed changes"); + } - perfLogger.dispose(); + // Apply the required change sets + try { + await BriefcaseManager.processChangeSets(actx, accessToken, briefcase, version); + } catch (error) { + actx.enter(); + Logger.logWarning(loggingCategory, "Error applying changes to briefcase. Deleting it so that it can be re-fetched again.", () => ({ ...briefcase })); + await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); + actx.enter(); + throw error; + } + + // Reopen the briefcase if the briefcase hasn't been opened with the required OpenMode + if (briefcase.openParams!.openMode !== openParams.openMode) { + briefcase.openParams = openParams; + briefcase.nativeDb!.closeIModelFile(); + const r: DbResult = briefcase.nativeDb!.openIModelFile(briefcase.pathname, openParams.openMode); + if (r !== DbResult.BE_SQLITE_OK) + throw new IModelError(r, briefcase.pathname); + } // Add briefcase to cache if necessary - if (isNewBriefcase) { + if (createNewBriefcase) { // Note: This cannot be done right after creation since the version (that's part of the key to the cache) // is not established until the change sets are merged BriefcaseManager._cache.addBriefcase(briefcase); @@ -563,20 +580,9 @@ export class BriefcaseManager { } } - /** Get the change set from the specified id */ - private static async getChangeSetFromId(actx: ActivityLoggingContext, accessToken: AccessToken, iModelId: GuidString, changeSetId: string): Promise { - actx.enter(); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, iModelId, new ChangeSetQuery().byId(changeSetId)); - if (changeSets.length > 0) - return changeSets[0]; - - actx.enter(); - return Promise.reject(new IModelError(BriefcaseStatus.VersionNotFound, changeSetId)); - } - /** Finds any existing briefcase for the specified parameters. Pass null for the requiredChangeSet if the first version is to be retrieved */ private static findCachedBriefcaseToOpen(accessToken: AccessToken, iModelId: GuidString, requiredChangeSetIndex: number, requiredOpenParams: OpenParams): BriefcaseEntry | undefined { - const requiredUserId = accessToken.getUserProfile()!.userId; + const requiredUserId = accessToken.getUserInfo()!.id; // Narrow down briefcases by various criteria (except their versions) const filterBriefcaseFn = (entry: BriefcaseEntry): boolean => { @@ -584,139 +590,153 @@ export class BriefcaseManager { if (entry.iModelId !== iModelId) return false; - // Narrow down open briefcases - if (entry.isOpen) { - // If exclusive access is required, do not reuse if the user preference indicates so, or if the briefcase wasn't previously created by the same user - if (requiredOpenParams.accessMode === AccessMode.Exclusive && (requiredOpenParams.exclusiveAccessOption !== ExclusiveAccessOption.TryReuseOpenBriefcase || requiredUserId !== entry.userId)) + // Narrow down briefcases for PullAndPush access + if (requiredOpenParams.syncMode === SyncMode.PullAndPush) { + if (entry.briefcaseId === BriefcaseId.Standalone) // only acquired briefcases return false; - - // Any open briefcase must have been opened with the exact same open parameters - if (!entry.openParams!.equals(requiredOpenParams)) - return false; - - if (entry.openParams!.exclusiveAccessOption === ExclusiveAccessOption.CreateNewBriefcase) + assert(requiredOpenParams.accessMode === AccessMode.Exclusive); // only exclusive access can be requested + if (!entry.userId || entry.userId !== requiredUserId) // must've been acquired by same user return false; + if (entry.isOpen) { + if (requiredOpenParams.exclusiveAccessOption === ExclusiveAccessOption.CreateNewBriefcase || entry.openParams!.exclusiveAccessOption === ExclusiveAccessOption.CreateNewBriefcase) + return false; // user doesn't want to reuse + assert(entry.openParams!.equals(requiredOpenParams)); + } else { + if (requiredChangeSetIndex < entry.changeSetIndex!) // Cannot push (write) to a reversed briefcase + return false; + } + return true; } - // For PullOnly or FixedVersion briefcases, ensure that the briefcase is opened Standalone, and does NOT have any reversed changes + // Narrow down briefcase for PullOnly or FixedVersion access // Note: We currently allow reversal only in Exclusive briefcases. Also, we reinstate any reversed changes in all briefcases when the // cache is initialized, but that may be removed in the future. See addBriefcaseToCache() - if (requiredOpenParams.syncMode !== SyncMode.PullAndPush) - return entry.briefcaseId === BriefcaseId.Standalone && !entry.reversedChangeSetId; + assert(requiredOpenParams.syncMode === SyncMode.FixedVersion || requiredOpenParams.syncMode === SyncMode.PullOnly); + if (entry.briefcaseId !== BriefcaseId.Standalone) // only standalone briefcases + return false; + if (!entry.isOpen) // can reuse any closed standalone briefcase + return true; + + // Further narrow down open briefcase + if (entry.openParams!.accessMode !== requiredOpenParams.accessMode) // exclusive/shared briefcases cannot be re-purposed + return false; + if (entry.openParams!.syncMode !== requiredOpenParams.syncMode) // PullOnly and FixedVersion briefcases cannot be re-purposed for the other + return false; + if (entry.currentChangeSetIndex !== requiredChangeSetIndex) // current changeset index must exactly match + return false; + + if (requiredOpenParams.accessMode === AccessMode.Exclusive) { + assert(!!entry.userId); + if (entry.userId !== requiredUserId) // must've been opened by the same user + return false; + if (requiredOpenParams.exclusiveAccessOption === ExclusiveAccessOption.CreateNewBriefcase || entry.openParams!.exclusiveAccessOption === ExclusiveAccessOption.CreateNewBriefcase) + return false; // user doesn't want to reuse + } - // For PullAndPush briefcases, ensure that the user had acquired the briefcase the first time around - // else if (requiredOpenParams.syncMode === SyncMode.PullAndPush) - return entry.briefcaseId !== BriefcaseId.Standalone && entry.userId === requiredUserId; + return true; }; const briefcases = this._cache.getFilteredBriefcases(filterBriefcaseFn); if (!briefcases || briefcases.length === 0) return undefined; - let briefcase: BriefcaseEntry | undefined; - - // first prefer open briefcases, with a changeSetIndex that can be upgraded in case Exclusive access is required, or with the same exact changeSetIndex if Shared access is required - briefcase = briefcases.find((entry: BriefcaseEntry): boolean => { - if (!entry.isOpen) - return false; - - return (requiredOpenParams.accessMode === AccessMode.Exclusive) ? - entry.changeSetIndex! <= requiredChangeSetIndex : - entry.changeSetIndex === requiredChangeSetIndex; - }); - if (briefcase) - return briefcase; - - // next prefer a briefcase that's closed, and with changeSetIndex = requiredChangeSetIndex - briefcase = briefcases.find((entry: BriefcaseEntry): boolean => { - return !entry.isOpen && entry.changeSetIndex === requiredChangeSetIndex; - }); - if (briefcase) - return briefcase; - - // next prefer any standalone briefcase that's closed, and with changeSetIndex < requiredChangeSetIndex - briefcase = briefcases.find((entry: BriefcaseEntry): boolean => { - return !entry.isOpen && entry.changeSetIndex! < requiredChangeSetIndex; + const sortedBriefcases = briefcases.sort((a: BriefcaseEntry, b: BriefcaseEntry) => { // Return -1 if a < b, +1 if a > b, and 0 if a = b + // Prefer open + if (a.isOpen && !b.isOpen) + return -1; + if (!a.isOpen && b.isOpen) + return 1; + + // Prefer closer change set index (a better metric will be change set size) + const aDist = Math.abs(a.currentChangeSetIndex - requiredChangeSetIndex); + const bDist = Math.abs(b.currentChangeSetIndex - requiredChangeSetIndex); + if (aDist < bDist) + return -1; + if (aDist > bDist) + return 1; + return 0; }); - if (briefcase) - return briefcase; - return undefined; - } - - private static setupBriefcase(briefcase: BriefcaseEntry, openParams: OpenParams): DbResult { - const nativeDb: NativeDgnDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); - - assert(openParams.openMode === OpenMode.ReadWrite); // Expect to setup briefcase as ReadWrite to allow pull and merge of changes (irrespective of the real openMode) - - let res: DbResult = nativeDb.openIModel(briefcase.pathname, openParams.openMode); - if (DbResult.BE_SQLITE_OK !== res) { - Logger.logError(loggingCategory, `Unable to open briefcase at ${briefcase.pathname}`); - return res; - } - - res = nativeDb.setBriefcaseId(briefcase.briefcaseId); - if (DbResult.BE_SQLITE_OK !== res) { - Logger.logError(loggingCategory, `Unable to setup briefcase id for ${briefcase.pathname}`); - return res; - } - assert(nativeDb.getParentChangeSetId() === briefcase.changeSetId); - - briefcase.nativeDb = nativeDb; - briefcase.openParams = openParams; - briefcase.isOpen = true; - briefcase.isStandalone = false; - - Logger.logTrace(loggingCategory, `Created briefcase ${briefcase.pathname}`); - return DbResult.BE_SQLITE_OK; + return sortedBriefcases[0]; } /** Create a briefcase */ private static async createBriefcase(actx: ActivityLoggingContext, accessToken: AccessToken, contextId: string, iModelId: GuidString, openParams: OpenParams): Promise { actx.enter(); - const iModel: HubIModel = (await BriefcaseManager.imodelClient.IModels().get(actx, accessToken, contextId, new IModelQuery().byId(iModelId)))[0]; + const iModel: HubIModel = (await BriefcaseManager.imodelClient.iModels.get(actx, accessToken, contextId, new IModelQuery().byId(iModelId)))[0]; const briefcase = new BriefcaseEntry(); briefcase.iModelId = iModelId; - briefcase.userId = accessToken.getUserProfile()!.userId; + briefcase.userId = accessToken.getUserInfo()!.id; + let hubBriefcase: HubBriefcase; if (openParams.syncMode !== SyncMode.PullAndPush) { - /* FixedVersion, PullOnly => Create standalone briefcase */ + /* FixedVersion, PullOnly => Create standalone briefcase + * We attempt to get any briefcase for the iModel to download the latest copy. If there isn't + * such a briefcase available, we simply acquire one and keep it unused. The iModelHub team + * will be providing an API that helps avoid this acquisition and get to a checkpoint that's + * closest to the requested version. + */ + hubBriefcase = await BriefcaseManager.getOrAcquireBriefcase(actx, accessToken, iModelId); + actx.enter(); briefcase.pathname = BriefcaseManager.buildStandalonePathname(iModelId, iModel.name!); briefcase.briefcaseId = BriefcaseId.Standalone; - await BriefcaseManager.downloadSeedFile(actx, accessToken, iModelId, briefcase.pathname); - actx.enter(); - briefcase.changeSetId = ""; - briefcase.changeSetIndex = 0; } else { /* PullAndPush => Acquire a briefcase from the hub */ - const hubBriefcase: HubBriefcase = await BriefcaseManager.acquireBriefcase(actx, accessToken, iModelId); + hubBriefcase = await BriefcaseManager.acquireBriefcase(actx, accessToken, iModelId); + actx.enter(); briefcase.pathname = BriefcaseManager.buildAcquiredPathname(iModelId, +hubBriefcase.briefcaseId!, iModel.name!); briefcase.briefcaseId = hubBriefcase.briefcaseId!; - briefcase.fileId = hubBriefcase.fileId!.toString(); - await BriefcaseManager.downloadBriefcase(actx, hubBriefcase, briefcase.pathname); - briefcase.changeSetId = hubBriefcase.mergedChangeSetId!; - briefcase.changeSetIndex = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, iModelId, briefcase.changeSetId); } + briefcase.fileId = hubBriefcase.fileId!.toString(); + await BriefcaseManager.downloadBriefcase(actx, hubBriefcase, briefcase.pathname); + briefcase.changeSetId = hubBriefcase.mergedChangeSetId!; + briefcase.changeSetIndex = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, iModelId, briefcase.changeSetId); - const res: DbResult = BriefcaseManager.setupBriefcase(briefcase, openParams); + assert(openParams.openMode === OpenMode.ReadWrite); // Expect to setup briefcase as ReadWrite to allow pull and merge of changes (irrespective of the real openMode) + const nativeDb: NativeDgnDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); + let res: DbResult = nativeDb.openIModel(accessToken.toTokenString(), contextId, briefcase.pathname, openParams.openMode); if (DbResult.BE_SQLITE_OK !== res) { + Logger.logError(loggingCategory, `Unable to open briefcase at ${briefcase.pathname}`); Logger.logWarning(loggingCategory, `Unable to create briefcase ${briefcase.pathname}. Deleting any remnants of it`); await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); actx.enter(); throw new IModelError(res, briefcase.pathname); } + res = nativeDb.setBriefcaseId(briefcase.briefcaseId); + if (DbResult.BE_SQLITE_OK !== res) { + Logger.logError(loggingCategory, `Unable to setup briefcase id for ${briefcase.pathname}`); + Logger.logWarning(loggingCategory, `Unable to create briefcase ${briefcase.pathname}. Deleting any remnants of it`); + await BriefcaseManager.deleteBriefcase(actx, accessToken, briefcase); + actx.enter(); + throw new IModelError(res, briefcase.pathname); + } + assert(nativeDb.getParentChangeSetId() === briefcase.changeSetId); + + briefcase.nativeDb = nativeDb; + briefcase.openParams = openParams; + briefcase.isOpen = true; + briefcase.isStandalone = false; briefcase.imodelClientContext = contextId; + Logger.logTrace(loggingCategory, `Created briefcase ${briefcase.pathname}`); return briefcase; } + private static async getOrAcquireBriefcase(actx: ActivityLoggingContext, accessToken: AccessToken, iModelId: GuidString): Promise { + actx.enter(); + const briefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, iModelId, (new BriefcaseQuery()).selectDownloadUrl()); + const someBriefcase = briefcases.length > 0 ? briefcases[0] : await BriefcaseManager.acquireBriefcase(actx, accessToken, iModelId); + actx.enter(); + return someBriefcase; + } + /** Acquire a briefcase */ private static async acquireBriefcase(actx: ActivityLoggingContext, accessToken: AccessToken, iModelId: GuidString): Promise { actx.enter(); - const briefcase: HubBriefcase = await BriefcaseManager.imodelClient.Briefcases().create(actx, accessToken, iModelId); + const briefcase: HubBriefcase = await BriefcaseManager.imodelClient.briefcases.create(actx, accessToken, iModelId); actx.enter(); if (!briefcase) { // Could well be that the current user does not have the appropriate access @@ -730,30 +750,16 @@ export class BriefcaseManager { actx.enter(); if (IModelJsFs.existsSync(seedPathname)) return; - return BriefcaseManager.imodelClient.Briefcases().download(actx, briefcase, seedPathname) - .catch(() => { + return BriefcaseManager.imodelClient.briefcases.download(actx, briefcase, seedPathname) + .catch(async () => { actx.enter(); return Promise.reject(new IModelError(BriefcaseStatus.CannotDownload, "Could not download briefcase", Logger.logError, loggingCategory)); }); } - /** Downloads the briefcase seed file */ - private static async downloadSeedFile(actx: ActivityLoggingContext, accessToken: AccessToken, imodelId: GuidString, seedPathname: string): Promise { - actx.enter(); - if (IModelJsFs.existsSync(seedPathname)) - return; - - const perfLogger = new PerfLogger("BriefcaseManager.downloadSeedFile"); - await BriefcaseManager.imodelClient.IModels().download(actx, accessToken, imodelId, seedPathname) - .catch(() => { - actx.enter(); - return Promise.reject(new IModelError(BriefcaseStatus.CannotDownload, "Could not download briefcase seed file", Logger.logError, loggingCategory)); - }); - perfLogger.dispose(); - } - /** Deletes a briefcase from the local disk (if it exists) */ private static deleteBriefcaseFromLocalDisk(briefcase: BriefcaseEntry) { + assert(!briefcase.isOpen); const dirName = path.dirname(briefcase.pathname); BriefcaseManager.deleteFolderRecursive(dirName); } @@ -767,16 +773,16 @@ export class BriefcaseManager { actx.enter(); try { - await BriefcaseManager.imodelClient.Briefcases().get(actx, accessToken, briefcase.iModelId, new BriefcaseQuery().byId(briefcase.briefcaseId)); + await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, briefcase.iModelId, new BriefcaseQuery().byId(briefcase.briefcaseId)); } catch (err) { return; // Briefcase does not exist on the hub, or cannot be accessed } actx.enter(); - await BriefcaseManager.imodelClient.Briefcases().delete(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId) + await BriefcaseManager.imodelClient.briefcases.delete(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId) .catch(() => { actx.enter(); - Logger.logError(loggingCategory, "Could not delete the acquired briefcase"); // Could well be that the current user does not have the appropriate access + Logger.logError(loggingCategory, "Could not delete the acquired briefcase", () => ({ ...briefcase })); // Could well be that the current user does not have the appropriate access }); } @@ -791,6 +797,9 @@ export class BriefcaseManager { /** Deletes a briefcase, and releases its references in iModelHub if necessary */ private static async deleteBriefcase(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry): Promise { actx.enter(); + if (briefcase.isOpen) + BriefcaseManager.closeBriefcase(briefcase); + BriefcaseManager.deleteBriefcaseFromCache(briefcase); await BriefcaseManager.deleteBriefcaseFromServer(actx, accessToken, briefcase); actx.enter(); @@ -811,7 +820,7 @@ export class BriefcaseManager { query.fromId(fromChangeSetId); if (includeDownloadLink) query.selectDownloadUrl(); - const allChangeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, iModelId, query); + const allChangeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, iModelId, query); actx.enter(); const changeSets = new Array(); @@ -838,8 +847,8 @@ export class BriefcaseManager { // download if (changeSetsToDownload.length > 0) { const perfLogger = new PerfLogger("BriefcaseManager.downloadChangeSets"); - await BriefcaseManager.imodelClient.ChangeSets().download(actx, changeSetsToDownload, changeSetsPath) - .catch(() => { + await BriefcaseManager.imodelClient.changeSets.download(actx, changeSetsToDownload, changeSetsPath) + .catch(async () => { actx.enter(); return Promise.reject(new IModelError(BriefcaseStatus.CannotDownload, "Could not download changesets", Logger.logError, loggingCategory)); }); @@ -868,7 +877,7 @@ export class BriefcaseManager { const nativeDb: NativeDgnDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); - const res = nativeDb.openIModel(pathname, openMode); + const res = nativeDb.openIModelFile(pathname, openMode); if (DbResult.BE_SQLITE_OK !== res) throw new IModelError(res, pathname); @@ -902,7 +911,7 @@ export class BriefcaseManager { const nativeDb: NativeDgnDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); - const res: DbResult = nativeDb.createIModel(fileName, JSON.stringify(args)); + const res: DbResult = nativeDb.createStandaloneIModel(fileName, JSON.stringify(args)); if (DbResult.BE_SQLITE_OK !== res) throw new IModelError(res, fileName); @@ -1002,26 +1011,6 @@ export class BriefcaseManager { return changeSetTokens; } - private static openBriefcase(iModelId: GuidString, pathname: string, openParams: OpenParams): BriefcaseEntry { - const briefcase = new BriefcaseEntry(); - briefcase.iModelId = iModelId; - briefcase.pathname = pathname; - - briefcase.nativeDb = new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); - const res: DbResult = briefcase.nativeDb.openIModel(briefcase.pathname, openParams.openMode); - if (DbResult.BE_SQLITE_OK !== res) - throw new IModelError(DbResult.BE_SQLITE_ERROR, `Unable to open briefcase at ${briefcase.pathname}`); - - briefcase.isOpen = true; - briefcase.openParams = openParams; - briefcase.isStandalone = false; - briefcase.briefcaseId = briefcase.nativeDb.getBriefcaseId(); - briefcase.changeSetId = briefcase.nativeDb.getParentChangeSetId(); - briefcase.reversedChangeSetId = briefcase.nativeDb.getReversedChangeSetId(); - - return briefcase; - } - private static closeBriefcase(briefcase: BriefcaseEntry) { assert(briefcase.isOpen, "Briefcase must be open for it to be closed"); briefcase.nativeDb.closeIModel(); @@ -1029,70 +1018,115 @@ export class BriefcaseManager { briefcase.openParams = undefined; } - private static reopenBriefcase(accessToken: AccessToken, briefcase: BriefcaseEntry, openParams: OpenParams) { + private static openBriefcase(accessToken: AccessToken, contextId: string, briefcase: BriefcaseEntry, openParams: OpenParams): void { if (briefcase.isOpen) - BriefcaseManager.closeBriefcase(briefcase); + throw new Error(`Briefcase ${briefcase.pathname} is already open.`); briefcase.nativeDb = briefcase.nativeDb || new (NativePlatformRegistry.getNativePlatform()).NativeDgnDb(); - const res: DbResult = briefcase.nativeDb!.openIModel(briefcase.pathname, openParams.openMode); + const res: DbResult = briefcase.nativeDb!.openIModel(accessToken.toTokenString(), contextId, briefcase.pathname, openParams.openMode); if (DbResult.BE_SQLITE_OK !== res) throw new IModelError(res, briefcase.pathname); briefcase.openParams = openParams; - briefcase.userId = accessToken.getUserProfile()!.userId; + briefcase.userId = accessToken.getUserInfo()!.id; briefcase.isOpen = true; } - private static async applyChangeSets(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, targetVersion: IModelVersion, processOption: ChangeSetApplyOption): Promise { + private static async processChangeSets(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, targetVersion: IModelVersion, requestedChangeSetOption?: ChangeSetApplyOption): Promise { actx.enter(); - assert(!!briefcase.nativeDb && briefcase.isOpen); - assert(briefcase.nativeDb.getParentChangeSetId() === briefcase.changeSetId, "Mismatch between briefcase and the native Db"); + const perfLogger = new PerfLogger("BriefcaseManager.processChangeSets"); + if (!briefcase.nativeDb || !briefcase.isOpen) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Briefcase must be open to process change sets")); + if (briefcase.openParams!.openMode !== OpenMode.ReadWrite) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Briefcase must be open ReadWrite to process change sets")); + assert(briefcase.nativeDb.getParentChangeSetId() === briefcase.changeSetId, "Mismatch between briefcase and the native Db"); if (briefcase.changeSetIndex === undefined) return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot apply changes to a standalone file")); const targetChangeSetId: string = await targetVersion.evaluateChangeSet(actx, accessToken, briefcase.iModelId, BriefcaseManager.imodelClient); const targetChangeSetIndex: number = await BriefcaseManager.getChangeSetIndexFromId(actx, accessToken, briefcase.iModelId, targetChangeSetId); actx.enter(); - if (targetChangeSetIndex === undefined) + if (typeof targetChangeSetIndex === "undefined") return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Could not determine change set information from the Hub")); - const hasReversedChanges = briefcase.reversedChangeSetId !== undefined; - - const currentChangeSetId: string = hasReversedChanges ? briefcase.reversedChangeSetId! : briefcase.changeSetId!; - const currentChangeSetIndex: number = hasReversedChanges ? briefcase.reversedChangeSetIndex! : briefcase.changeSetIndex!; + // Error check the requested change set option + if (requestedChangeSetOption) { + const currentChangeSetIndex: number = briefcase.currentChangeSetIndex; + switch (requestedChangeSetOption) { + case ChangeSetApplyOption.Merge: + if (targetChangeSetIndex < currentChangeSetIndex) + return Promise.reject(new IModelError(ChangeSetStatus.NothingToMerge, "Nothing to merge")); + break; + case ChangeSetApplyOption.Reinstate: + if (targetChangeSetIndex < currentChangeSetIndex) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reinstate to an earlier version")); + break; + case ChangeSetApplyOption.Reverse: + if (targetChangeSetIndex >= currentChangeSetIndex) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reverse to a later version")); + break; + default: + assert(false, "Unknown change set process option"); + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Unknown ChangeSet process option")); + } + } - if (targetChangeSetIndex === currentChangeSetIndex) - return Promise.resolve(); // nothing to apply + // Determine the reinstates, reversals or merges required + let reverseToId: string | undefined, reinstateToId: string | undefined, mergeToId: string | undefined; + let reverseToIndex: number | undefined, reinstateToIndex: number | undefined, mergeToIndex: number | undefined; + if (briefcase.hasReversedChanges) { + if (targetChangeSetIndex < briefcase.reversedChangeSetIndex!) { + reverseToId = targetChangeSetId; + reverseToIndex = targetChangeSetIndex; + } else if (targetChangeSetIndex > briefcase.reversedChangeSetIndex!) { + reinstateToId = targetChangeSetId; + reinstateToIndex = targetChangeSetIndex; + if (targetChangeSetIndex > briefcase.changeSetIndex!) { + reinstateToId = briefcase.changeSetId; + reinstateToIndex = briefcase.changeSetIndex; + mergeToId = targetChangeSetId; + mergeToIndex = targetChangeSetIndex; + } + } + } else { + if (targetChangeSetIndex < briefcase.changeSetIndex!) { + reverseToId = targetChangeSetId; + reverseToIndex = targetChangeSetIndex; + } else if (targetChangeSetIndex > briefcase.changeSetIndex!) { + mergeToId = targetChangeSetId; + mergeToIndex = targetChangeSetIndex; + } + } - switch (processOption) { - case ChangeSetApplyOption.Merge: - if (hasReversedChanges) - return Promise.reject(new IModelError(ChangeSetStatus.CannotMergeIntoReversed, "Cannot merge when there are reversed changes")); - if (targetChangeSetIndex < currentChangeSetIndex) - return Promise.reject(new IModelError(ChangeSetStatus.NothingToMerge, "Nothing to merge")); + // Reverse, reinstate and merge as necessary + if (typeof reverseToId !== "undefined") { + Logger.logTrace(loggingCategory, `Reversing briefcase to ${reverseToId}`, () => ({ ...briefcase })); + await BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, reverseToId, reverseToIndex!, ChangeSetApplyOption.Reverse); + actx.enter(); + } + if (typeof reinstateToId !== "undefined") { + Logger.logTrace(loggingCategory, `Reinstating briefcase to ${reinstateToId}`, () => ({ ...briefcase })); + await BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, reinstateToId, reinstateToIndex!, ChangeSetApplyOption.Reinstate); + actx.enter(); + } + if (typeof mergeToId !== "undefined") { + Logger.logTrace(loggingCategory, `Merging briefcase to ${mergeToId}`, () => ({ ...briefcase })); + await BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, mergeToId, mergeToIndex!, ChangeSetApplyOption.Merge); + actx.enter(); + } + perfLogger.dispose(); + } - break; - case ChangeSetApplyOption.Reinstate: - if (!hasReversedChanges) - return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "No reversed changes to reinstate")); - if (targetChangeSetIndex < currentChangeSetIndex) - return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reinstate to an earlier version")); - assert(briefcase.openParams!.accessMode !== AccessMode.Shared, "Cannot reinstate. If a Db has shared access, we should NOT have allowed to reverse in the first place!"); + private static async applyChangeSets(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, targetChangeSetId: string, targetChangeSetIndex: number, processOption: ChangeSetApplyOption): Promise { + actx.enter(); - break; - case ChangeSetApplyOption.Reverse: - if (targetChangeSetIndex >= currentChangeSetIndex) - return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reverse to a later version")); - if (briefcase.openParams!.accessMode === AccessMode.Shared) - return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reverse changes when the Db allows shared access - open with AccessMode.Exclusive")); + const currentChangeSetId: string = briefcase.currentChangeSetId; + const currentChangeSetIndex: number = briefcase.currentChangeSetIndex; - break; - default: - assert(false, "Unknown change set process option"); - return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Unknown ChangeSet process option")); - } + if (targetChangeSetIndex === currentChangeSetIndex) + return Promise.resolve(); // nothing to apply const reverse: boolean = (targetChangeSetIndex < currentChangeSetIndex); const changeSets: ChangeSet[] = await BriefcaseManager.downloadChangeSets(actx, accessToken, briefcase.iModelId, reverse ? targetChangeSetId : currentChangeSetId, reverse ? currentChangeSetId : targetChangeSetId); @@ -1117,9 +1151,11 @@ export class BriefcaseManager { if (containsSchemaChanges) briefcase.isOpen = true; + const oldKey = briefcase.getKey(); switch (processOption) { case ChangeSetApplyOption.Merge: - BriefcaseManager.updateBriefcaseVersion(briefcase, targetChangeSetId, targetChangeSetIndex); + briefcase.changeSetId = targetChangeSetId; + briefcase.changeSetIndex = targetChangeSetIndex; assert(briefcase.nativeDb.getParentChangeSetId() === briefcase.changeSetId); break; case ChangeSetApplyOption.Reinstate: @@ -1142,28 +1178,26 @@ export class BriefcaseManager { return Promise.reject(new IModelError(BriefcaseStatus.CannotApplyChanges, "Unknown ChangeSet process option")); } - briefcase.onChangesetApplied.raiseEvent(); - } - - private static updateBriefcaseVersion(briefcase: BriefcaseEntry, changeSetId: string, changeSetIndex: number) { - const oldKey = briefcase.getKey(); - briefcase.changeSetId = changeSetId; - briefcase.changeSetIndex = changeSetIndex; - // Update cache if necessary if (BriefcaseManager._cache.findBriefcaseByKey(oldKey)) { BriefcaseManager._cache.deleteBriefcaseByKey(oldKey); BriefcaseManager._cache.addBriefcase(briefcase); } + + briefcase.onChangesetApplied.raiseEvent(); } public static async reverseChanges(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, reverseToVersion: IModelVersion): Promise { - return BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, reverseToVersion, ChangeSetApplyOption.Reverse); + if (briefcase.openParams!.accessMode === AccessMode.Shared) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reverse changes when the Db allows shared access - open with AccessMode.Exclusive")); + return BriefcaseManager.processChangeSets(actx, accessToken, briefcase, reverseToVersion); } public static async reinstateChanges(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, reinstateToVersion?: IModelVersion): Promise { + if (briefcase.openParams!.accessMode === AccessMode.Shared) + return Promise.reject(new IModelError(ChangeSetStatus.ApplyError, "Cannot reinstate (or reverse) changes when the Db allows shared access - open with AccessMode.Exclusive")); const targetVersion: IModelVersion = reinstateToVersion || IModelVersion.asOfChangeSet(briefcase.changeSetId); - return BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, targetVersion, ChangeSetApplyOption.Reinstate); + return BriefcaseManager.processChangeSets(actx, accessToken, briefcase, targetVersion); } /** @@ -1174,7 +1208,8 @@ export class BriefcaseManager { */ public static async pullAndMergeChanges(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, mergeToVersion: IModelVersion = IModelVersion.latest()): Promise { await BriefcaseManager.updatePendingChangeSets(actx, accessToken, briefcase); - return BriefcaseManager.applyChangeSets(actx, accessToken, briefcase, mergeToVersion, ChangeSetApplyOption.Merge); + actx.enter(); + return BriefcaseManager.processChangeSets(actx, accessToken, briefcase, mergeToVersion); } private static startCreateChangeSet(briefcase: BriefcaseEntry): ChangeSetToken { @@ -1227,7 +1262,7 @@ export class BriefcaseManager { pendingChangeSets = pendingChangeSets.slice(0, 100); const query = new ChangeSetQuery().filter(`$id+in+[${pendingChangeSets.map((value: string) => `'${value}'`).join(",")}]`).selectDownloadUrl(); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, briefcase.iModelId, query); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, briefcase.iModelId, query); await BriefcaseManager.downloadChangeSetsInternal(actx, briefcase.iModelId, changeSets); actx.enter(); @@ -1237,7 +1272,7 @@ export class BriefcaseManager { for (const token of changeSetTokens) { try { const codes = BriefcaseManager.extractCodesFromFile(briefcase, [token]); - await BriefcaseManager.imodelClient.Codes().update(actx, accessToken, briefcase.iModelId, codes, { deniedCodes: true, continueOnConflict: true }); + await BriefcaseManager.imodelClient.codes.update(actx, accessToken, briefcase.iModelId, codes, { deniedCodes: true, continueOnConflict: true }); actx.enter(); BriefcaseManager.removePendingChangeSet(briefcase, token.id); } catch (error) { @@ -1291,13 +1326,12 @@ export class BriefcaseManager { let failedUpdating = false; try { - await BriefcaseManager.imodelClient.Codes().update(actx, accessToken, briefcase.iModelId, BriefcaseManager.extractCodes(briefcase), { deniedCodes: true, continueOnConflict: true }); + await BriefcaseManager.imodelClient.codes.update(actx, accessToken, briefcase.iModelId, BriefcaseManager.extractCodes(briefcase), { deniedCodes: true, continueOnConflict: true }); actx.enter(); } catch (error) { actx.enter(); if (error instanceof ConflictingCodesError) { - const msg = `Found conflicting codes when pushing briefcase ${briefcase.iModelId}:${briefcase.briefcaseId} changes.`; - Logger.logError(loggingCategory, msg); + Logger.logError(loggingCategory, "Found conflicting codes when pushing briefcase changes", () => ({ ...briefcase })); briefcase.conflictError = error; } else { failedUpdating = true; @@ -1307,14 +1341,13 @@ export class BriefcaseManager { // Cannot retry relinquishing later, ignore error try { if (relinquishCodesLocks) { - await BriefcaseManager.imodelClient.Codes().deleteAll(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId); - await BriefcaseManager.imodelClient.Locks().deleteAll(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId); + await BriefcaseManager.imodelClient.codes.deleteAll(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId); + await BriefcaseManager.imodelClient.locks.deleteAll(actx, accessToken, briefcase.iModelId, briefcase.briefcaseId); actx.enter(); } } catch (error) { actx.enter(); - const msg = `Relinquishing codes or locks has failed with: ${error}`; - Logger.logError(loggingCategory, msg); + Logger.logError(loggingCategory, "`Relinquishing codes or locks has failed with: ${error}`", () => ({ ...briefcase })); } // Remove ChangeSet id if it succeeded or failed with conflicts @@ -1372,11 +1405,11 @@ export class BriefcaseManager { let postedChangeSet: ChangeSet | undefined; try { - postedChangeSet = await BriefcaseManager.imodelClient.ChangeSets().create(actx, accessToken, briefcase.iModelId, changeSet, changeSetToken.pathname); + postedChangeSet = await BriefcaseManager.imodelClient.changeSets.create(actx, accessToken, briefcase.iModelId, changeSet, changeSetToken.pathname); } catch (error) { // If ChangeSet already exists, updating codes and locks might have timed out. if (!(error instanceof IModelHubError) || error.errorNumber !== IModelHubStatus.ChangeSetAlreadyExists) { - Promise.reject(error); + return Promise.reject(error); } } @@ -1384,13 +1417,22 @@ export class BriefcaseManager { actx.enter(); BriefcaseManager.finishCreateChangeSet(briefcase); - BriefcaseManager.updateBriefcaseVersion(briefcase, postedChangeSet!.wsgId, +postedChangeSet!.index!); + + const oldKey = briefcase.getKey(); + briefcase.changeSetId = postedChangeSet!.wsgId; + briefcase.changeSetIndex = +postedChangeSet!.index!; + + // Update cache if necessary + if (BriefcaseManager._cache.findBriefcaseByKey(oldKey)) { + BriefcaseManager._cache.deleteBriefcaseByKey(oldKey); + BriefcaseManager._cache.addBriefcase(briefcase); + } } /** Attempt to pull merge and push once */ private static async pushChangesOnce(actx: ActivityLoggingContext, accessToken: AccessToken, briefcase: BriefcaseEntry, description: string, relinquishCodesLocks: boolean): Promise { await BriefcaseManager.pullAndMergeChanges(actx, accessToken, briefcase, IModelVersion.latest()); - await BriefcaseManager.pushChangeSet(actx, accessToken, briefcase, description, relinquishCodesLocks).catch((err) => { + await BriefcaseManager.pushChangeSet(actx, accessToken, briefcase, description, relinquishCodesLocks).catch(async (err) => { actx.enter(); BriefcaseManager.abandonCreateChangeSet(briefcase); return Promise.reject(err); @@ -1460,7 +1502,7 @@ export class BriefcaseManager { if (IModelJsFs.existsSync(fileName)) IModelJsFs.unlinkSync(fileName); // Note: Cannot create two files with the same name at the same time with multiple async calls. - let res: DbResult = nativeDb.createIModel(fileName, JSON.stringify(args)); + let res: DbResult = nativeDb.createIModel(accessToken.toTokenString(), projectId, fileName, JSON.stringify(args)); if (DbResult.BE_SQLITE_OK !== res) throw new IModelError(res, fileName); @@ -1481,7 +1523,7 @@ export class BriefcaseManager { hubName = hubName || path.basename(pathname, ".bim"); actx.enter(); - const iModel: HubIModel = await BriefcaseManager.imodelClient.IModels().create(actx, accessToken, projectId, hubName, pathname, hubDescription, undefined, timeOutInMilliseconds); + const iModel: HubIModel = await BriefcaseManager.imodelClient.iModels.create(actx, accessToken, projectId, hubName, pathname, hubDescription, undefined, timeOutInMilliseconds); return iModel.wsgId; } @@ -1493,10 +1535,10 @@ export class BriefcaseManager { return; actx.enter(); const promises = new Array>(); - const briefcases = await BriefcaseManager.imodelClient.Briefcases().get(actx, accessToken, iModelId); + const briefcases = await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, iModelId); actx.enter(); briefcases.forEach((briefcase: Briefcase) => { - promises.push(BriefcaseManager.imodelClient.Briefcases().delete(actx, accessToken, iModelId, briefcase.briefcaseId!)); + promises.push(BriefcaseManager.imodelClient.briefcases.delete(actx, accessToken, iModelId, briefcase.briefcaseId!)); }); return Promise.all(promises); } diff --git a/core/backend/src/Category.ts b/core/backend/src/Category.ts index b174292..d85ba3b 100644 --- a/core/backend/src/Category.ts +++ b/core/backend/src/Category.ts @@ -9,9 +9,10 @@ import { BisCodeSpec, Code, CodeScopeProps, CodeSpec, ElementProps, SubCategoryA import { DefinitionElement } from "./Element"; import { IModelDb } from "./IModelDb"; import { DefinitionModel } from "./Model"; +import { CategoryOwnsSubCategories } from "./NavigationRelationship"; /** Defines the appearance for graphics in Geometric elements */ -export class SubCategory extends DefinitionElement { +export class SubCategory extends DefinitionElement implements SubCategoryProps { /** The Appearance parameters for this SubCategory */ public appearance: SubCategoryAppearance; /** Optional description of this SubCategory. */ @@ -45,6 +46,37 @@ export class SubCategory extends DefinitionElement { public getCategoryId(): Id64String { return this.parent ? this.parent.id : Id64.invalid; } /** Check if this is the default SubCategory of its parent Category. */ public get isDefaultSubCategory(): boolean { return IModelDb.getDefaultSubCategoryId(this.getCategoryId()) === this.getSubCategoryId(); } + + /** Create a Code for a SubCategory given a name that is meant to be unique within the scope of the specified parent Category. + * @param iModel The IModel + * @param parentCategoryId The Id of the parent Category that owns the SubCategory and provides the scope for its name. + * @param codeValue The name of the SubCategory + */ + public static createCode(iModel: IModelDb, parentCategoryId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.subCategory); + return new Code({ spec: codeSpec.id, scope: parentCategoryId, value: codeValue }); + } + + /** Insert a new SubCategory + * @param iModelDb Insert into this iModel + * @param parentCategoryId Insert the new SubCategory as a child of this Category + * @param name The name of the SubCategory + * @param appearance The appearance settings to use for this SubCategory + * @returns The Id of the newly inserted SubCategory element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, parentCategoryId: Id64String, name: string, appearance: SubCategoryAppearance.Props): Id64String { + const elements = iModelDb.elements; + const parentCategory = elements.getElement(parentCategoryId); + const subCategoryProps: SubCategoryProps = { + classFullName: this.classFullName, + model: parentCategory.model, + parent: new CategoryOwnsSubCategories(parentCategoryId), + code: this.createCode(iModelDb, parentCategoryId, name), + appearance, + }; + return elements.insertElement(subCategoryProps); + } } /** A Category element is the target of the `category` member of [[GeometricElement]]. */ @@ -71,9 +103,9 @@ export class Category extends DefinitionElement implements CategoryProps { public myDefaultSubCategoryId(): Id64String { return IModelDb.getDefaultSubCategoryId(this.id); } /** Set the appearance of the default SubCategory for this Category */ - public setDefaultAppearance(app: SubCategoryAppearance): void { - const subCat: SubCategory = this.iModel.elements.getElement(this.myDefaultSubCategoryId()).clone(); - subCat.appearance = app; + public setDefaultAppearance(props: SubCategoryAppearance.Props): void { + const subCat = this.iModel.elements.getElement(this.myDefaultSubCategoryId()); + subCat.appearance = new SubCategoryAppearance(props); this.iModel.elements.updateElement(subCat); } } @@ -106,6 +138,29 @@ export class DrawingCategory extends Category { const codeSpec: CodeSpec = iModel.codeSpecs.getByName(DrawingCategory.getCodeSpecName()); return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); } + + /** + * Insert a new DrawingCategory + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new DrawingCategory into this DefinitionModel + * @param name The name of the DrawingCategory + * @param defaultAppearance The appearance settings to use for the default SubCategory of this DrawingCategory + * @returns The Id of the newly inserted DrawingCategory element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, defaultAppearance: SubCategoryAppearance.Props): Id64String { + const categoryProps: CategoryProps = { + classFullName: this.classFullName, + model: definitionModelId, + code: this.createCode(iModelDb, definitionModelId, name), + isPrivate: false, + }; + const elements = iModelDb.elements; + const categoryId = elements.insertElement(categoryProps); + const category = elements.getElement(categoryId); + category.setDefaultAppearance(defaultAppearance); + return categoryId; + } } /** Categorizes SpatialElements. See [how to create a SpatialCategory]$(docs/learning/backend/CreateElements.md#SpatialCategory). */ @@ -150,4 +205,27 @@ export class SpatialCategory extends Category { code: SpatialCategory.createCode(scopeModel.iModel, scopeModel.id, categoryName), }) as SpatialCategory; } + + /** + * Insert a new SpatialCategory + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new SpatialCategory into this DefinitionModel + * @param name The name of the SpatialCategory + * @param defaultAppearance The appearance settings to use for the default SubCategory of this SpatialCategory + * @returns The Id of the newly inserted SpatialCategory element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, defaultAppearance: SubCategoryAppearance.Props): Id64String { + const categoryProps: CategoryProps = { + classFullName: this.classFullName, + model: definitionModelId, + code: this.createCode(iModelDb, definitionModelId, name), + isPrivate: false, + }; + const elements = iModelDb.elements; + const categoryId = elements.insertElement(categoryProps); + const category = elements.getElement(categoryId); + category.setDefaultAppearance(defaultAppearance); + return categoryId; + } } diff --git a/core/backend/src/ChangeSummaryManager.ts b/core/backend/src/ChangeSummaryManager.ts index 0f54b3a..c2123c5 100644 --- a/core/backend/src/ChangeSummaryManager.ts +++ b/core/backend/src/ChangeSummaryManager.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModels */ -import { AccessToken, ChangeSet, UserInfo, UserInfoQuery, ChangeSetQuery } from "@bentley/imodeljs-clients"; +import { AccessToken, ChangeSet, HubUserInfo, UserInfoQuery, ChangeSetQuery } from "@bentley/imodeljs-clients"; import { ErrorStatusOrResult } from "./imodeljs-native-platform-api"; import { Id64String, GuidString, using, assert, Logger, PerfLogger, DbResult, ActivityLoggingContext } from "@bentley/bentleyjs-core"; import { IModelDb } from "./IModelDb"; @@ -139,7 +139,7 @@ export class ChangeSummaryManager { const ctx = new ChangeSummaryExtractContext(accessToken, iModel); - const endChangeSetId: string = iModel.briefcase.reversedChangeSetId || iModel.briefcase.changeSetId; + const endChangeSetId: string = iModel.briefcase.currentChangeSetId; assert(endChangeSetId.length !== 0); let startChangeSetId: string = ""; @@ -221,11 +221,11 @@ export class ChangeSummaryManager { const userId: string = currentChangeSetInfo.userCreated; const foundUserEmail: string | undefined = userInfoCache.get(userId); if (foundUserEmail === undefined) { - const userInfos: UserInfo[] = await BriefcaseManager.imodelClient.Users().get(actx, ctx.accessToken, ctx.iModelId, new UserInfoQuery().byId(userId)); + const userInfos: HubUserInfo[] = await BriefcaseManager.imodelClient.users.get(actx, ctx.accessToken, ctx.iModelId, new UserInfoQuery().byId(userId)); actx.enter(); assert(userInfos.length !== 0); if (userInfos.length !== 0) { - const userInfo: UserInfo = userInfos[0]; + const userInfo: HubUserInfo = userInfos[0]; userEmail = userInfo.email; // in the cache, add empty e-mail to mark that this user has already been looked up userInfoCache.set(userId, !!userEmail ? userEmail : ""); @@ -247,7 +247,8 @@ export class ChangeSummaryManager { changesFile.dispose(); perfLogger = new PerfLogger("ChangeSummaryManager.extractChangeSummaries>Move iModel to original changeset"); - await iModel.reinstateChanges(actx, accessToken, IModelVersion.asOfChangeSet(endChangeSetId)); + if (iModel.briefcase.currentChangeSetId !== endChangeSetId) + await iModel.reinstateChanges(actx, accessToken, IModelVersion.asOfChangeSet(endChangeSetId)); actx.enter(); perfLogger.dispose(); Logger.logTrace(loggingCategory, "Moved iModel to initial changeset (the end changeset).", () => ({ iModel: ctx.iModelId, startChangeset: startChangeSetId, endChangeset: endChangeSetId })); @@ -267,7 +268,7 @@ export class ChangeSummaryManager { const query = new ChangeSetQuery(); query.byId(startChangeSetId); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, ctx.accessToken, ctx.iModelId, query); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, ctx.accessToken, ctx.iModelId, query); actx.enter(); if (changeSets.length === 0) throw new Error(`Unable to find change set ${startChangeSetId} for iModel ${ctx.iModelId}`); diff --git a/core/backend/src/ClassRegistry.ts b/core/backend/src/ClassRegistry.ts index fd979dd..9b72783 100644 --- a/core/backend/src/ClassRegistry.ts +++ b/core/backend/src/ClassRegistry.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module Schema */ -import { EntityProps, IModelError, IModelStatus, EntityMetaData } from "@bentley/imodeljs-common"; +import { IModelError, IModelStatus, EntityMetaData } from "@bentley/imodeljs-common"; import { Entity } from "./Entity"; import { IModelDb } from "./IModelDb"; import { Schema, Schemas } from "./Schema"; @@ -18,31 +18,11 @@ export class ClassRegistry { /** @hidden */ public static isNotFoundError(err: any) { return (err instanceof IModelError) && (err.errorNumber === IModelStatus.NotFound); } - private static makeClassNotFoundError(className: string): IModelError { return new IModelError(IModelStatus.NotFound, "class " + className + "not found"); } - /** @hidden */ public static makeMetaDataNotFoundError(className: string): IModelError { return new IModelError(IModelStatus.NotFound, "metadata not found for " + className); } - /** - * Construct an instance of an Entity class, given its EntityProps. - * @throws IModelError if the class or class metadata is not available. - * @hidden - */ - public static createInstance(props: EntityProps, iModel: IModelDb): Entity { - if (!props.classFullName) - throw new IModelError(IModelStatus.BadArg, "props must have a classFullName member"); - - let entityClass = this._classMap.get(props.classFullName.toLowerCase()); - if (!entityClass) { - entityClass = this.generateClass(props.classFullName, iModel); - if (!entityClass) - throw this.makeClassNotFoundError(props.classFullName); - } - return new entityClass(props, iModel); - } - /** @hidden */ - public static register(entityClass: typeof Entity) { this._classMap.set(this.getKey(entityClass.schema.name, entityClass.name), entityClass); } + public static register(entityClass: typeof Entity, schema: Schema) { entityClass.schema = schema; this._classMap.set(this.getKey(entityClass.schema.name, entityClass.name), entityClass); } /** @hidden */ public static registerSchema(schema: Schema) { Schemas.registerSchema(schema); } /** @hidden */ @@ -85,8 +65,7 @@ export class ClassRegistry { // the above line creates an "anonymous" class. We rely on the "constructor.name" property to be eponymous with the EcClass name. Object.defineProperty(generatedClass, "name", { get: () => className }); // this is the (only) way to change that readonly property. - generatedClass.schema = schema; // save the schema property - this.register(generatedClass); // register it before returning + this.register(generatedClass, schema); // register it before returning return generatedClass; } @@ -101,10 +80,8 @@ export class ClassRegistry { continue; const thisClass = moduleObj[thisMember]; - if (thisClass.prototype instanceof Entity) { - thisClass.schema = schema; - this.register(thisClass); - } + if (thisClass.prototype instanceof Entity) + this.register(thisClass, schema); } } @@ -126,28 +103,29 @@ export class ClassRegistry { return this.generateClassForEntity(metadata); } + /** + * Find a registered class by classFullName (must be all lowercase, caller should ensure that) + * @param classFullName class to find + * @param iModel The IModel that contains the class definitions + */ + public static findRegisteredClass(classFullName: string): typeof Entity | undefined { return this._classMap.get(classFullName); } + /** * Get the Entity class for the specified Entity. * @param fullName The name of the Entity * @param iModel The IModel that contains the class definitions - * @returns A promise that resolves to an object containing a result property set to the Entity. - * @throws [[IModelError]] if the class is not found. + * @returns The Entity class */ public static getClass(fullName: string, iModel: IModelDb): typeof Entity { const key = fullName.toLowerCase(); - if (!this._classMap.has(key)) - return this.generateClass(fullName, iModel); - - const ctor = this._classMap.get(key); - if (!ctor) - throw this.makeClassNotFoundError(fullName); - - return ctor; + const ctor = this.findRegisteredClass(key); + return ctor ? ctor : this.generateClass(key, iModel); } } /** - * A cache that records mappings between class names and class metadata + * A cache that records the mapping between class names and class metadata + * @hidden */ export class MetaDataRegistry { private _registry: Map = new Map(); diff --git a/core/backend/src/CodeSpecs.ts b/core/backend/src/CodeSpecs.ts index 9360087..8316051 100644 --- a/core/backend/src/CodeSpecs.ts +++ b/core/backend/src/CodeSpecs.ts @@ -5,7 +5,7 @@ /** @module Codes */ import { DbResult, Id64String, Id64, Logger } from "@bentley/bentleyjs-core"; -import { IModelError, IModelStatus, CodeSpec } from "@bentley/imodeljs-common"; +import { CodeSpec, CodeScopeSpec, IModelError, IModelStatus } from "@bentley/imodeljs-common"; import { ECSqlStatement } from "./ECSqlStatement"; import { IModelDb } from "./IModelDb"; @@ -73,19 +73,33 @@ export class CodeSpecs { } /** Add a new CodeSpec to the IModelDb. - *

Example: - * ``` ts - * [[include:CodeSpecs.insert]] - * ``` - * @param codeSpec The new entry to add. - * @return The id of the persistent CodeSpec. + * @param codeSpec The CodeSpec to insert + * @returns The Id of the persistent CodeSpec. * @note If successful, this method will assign a valid CodeSpecId to the supplied CodeSpec * @throws IModelError if the insertion fails */ - public insert(codeSpec: CodeSpec): Id64String { - const id: Id64String = this._imodel.insertCodeSpec(codeSpec); - codeSpec.id = id; - return id; + public insert(codeSpec: CodeSpec): Id64String; + /** Add a new CodeSpec to the IModelDb. + * @param name The name for the new CodeSpec. + * @param scopeType The scope type + * @returns The Id of the persistent CodeSpec. + * @throws IModelError if the insertion fails + */ + public insert(name: string, scopeType: CodeScopeSpec.Type): Id64String; + // Overloads funnel here... + public insert(codeSpecOrName: CodeSpec | string, scopeType?: CodeScopeSpec.Type): Id64String { + if (codeSpecOrName instanceof CodeSpec) { + const codeSpec = codeSpecOrName as CodeSpec; + const id: Id64String = this._imodel.insertCodeSpec(codeSpec); + codeSpec.id = id; + return id; + } + if (typeof codeSpecOrName === "string") { + const name = codeSpecOrName as string; + if (scopeType) + return this._imodel.insertCodeSpec(new CodeSpec(this._imodel, Id64.invalid, name, scopeType)); + } + throw new IModelError(IModelStatus.BadArg, "Invalid argument", Logger.logError, loggingCategory); } /** Load a CodeSpec from IModel diff --git a/core/backend/src/ConcurrencyControl.ts b/core/backend/src/ConcurrencyControl.ts index 8775e42..301d19c 100644 --- a/core/backend/src/ConcurrencyControl.ts +++ b/core/backend/src/ConcurrencyControl.ts @@ -11,7 +11,7 @@ import { Code, IModelError, IModelStatus } from "@bentley/imodeljs-common"; import { Element } from "./Element"; import { Model } from "./Model"; import { BriefcaseEntry, BriefcaseManager } from "./BriefcaseManager"; -import { LinkTableRelationship } from "./LinkTableRelationship"; +import { Relationship } from "./Relationship"; import { NativePlatformRegistry } from "./NativePlatformRegistry"; import { IModelDb } from "./IModelDb"; @@ -84,7 +84,7 @@ export class ConcurrencyControl { } /** @hidden [[LinkTableRelationship.buildConcurrencyControlRequest]] */ - public buildRequestForLinkTableRelationship(instance: LinkTableRelationship, opcode: DbOpcode): void { + public buildRequestForRelationship(instance: Relationship, opcode: DbOpcode): void { if (!this._iModel.briefcase) throw new IModelError(IModelStatus.BadRequest, "Invalid briefcase", Logger.logError, loggingCategory); const rc: RepositoryStatus = this._iModel.briefcase.nativeDb.buildBriefcaseManagerResourcesRequestForLinkTableRelationship(this._pendingRequest as NativeBriefcaseManagerResourcesRequest, JSON.stringify(instance), opcode); @@ -198,7 +198,7 @@ export class ConcurrencyControl { } /** Obtain the schema lock. This is always an immediate request, never deferred. */ - public lockSchema(actx: ActivityLoggingContext, accessToken: AccessToken): Promise { + public async lockSchema(actx: ActivityLoggingContext, accessToken: AccessToken): Promise { actx.enter(); const locks: Lock[] = [ { @@ -213,7 +213,7 @@ export class ConcurrencyControl { }, ]; assert(this.inBulkOperation(), "should always be in bulk mode"); - const res = BriefcaseManager.imodelClient.Locks().update(actx, accessToken, this._iModel.iModelToken.iModelId!, locks); + const res = BriefcaseManager.imodelClient.locks.update(actx, accessToken, this._iModel.iModelToken.iModelId!, locks); assert(this.inBulkOperation(), "should always be in bulk mode"); return res; } @@ -244,13 +244,13 @@ export class ConcurrencyControl { const locks = this.buildLockRequests(briefcaseEntry, req); if (locks === undefined) return []; - return BriefcaseManager.imodelClient.Locks().update(actx, accessToken, this._iModel.iModelToken.iModelId!, locks); + return BriefcaseManager.imodelClient.locks.update(actx, accessToken, this._iModel.iModelToken.iModelId!, locks); } /** process a Code-reservation request. The requests in bySpecId must already be in iModelHub REST format. */ private async reserveCodes2(actx: ActivityLoggingContext, request: HubCode[], briefcaseEntry: BriefcaseEntry, accessToken: AccessToken): Promise { actx.enter(); - return BriefcaseManager.imodelClient.Codes().update(actx, accessToken, briefcaseEntry.iModelId, request); + return BriefcaseManager.imodelClient.codes.update(actx, accessToken, briefcaseEntry.iModelId, request); } /** process the Code-specific part of the request. */ @@ -290,7 +290,7 @@ export class ConcurrencyControl { } */ - return BriefcaseManager.imodelClient.Codes().get(actx, accessToken, this._iModel.briefcase.iModelId, query); + return BriefcaseManager.imodelClient.codes.get(actx, accessToken, this._iModel.briefcase.iModelId, query); } /** Abandon any pending requests for locks or codes. */ @@ -314,7 +314,7 @@ export class ConcurrencyControl { if (!hubCodes) return true; - const codesHandler = BriefcaseManager.imodelClient.Codes(); + const codesHandler = BriefcaseManager.imodelClient.codes; const chunkSize = 100; for (let i = 0; i < hubCodes.length; i += chunkSize) { const query = new CodeQuery().byCodes(hubCodes.slice(i, i + chunkSize)); diff --git a/core/backend/src/Element.ts b/core/backend/src/Element.ts index 823b8fc..5b952be 100644 --- a/core/backend/src/Element.ts +++ b/core/backend/src/Element.ts @@ -4,15 +4,17 @@ *--------------------------------------------------------------------------------------------*/ /** @module Elements */ -import { Id64String, Id64, GuidString, DbOpcode, JsonUtils } from "@bentley/bentleyjs-core"; +import { Id64String, Id64, GuidString, DbOpcode, JsonUtils, IModelStatus } from "@bentley/bentleyjs-core"; import { Transform } from "@bentley/geometry-core"; +import { DrawingModel } from "./Model"; import { Entity } from "./Entity"; import { IModelDb } from "./IModelDb"; +import { SubjectOwnsSubjects } from "./NavigationRelationship"; import { BisCodeSpec, Code, CodeScopeProps, CodeSpec, Placement3d, Placement2d, AxisAlignedBox3d, GeometryStreamProps, ElementAlignedBox3d, ElementProps, RelatedElement, GeometricElementProps, TypeDefinition, GeometricElement3dProps, GeometricElement2dProps, SubjectProps, SheetBorderTemplateProps, SheetTemplateProps, SheetProps, TypeDefinitionElementProps, - InformationPartitionElementProps, DefinitionElementProps, LineStyleProps, GeometryPartProps, EntityMetaData, + InformationPartitionElementProps, DefinitionElementProps, LineStyleProps, GeometryPartProps, EntityMetaData, IModel, } from "@bentley/imodeljs-common"; /** @@ -32,7 +34,7 @@ import { * * [Working with schemas and elements in TypeScript]($docs/learning/backend/SchemasAndElementsInTypeScript.md) * * [Creating elements]($docs/learning/backend/CreateElements.md) */ -export abstract class Element extends Entity implements ElementProps { +export class Element extends Entity implements ElementProps { /** The ModelId of the [Model]($docs/bis/intro/model-fundamentals.md) containing this element */ public readonly model: Id64String; /** The [Code]($docs/bis/intro/codes.md) for this element */ @@ -44,7 +46,7 @@ export abstract class Element extends Entity implements ElementProps { /** A [user-assigned label]($docs/bis/intro/element-fundamentals.md#userlabel) for this element. */ public userLabel?: string; /** Optional [json properties]($docs/bis/intro/element-fundamentals.md#jsonproperties) of this element. */ - public readonly jsonProperties: any; + public readonly jsonProperties: { [key: string]: any }; /** constructor for Element. * @hidden @@ -59,13 +61,20 @@ export abstract class Element extends Entity implements ElementProps { this.jsonProperties = Object.assign({}, props.jsonProperties); // make sure we have our own copy } + public static onInsert(_props: ElementProps, _iModel: IModelDb): IModelStatus { return IModelStatus.Success; } + public static onUpdate(_props: ElementProps, _iModel: IModelDb): IModelStatus { return IModelStatus.Success; } + public static onDelete(_props: ElementProps, _iModel: IModelDb): IModelStatus { return IModelStatus.Success; } + public static onInserted(_props: ElementProps, _iModel: IModelDb): void { } + public static onUpdated(_props: ElementProps, _iModel: IModelDb): void { } + public static onDeleted(_props: ElementProps, _iModel: IModelDb): void { } + public static onBeforeOutputsHandled(_id: Id64String, _iModel: IModelDb): void { } + public static onAllInputsHandled(_id: Id64String, _iModel: IModelDb): void { } + /** Add this Element's properties to an object for serializing to JSON. * @hidden */ public toJSON(): ElementProps { const val = super.toJSON() as ElementProps; - if (Id64.isValid(this.id)) - val.id = this.id; if (Id64.isValid(this.code.spec)) val.code = this.code; @@ -117,6 +126,13 @@ export abstract class Element extends Entity implements ElementProps { return msg; } + /** Insert this Element into the iModel. */ + public insert() { return this.iModel.elements.insertElement(this); } + /** Update this Element in the iModel. */ + public update() { this.iModel.elements.updateElement(this); } + /** Delete this Element from the iModel. */ + public delete() { this.iModel.elements.deleteElement(this.id); } + /** * Add a request for locks, code reservations, and anything else that would be needed to carry out the specified operation. * @param opcode The operation that will be performed on the element. @@ -321,6 +337,35 @@ export class Subject extends InformationReferenceElement implements SubjectProps public description?: string; /** @hidden */ public constructor(props: SubjectProps, iModel: IModelDb) { super(props, iModel); } + /** + * Create a Code for a Subject given a name that is meant to be unique within the scope of its parent Subject. + * @param iModelDb The IModelDb + * @param parentSubjectId The Id of the DocumentListModel that contains the Drawing and provides the scope for its name. + * @param codeValue The Drawing name + */ + public static createCode(iModelDb: IModelDb, parentSubjectId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModelDb.codeSpecs.getByName(BisCodeSpec.subject); + return new Code({ spec: codeSpec.id, scope: parentSubjectId, value: codeValue }); + } + /** + * Insert a Subject + * @param iModelDb Insert into this IModelDb + * @param parentSubjectId The new Subject will be inserted as a child of this Subject + * @param name The name (codeValue) of the Subject + * @param description The optional description of the Subject + * @returns The Id of the newly inserted Subject + * @throws [[IModelError]] if there is a problem inserting the Subject + */ + public static insert(iModelDb: IModelDb, parentSubjectId: Id64String, name: string, description?: string): Id64String { + const subjectProps: SubjectProps = { + classFullName: this.classFullName, + model: IModel.repositoryModelId, + parent: new SubjectOwnsSubjects(parentSubjectId), + code: this.createCode(iModelDb, parentSubjectId, name), + description, + }; + return iModelDb.elements.insertElement(subjectProps); + } } /** @@ -349,6 +394,28 @@ export class Drawing extends Document { const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.drawing); return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); } + + /** + * Insert a Drawing element and a DrawingModel that breaks it down. + * @param iModelDb Insert into this iModel + * @param documentListModelId Insert the new Drawing into this DocumentListModel + * @param name The name of the Drawing. + * @returns The Id of the newly inserted Drawing element and the DrawingModel that breaks it down (same value). + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, documentListModelId: Id64String, name: string): Id64String { + const drawingProps: ElementProps = { + classFullName: this.classFullName, + model: documentListModelId, + code: this.createCode(iModelDb, documentListModelId, name), + }; + const drawingId: Id64String = iModelDb.elements.insertElement(drawingProps); + const model: DrawingModel = iModelDb.models.createModel({ + classFullName: DrawingModel.classFullName, + modeledElement: { id: drawingId }, + }) as DrawingModel; + return iModelDb.models.insertModel(model); + } } /** @@ -391,6 +458,16 @@ export class Sheet extends Document implements SheetProps { this.scale = props.scale; this.sheetTemplate = props.sheetTemplate ? Id64.fromJSON(props.sheetTemplate) : undefined; } + + /** Create a Code for a Sheet given a name that is meant to be unique within the scope of the specified DocumentListModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DocumentListModel that contains the Sheet and provides the scope for its name. + * @param codeValue The Sheet name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.sheet); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -460,6 +537,16 @@ export abstract class RecipeDefinitionElement extends DefinitionElement { export abstract class PhysicalType extends TypeDefinitionElement { /** @hidden */ constructor(props: TypeDefinitionElementProps, iModel: IModelDb) { super(props, iModel); } + + /** Create a Code for a PhysicalType element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the PhysicalType element and provides the scope for its name. + * @param codeValue The PhysicalType name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.physicalType); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -468,6 +555,16 @@ export abstract class PhysicalType extends TypeDefinitionElement { export abstract class SpatialLocationType extends TypeDefinitionElement { /** @hidden */ constructor(props: TypeDefinitionElementProps, iModel: IModelDb) { super(props, iModel); } + + /** Create a Code for a SpatialLocationType element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the SpatialLocationType element and provides the scope for its name. + * @param codeValue The SpatialLocationType name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.spatialLocationType); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -484,6 +581,16 @@ export class TemplateRecipe3d extends RecipeDefinitionElement { export abstract class GraphicalType2d extends TypeDefinitionElement { /** @hidden */ public constructor(props: ElementProps, iModel: IModelDb) { super(props, iModel); } + + /** Create a Code for a GraphicalType2d element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the GraphicalType2d element and provides the scope for its name. + * @param codeValue The GraphicalType2d name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.graphicalType2d); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -573,10 +680,20 @@ export class SpatialLocationPartition extends InformationPartitionElement { */ export abstract class GroupInformationElement extends InformationReferenceElement { } + /** * An information element that specifies a link. */ export abstract class LinkElement extends InformationReferenceElement { + /** Create a Code for a LinkElement given a name that is meant to be unique within the scope of the specified Model. + * @param iModel The IModelDb + * @param scopeModelId The Id of the Model that contains the LinkElement and provides the scope for its name. + * @param codeValue The LinkElement name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.linkElement); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -628,6 +745,17 @@ export class GeometryPart extends DefinitionElement implements GeometryPartProps val.bbox = this.bbox; return val; } + + /** Create a Code for a GeometryPart element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the GeometryPart element and provides the scope for its name. + * @param codeValue The GeometryPart name + * @note GeometryPart elements are not required to be named (have a non-empty Code). + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.geometryPart); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -643,7 +771,7 @@ export class LineStyle extends DefinitionElement implements LineStyleProps { * @param iModel The IModel * @param scopeModelId The Id of the DefinitionModel that contains the LineStyle and provides the scope for its name. * @param codeValue The name of the LineStyle - * @return A LineStyle Code + * @returns A LineStyle Code */ public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { return new Code({ spec: iModel.codeSpecs.getByName(BisCodeSpec.lineStyle).id, scope: scopeModelId, value: codeValue }); diff --git a/core/backend/src/ElementAspect.ts b/core/backend/src/ElementAspect.ts index 5e88de6..fd11211 100644 --- a/core/backend/src/ElementAspect.ts +++ b/core/backend/src/ElementAspect.ts @@ -20,6 +20,12 @@ export class ElementAspect extends Entity implements ElementAspectProps { super(props, iModel); this.element = RelatedElement.fromJSON(props.element)!; } + + public toJSON(): ElementAspectProps { + const val = super.toJSON() as ElementAspectProps; + val.element = this.element; + return val; + } } /** An Element Unique Aspect is an ElementAspect where there can be only zero or one instance of the Element Aspect class per Element. */ diff --git a/core/backend/src/Entity.ts b/core/backend/src/Entity.ts index 8c7f64a..4316a00 100644 --- a/core/backend/src/Entity.ts +++ b/core/backend/src/Entity.ts @@ -34,6 +34,8 @@ export class Entity implements EntityProps { public toJSON(): EntityProps { const val: any = {}; val.classFullName = this.classFullName; + if (Id64.isValid(this.id)) + val.id = this.id; this.forEachProperty((propName: string) => val[propName] = this[propName]); return val; } diff --git a/core/backend/src/ExpressServer.ts b/core/backend/src/ExpressServer.ts index 470415e..0406093 100644 --- a/core/backend/src/ExpressServer.ts +++ b/core/backend/src/ExpressServer.ts @@ -13,6 +13,9 @@ type HttpServer = import("http").Server; // tslint:enable:whitespace /** * An express web server with some reasonable defaults for web applications built with @bentley/webpack-tools. + * @note This server is not designed to be a hardened, secure endpoint on the public internet. + * It is intended to participate in a private HTTP exchange with a public-facing routing and provisioning infrastructure + * that should be supplied by the application's deployment environment. */ export class IModelJsExpressServer { private _protocol: WebAppRpcProtocol; @@ -39,20 +42,20 @@ export class IModelJsExpressServer { protected _configureRoutes() { this._app.get("/v3/swagger.json", (req, res) => this._protocol.handleOpenApiDescriptionRequest(req, res)); this._app.post("*", async (req, res) => this._protocol.handleOperationPostRequest(req, res)); - this._app.get(/\/imodel\//, (req, res) => this._protocol.handleOperationGetRequest(req, res)); + this._app.get(/\/imodel\//, async (req, res) => this._protocol.handleOperationGetRequest(req, res)); } /** * Configure the express application with necessary headers, routes, and middleware, then starts listening on the given port. * @param port The port to listen on */ - public initialize(port: number): Promise { + public async initialize(port: number): Promise { this._configureMiddleware(); this._configureHeaders(); this._configureRoutes(); this._app.set("port", port); - return new Promise((resolve) => { + return new Promise((resolve) => { const server = this._app.listen(this._app.get("port"), () => resolve(server)); }); } diff --git a/core/backend/src/IModelDb.ts b/core/backend/src/IModelDb.ts index 903cad2..22a35f9 100644 --- a/core/backend/src/IModelDb.ts +++ b/core/backend/src/IModelDb.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ /** @module iModels */ -import { ActivityLoggingContext, BeEvent, BentleyStatus, DbResult, GuidString, Id64, Id64Arg, Id64Set, Id64String, JsonUtils, Logger, OpenMode, Guid } from "@bentley/bentleyjs-core"; +import { ActivityLoggingContext, BeEvent, BentleyStatus, DbResult, GuidString, Id64, Id64Arg, Id64Set, Id64String, JsonUtils, Logger, OpenMode } from "@bentley/bentleyjs-core"; import { AccessToken } from "@bentley/imodeljs-clients"; import { AxisAlignedBox3d, CategorySelectorProps, Code, CodeSpec, CreateIModelProps, DisplayStyleProps, EcefLocation, @@ -22,9 +22,9 @@ import { ECSqlStatement, ECSqlStatementCache } from "./ECSqlStatement"; import { Element, Subject } from "./Element"; import { ElementAspect } from "./ElementAspect"; import { Entity } from "./Entity"; -import { ErrorStatusOrResult, NativeDgnDb, SnapRequest } from "./imodeljs-native-platform-api"; +import { ErrorStatusOrResult, NativeDgnDb, SnapRequest, TxnIdString } from "./imodeljs-native-platform-api"; import { IModelJsFs } from "./IModelJsFs"; -import { IModelDbLinkTableRelationships } from "./LinkTableRelationship"; +import { Relationships, Relationship, RelationshipProps } from "./Relationship"; import { Model } from "./Model"; import { NativePlatformRegistry } from "./NativePlatformRegistry"; import { KnownLocations } from "./Platform"; @@ -35,7 +35,7 @@ import { SheetViewDefinition, ViewDefinition } from "./ViewDefinition"; const loggingCategory = "imodeljs-backend.IModelDb"; /** The signature of a function that can supply a description of local Txns in the specified briefcase up to and including the specified endTxnId. */ -export type ChangeSetDescriber = (endTxnId: TxnManager.TxnId) => string; +export type ChangeSetDescriber = (endTxnId: TxnIdString) => string; /** Operations allowed when synchronizing changes between the IModelDb and the iModel Hub */ export enum SyncMode { FixedVersion = 1, PullOnly = 2, PullAndPush = 3 } @@ -52,7 +52,7 @@ export enum ExclusiveAccessOption { TryReuseOpenBriefcase = 2, } -/** Parameters to open the iModelDb */ +/** Parameters to open an IModelDb */ export class OpenParams { // Constructor public constructor( @@ -62,7 +62,7 @@ export class OpenParams { /** Mode to access the IModelDb */ public readonly accessMode?: AccessMode, - /** Operations allowed when synchronizing changes between the IModelDb and the iModel Hub */ + /** Operations allowed when synchronizing changes between the IModelDb and IModelHub */ public readonly syncMode?: SyncMode, /** Additional hint for exclusive access to either create a new briefcase or try and reuse a previously opened briefcase */ @@ -72,7 +72,7 @@ export class OpenParams { this.validate(); } - /** Returns true if the open params are setup to open a standalone Db */ + /** Returns true if the open params open a standalone Db */ public get isStandalone(): boolean { return this.accessMode === undefined || this.syncMode === undefined; } private validate() { @@ -80,7 +80,7 @@ export class OpenParams { throw new IModelError(BentleyStatus.ERROR, "Invalid parameters - only openMode can be defined if opening a standalone Db"); if (this.openMode === OpenMode.Readonly && this.syncMode && this.syncMode !== SyncMode.FixedVersion) { - throw new IModelError(BentleyStatus.ERROR, "Cannot pull changes into a ReadOnly IModelDb"); + throw new IModelError(BentleyStatus.ERROR, "Cannot pull changes into a ReadOnly IModel"); } if (this.syncMode === SyncMode.PullAndPush && this.accessMode === AccessMode.Shared) { @@ -122,21 +122,22 @@ export class IModelDb extends IModel { private static _accessTokens?: Map; /** Event called after a changeset is applied to this IModelDb. */ public readonly onChangesetApplied = new BeEvent<() => void>(); - public models = new IModelDb.Models(this); - public elements = new IModelDb.Elements(this); - public views = new IModelDb.Views(this); - public tiles = new IModelDb.Tiles(this); - private _linkTableRelationships?: IModelDbLinkTableRelationships; + public readonly models = new IModelDb.Models(this); + public readonly elements = new IModelDb.Elements(this); + public readonly views = new IModelDb.Views(this); + public readonly tiles = new IModelDb.Tiles(this); + public readonly txns = new TxnManager(this); + private _relationships?: Relationships; private readonly _statementCache = new ECSqlStatementCache(); private readonly _sqliteStatementCache = new SqliteStatementCache(); private _codeSpecs?: CodeSpecs; private _classMetaDataRegistry?: MetaDataRegistry; private _concurrency?: ConcurrencyControl; - private _txnManager?: TxnManager; protected _fontMap?: FontMap; private readonly _snaps = new Map(); + public readFontJson(): string { return this.nativeDb.readFontMap(); } - public getFontMap(): FontMap { return this._fontMap || (this._fontMap = new FontMap(JSON.parse(this.readFontJson()) as FontMapProps)); } + public get fontMap(): FontMap { return this._fontMap || (this._fontMap = new FontMap(JSON.parse(this.readFontJson()) as FontMapProps)); } public embedFont(prop: FontProps): FontProps { this._fontMap = undefined; return JSON.parse(this.nativeDb.embedFont(JSON.stringify(prop))) as FontProps; } /** Get the parameters used to open this iModel */ @@ -181,11 +182,7 @@ export class IModelDb extends IModel { } private initializeIModelDb() { - let props: any; - try { - props = JSON.parse(this.nativeDb.getIModelProps()) as IModelProps; - } catch (error) { } - + const props = JSON.parse(this.nativeDb.getIModelProps()) as IModelProps; const name = props.rootSubject ? props.rootSubject.name : path.basename(this.briefcase.pathname); super.initialize(name, props); } @@ -193,7 +190,7 @@ export class IModelDb extends IModel { private static constructIModelDb(briefcaseEntry: BriefcaseEntry, openParams: OpenParams, contextId?: string): IModelDb { if (briefcaseEntry.iModelDb) return briefcaseEntry.iModelDb; // If there's an IModelDb already associated with the briefcase, that should be reused. - const iModelToken = new IModelToken(briefcaseEntry.getKey(), contextId, briefcaseEntry.iModelId, briefcaseEntry.changeSetId, openParams.openMode); + const iModelToken = new IModelToken(briefcaseEntry.getKey(), contextId, briefcaseEntry.iModelId, briefcaseEntry.currentChangeSetId, openParams.openMode); return new IModelDb(briefcaseEntry, iModelToken, openParams); } @@ -242,11 +239,11 @@ export class IModelDb extends IModel { } /** Create an iModel on iModelHub */ - public static async create(actx: ActivityLoggingContext, accessToken: AccessToken, contextId: string, fileName: string, args: CreateIModelProps): Promise { - actx.enter(); + public static async create(activity: ActivityLoggingContext, accessToken: AccessToken, contextId: string, fileName: string, args: CreateIModelProps): Promise { + activity.enter(); IModelDb.onCreate.raiseEvent(accessToken, contextId, args); - const iModelId: string = await BriefcaseManager.create(actx, accessToken, contextId, fileName, args); - return IModelDb.open(actx, accessToken, contextId, iModelId); + const iModelId: string = await BriefcaseManager.create(activity, accessToken, contextId, fileName, args); + return IModelDb.open(activity, accessToken, contextId, iModelId); } /** Open an iModel from a local file. @@ -261,7 +258,7 @@ export class IModelDb extends IModel { } /** - * Open an iModel from iModelHub. IModelDb files are cached locally. The requested version may be downloaded from the iModelHub to the + * Open an iModel from iModelHub. IModelDb files are cached locally. The requested version may be downloaded from iModelHub to the * cache, or a previously downloaded version re-used from the cache - this behavior can optionally be configured through OpenParams. * Every open call must be matched with a call to close the IModelDb. * @param accessToken Delegation token of the authorized user. @@ -270,14 +267,14 @@ export class IModelDb extends IModel { * @param version Version of the iModel to open * @param openParams Parameters to open the iModel */ - public static async open(actx: ActivityLoggingContext, accessToken: AccessToken, contextId: string, iModelId: string, openParams: OpenParams = OpenParams.pullAndPush(), version: IModelVersion = IModelVersion.latest()): Promise { - actx.enter(); - IModelDb.onOpen.raiseEvent(accessToken, contextId, iModelId, openParams, version, actx); - const briefcaseEntry: BriefcaseEntry = await BriefcaseManager.open(actx, accessToken, contextId, iModelId, openParams, version); - actx.enter(); + public static async open(activity: ActivityLoggingContext, accessToken: AccessToken, contextId: string, iModelId: string, openParams: OpenParams = OpenParams.pullAndPush(), version: IModelVersion = IModelVersion.latest()): Promise { + activity.enter(); + IModelDb.onOpen.raiseEvent(accessToken, contextId, iModelId, openParams, version, activity); + const briefcaseEntry: BriefcaseEntry = await BriefcaseManager.open(activity, accessToken, contextId, iModelId, openParams, version); + activity.enter(); const imodelDb = IModelDb.constructIModelDb(briefcaseEntry, openParams, contextId); IModelDb.setFirstAccessToken(imodelDb.briefcase.iModelId, accessToken); - IModelDb.onOpened.raiseEvent(imodelDb, actx); + IModelDb.onOpened.raiseEvent(imodelDb, activity); Logger.logTrace(loggingCategory, "IModelDb.open", () => ({ ...imodelDb._token, ...openParams })); return imodelDb; } @@ -307,18 +304,18 @@ export class IModelDb extends IModel { * @param keepBriefcase Hint to discard or keep the briefcase for potential future use. * @throws IModelError if the iModel is not open, or is really a standalone iModel */ - public async close(actx: ActivityLoggingContext, accessToken: AccessToken, keepBriefcase: KeepBriefcase = KeepBriefcase.Yes): Promise { + public async close(activity: ActivityLoggingContext, accessToken: AccessToken, keepBriefcase: KeepBriefcase = KeepBriefcase.Yes): Promise { if (!this.briefcase) throw this.newNotOpenError(); if (this.briefcase.isStandalone) throw new IModelError(BentleyStatus.ERROR, "Cannot use IModelDb.close() to close a standalone iModel. Use IModelDb.closeStandalone() instead"); try { - await BriefcaseManager.close(actx, accessToken, this.briefcase, keepBriefcase); + await BriefcaseManager.close(activity, accessToken, this.briefcase, keepBriefcase); } catch (error) { throw error; } finally { - actx.enter(); + activity.enter(); this.clearBriefcaseEntry(); } } @@ -581,32 +578,32 @@ export class IModelDb extends IModel { } /** - * Pull and Merge changes from the iModelHub + * Pull and Merge changes from iModelHub * @param accessToken Delegation token of the authorized user. * @param version Version to pull and merge to. * @throws [[IModelError]] If the pull and merge fails. */ - public async pullAndMergeChanges(actx: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { - actx.enter(); + public async pullAndMergeChanges(activity: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { + activity.enter(); this.concurrencyControl.onMergeChanges(); - await BriefcaseManager.pullAndMergeChanges(actx, accessToken, this.briefcase, version); - actx.enter(); + await BriefcaseManager.pullAndMergeChanges(activity, accessToken, this.briefcase, version); + activity.enter(); this.concurrencyControl.onMergedChanges(); this._token.changeSetId = this.briefcase.changeSetId; this.initializeIModelDb(); } /** - * Push changes to the iModelHub + * Push changes to iModelHub * @param accessToken Delegation token of the authorized user. * @param describer A function that returns a description of the changeset. Defaults to the combination of the descriptions of all local Txns. * @throws [[IModelError]] If the pull and merge fails. */ - public async pushChanges(actx: ActivityLoggingContext, accessToken: AccessToken, describer?: ChangeSetDescriber): Promise { - actx.enter(); + public async pushChanges(activity: ActivityLoggingContext, accessToken: AccessToken, describer?: ChangeSetDescriber): Promise { + activity.enter(); const description = describer ? describer(this.txns.getCurrentTxnId()) : this.txns.describeChangeSet(); - await BriefcaseManager.pushChanges(actx, accessToken, this.briefcase, description); - actx.enter(); + await BriefcaseManager.pushChanges(activity, accessToken, this.briefcase, description); + activity.enter(); this._token.changeSetId = this.briefcase.changeSetId; this.initializeIModelDb(); } @@ -617,9 +614,9 @@ export class IModelDb extends IModel { * @param version Version to reverse changes to. * @throws [[IModelError]] If the reversal fails. */ - public async reverseChanges(actx: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { - await BriefcaseManager.reverseChanges(actx, accessToken, this.briefcase, version); - actx.enter(); + public async reverseChanges(activity: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { + await BriefcaseManager.reverseChanges(activity, accessToken, this.briefcase, version); + activity.enter(); this.initializeIModelDb(); } @@ -629,9 +626,9 @@ export class IModelDb extends IModel { * @param version Version to reinstate changes to. * @throws [[IModelError]] If the reinstate fails. */ - public async reinstateChanges(actx: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { - await BriefcaseManager.reinstateChanges(actx, accessToken, this.briefcase, version); - actx.enter(); + public async reinstateChanges(activity: ActivityLoggingContext, accessToken: AccessToken, version: IModelVersion = IModelVersion.latest()): Promise { + await BriefcaseManager.reinstateChanges(activity, accessToken, this.briefcase, version); + activity.enter(); this.initializeIModelDb(); } @@ -656,15 +653,15 @@ export class IModelDb extends IModel { * @throws IModelError if the schema lock cannot be obtained. * @see containsClass */ - public async importSchema(actx: ActivityLoggingContext, schemaFileName: string): Promise { - actx.enter(); + public async importSchema(activity: ActivityLoggingContext, schemaFileName: string): Promise { + activity.enter(); if (!this.briefcase) throw this.newNotOpenError(); if (!this.briefcase.isStandalone) { - await this.concurrencyControl.lockSchema(actx, IModelDb.getAccessToken(this.iModelToken.iModelId!)); - actx.enter(); + await this.concurrencyControl.lockSchema(activity, IModelDb.getAccessToken(this.iModelToken.iModelId!)); + activity.enter(); } const stat = this.briefcase.nativeDb.importSchema(schemaFileName); if (DbResult.BE_SQLITE_OK !== stat) { @@ -674,9 +671,9 @@ export class IModelDb extends IModel { try { // The schema import logic and/or imported Domains may have created new elements and models. // Make sure we have the supporting locks and codes. - await this.concurrencyControl.request(actx, IModelDb.getAccessToken(this.iModelToken.iModelId!)); + await this.concurrencyControl.request(activity, IModelDb.getAccessToken(this.iModelToken.iModelId!)); } catch (err) { - actx.enter(); + activity.enter(); this.abandonChanges(); throw err; } @@ -704,14 +701,11 @@ export class IModelDb extends IModel { } /** Get the linkTableRelationships for this IModel */ - public get linkTableRelationships(): IModelDbLinkTableRelationships { return this._linkTableRelationships || (this._linkTableRelationships = new IModelDbLinkTableRelationships(this)); } + public get relationships(): Relationships { return this._relationships || (this._relationships = new Relationships(this)); } /** Get the ConcurrencyControl for this IModel. */ public get concurrencyControl(): ConcurrencyControl { return (this._concurrency !== undefined) ? this._concurrency : (this._concurrency = new ConcurrencyControl(this)); } - /** Get the TxnManager for this IModelDb. */ - public get txns(): TxnManager { return (this._txnManager !== undefined) ? this._txnManager : (this._txnManager = new TxnManager(this)); } - /** Get the CodeSpecs in this IModel. */ public get codeSpecs(): CodeSpecs { return (this._codeSpecs !== undefined) ? this._codeSpecs : (this._codeSpecs = new CodeSpecs(this)); } @@ -750,21 +744,24 @@ export class IModelDb extends IModel { /** Construct an entity (Element or Model) from an iModel. * @throws [[IModelError]] if the entity cannot be constructed. */ - public constructEntity(props: EntityProps): Entity { - let entity: Entity; + public constructEntity(props: EntityProps): T { + const jsClass = this.getJsClass(props.classFullName); + return new jsClass(props, this) as T; + } + + /** Get the JavaScript class that handles a given entity class. */ + public getJsClass(classFullName: string): T { try { - entity = ClassRegistry.createInstance(props, this); + return ClassRegistry.getClass(classFullName, this) as T; } catch (err) { if (!ClassRegistry.isNotFoundError(err)) { Logger.logError(loggingCategory, err.toString()); throw err; } - // Probably, we have not yet loaded the metadata for this class and/or its superclasses. Do that now, and retry the create. - this.loadMetaData(props.classFullName!); - entity = ClassRegistry.createInstance(props, this); + this.loadMetaData(classFullName); + return ClassRegistry.getClass(classFullName, this) as T; } - return entity; } /** Get metadata for a class. This method will load the metadata from the iModel into the cache as a side-effect, if necessary. @@ -805,27 +802,23 @@ export class IModelDb extends IModel { /*** @hidden */ private loadMetaData(classFullName: string) { - if (!this.briefcase) - throw this.newNotOpenError(); - if (this.classMetaDataRegistry.find(classFullName)) return; + const className = classFullName.split(":"); if (className.length !== 2) throw new IModelError(IModelStatus.BadArg, "Invalid classFullName", Logger.logError, loggingCategory, () => ({ iModelId: this._token.iModelId, classFullName })); - const { error, result: metaDataJson } = this.nativeDb.getECClassMetaData(className[0], className[1]); - if (error) - throw new IModelError(error.status, "Error getting class meta data", Logger.logError, loggingCategory, () => ({ iModelId: this._token.iModelId, classFullName })); + const val = this.nativeDb.getECClassMetaData(className[0], className[1]); + if (val.error) + throw new IModelError(val.error.status, "Error getting class meta data for: " + classFullName, Logger.logError, loggingCategory, () => ({ iModelId: this._token.iModelId, classFullName })); - const metaData = new EntityMetaData(JSON.parse(metaDataJson!)); + const metaData = new EntityMetaData(JSON.parse(val.result!)); this.classMetaDataRegistry.add(classFullName, metaData); - // Recursive, to make sure that base class is cached. - if (metaData.baseClasses !== undefined && metaData.baseClasses.length > 0) { - metaData.baseClasses.forEach((baseClassName: string) => { - this.loadMetaData(baseClassName); - }); - } + + // Recursive, to make sure that base classes are cached. + if (metaData.baseClasses !== undefined && metaData.baseClasses.length > 0) + metaData.baseClasses.forEach((baseClassName: string) => this.loadMetaData(baseClassName)); } /** Query if this iModel contains the definition of the specified class. @@ -835,10 +828,7 @@ export class IModelDb extends IModel { */ public containsClass(classFullName: string): boolean { const className = classFullName.split(":"); - if (className.length !== 2) - throw new IModelError(IModelStatus.BadArg, "Invalid classFullName", Logger.logError, loggingCategory, () => ({ iModelId: this._token.iModelId, classFullName })); - const { error } = this.nativeDb.getECClassMetaData(className[0], className[1]); - return (error === undefined); + return className.length === 2 && this.nativeDb.getECClassMetaData(className[0], className[1]).error === undefined; } /** Query a "file property" from this iModel, as a string. @@ -870,8 +860,8 @@ export class IModelDb extends IModel { */ public queryNextAvailableFileProperty(prop: FilePropertyProps) { return this.nativeDb.queryNextAvailableFileProperty(JSON.stringify(prop)); } - public requestSnap(actx: ActivityLoggingContext, connectionId: string, props: SnapRequestProps): Promise { - actx.enter(); + public async requestSnap(activity: ActivityLoggingContext, connectionId: string, props: SnapRequestProps): Promise { + activity.enter(); let request = this._snaps.get(connectionId); if (undefined === request) { request = (new (NativePlatformRegistry.getNativePlatform()).SnapRequest()) as SnapRequest; @@ -926,12 +916,17 @@ export namespace IModelDb { * @param modelId The Model identifier. * @throws [[IModelError]] */ - public getModel(modelId: Id64String): Model { + public getModelProps(modelId: Id64String): T { const json = this.getModelJson(JSON.stringify({ id: modelId.toString() })); - const props = JSON.parse(json!) as ModelProps; - return this._iModel.constructEntity(props) as Model; + return JSON.parse(json) as T; } + /** Get the Model with the specified identifier. + * @param modelId The Model identifier. + * @throws [[IModelError]] + */ + public getModel(modelId: Id64String): T { return this._iModel.constructEntity(this.getModelProps(modelId)); } + /** * Read the properties for a Model as a json string. * @param modelIdArg a json string with the identity of the model to load. Must have either "id" or "code". @@ -939,9 +934,10 @@ export namespace IModelDb { */ public getModelJson(modelIdArg: string): string { if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); - const { error, result } = this._iModel.nativeDb.getModel(modelIdArg); - if (error) throw new IModelError(error.status, "Model=" + modelIdArg); - return result!; + const val = this._iModel.nativeDb.getModel(modelIdArg); + if (val.error) + throw new IModelError(val.error.status, "Model=" + modelIdArg); + return val.result!; } /** Get the sub-model of the specified Element. @@ -949,12 +945,12 @@ export namespace IModelDb { * @param modeledElementId Identifies the modeled element. * @throws [[IModelError]] */ - public getSubModel(modeledElementId: Id64String | GuidString | Code): Model { + public getSubModel(modeledElementId: Id64String | GuidString | Code): T { const modeledElement = this._iModel.elements.getElement(modeledElementId); if (modeledElement.id === IModel.rootSubjectId) throw new IModelError(IModelStatus.NotFound, "Root subject does not have a sub-model", Logger.logWarning, loggingCategory); - return this.getModel(modeledElement.id); + return this.getModel(modeledElement.id); } /** Create a new model in memory. @@ -962,89 +958,91 @@ export namespace IModelDb { * @param modelProps The properties to use when creating the model. * @throws [[IModelError]] if there is a problem creating the model. */ - public createModel(modelProps: ModelProps): Model { return this._iModel.constructEntity(modelProps) as Model; } + public createModel(modelProps: ModelProps): T { return this._iModel.constructEntity(modelProps); } /** Insert a new model. - * @param model The data for the new model. + * @param props The data for the new model. * @returns The newly inserted model's Id. * @throws [[IModelError]] if unable to insert the model. */ - public insertModel(model: Model): Id64String { - if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); - const { error, result } = this._iModel.nativeDb.insertModel(JSON.stringify(model)); - if (error) throw new IModelError(error.status, "inserting model", Logger.logWarning, loggingCategory); - return model.id = Id64.fromJSON(JSON.parse(result!).id); + public insertModel(props: ModelProps): Id64String { + const jsClass = this._iModel.getJsClass(props.classFullName); + if (IModelStatus.Success !== jsClass.onInsert(props)) + return Id64.invalid; + + const val = this._iModel.nativeDb.insertModel(JSON.stringify(props)); + if (val.error) + throw new IModelError(val.error.status, "inserting model", Logger.logWarning, loggingCategory); + + props.id = Id64.fromJSON(JSON.parse(val.result!).id); + jsClass.onInserted(props.id); + return props.id; } /** Update an existing model. - * @param model An editable copy of the model, containing the new/proposed data. + * @param props the properties of the model to change * @throws [[IModelError]] if unable to update the model. */ - public updateModel(model: ModelProps): void { - if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); - const error: IModelStatus = this._iModel.nativeDb.updateModel(JSON.stringify(model)); + public updateModel(props: ModelProps): void { + const jsClass = this._iModel.getJsClass(props.classFullName); + if (IModelStatus.Success !== jsClass.onUpdate(props)) + return; + + const error = this._iModel.nativeDb.updateModel(JSON.stringify(props)); if (error !== IModelStatus.Success) - throw new IModelError(error, "updating model id=" + model.id, Logger.logWarning, loggingCategory); + throw new IModelError(error, "updating model id=" + props.id, Logger.logWarning, loggingCategory); + + jsClass.onUpdated(props); } - /** Delete an existing model. - * @param model The model to be deleted + /** Delete one or more existing models. + * @param ids The Ids of the models to be deleted * @throws [[IModelError]] */ - public deleteModel(model: Model): void { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); + public deleteModel(ids: Id64Arg): void { + Id64.toIdSet(ids).forEach((id) => { + const props = this.getModelProps(id); + const jsClass = this._iModel.getJsClass(props.classFullName); + if (IModelStatus.Success !== jsClass.onDelete(props)) + return; - const error: IModelStatus = this._iModel.nativeDb.deleteModel(model.id); - if (error !== IModelStatus.Success) - throw new IModelError(error, "deleting model id=" + model.id, Logger.logWarning, loggingCategory); + const error = this._iModel.nativeDb.deleteModel(id); + if (error !== IModelStatus.Success) + throw new IModelError(error, "", Logger.logWarning, loggingCategory); + + jsClass.onDeleted(props); + }); } } /** The collection of elements in an [[IModelDb]]. */ export class Elements { /** @hidden */ - public constructor(private _iModel: IModelDb) { - } - - /** Private implementation details of getElementProps */ - private _getElementProps(opts: ElementLoadProps): ElementProps { - const json = this.getElementJson(JSON.stringify(opts)); - const props = json as ElementProps; - return props; - } + public constructor(private _iModel: IModelDb) { } /** * Read element data from iModel as a json string * @param elementIdArg a json string with the identity of the element to load. Must have one of "id", "federationGuid", or "code". * @return a json string with the properties of the element. */ - public getElementJson(elementIdArg: string): any { - const { error, result } = this._iModel.nativeDb.getElement(elementIdArg); - if (error) throw new IModelError(error.status, "reading element=" + elementIdArg, Logger.logWarning, loggingCategory); - return result!; - } - - /** Private implementation details of getElement */ - private _doGetElement(opts: ElementLoadProps): Element { - const props = this._getElementProps(opts); - return this._iModel.constructEntity(props) as Element; + public getElementJson(elementIdArg: string): T { + const val = this._iModel.nativeDb.getElement(elementIdArg); + if (val.error) + throw new IModelError(val.error.status, "reading element=" + elementIdArg, Logger.logWarning, loggingCategory); + return val.result! as T; } /** * Get properties of an Element by Id, FederationGuid, or Code * @throws [[IModelError]] if the element is not found. */ - public getElementProps(elementId: Id64String | GuidString | Code | ElementLoadProps): ElementProps { - if (typeof elementId === "string") { - if (Guid.isGuid(elementId)) - elementId = { federationGuid: elementId }; - else - elementId = { id: elementId }; - } else if (elementId instanceof Code) + public getElementProps(elementId: Id64String | GuidString | Code | ElementLoadProps): T { + if (typeof elementId === "string") + elementId = Id64.isId64(elementId) ? { id: elementId } : { federationGuid: elementId }; + else if (elementId instanceof Code) elementId = { code: elementId }; - return this._getElementProps(elementId); + return this.getElementJson(JSON.stringify(elementId)); } /** @@ -1052,20 +1050,17 @@ export namespace IModelDb { * @param elementId either the element's Id, Code, or FederationGuid, or an ElementLoadProps * @throws [[IModelError]] if the element is not found. */ - public getElement(elementId: Id64String | GuidString | Code | ElementLoadProps): Element { - if (typeof elementId === "string") { - if (Guid.isGuid(elementId)) - elementId = { federationGuid: elementId }; - else - elementId = { id: elementId }; - } else if (elementId instanceof Code) + public getElement(elementId: Id64String | GuidString | Code | ElementLoadProps): T { + if (typeof elementId === "string") + elementId = Id64.isId64(elementId) ? { id: elementId } : { federationGuid: elementId }; + else if (elementId instanceof Code) elementId = { code: elementId }; - return this._doGetElement(elementId); + return this._iModel.constructEntity(this.getElementJson(JSON.stringify(elementId))); } /** - * Query for the DgnElementId of the element that has the specified code. + * Query for the Id of the element that has a specified code. * This method is for the case where you know the element's Code. * If you only know the code *value*, then in the simplest case, you can query on that * and filter the results. @@ -1099,7 +1094,7 @@ export namespace IModelDb { * @param elProps The properties of the new element. * @throws [[IModelError]] if there is a problem creating the element. */ - public createElement(elProps: ElementProps): Element { return this._iModel.constructEntity(elProps) as Element; } + public createElement(elProps: ElementProps): T { return this._iModel.constructEntity(elProps); } /** * Insert a new element into the iModel. @@ -1108,27 +1103,35 @@ export namespace IModelDb { * @throws [[IModelError]] if unable to insert the element. */ public insertElement(elProps: ElementProps): Id64String { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); - - const { error, result: json } = this._iModel.nativeDb.insertElement(JSON.stringify(elProps)); - if (error) - throw new IModelError(error.status, "Problem inserting element", Logger.logWarning, loggingCategory); - - return Id64.fromJSON(JSON.parse(json!).id); + const iModel = this._iModel; + const jsClass = iModel.getJsClass(elProps.classFullName) as unknown as typeof Element; + if (IModelStatus.Success !== jsClass.onInsert(elProps, iModel)) + return Id64.invalid; + + const val = iModel.nativeDb.insertElement(JSON.stringify(elProps)); + if (val.error) + throw new IModelError(val.error.status, "Problem inserting element", Logger.logWarning, loggingCategory); + + elProps.id = Id64.fromJSON(JSON.parse(val.result!).id); + jsClass.onInserted(elProps, iModel); + return elProps.id; } /** Update some properties of an existing element. * @param el the properties of the element to update. * @throws [[IModelError]] if unable to update the element. */ - public updateElement(props: ElementProps): void { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); + public updateElement(elProps: ElementProps): void { + const iModel = this._iModel; + const jsClass = iModel.getJsClass(elProps.classFullName); + if (IModelStatus.Success !== jsClass.onUpdate(elProps, iModel)) + return; - const error: IModelStatus = this._iModel.nativeDb.updateElement(JSON.stringify(props)); + const error = iModel.nativeDb.updateElement(JSON.stringify(elProps)); if (error !== IModelStatus.Success) throw new IModelError(error, "", Logger.logWarning, loggingCategory); + + jsClass.onUpdated(elProps, iModel); } /** @@ -1137,10 +1140,18 @@ export namespace IModelDb { * @throws [[IModelError]] */ public deleteElement(ids: Id64Arg): void { + const iModel = this._iModel; Id64.toIdSet(ids).forEach((id) => { - const error: IModelStatus = this._iModel.nativeDb.deleteElement(id); + const props = this.getElementProps(id); + const jsClass = iModel.getJsClass(props.classFullName); + if (IModelStatus.Success !== jsClass.onDelete(props, iModel)) + return; + + const error = iModel.nativeDb.deleteElement(id); if (error !== IModelStatus.Success) throw new IModelError(error, "", Logger.logWarning, loggingCategory); + + jsClass.onDeleted(props, iModel); }); } @@ -1164,7 +1175,7 @@ export namespace IModelDb { * @throws [[IModelError]] */ private _queryAspects(elementId: Id64String, aspectClassName: string): ElementAspect[] { - const rows: any[] = this._iModel.executeQuery(`SELECT * FROM ${aspectClassName} WHERE Element.Id=?`, [elementId]); + const rows = this._iModel.executeQuery(`SELECT * FROM ${aspectClassName} WHERE Element.Id=?`, [elementId]); if (rows.length === 0) throw new IModelError(IModelStatus.NotFound, "ElementAspect class not found", Logger.logWarning, loggingCategory, () => ({ aspectClassName })); @@ -1174,8 +1185,7 @@ export namespace IModelDb { aspectProps.classFullName = aspectClassName; // add in property required by EntityProps aspectProps.className = undefined; // clear property from SELECT * that we don't want in the final instance - const entity = this._iModel.constructEntity(aspectProps); - const aspect = entity as ElementAspect; + const aspect = this._iModel.constructEntity(aspectProps); aspects.push(aspect); } return aspects; @@ -1199,7 +1209,7 @@ export namespace IModelDb { if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); - const status: IModelStatus = this._iModel.nativeDb.insertElementAspect(JSON.stringify(aspectProps)); + const status = this._iModel.nativeDb.insertElementAspect(JSON.stringify(aspectProps)); if (status !== IModelStatus.Success) throw new IModelError(status, "Error inserting ElementAspect", Logger.logWarning, loggingCategory); } @@ -1211,7 +1221,7 @@ export namespace IModelDb { */ public deleteAspect(ids: Id64Arg): void { Id64.toIdSet(ids).forEach((id) => { - const status: IModelStatus = this._iModel.nativeDb.deleteElementAspect(id); + const status = this._iModel.nativeDb.deleteElementAspect(id); if (status !== IModelStatus.Success) throw new IModelError(status, "Error deleting ElementAspect", Logger.logWarning, loggingCategory); }); @@ -1235,7 +1245,7 @@ export namespace IModelDb { const imodel = this._iModel; ids.forEach((id) => { try { - props.push(imodel.elements.getElementProps(id) as ViewDefinitionProps); + props.push(imodel.elements.getElementProps(id)); } catch (err) { } }); @@ -1275,14 +1285,14 @@ export namespace IModelDb { public getViewStateData(viewDefinitionId: string): ViewStateData { const viewStateData: ViewStateData = {} as any; const elements = this._iModel.elements; - const viewDefinitionElement = elements.getElement(viewDefinitionId) as ViewDefinition; + const viewDefinitionElement = elements.getElement(viewDefinitionId); viewStateData.viewDefinitionProps = viewDefinitionElement.toJSON(); - viewStateData.categorySelectorProps = elements.getElementProps(viewStateData.viewDefinitionProps.categorySelectorId) as CategorySelectorProps; - viewStateData.displayStyleProps = elements.getElementProps(viewStateData.viewDefinitionProps.displayStyleId) as DisplayStyleProps; + viewStateData.categorySelectorProps = elements.getElementProps(viewStateData.viewDefinitionProps.categorySelectorId); + viewStateData.displayStyleProps = elements.getElementProps(viewStateData.viewDefinitionProps.displayStyleId); if (viewStateData.viewDefinitionProps.modelSelectorId !== undefined) - viewStateData.modelSelectorProps = elements.getElementProps(viewStateData.viewDefinitionProps.modelSelectorId) as ModelSelectorProps; + viewStateData.modelSelectorProps = elements.getElementProps(viewStateData.viewDefinitionProps.modelSelectorId); else if (viewDefinitionElement instanceof SheetViewDefinition) { - viewStateData.sheetProps = elements.getElementProps(viewDefinitionElement.baseModelId) as SheetProps; + viewStateData.sheetProps = elements.getElementProps(viewDefinitionElement.baseModelId); viewStateData.sheetAttachments = Array.from(this._iModel.queryEntityIds({ from: "BisCore.ViewAttachment", where: "Model.Id=" + viewDefinitionElement.baseModelId, @@ -1291,8 +1301,8 @@ export namespace IModelDb { return viewStateData; } - private getViewThumbnailArg(viewDefinitionId: Id64Arg): string { - const viewProps: FilePropertyProps = { namespace: "dgn_View", name: "Thumbnail", id: viewDefinitionId.toString() }; + private getViewThumbnailArg(viewDefinitionId: Id64String): string { + const viewProps: FilePropertyProps = { namespace: "dgn_View", name: "Thumbnail", id: viewDefinitionId }; return JSON.stringify(viewProps); } @@ -1321,6 +1331,18 @@ export namespace IModelDb { const props = { format: thumbnail.format, height: thumbnail.height, width: thumbnail.width }; return this._iModel.nativeDb.saveFileProperty(viewArg, JSON.stringify(props), thumbnail.image); } + + /** Set the default view property the iModel + * @param viewId The Id of the ViewDefinition to use as the default + */ + public setDefaultViewId(viewId: Id64String): void { + const spec = { namespace: "dgn_View", name: "DefaultView" }; + const blob32 = new Uint32Array(2); + blob32[0] = Id64.getLowerUint32(viewId); + blob32[1] = Id64.getUpperUint32(viewId); + const blob8 = new Uint8Array(blob32.buffer); + this._iModel.saveFileProperty(spec, undefined, blob8); + } } /** @hidden */ @@ -1329,13 +1351,13 @@ export namespace IModelDb { public constructor(private _iModel: IModelDb) { } /** @hidden */ - public requestTileTreeProps(actx: ActivityLoggingContext, id: string): Promise { - actx.enter(); + public async requestTileTreeProps(activity: ActivityLoggingContext, id: string): Promise { + activity.enter(); if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); return new Promise((resolve, reject) => { - actx.enter(); + activity.enter(); this._iModel.nativeDb.getTileTree(id, (ret: ErrorStatusOrResult) => { if (undefined !== ret.error) reject(new IModelError(ret.error.status, "TreeId=" + id)); @@ -1346,13 +1368,13 @@ export namespace IModelDb { } /** @hidden */ - public requestTileContent(actx: ActivityLoggingContext, treeId: string, tileId: string): Promise { - actx.enter(); + public async requestTileContent(activity: ActivityLoggingContext, treeId: string, tileId: string): Promise { + activity.enter(); if (!this._iModel.briefcase) throw this._iModel.newNotOpenError(); return new Promise((resolve, reject) => { - actx.enter(); + activity.enter(); this._iModel.nativeDb.getTileContent(treeId, tileId, (ret: ErrorStatusOrResult) => { if (undefined !== ret.error) reject(new IModelError(ret.error.status, "TreeId=" + treeId + " TileId=" + tileId)); @@ -1364,43 +1386,163 @@ export namespace IModelDb { } } +export const enum TxnAction { None = 0, Commit = 1, Abandon = 2, Reverse = 3, Reinstate = 4, Merge = 5 } + +/** An error generated during dependency validation. */ +export interface ValidationError { + /** If true, txn is aborted. */ + fatal: boolean; + /** The type of error. */ + errorType: string; + /** Optional description of what went wrong. */ + message?: string; +} + /** * Local Txns in an IModelDb. Local Txns persist only until [[IModelDb.pushChanges]] is called. */ export class TxnManager { constructor(private _iModel: IModelDb) { } + /** Array of errors from dependency propagation */ + public readonly validationErrors: ValidationError[] = []; + + private get _nativeDb() { return this._iModel.nativeDb!; } + private _getElementClass(elClassName: string): typeof Element { return this._iModel.getJsClass(elClassName) as unknown as typeof Element; } + private _getRelationshipClass(relClassName: string): typeof Relationship { return this._iModel.getJsClass(relClassName); } + + /** @hidden */ + protected _onBeforeOutputsHandled(elClassName: string, elId: Id64String): void { this._getElementClass(elClassName).onBeforeOutputsHandled(elId, this._iModel); } + /** @hidden */ + protected _onAllInputsHandled(elClassName: string, elId: Id64String): void { this._getElementClass(elClassName).onAllInputsHandled(elId, this._iModel); } + + /** @hidden */ + protected _onRootChanged(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onRootChanged(props, this._iModel); } + /** @hidden */ + protected _onValidateOutput(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onValidateOutput(props, this._iModel); } + /** @hidden */ + protected _onDeletedDependency(props: RelationshipProps): void { this._getRelationshipClass(props.classFullName).onDeletedDependency(props, this._iModel); } + + /** @hidden */ + protected _onBeginValidate() { this.validationErrors.length = 0; } + /** @hidden */ + protected _onEndValidate() { } + + /** Dependency handlers may call method this to report a validation error. + * @param error The error. If error.fatal === true, the transaction will cancel rather than commit. + */ + public reportError(error: ValidationError) { this.validationErrors.push(error); this._nativeDb.logTxnError(error.fatal); } + + /** Determine whether any fatal validation errors have occurred during dependency propagation. */ + public get hasFatalError(): boolean { return this._nativeDb.hasFatalTxnError(); } + + /** Event raised before a commit operation is performed. Initiated by a call to [[IModelDb.saveChanges]] */ + public readonly onCommit = new BeEvent<() => void>(); + /** Event raised after a commit operation has been performed. Initiated by a call to [[IModelDb.saveChanges]] */ + public readonly onCommitted = new BeEvent<() => void>(); + /** Event raised after a ChangeSet has been applied to this briefcase */ + public readonly onChangesApplied = new BeEvent<() => void>(); + /** Event raised before an undo/redo operation is performed. */ + public readonly onBeforeUndoRedo = new BeEvent<() => void>(); + /** Event raised after an undo/redo operation has been performed. + * @param _action The action that was performed. + */ + public readonly onAfterUndoRedo = new BeEvent<(_action: TxnAction) => void>(); + + /** Determine if there are currently any reversible (undoable) changes to this IModelDb. */ + public get isUndoPossible(): boolean { return this._nativeDb.isUndoPossible(); } + + /** Determine if there are currently any reinstatable (redoable) changes to this IModelDb */ + public get isRedoPossible(): boolean { return this._nativeDb.isRedoPossible(); } + + /** Get the description of the operation that would be reversed by calling reverseTxns(1). + * This is useful for showing the operation that would be undone, for example in a menu. + */ + public getUndoString(): string { return this._nativeDb.getUndoString(); } + + /** Get a description of the operation that would be reinstated by calling reinstateTxn. + * This is useful for showing the operation that would be redone, in a pull-down menu for example. + */ + public getRedoString(): string { return this._nativeDb.getRedoString(); } + + /** Begin a new multi-Txn operation. This can be used to cause a series of Txns, that would normally + * be considered separate actions for undo, to be grouped into a single undoable operation. This means that when reverseTxns(1) is called, + * the entire group of changes are undone together. Multi-Txn operations can be nested, and until the outermost operation is closed, + * all changes constitute a single operation. + * @note This method must always be paired with a call to endMultiTxnAction. + */ + public beginMultiTxnOperation(): DbResult { return this._nativeDb.beginMultiTxnOperation(); } + + /** End a multi-Txn operation */ + public endMultiTxnOperation(): DbResult { return this._nativeDb.endMultiTxnOperation(); } + + /** Return the depth of the multi-Txn stack. Generally for diagnostic use only. */ + public getMultiTxnOperationDepth(): number { return this._nativeDb.getMultiTxnOperationDepth(); } + + /** Reverse (undo) the most recent operation(s) to this IModelDb. + * @param numOperations the number of operations to reverse. If this is greater than 1, the entire set of operations will + * be reinstated together when/if ReinstateTxn is called. + * @note If there are any outstanding uncommitted changes, they are reversed. + * @note The term "operation" is used rather than Txn, since multiple Txns can be grouped together via [[beginMultiTxnOperation]]. So, + * even if numOperations is 1, multiple Txns may be reversed if they were grouped together when they were made. + * @note If numOperations is too large only the operations are reversible are reversed. + */ + public reverseTxns(numOperations: number): IModelStatus { return this._nativeDb.reverseTxns(numOperations); } + + /** Reverse the most recent operation. */ + public reverseSingleTxn(): IModelStatus { return this.reverseTxns(1); } + + /** Reverse all changes back to the beginning of the session. */ + public reverseAll(): IModelStatus { return this._nativeDb.reverseAll(); } + + /** Reverse all changes back to a previously saved TxnId. + * @param txnId a TxnId obtained from a previous call to GetCurrentTxnId. + * @returns Success if the transactions were reversed, error status otherwise. + * @see [[getCurrentTxnId]] [[cancelTo]] + */ + public reverseTo(txnId: TxnIdString) { return this._nativeDb.reverseTo(txnId); } + + /** Reverse and then cancel (make non-reinstatable) all changes back to a previous TxnId. + * @param txnId a TxnId obtained from a previous call to [[getCurrentTxnId]] + * @returns Success if the transactions were reversed and cleared, error status otherwise. + */ + public cancelTo(txnId: TxnIdString) { return this._nativeDb.cancelTo(txnId); } + + /** Reinstate the most recently reversed transaction. Since at any time multiple transactions can be reversed, it + * may take multiple calls to this method to reinstate all reversed operations. + * @returns Success if a reversed transaction was reinstated, error status otherwise. + * @note If there are any outstanding uncommitted changes, they are reversed before the Txn is reinstated. + */ + public reinstateTxn(): IModelStatus { return this._nativeDb.reinstateTxn(); } /** Get the Id of the first transaction, if any. */ - public queryFirstTxnId(): TxnManager.TxnId { return this._iModel.nativeDb!.txnManagerQueryFirstTxnId(); } + public queryFirstTxnId(): TxnIdString { return this._nativeDb.queryFirstTxnId(); } /** Get the successor of the specified TxnId */ - public queryNextTxnId(txnId: TxnManager.TxnId): TxnManager.TxnId { return this._iModel.nativeDb!.txnManagerQueryNextTxnId(txnId); } + public queryNextTxnId(txnId: TxnIdString): TxnIdString { return this._nativeDb.queryNextTxnId(txnId); } /** Get the predecessor of the specified TxnId */ - public queryPreviousTxnId(txnId: TxnManager.TxnId): TxnManager.TxnId { return this._iModel.nativeDb!.txnManagerQueryPreviousTxnId(txnId); } + public queryPreviousTxnId(txnId: TxnIdString): TxnIdString { return this._nativeDb.queryPreviousTxnId(txnId); } /** Get the Id of the current (tip) transaction. */ - public getCurrentTxnId(): TxnManager.TxnId { return this._iModel.nativeDb!.txnManagerGetCurrentTxnId(); } + public getCurrentTxnId(): TxnIdString { return this._nativeDb.getCurrentTxnId(); } /** Get the description that was supplied when the specified transaction was saved. */ - public getTxnDescription(txnId: TxnManager.TxnId): string { return this._iModel.nativeDb!.txnManagerGetTxnDescription(txnId); } + public getTxnDescription(txnId: TxnIdString): string { return this._nativeDb.getTxnDescription(txnId); } /** Test if a TxnId is valid */ - public isTxnIdValid(txnId: TxnManager.TxnId): boolean { return this._iModel.nativeDb!.txnManagerIsTxnIdValid(txnId); } + public isTxnIdValid(txnId: TxnIdString): boolean { return this._nativeDb.isTxnIdValid(txnId); } /** Query if there are any pending Txns in this IModelDb that are waiting to be pushed. */ - public findPendingTxns(): boolean { return this.isTxnIdValid(this.queryFirstTxnId()); } + public get hasPendingTxns(): boolean { return this.isTxnIdValid(this.queryFirstTxnId()); } /** Query if there are any changes in memory that have yet to be saved to the IModelDb. */ - public findUnsavedChanges(): boolean { - return this._iModel.nativeDb!.txnManagerHasUnsavedChanges(); - } + public get hasUnsavedChanges(): boolean { return this._nativeDb.hasUnsavedChanges(); } /** Query if there are un-saved or un-pushed local changes. */ - public findLocalChanges(): boolean { return this.findUnsavedChanges() || this.findPendingTxns(); } + public get hasLocalChanges(): boolean { return this.hasUnsavedChanges || this.hasPendingTxns; } /** Make a description of the changeset by combining all local txn comments. */ - public describeChangeSet(endTxnId?: TxnManager.TxnId): string { + public describeChangeSet(endTxnId?: TxnIdString): string { if (endTxnId === undefined) endTxnId = this.getCurrentTxnId(); @@ -1422,10 +1564,3 @@ export class TxnManager { return JSON.stringify(changes); } } - -export namespace TxnManager { - /** Identifies a transaction that is local to a specific IModelDb. */ - export interface TxnId { - readonly _id: string; - } -} diff --git a/core/backend/src/IModelHost.ts b/core/backend/src/IModelHost.ts index a936e69..93d0a67 100644 --- a/core/backend/src/IModelHost.ts +++ b/core/backend/src/IModelHost.ts @@ -19,6 +19,8 @@ import { BisCore } from "./BisCore"; import { NativePlatformRegistry } from "./NativePlatformRegistry"; import { BriefcaseManager } from "./BriefcaseManager"; import { initializeRpcBackend } from "./RpcBackend"; +import { Generic } from "./domains/Generic"; +import { Functional } from "./domains/Functional"; /** * Configuration of imodeljs-backend. @@ -88,6 +90,8 @@ export class IModelHost { WipRpcImpl.register(); BisCore.registerSchema(); + Generic.registerSchema(); + Functional.registerSchema(); IModelHost.configuration = configuration; IModelHost.onAfterStartup.raiseEvent(); diff --git a/core/backend/src/LinkTableRelationship.ts b/core/backend/src/LinkTableRelationship.ts deleted file mode 100644 index 0dbbacf..0000000 --- a/core/backend/src/LinkTableRelationship.ts +++ /dev/null @@ -1,228 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -/** @module Relationships */ - -import { Entity } from "./Entity"; -import { IModelDb } from "./IModelDb"; -import { EntityProps, IModelError, IModelStatus } from "@bentley/imodeljs-common"; -import { Id64, Id64String, Logger, DbOpcode, DbResult } from "@bentley/bentleyjs-core"; -import { ECSqlStatement } from "./ECSqlStatement"; - -/** @hidden */ -const loggingCategory = "imodeljs-backend.LinkTableRelationship"; - -/** Properties that are common to all types of link table ECRelationships */ -export interface LinkTableRelationshipProps extends EntityProps { - sourceId: Id64String; - targetId: Id64String; - sourceClassName?: string; - targetClassName?: string; -} - -/** Base class for all link table ECRelationships */ -export class LinkTableRelationship extends Entity implements LinkTableRelationshipProps { - public readonly sourceId: Id64String; // Warning: Do not change these property names. They must match the internal names that EC/ECSQL assigns to source and target. - public readonly targetId: Id64String; - public readonly sourceClassName?: string; - public readonly targetClassName?: string; - - /** @hidden */ - protected constructor(props: LinkTableRelationshipProps, iModel: IModelDb) { - super(props, iModel); - this.id = Id64.fromJSON(props.id); - this.sourceId = Id64.fromJSON(props.sourceId); - this.targetId = Id64.fromJSON(props.targetId); - this.sourceClassName = props.sourceClassName; - this.targetClassName = props.targetClassName; - } - - /** @hidden */ - public toJSON(): LinkTableRelationshipProps { - const val = super.toJSON() as LinkTableRelationshipProps; - val.id = this.id; - val.sourceId = this.sourceId; - val.targetId = this.targetId; - val.sourceClassName = this.sourceClassName; - val.targetClassName = this.targetClassName; - return val; - } - - // TODO: Expose properties for 'strength' and 'direction' - - /** - * Add a request for the locks that would be needed to carry out the specified operation. - * @param opcode The operation that will be performed on the LinkTableRelationship instance. - */ - public buildConcurrencyControlRequest(opcode: DbOpcode): void { this.iModel.concurrencyControl.buildRequestForLinkTableRelationship(this, opcode); } -} - -/** - * A LinkTableRelationship where one Element refers to another Element - */ -export class ElementRefersToElements extends LinkTableRelationship { - /** Create an instance of the ElementRefersToElements relationship. - * @param iModel The iModel that will contain the relationship - * @param sourceId The sourceId of the relationship, that is, the driver element - * @param targetId The targetId of the relationship, that is, the driven element - * @param classFullName The full name of the ElementRefersToElements class. Must be specified to create an instance of a derived class. May be omitted to create an instance of the ElementRefersToElements base class. - * @return an instance of the specified class. - */ - public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String, classFullName: string = ElementRefersToElements.classFullName): ElementRefersToElements { - return iModel.linkTableRelationships.createInstance({ sourceId, targetId, classFullName }) as ElementRefersToElements; - } -} - -/** Properties that are common to all types of link table ECRelationships */ -export interface ElementGroupsMembersProps extends LinkTableRelationshipProps { - memberPriority: number; -} - -/** - * An ElementRefersToElements relationship where one Element *groups* a set of other Elements. - */ -export class ElementGroupsMembers extends ElementRefersToElements { - public memberPriority!: number; - - /** @hidden */ - constructor(props: ElementGroupsMembersProps, iModel: IModelDb) { super(props, iModel); } - - /** Create an instance of the ElementGroupsMembers relationship. - * @param iModel The iModel that will contain the relationship - * @param sourceId The sourceId of the relationship, that is, the driver element - * @param targetId The targetId of the relationship, that is, the driven element - * @param classFullName The full name of the ElementGroupsMembers class. Must be specified to create an instance of a derived class. May be omitted to create an instance of the ElementGroupsMembers base class. - * @return an instance of the specified class. - */ - public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String, classFullName: string = ElementGroupsMembers.classFullName, memberPriority: number = 0): ElementGroupsMembers { - return iModel.linkTableRelationships.createInstance({ sourceId, targetId, memberPriority, classFullName }) as ElementGroupsMembers; - } -} - -/** Properties that are common to all types of ElementDrivesElements */ -export interface ElementDrivesElementProps extends LinkTableRelationshipProps { - status: number; - priority: number; -} - -/** - * A LinkTableRelationship where one Element *drives* another Element - */ -export class ElementDrivesElement extends LinkTableRelationship implements ElementDrivesElementProps { - public status!: number; - public priority!: number; - - /** @hidden */ - constructor(props: ElementDrivesElementProps, iModel: IModelDb) { super(props, iModel); } - - /** Create an instance of the ElementDrivesElement relationship. - * @param iModel The iModel that will contain the relationship - * @param sourceId The sourceId of the relationship, that is, the driver element - * @param targetId The targetId of the relationship, that is, the driven element - * @param classFullName The full name of the ElementDrivesElement class. Must be specified to create an instance of a derived class. May be omitted to create an instance of the ElementDrivesElement base class. - * @return an instance of the specified class. - */ - public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String, priority: number = 0, classFullName: string = ElementDrivesElement.classFullName): ElementDrivesElement { - return iModel.linkTableRelationships.createInstance({ sourceId, targetId, priority, classFullName }) as ElementDrivesElement; - } -} - -/** Specifies the source and target elements of a [[IModelDbLinkTableRelationships]] instance. */ -export interface SourceAndTarget { - sourceId: Id64String; - targetId: Id64String; -} - -/** Manages [[LinkTableRelationship]]s. */ -export class IModelDbLinkTableRelationships { - private _iModel: IModelDb; - - /** @hidden */ - public constructor(iModel: IModelDb) { this._iModel = iModel; } - - /** - * Create a new instance of a LinkTableRelationship. - * @param props The properties of the new LinkTableRelationship. - * @throws [[IModelError]] if there is a problem creating the LinkTableRelationship. - */ - public createInstance(elProps: LinkTableRelationshipProps): LinkTableRelationship { return this._iModel.constructEntity(elProps) as LinkTableRelationship; } - - /** - * Insert a new relationship instance into the iModel. - * @param props The properties of the new relationship instance. - * @returns The Id of the newly inserted relationship instance. - * @note The id property of the props object is set as a side effect of this function. - * @throws [[IModelError]] if unable to insert the relationship instance. - */ - public insertInstance(props: LinkTableRelationshipProps): Id64String { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); - - const { error, result } = this._iModel.briefcase.nativeDb.insertLinkTableRelationship(JSON.stringify(props)); - if (error) - throw new IModelError(error.status, "Problem inserting relationship instance", Logger.logWarning, loggingCategory); - - props.id = Id64.fromJSON(result); - return props.id; - } - - /** - * Update the properties of an existing relationship instance in the iModel. - * @param props the properties of the relationship instance to update. Any properties that are not present will be left unchanged. - * @throws [[IModelError]] if unable to update the relationship instance. - */ - public updateInstance(props: LinkTableRelationshipProps): void { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); - - const error: DbResult = this._iModel.briefcase.nativeDb.updateLinkTableRelationship(JSON.stringify(props)); - if (error !== DbResult.BE_SQLITE_OK) - throw new IModelError(error, "", Logger.logWarning, loggingCategory); - } - - /** - * Delete an relationship instance from this iModel. - * @param id The Id of the relationship instance to be deleted - * @throws [[IModelError]] - */ - public deleteInstance(props: LinkTableRelationshipProps): void { - if (!this._iModel.briefcase) - throw this._iModel.newNotOpenError(); - - const error: DbResult = this._iModel.briefcase.nativeDb.deleteLinkTableRelationship(JSON.stringify(props)); - if (error !== DbResult.BE_SQLITE_DONE) - throw new IModelError(error, "", Logger.logWarning, loggingCategory); - } - - /** get the props of a relationship instance */ - private getInstanceProps(relClassSqlName: string, criteria: Id64String | SourceAndTarget): LinkTableRelationshipProps { - if (typeof criteria === "string") { - return this._iModel.withPreparedStatement(`SELECT * FROM ${relClassSqlName} WHERE ecinstanceid=?`, (stmt: ECSqlStatement) => { - stmt.bindId(1, criteria); - if (DbResult.BE_SQLITE_ROW !== stmt.step()) - throw new IModelError(IModelStatus.NotFound, "LinkTableRelationship not found", Logger.logWarning, loggingCategory); - return stmt.getRow() as LinkTableRelationshipProps; - }); - } - - return this._iModel.withPreparedStatement("SELECT * FROM " + relClassSqlName + " WHERE SourceECInstanceId=? AND TargetECInstanceId=?", (stmt: ECSqlStatement) => { - stmt.bindId(1, criteria.sourceId); - stmt.bindId(2, criteria.targetId); - if (DbResult.BE_SQLITE_ROW !== stmt.step()) - throw new IModelError(IModelStatus.NotFound, "LinkTableRelationship not found", Logger.logWarning, loggingCategory); - return stmt.getRow() as LinkTableRelationshipProps; - }); - } - - /** get a relationship instance */ - public getInstance(relClassSqlName: string, criteria: Id64String | SourceAndTarget): LinkTableRelationship { - const props = this.getInstanceProps(relClassSqlName, criteria); - props.classFullName = props.className.replace(".", ":"); - if (props.sourceClassName !== undefined) - props.sourceClassName = props.sourceClassName.replace(".", ":"); - if (props.targetClassName !== undefined) - props.targetClassName = props.targetClassName.replace(".", ":"); - return this._iModel.constructEntity(props) as LinkTableRelationship; - } -} diff --git a/core/backend/src/Model.ts b/core/backend/src/Model.ts index 65cb71e..e218519 100644 --- a/core/backend/src/Model.ts +++ b/core/backend/src/Model.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ /** @module Models */ -import { Id64String, Id64, DbOpcode, JsonUtils } from "@bentley/bentleyjs-core"; -import { AxisAlignedBox3d, GeometricModel2dProps, IModelError, ModelProps, RelatedElement } from "@bentley/imodeljs-common"; +import { Id64String, Id64, DbOpcode, JsonUtils, IModelStatus } from "@bentley/bentleyjs-core"; +import { AxisAlignedBox3d, GeometricModel2dProps, IModel, IModelError, InformationPartitionElementProps, ModelProps, RelatedElement } from "@bentley/imodeljs-common"; import { Point2d } from "@bentley/geometry-core"; +import { DefinitionPartition, DocumentPartition, PhysicalPartition } from "./Element"; import { Entity } from "./Entity"; import { IModelDb } from "./IModelDb"; +import { SubjectOwnsPartitionElements } from "./NavigationRelationship"; /** * A Model is a container for persisting a collection of related elements within an iModel. @@ -53,6 +55,13 @@ export class Model extends Entity implements ModelProps { return val; } + public static onInsert(_props: ModelProps): IModelStatus { return IModelStatus.Success; } + public static onInserted(_id: string): void { } + public static onUpdate(_props: ModelProps): IModelStatus { return IModelStatus.Success; } + public static onUpdated(_props: ModelProps): void { } + public static onDelete(_props: ModelProps): IModelStatus { return IModelStatus.Success; } + public static onDeleted(_props: ModelProps): void { } + private getAllUserProperties(): any { if (!this.jsonProperties.UserProps) this.jsonProperties.UserProps = new Object(); return this.jsonProperties.UserProps; } /** Get a set of JSON user properties by namespace */ @@ -116,7 +125,29 @@ export abstract class SpatialModel extends GeometricModel3d { * A container for persisting physical elements that model physical space. */ export class PhysicalModel extends SpatialModel { + /** + * Insert a PhysicalPartition and a PhysicalModel that breaks it down. + * @param iModelDb Insert into this iModel + * @param parentSubjectId The PhysicalPartition will be inserted as a child of this Subject element. + * @param name The name of the PhysicalPartition that the new PhysicalModel will break down. + * @returns The Id of the newly inserted PhysicalPartition and PhysicalModel (same value). + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, parentSubjectId: Id64String, name: string): Id64String { + const partitionProps: InformationPartitionElementProps = { + classFullName: PhysicalPartition.classFullName, + model: IModel.repositoryModelId, + parent: new SubjectOwnsPartitionElements(parentSubjectId), + code: PhysicalPartition.createCode(iModelDb, parentSubjectId, name), + }; + const partitionId = iModelDb.elements.insertElement(partitionProps); + return iModelDb.models.insertModel({ + classFullName: this.classFullName, + modeledElement: { id: partitionId }, + }); + } } + /** * A container for persisting spatial location elements. */ @@ -171,6 +202,27 @@ export class InformationRecordModel extends InformationModel { * A container for persisting definition elements. */ export class DefinitionModel extends InformationModel { + /** + * Insert a DefinitionPartition and a DefinitionModel that breaks it down. + * @param iModelDb Insert into this iModel + * @param parentSubjectId The DefinitionPartition will be inserted as a child of this Subject element. + * @param name The name of the DefinitionPartition that the new DefinitionModel will break down. + * @returns The Id of the newly inserted DefinitionModel. + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, parentSubjectId: Id64String, name: string): Id64String { + const partitionProps: InformationPartitionElementProps = { + classFullName: DefinitionPartition.classFullName, + model: IModel.repositoryModelId, + parent: new SubjectOwnsPartitionElements(parentSubjectId), + code: DefinitionPartition.createCode(iModelDb, parentSubjectId, name), + }; + const partitionId = iModelDb.elements.insertElement(partitionProps); + return iModelDb.models.insertModel({ + classFullName: this.classFullName, + modeledElement: { id: partitionId }, + }); + } } /** @@ -183,6 +235,27 @@ export class RepositoryModel extends DefinitionModel { * Contains a list of document elements. */ export class DocumentListModel extends InformationModel { + /** + * Insert a DocumentPartition and a DocumentListModel that breaks it down. + * @param iModelDb Insert into this iModel + * @param parentSubjectId The DocumentPartition will be inserted as a child of this Subject element. + * @param name The name of the DocumentPartition that the new DocumentListModel will break down. + * @returns The Id of the newly inserted DocumentPartition and DocumentListModel (same value) + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, parentSubjectId: Id64String, name: string): Id64String { + const partitionProps: InformationPartitionElementProps = { + classFullName: DocumentPartition.classFullName, + model: IModel.repositoryModelId, + parent: new SubjectOwnsPartitionElements(parentSubjectId), + code: DocumentPartition.createCode(iModelDb, parentSubjectId, name), + }; + const partitionId: Id64String = iModelDb.elements.insertElement(partitionProps); + return iModelDb.models.insertModel({ + classFullName: this.classFullName, + modeledElement: { id: partitionId }, + }); + } } /** diff --git a/core/backend/src/NavigationRelationship.ts b/core/backend/src/NavigationRelationship.ts new file mode 100644 index 0000000..895b634 --- /dev/null +++ b/core/backend/src/NavigationRelationship.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Relationships */ + +// NOTE: A NavigationRelationship is not an Entity, so is not registered in the ClassRegistry. +// NOTE: It does, however, have a classFullName property for consistency with Entity subclasses. + +import { Id64String } from "@bentley/bentleyjs-core"; +import { RelatedElement } from "@bentley/imodeljs-common"; + +/** Relates a parent Element to child Elements which represent parts of the Entity modeled by the parent Element. */ +export class ElementOwnsChildElements extends RelatedElement { + public static classFullName = "BisCore:ElementOwnsChildElements"; + public constructor(parentId: Id64String, relClassName: string = ElementOwnsChildElements.classFullName) { + super({ id: parentId, relClassName }); + } +} + +/** Relates a parent [[Subject]] to [[Subject]] child elements. */ +export class SubjectOwnsSubjects extends ElementOwnsChildElements { + public static classFullName = "BisCore:SubjectOwnsSubjects"; + public constructor(parentId: Id64String, relClassName: string = SubjectOwnsSubjects.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a parent [[Subject]] to [[InformationPartitionElement]] child elements. */ +export class SubjectOwnsPartitionElements extends ElementOwnsChildElements { + public static classFullName = "BisCore:SubjectOwnsPartitionElements"; + public constructor(parentId: Id64String, relClassName: string = SubjectOwnsPartitionElements.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a parent [[Category]] to [[SubCategory]] child elements. */ +export class CategoryOwnsSubCategories extends ElementOwnsChildElements { + public static classFullName = "BisCore:CategoryOwnsSubCategories"; + public constructor(parentId: Id64String, relClassName: string = CategoryOwnsSubCategories.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a parent [[RenderMaterial]] to [[RenderMaterial]] child elements. */ +export class RenderMaterialOwnsRenderMaterials extends ElementOwnsChildElements { + public static classFullName = "BisCore:RenderMaterialOwnsRenderMaterials"; + public constructor(parentId: Id64String, relClassName: string = RenderMaterialOwnsRenderMaterials.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a parent Element to child Elements which represent **hidden** parts of the Entity. */ +export class ElementEncapsulatesElements extends ElementOwnsChildElements { + public static classFullName = "BisCore:ElementEncapsulatesElements"; + public constructor(parentId: Id64String, relClassName: string = ElementEncapsulatesElements.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a parent [[PhysicalElement]] to [[PhysicalElement]] children that it assembles. */ +export class PhysicalElementAssemblesElements extends ElementOwnsChildElements { + public static classFullName = "BisCore:PhysicalElementAssemblesElements"; + public constructor(parentId: Id64String, relClassName: string = PhysicalElementAssemblesElements.classFullName) { + super(parentId, relClassName); + } +} + +/** Relates a [[GraphicalElement2d]] to its [[GraphicalType2d]] */ +export class GraphicalElement2dIsOfType extends RelatedElement { + public static classFullName = "BisCore:GraphicalElement2dIsOfType"; + public constructor(id: Id64String, relClassName: string = GraphicalElement2dIsOfType.classFullName) { + super({ id, relClassName }); + } +} + +/** Relates a [[PhysicalElement]] to its [[PhysicalType]] */ +export class PhysicalElementIsOfType extends RelatedElement { + public static classFullName = "BisCore:PhysicalElementIsOfType"; + public constructor(id: Id64String, relClassName: string = PhysicalElementIsOfType.classFullName) { + super({ id, relClassName }); + } +} + +/** Relates a [[SpatialLocationElement]] to its [[SpatialLocationType]] */ +export class SpatialLocationIsOfType extends RelatedElement { + public static classFullName = "BisCore:SpatialLocationIsOfType"; + public constructor(id: Id64String, relClassName: string = SpatialLocationIsOfType.classFullName) { + super({ id, relClassName }); + } +} diff --git a/core/backend/src/PromiseMemoizer.ts b/core/backend/src/PromiseMemoizer.ts index c3d7b62..9fcd98d 100644 --- a/core/backend/src/PromiseMemoizer.ts +++ b/core/backend/src/PromiseMemoizer.ts @@ -2,9 +2,6 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ - -import { assert } from "@bentley/bentleyjs-core"; - /** Wrapper around a promise that allows synchronous queries of it's state * @hidden */ @@ -44,7 +41,6 @@ export class PromiseMemoizer { public deleteMemoized = (...args: any[]) => { const key: string = this._generateKeyFn(...args); - const ret = this._cachedPromises.delete(key); - assert(ret, "Memoized function not found in cache"); + this._cachedPromises.delete(key); } } diff --git a/core/backend/src/Relationship.ts b/core/backend/src/Relationship.ts new file mode 100644 index 0000000..6c671e3 --- /dev/null +++ b/core/backend/src/Relationship.ts @@ -0,0 +1,222 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Relationships */ + +import { Entity } from "./Entity"; +import { IModelDb } from "./IModelDb"; +import { EntityProps, IModelError, IModelStatus } from "@bentley/imodeljs-common"; +import { Id64, Id64String, Logger, DbOpcode, DbResult } from "@bentley/bentleyjs-core"; +import { ECSqlStatement } from "./ECSqlStatement"; + +/** @hidden */ +const loggingCategory = "imodeljs-backend.Relationship"; + +/** Specifies the source and target elements of a [[Relationship]] instance. */ +export interface SourceAndTarget { + sourceId: Id64String; + targetId: Id64String; +} + +/** Properties that are common to all types of link table ECRelationships */ +export interface RelationshipProps extends EntityProps, SourceAndTarget { +} + +/** Base class for all link table ECRelationships */ +export class Relationship extends Entity implements RelationshipProps { + public readonly sourceId: Id64String; + public readonly targetId: Id64String; + + /** @hidden */ + constructor(props: RelationshipProps, iModel: IModelDb) { + super(props, iModel); + this.sourceId = Id64.fromJSON(props.sourceId); + this.targetId = Id64.fromJSON(props.targetId); + } + + /** @hidden */ + public toJSON(): RelationshipProps { + const val = super.toJSON() as RelationshipProps; + val.sourceId = this.sourceId; + val.targetId = this.targetId; + return val; + } + + public static onRootChanged(_props: RelationshipProps, _iModel: IModelDb): void { } + public static onValidateOutput(_props: RelationshipProps, _iModel: IModelDb): void { } + public static onDeletedDependency(_props: RelationshipProps, _iModel: IModelDb): void { } + + /** Insert this Relationship into the iModel. */ + public insert(): Id64String { return this.iModel.relationships.insertInstance(this); } + /** Update this Relationship in the iModel. */ + public update() { this.iModel.relationships.updateInstance(this); } + /** Delete this Relationship from the iModel. */ + public delete() { this.iModel.relationships.deleteInstance(this); } + + public static getInstance(iModel: IModelDb, criteria: Id64String | SourceAndTarget): T { return iModel.relationships.getInstance(this.classFullName, criteria); } + + /** + * Add a request for the locks that would be needed to carry out the specified operation. + * @param opcode The operation that will be performed on the Relationship instance. + */ + public buildConcurrencyControlRequest(opcode: DbOpcode): void { this.iModel.concurrencyControl.buildRequestForRelationship(this, opcode); } +} + +/** + * A Relationship where one Element refers to another Element + */ +export class ElementRefersToElements extends Relationship { + /** Create an instance of the Relationship. + * @param iModel The iModel that will contain the relationship + * @param sourceId The sourceId of the relationship, that is, the driver element + * @param targetId The targetId of the relationship, that is, the driven element + * @return an instance of the specified class. + */ + public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String): T { + return iModel.relationships.createInstance({ sourceId, targetId, classFullName: this.classFullName }) as T; + } + /** Insert a new instance of the Relationship. + * @param iModel The iModel that will contain the relationship + * @param sourceId The sourceId of the relationship, that is, the driver element + * @param targetId The targetId of the relationship, that is, the driven element + * @return The Id of the inserted Relationship. + */ + public static insert(iModel: IModelDb, sourceId: Id64String, targetId: Id64String): Id64String { + const relationship: T = this.create(iModel, sourceId, targetId); + return iModel.relationships.insertInstance(relationship); + } +} + +/** Relates a [[DrawingGraphic]] to the [[Element]] that it represents */ +export class DrawingGraphicRepresentsElement extends ElementRefersToElements { +} + +/** Properties that are common to all types of link table ECRelationships */ +export interface ElementGroupsMembersProps extends RelationshipProps { + memberPriority: number; +} + +/** + * An ElementRefersToElements relationship where one Element *groups* a set of other Elements. + */ +export class ElementGroupsMembers extends ElementRefersToElements { + public memberPriority: number; + + constructor(props: ElementGroupsMembersProps, iModel: IModelDb) { + super(props, iModel); + this.memberPriority = props.memberPriority; + } + + public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String, memberPriority: number = 0): T { + const props: ElementGroupsMembersProps = { sourceId, targetId, memberPriority, classFullName: this.classFullName }; + return iModel.relationships.createInstance(props) as T; + } +} + +/** Properties that are common to all types of ElementDrivesElements */ +export interface ElementDrivesElementProps extends RelationshipProps { + status: number; + priority: number; +} + +/** + * A Relationship where one Element *drives* another Element + */ +export class ElementDrivesElement extends Relationship implements ElementDrivesElementProps { + public status: number; + public priority: number; + + /** @hidden */ + constructor(props: ElementDrivesElementProps, iModel: IModelDb) { + super(props, iModel); + this.status = props.status; + this.priority = props.priority; + } + + public static create(iModel: IModelDb, sourceId: Id64String, targetId: Id64String, priority: number = 0): T { + const props: ElementDrivesElementProps = { sourceId, targetId, priority, status: 0, classFullName: this.classFullName }; + return iModel.relationships.createInstance(props) as T; + } +} + +/** Manages [[Relationship]]s. */ +export class Relationships { + private _iModel: IModelDb; + + /** @hidden */ + constructor(iModel: IModelDb) { this._iModel = iModel; } + + /** + * Create a new instance of a Relationship. + * @param props The properties of the new Relationship. + * @throws [[IModelError]] if there is a problem creating the Relationship. + */ + public createInstance(props: RelationshipProps): Relationship { return this._iModel.constructEntity(props); } + + /** + * Insert a new relationship instance into the iModel. + * @param props The properties of the new relationship. + * @returns The Id of the newly inserted relationship. + * @note The id property of the props object is set as a side effect of this function. + * @throws [[IModelError]] if unable to insert the relationship instance. + */ + public insertInstance(props: RelationshipProps): Id64String { + const val = this._iModel.briefcase.nativeDb.insertLinkTableRelationship(JSON.stringify(props)); + if (val.error) + throw new IModelError(val.error.status, "Problem inserting relationship instance", Logger.logWarning, loggingCategory); + + props.id = Id64.fromJSON(val.result); + return props.id; + } + + /** + * Update the properties of an existing relationship instance in the iModel. + * @param props the properties of the relationship instance to update. Any properties that are not present will be left unchanged. + * @throws [[IModelError]] if unable to update the relationship instance. + */ + public updateInstance(props: RelationshipProps): void { + const error = this._iModel.briefcase.nativeDb.updateLinkTableRelationship(JSON.stringify(props)); + if (error !== DbResult.BE_SQLITE_OK) + throw new IModelError(error, "", Logger.logWarning, loggingCategory); + } + + /** + * Delete an Relationship instance from this iModel. + * @param id The Id of the Relationship to be deleted + * @throws [[IModelError]] + */ + public deleteInstance(props: RelationshipProps): void { + const error = this._iModel.briefcase.nativeDb.deleteLinkTableRelationship(JSON.stringify(props)); + if (error !== DbResult.BE_SQLITE_DONE) + throw new IModelError(error, "", Logger.logWarning, loggingCategory); + } + + /** Get the props of a Relationship instance */ + public getInstanceProps(relClassSqlName: string, criteria: Id64String | SourceAndTarget): T { + let props: T; + if (typeof criteria === "string") { + props = this._iModel.withPreparedStatement(`SELECT * FROM ${relClassSqlName} WHERE ecinstanceid=?`, (stmt: ECSqlStatement) => { + stmt.bindId(1, criteria); + if (DbResult.BE_SQLITE_ROW !== stmt.step()) + throw new IModelError(IModelStatus.NotFound, "Relationship not found", Logger.logWarning, loggingCategory); + return stmt.getRow() as T; + }); + } else { + props = this._iModel.withPreparedStatement("SELECT * FROM " + relClassSqlName + " WHERE SourceECInstanceId=? AND TargetECInstanceId=?", (stmt: ECSqlStatement) => { + stmt.bindId(1, criteria.sourceId); + stmt.bindId(2, criteria.targetId); + if (DbResult.BE_SQLITE_ROW !== stmt.step()) + throw new IModelError(IModelStatus.NotFound, "Relationship not found", Logger.logWarning, loggingCategory); + return stmt.getRow() as T; + }); + } + props.classFullName = props.className.replace(".", ":"); + return props; + } + + /** Get a Relationship instance */ + public getInstance(relClassSqlName: string, criteria: Id64String | SourceAndTarget): T { + return this._iModel.constructEntity(this.getInstanceProps(relClassSqlName, criteria)); + } +} diff --git a/core/backend/src/RpcBackend.ts b/core/backend/src/RpcBackend.ts index 74d4d29..b351707 100644 --- a/core/backend/src/RpcBackend.ts +++ b/core/backend/src/RpcBackend.ts @@ -23,7 +23,7 @@ export function initializeRpcBackend() { return form; }; - RpcMultipart.parseRequest = (req: HttpServerRequest) => { + RpcMultipart.parseRequest = async (req: HttpServerRequest) => { return new Promise((resolve, reject) => { const form = new multiparty.Form({ maxFieldsSize: Infinity }); form.on("error", (err) => { diff --git a/core/backend/src/SqliteStatement.ts b/core/backend/src/SqliteStatement.ts index 779dc02..2cf341e 100644 --- a/core/backend/src/SqliteStatement.ts +++ b/core/backend/src/SqliteStatement.ts @@ -10,8 +10,8 @@ import { NativePlatformRegistry } from "./NativePlatformRegistry"; import { NativeSqliteStatement, NativeECDb, NativeDgnDb } from "./imodeljs-native-platform-api"; /** Marks a string as either an [Id64String]($bentleyjs-core) or [GuidString]($bentleyjs-core), so - * that it can be passed to [SqliteStatement's]($backend) [bindValue]($backend.SqliteStatement) or [bindValues]($backend.SqliteStatement) - * methods. + * that it can be passed to the [bindValue]($backend.SqliteStatement) or [bindValues]($backend.SqliteStatement) + * methods of [SqliteStatement]($backend). */ export interface StringParam { id?: Id64String; diff --git a/core/backend/src/ViewDefinition.ts b/core/backend/src/ViewDefinition.ts index d09c727..5cb16e5 100644 --- a/core/backend/src/ViewDefinition.ts +++ b/core/backend/src/ViewDefinition.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ /** @module ViewDefinitions */ -import { Id64String, Id64, JsonUtils } from "@bentley/bentleyjs-core"; -import { Vector3d, Point3d, Point2d, YawPitchRollAngles, Angle } from "@bentley/geometry-core"; +import { Id64String, Id64, Id64Array, JsonUtils } from "@bentley/bentleyjs-core"; +import { Angle, Matrix3d, Point2d, Point3d, Range2d, Range3d, StandardViewIndex, Transform, Vector3d, YawPitchRollAngles } from "@bentley/geometry-core"; import { + AnalysisStyleProps, BisCodeSpec, Code, CodeScopeProps, @@ -23,10 +24,12 @@ import { AuxCoordSystem2dProps, AuxCoordSystem3dProps, ViewAttachmentProps, + ViewFlags, LightLocationProps, RelatedElement, DisplayStyleProps, - ViewFlags, + DisplayStyleSettings, + DisplayStyle3dSettings, } from "@bentley/imodeljs-common"; import { DefinitionElement, GraphicalElement2d, SpatialLocationElement } from "./Element"; import { IModelDb } from "./IModelDb"; @@ -35,60 +38,13 @@ import { IModelDb } from "./IModelDb"; * Internally a DisplayStyle consists of a dictionary of several named 'styles' describing specific aspects of the display style as a whole. * Many ViewDefinitions may share the same DisplayStyle. */ -export class DisplayStyle extends DefinitionElement implements DisplayStyleProps { - private readonly _viewFlags: ViewFlags; - private readonly _background: ColorDef; - private readonly _monochrome: ColorDef; +export abstract class DisplayStyle extends DefinitionElement implements DisplayStyleProps { + public abstract get settings(): DisplayStyleSettings; - public constructor(props: DisplayStyleProps, iModel: IModelDb) { + protected constructor(props: DisplayStyleProps, iModel: IModelDb) { super(props, iModel); - - this._viewFlags = ViewFlags.fromJSON(this.getStyle("viewflags")); - this._background = ColorDef.fromJSON(this.getStyle("backgroundColor")); - const monoName = "monochromeColor"; // because tslint: "object access via string literals is disallowed"... - const monoJson = this.styles[monoName]; - this._monochrome = undefined !== monoJson ? ColorDef.fromJSON(monoJson) : ColorDef.white.clone(); } - /** Get the flags controlling how aspects of graphics are rendered using this display style. */ - public get viewFlags(): ViewFlags { return this._viewFlags; } - /** Set the flags controlling how aspects of graphics are rendered using this display style. */ - public set viewFlags(flags: ViewFlags) { - flags.clone(this._viewFlags); - this.setStyle("viewflags", flags); - } - - /** Get the dictionary of named styles. */ - public get styles(): any { - const p = this.jsonProperties as any; - if (undefined === p.styles) - p.styles = new Object(); - - return p.styles; - } - - /** Get a named style from the dictionary. */ - public getStyle(name: string): any { - const style: object = this.styles[name]; - return style ? style : {}; - } - - /** change the value of a named style on this DisplayStyle */ - public setStyle(name: string, value: any): void { this.styles[name] = value; } - - /** Remove a style from this DisplayStyle. */ - public removeStyle(name: string) { delete this.styles[name]; } - - /** Get the background color for this DisplayStyle */ - public get backgroundColor(): ColorDef { return this._background; } - /** Set the background color for this DisplayStyle */ - public set backgroundColor(val: ColorDef) { this._background.setFrom(val); this.setStyle("backgroundColor", val); } - - /** Get the color with which graphics are rendered by this DisplayStyle when the monochrome view flag is enabled. */ - public get monochromeColor(): ColorDef { return this._monochrome; } - /** Set the color with which graphics are rendered by this DisplayStyle when the monochrome view flag is enabled. */ - public set monochromeColor(val: ColorDef) { this._monochrome.setFrom(val); this.setStyle("monochromeColor", val); } - /** Create a Code for a DisplayStyle given a name that is meant to be unique within the scope of the specified DefinitionModel. * @param iModel The IModelDb * @param scopeModelId The Id of the DefinitionModel that contains the DisplayStyle and provides the scope for its name. @@ -102,14 +58,79 @@ export class DisplayStyle extends DefinitionElement implements DisplayStyleProps /** A DisplayStyle for 2d views. */ export class DisplayStyle2d extends DisplayStyle { - public constructor(props: DisplayStyleProps, iModel: IModelDb) { super(props, iModel); } + private readonly _settings: DisplayStyleSettings; + + public get settings(): DisplayStyleSettings { return this._settings; } + + public constructor(props: DisplayStyleProps, iModel: IModelDb) { + super(props, iModel); + this._settings = new DisplayStyleSettings(this.jsonProperties); + } + + /** + * Insert a DisplayStyle2d for use by a ViewDefinition. + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new DisplayStyle2d into this DefinitionModel + * @param name The name of the DisplayStyle2d + * @returns The Id of the newly inserted DisplayStyle2d element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string): Id64String { + const displayStyleProps: DisplayStyleProps = { + classFullName: this.classFullName, + code: this.createCode(iModelDb, definitionModelId, name), + model: definitionModelId, + isPrivate: false, + backgroundColor: new ColorDef(), + monochromeColor: ColorDef.white, + viewFlags: ViewFlags.createFrom(), + }; + return iModelDb.elements.insertElement(displayStyleProps); + } } /** A DisplayStyle for 3d views. * See [how to create a DisplayStyle3d]$(docs/learning/backend/CreateElements.md#DisplayStyle3d). */ export class DisplayStyle3d extends DisplayStyle { - public constructor(props: DisplayStyleProps, iModel: IModelDb) { super(props, iModel); } + private readonly _settings: DisplayStyle3dSettings; + + public get settings(): DisplayStyle3dSettings { return this._settings; } + + public constructor(props: DisplayStyleProps, iModel: IModelDb) { + super(props, iModel); + this._settings = new DisplayStyle3dSettings(this.jsonProperties); + } + + /** + * Insert a DisplayStyle3d for use by a ViewDefinition. + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new DisplayStyle3d into this DefinitionModel + * @param name The name of the DisplayStyle3d + * @returns The Id of the newly inserted DisplayStyle3d element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, viewFlagsIn?: ViewFlags, backgroundColor?: ColorDef, analysisStyle?: AnalysisStyleProps): Id64String { + const stylesIn: { [k: string]: any } = { viewflags: viewFlagsIn ? viewFlagsIn : new ViewFlags() }; + + if (analysisStyle) + stylesIn.analysisStyle = analysisStyle; + + if (backgroundColor) + stylesIn.backgroundColor = backgroundColor; + + const displayStyleProps: DisplayStyleProps = { + classFullName: this.classFullName, + code: this.createCode(iModelDb, definitionModelId, name), + model: definitionModelId, + jsonProperties: { styles: stylesIn }, + isPrivate: false, + backgroundColor: new ColorDef(), + monochromeColor: ColorDef.white, + viewFlags: ViewFlags.createFrom(), + }; + return iModelDb.elements.insertElement(displayStyleProps); + } } /** @@ -138,6 +159,26 @@ export class ModelSelector extends DefinitionElement implements ModelSelectorPro const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.modelSelector); return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); } + + /** + * Insert a ModelSelector which is used to select which Models are displayed by a ViewDefinition. + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new ModelSelector into this DefinitionModel + * @param name The name of the ModelSelector + * @param models Array of models to select for display + * @returns The Id of the newly inserted ModelSelector element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, models: Id64Array): Id64String { + const modelSelectorProps: ModelSelectorProps = { + classFullName: this.classFullName, + code: this.createCode(iModelDb, definitionModelId, name), + model: definitionModelId, + models, + isPrivate: false, + }; + return iModelDb.elements.insertElement(modelSelectorProps); + } } /** @@ -166,6 +207,26 @@ export class CategorySelector extends DefinitionElement implements CategorySelec const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.categorySelector); return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); } + + /** + * Insert a CategorySelector which is used to select which categories are displayed by a ViewDefinition. + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new CategorySelector into this DefinitionModel + * @param name The name of the CategorySelector + * @param categories Array of categories to select for display + * @returns The Id of the newly inserted CategorySelector element. + * @throws [[IModelError]] if unable to insert the element. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, categories: Id64Array): Id64String { + const categorySelectorProps: CategorySelectorProps = { + classFullName: this.classFullName, + code: this.createCode(iModelDb, definitionModelId, name), + model: definitionModelId, + categories, + isPrivate: false, + }; + return iModelDb.elements.insertElement(categorySelectorProps); + } } /** @@ -212,10 +273,10 @@ export abstract class ViewDefinition extends DefinitionElement implements ViewDe public isDrawingView(): this is DrawingViewDefinition { return this instanceof DrawingViewDefinition; } /** Load this view's DisplayStyle from the IModelDb. */ - public loadDisplayStyle(): DisplayStyle { return this.iModel.elements.getElement(this.displayStyleId) as DisplayStyle; } + public loadDisplayStyle(): DisplayStyle { return this.iModel.elements.getElement(this.displayStyleId); } /** Load this view's CategorySelector from the IModelDb. */ - public loadCategorySelector(): CategorySelector { return this.iModel.elements.getElement(this.categorySelectorId) as CategorySelector; } + public loadCategorySelector(): CategorySelector { return this.iModel.elements.getElement(this.categorySelectorId); } /** Create a Code for a ViewDefinition given a name that is meant to be unique within the scope of the specified DefinitionModel. * @param iModel The IModelDb @@ -263,7 +324,7 @@ export abstract class ViewDefinition3d extends ViewDefinition implements ViewDef } /** Load this view's DisplayStyle3d from the IModelDb. */ - public loadDisplayStyle3d(): DisplayStyle3d { return this.iModel.elements.getElement(this.displayStyleId) as DisplayStyle3d; } + public loadDisplayStyle3d(): DisplayStyle3d { return this.iModel.elements.getElement(this.displayStyleId); } } /** @@ -291,7 +352,7 @@ export class SpatialViewDefinition extends ViewDefinition3d implements SpatialVi } /** Load this view's ModelSelector from the IModelDb. */ - public loadModelSelector(): ModelSelector { return this.iModel.elements.getElement(this.modelSelectorId) as ModelSelector; } + public loadModelSelector(): ModelSelector { return this.iModel.elements.getElement(this.modelSelectorId); } } /** Defines a spatial view that displays geometry on the image plane using a parallel orthographic projection. @@ -299,6 +360,48 @@ export class SpatialViewDefinition extends ViewDefinition3d implements SpatialVi */ export class OrthographicViewDefinition extends SpatialViewDefinition { constructor(props: SpatialViewDefinitionProps, iModel: IModelDb) { super(props, iModel); } + /** + * Insert an OrthographicViewDefinition + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new OrthographicViewDefinition into this DefinitionModel + * @param name The name/CodeValue of the view + * @param modelSelectorId The [[ModelSelector]] that this view should use + * @param categorySelectorId The [[CategorySelector]] that this view should use + * @param displayStyleId The [[DisplayStyle3d]] that this view should use + * @param range Defines the view origin and extents + * @param standardView Optionally defines the view's rotation + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, modelSelectorId: Id64String, categorySelectorId: Id64String, displayStyleId: Id64String, range: Range3d, standardView = StandardViewIndex.Iso): Id64String { + const rotation = Matrix3d.createStandardWorldToView(standardView); + const angles = YawPitchRollAngles.createFromMatrix3d(rotation); + const rotationTransform = Transform.createOriginAndMatrix(undefined, rotation); + const rotatedRange = rotationTransform.multiplyRange(range); + const viewOrigin = rotation.multiplyTransposeXYZ(rotatedRange.low.x, rotatedRange.low.y, rotatedRange.low.z); + const viewExtents = rotatedRange.diagonal(); + const viewDefinitionProps: SpatialViewDefinitionProps = { + classFullName: this.classFullName, + model: definitionModelId, + code: this.createCode(iModelDb, definitionModelId, name), + modelSelectorId, + categorySelectorId, + displayStyleId, + origin: viewOrigin, + extents: viewExtents, + angles, + cameraOn: false, + camera: new Camera(), // not used when cameraOn === false + }; + return iModelDb.elements.insertElement(viewDefinitionProps); + } + /** Set a new viewed range without changing the rotation or any other properties. */ + public setRange(range: Range3d): void { + const rotation = this.angles.toMatrix3d(); + const rotationTransform = Transform.createOriginAndMatrix(undefined, rotation); + const rotatedRange = rotationTransform.multiplyRange(range); + this.origin = Point3d.createFrom(rotation.multiplyTransposeXYZ(rotatedRange.low.x, rotatedRange.low.y, rotatedRange.low.z)); + this.extents = rotatedRange.diagonal(); + } } /** Defines a view of a single 2d model. Each 2d model has its own coordinate system, so only one may appear per view. */ @@ -331,7 +434,7 @@ export class ViewDefinition2d extends ViewDefinition implements ViewDefinition2d } /** Load this view's DisplayStyle2d from the IModelDb. */ - public loadDisplayStyle2d(): DisplayStyle2d { return this.iModel.elements.getElement(this.displayStyleId) as DisplayStyle2d; } + public loadDisplayStyle2d(): DisplayStyle2d { return this.iModel.elements.getElement(this.displayStyleId); } } /** Defines a view of a [[DrawingModel]]. */ @@ -339,6 +442,31 @@ export class DrawingViewDefinition extends ViewDefinition2d { public constructor(props: ViewDefinition2dProps, iModel: IModelDb) { super(props, iModel); } + /** + * Insert an DrawingViewDefinition + * @param iModelDb Insert into this iModel + * @param definitionModelId Insert the new DrawingViewDefinition into this [[DefinitionModel]] + * @param name The name/CodeValue of the view + * @param baseModelId The base [[DrawingModel]] + * @param categorySelectorId The [[CategorySelector]] that this view should use + * @param displayStyleId The [[DisplayStyle2d]] that this view should use + * @param range Defines the view origin and extents + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, definitionModelId: Id64String, name: string, baseModelId: Id64String, categorySelectorId: Id64String, displayStyleId: Id64String, range: Range2d): Id64String { + const viewDefinitionProps: ViewDefinition2dProps = { + classFullName: this.classFullName, + model: definitionModelId, + code: this.createCode(iModelDb, definitionModelId, name), + baseModelId, + categorySelectorId, + displayStyleId, + origin: { x: range.low.x, y: range.low.y }, + delta: range.diagonal(), + angle: 0, + }; + return iModelDb.elements.insertElement(viewDefinitionProps); + } } /** Defines a view of a [[SheetModel]]. */ @@ -379,6 +507,16 @@ export class AuxCoordSystem2d extends AuxCoordSystem implements AuxCoordSystem2d public origin?: Point2d; public angle!: number; public constructor(props: AuxCoordSystem2dProps, iModel: IModelDb) { super(props, iModel); } + + /** Create a Code for a AuxCoordSystem2d element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the AuxCoordSystem2d element and provides the scope for its name. + * @param codeValue The AuxCoordSystem2d name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.auxCoordSystem2d); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** @@ -390,12 +528,31 @@ export class AuxCoordSystem3d extends AuxCoordSystem implements AuxCoordSystem3d public pitch!: number; public roll!: number; public constructor(props: AuxCoordSystem3dProps, iModel: IModelDb) { super(props, iModel); } + + /** Create a Code for a AuxCoordSystem3d element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the AuxCoordSystem3d element and provides the scope for its name. + * @param codeValue The AuxCoordSystem3d name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.auxCoordSystem3d); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** * A spatial auxiliary coordinate system. */ export class AuxCoordSystemSpatial extends AuxCoordSystem3d { + /** Create a Code for a AuxCoordSystemSpatial element given a name that is meant to be unique within the scope of the specified DefinitionModel. + * @param iModel The IModelDb + * @param scopeModelId The Id of the DefinitionModel that contains the AuxCoordSystemSpatial element and provides the scope for its name. + * @param codeValue The AuxCoordSystemSpatial name + */ + public static createCode(iModel: IModelDb, scopeModelId: CodeScopeProps, codeValue: string): Code { + const codeSpec: CodeSpec = iModel.codeSpecs.getByName(BisCodeSpec.auxCoordSystemSpatial); + return new Code({ spec: codeSpec.id, scope: scopeModelId, value: codeValue }); + } } /** diff --git a/core/backend/src/backend.ts b/core/backend/src/backend.ts index ef19dc9..4cd7a26 100644 --- a/core/backend/src/backend.ts +++ b/core/backend/src/backend.ts @@ -22,9 +22,10 @@ export * from "./ExpressServer"; export * from "./IModelJsFs"; export * from "./IModelHost"; export * from "./Platform"; -export * from "./LinkTableRelationship"; +export * from "./Relationship"; export * from "./LineStyle"; export * from "./Model"; +export * from "./NavigationRelationship"; export * from "./Schema"; export * from "./SqliteStatement"; export * from "./ViewDefinition"; diff --git a/core/backend/src/domains/FunctionalElements.ts b/core/backend/src/domains/FunctionalElements.ts index c35b6b0..6b2f18c 100644 --- a/core/backend/src/domains/FunctionalElements.ts +++ b/core/backend/src/domains/FunctionalElements.ts @@ -2,10 +2,13 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { FunctionalElementProps, InformationPartitionElementProps, ModelProps, TypeDefinitionElementProps } from "@bentley/imodeljs-common"; +import { Id64String } from "@bentley/bentleyjs-core"; +import { FunctionalElementProps, IModel, InformationPartitionElementProps, ModelProps, RelatedElement, TypeDefinitionElementProps } from "@bentley/imodeljs-common"; import { InformationPartitionElement, RoleElement, TypeDefinitionElement } from "../Element"; import { IModelDb } from "../IModelDb"; +import { DrawingGraphicRepresentsElement, ElementRefersToElements } from "../Relationship"; import { RoleModel } from "../Model"; +import { SubjectOwnsPartitionElements } from "../NavigationRelationship"; /** * A FunctionalPartition element is a key part of the iModel information hierarchy and is always parented @@ -24,6 +27,27 @@ export class FunctionalModel extends RoleModel { public constructor(props: ModelProps, iModel: IModelDb) { super(props, iModel); } + /** + * Insert a FunctionalPartition and a FunctionalModel that breaks it down. + * @param iModelDb Insert into this iModel + * @param parentSubjectId The FunctionalPartition will be inserted as a child of this Subject element. + * @param name The name of the FunctionalPartition that the new FunctionalModel will break down. + * @returns The Id of the newly inserted FunctionalPartition and FunctionalModel (same value). + * @throws [[IModelError]] if there is an insert problem. + */ + public static insert(iModelDb: IModelDb, parentSubjectId: Id64String, name: string): Id64String { + const partitionProps: InformationPartitionElementProps = { + classFullName: FunctionalPartition.classFullName, + model: IModel.repositoryModelId, + parent: new SubjectOwnsPartitionElements(parentSubjectId), + code: FunctionalPartition.createCode(iModelDb, parentSubjectId, name), + }; + const partitionId = iModelDb.elements.insertElement(partitionProps); + return iModelDb.models.insertModel({ + classFullName: this.classFullName, + modeledElement: { id: partitionId }, + }); + } } /** @@ -67,3 +91,19 @@ export abstract class FunctionalType extends TypeDefinitionElement { super(props, iModel); } } + +/** Relates a [[FunctionalElement]] to its [[FunctionalType]] */ +export class FunctionalElementIsOfType extends RelatedElement { + public static classFullName = "Functional:FunctionalElementIsOfType"; + public constructor(id: Id64String, relClassName: string = FunctionalElementIsOfType.classFullName) { + super({ id, relClassName }); + } +} + +/** Relates a [[PhysicalElement]] to the [[FunctionalElement]] elements that it fulfills. */ +export class PhysicalElementFulfillsFunction extends ElementRefersToElements { +} + +/** Relates a [[DrawingGraphic]] to the [[FunctionalElement]] that it represents */ +export class DrawingGraphicRepresentsFunctionalElement extends DrawingGraphicRepresentsElement { +} diff --git a/core/backend/src/imodeljs-native-platform-api.ts b/core/backend/src/imodeljs-native-platform-api.ts index d5bf145..fbeba5f 100644 --- a/core/backend/src/imodeljs-native-platform-api.ts +++ b/core/backend/src/imodeljs-native-platform-api.ts @@ -5,14 +5,13 @@ import { IModelStatus, StatusCodeWithMessage, RepositoryStatus, BentleyStatus, ChangeSetApplyOption, DbResult, DbOpcode, OpenMode, IDisposable, ChangeSetStatus, Id64String, GuidString, } from "@bentley/bentleyjs-core"; +import { IModelDb } from "./IModelDb"; +import { ElementProps } from "@bentley/imodeljs-common"; /** - * The primary key for the DGN_TABLE_Txns table. * @hidden */ -export interface NativeTxnId { - readonly _id: string; -} +export type TxnIdString = string; /** * The return type of synchronous functions that may return an error or a successful result. @@ -27,29 +26,14 @@ export interface ErrorStatusOrResult { } /** - * A request to send on to iModelHub. * @hidden */ export declare class NativeBriefcaseManagerResourcesRequest { - /** Forget the requests. */ public reset(): void; - - /** Contains no requests? */ public isEmpty(): boolean; - - /** Get the request in JSON format */ public toJSON(): string; } -/* How to handle a conflict -export const enum NativeBriefcaseManagerOnConflict { - // Reject the incoming change - RejectIncomingChange = 0, - // Accept the incoming change - AcceptIncomingChange = 1, -} -*/ - /** * The options for how conflicts are to be handled during change-merging in an OptimisticConcurrencyControlPolicy. * The scenario is that the caller has made some changes to the *local* briefcase. Now, the caller is attempting to @@ -65,989 +49,250 @@ export interface NativeBriefcaseManagerOnConflictPolicy { deleteVsUpdate: /*NativeBriefcaseManagerOnConflict*/number; } -/** - * The NativeDgnDb class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeDgnDb { constructor(); - - /** Get the name of the *assets* directory. */ public static getAssetsDir(): string; - - /** Get the IModelProps of this iModel. */ - public getIModelProps(): string; - - /** - * Create a local iModel. - * @param fileName The file name for the new iModel - * @param props The properties of the new iModel. See CreateIModelProps in IModel.ts - * @return non-zero error status if operation failed. - */ - public createIModel(fileName: string, props: string): DbResult; - - /** - * Open a local iModel. - * @param dbName The full path to the iModel in the local file system - * @param mode The open mode - * @return non-zero error status if operation failed. - */ - public openIModel(dbName: string, mode: OpenMode): DbResult; - - /** Close this iModel. */ - public closeIModel(): void; - - /** - * Apply change sets - * @param cachePath Path to the root of the disk cache - */ - public applyChangeSets(changeSets: string, processOptions: ChangeSetApplyOption): ChangeSetStatus; - - /** - * Start creating a new change set with local changes - */ - public startCreateChangeSet(): ErrorStatusOrResult; - - /** - * Finish creating a new change set with local changes - */ - public finishCreateChangeSet(): ChangeSetStatus; - - /** - * Abandon creating a new change set with local changes - */ + public abandonChanges(): DbResult; public abandonCreateChangeSet(): void; - - /** - * Dumps a change set - */ - public dumpChangeSet(changeSet: string): void; - - /** - * Extract codes from change set that is being created - */ - public extractCodes(): ErrorStatusOrResult; - - /** - * Extract codes from a change set file - */ - public extractCodesFromFile(changeSets: string): ErrorStatusOrResult; - - /** - * Get list of change sets that failed updating their codes - */ - public getPendingChangeSets(): ErrorStatusOrResult; - - /** - * Mark change set as failed to update codes - */ public addPendingChangeSet(changeSetId: string): DbResult; - - /** - * Remove change set from failed change sets list - */ - public removePendingChangeSet(changeSetId: string): DbResult; - - /** Creates an EC change cache for this iModel (but does not attach it). - * @param changeCacheFile The created change cache ECDb file - * @param changeCachePath The full path to the EC change cache file in the local file system - * @return non-zero error status if operation failed. - */ - public createChangeCache(changeCacheFile: NativeECDb, changeCachePath: string): DbResult; - - /** Attaches an EC change cache file to this iModel. - * @param changeCachePath The full path to the EC change cache file in the local file system - * @return non-zero error status if operation failed. - */ + public appendBriefcaseManagerResourcesRequest(reqOut: NativeBriefcaseManagerResourcesRequest, reqIn: NativeBriefcaseManagerResourcesRequest): void; + public applyChangeSets(changeSets: string, processOptions: ChangeSetApplyOption): ChangeSetStatus; public attachChangeCache(changeCachePath: string): DbResult; - - /** Determines whether the EC Changes cache file is attached to this iModel. - * @return true if the changes cache is attached. false otherwise - */ - public isChangeCacheAttached(): boolean; - + public beginMultiTxnOperation(): DbResult; + public briefcaseManagerEndBulkOperation(): RepositoryStatus; + public briefcaseManagerStartBulkOperation(): RepositoryStatus; + public buildBriefcaseManagerResourcesRequestForElement(req: NativeBriefcaseManagerResourcesRequest, elemId: string, opcode: DbOpcode): RepositoryStatus; + public buildBriefcaseManagerResourcesRequestForLinkTableRelationship(req: NativeBriefcaseManagerResourcesRequest, relKey: string, opcode: DbOpcode): RepositoryStatus; + public buildBriefcaseManagerResourcesRequestForModel(req: NativeBriefcaseManagerResourcesRequest, modelId: string, opcode: DbOpcode): RepositoryStatus; + public cancelTo(txnId: TxnIdString): IModelStatus; + public closeIModel(): void; + public closeIModelFile(): void; + public createChangeCache(changeCacheFile: NativeECDb, changeCachePath: string): DbResult; + public createIModel(accessToken: string, projectId: GuidString, fileName: string, props: string): DbResult; + public createStandaloneIModel(fileName: string, props: string): DbResult; + public deleteElement(elemIdJson: string): IModelStatus; + public deleteElementAspect(aspectIdJson: string): IModelStatus; + public deleteLinkTableRelationship(props: string): DbResult; + public deleteModel(modelIdJson: string): IModelStatus; public detachChangeCache(): number; - - /** Extracts a change summary from the specified Changeset file - * @param changeCacheFile The change cache ECDb file where the extracted change summary will be persisted - * @param changesetFilePath The full path to the SQLite changeset file in the local file system - * @return The ChangeSummary ECInstanceId as hex string or error codes in case of failure - */ + public dumpChangeSet(changeSet: string): void; + public embedFont(fontProps: string): string; + public enableTxnTesting(): void; + public endMultiTxnOperation(): DbResult; + public executeTest(testName: string, params: string): string; + public extractBriefcaseManagerResourcesRequest(reqOut: NativeBriefcaseManagerResourcesRequest, reqIn: NativeBriefcaseManagerResourcesRequest, locks: boolean, codes: boolean): void; + public extractBulkResourcesRequest(req: NativeBriefcaseManagerResourcesRequest, locks: boolean, codes: boolean): void; public extractChangeSummary(changeCacheFile: NativeECDb, changesetFilePath: string): ErrorStatusOrResult; - - /** - * Set the briefcase Id of this iModel. - * @param idValue The briefcase Id value. - */ - public setBriefcaseId(idValue: number): DbResult; - - /** Get the briefcase Id of this iModel. */ + public extractCodes(): ErrorStatusOrResult; + public extractCodesFromFile(changeSets: string): ErrorStatusOrResult; + public finishCreateChangeSet(): ChangeSetStatus; public getBriefcaseId(): number; - - /** - * Get the change set the iModel was reversed to - * @return Returns the change set id if the iModel was reversed, or undefined if the iModel was not reversed. - */ - public getReversedChangeSetId(): string | undefined; - - /** - * Get the Id of the last change set that was merged into or created from the Db. This is the parent for any new change sets that will be created from the iModel. - * @return Returns an empty string if the iModel is in it's initial state (with no change sets), or if it's a standalone briefcase disconnected from the Hub. - */ - public getParentChangeSetId(): string; - - /* Get the GUID of this iModel */ + public getCurrentTxnId(): TxnIdString; public getDbGuid(): GuidString; - - /* Set the GUID of this iModel */ - public setDbGuid(guid: GuidString): DbResult; - - /* Set as master iModel - * @param guid optionally provide GUID for the iModel. If not provided one will be generated by the method. - */ - public setAsMaster(guid?: GuidString): DbResult; - - /** - * Save any pending changes to this iModel. - * @param description optional description of changes - * @return non-zero error status if save failed. - */ - public saveChanges(description?: string): DbResult; - - /** Abandon changes - * @return non-zero error status if operation failed. - */ - public abandonChanges(): DbResult; - - /** - * Import an EC schema. - * There are a number of restrictions when importing schemas into a briefcase. - * When importing into a briefcase, this function will acquire the schema lock. That means that that briefcase must be at the tip of the revision - * history in iModelHub. If not, this function will return SchemaLockFailed. - * Importing or upgrading a schema into a briefcase must be done in isolation from all other kinds of changes. That means two things: - * there must be no pending local changes. All local changes must be pushed to iModelHub. This function will return SchemaImportFailed if that is not true. - * Also, the caller must push the results of this function to iModelHub before making other changes to the briefcase. - * @param schemaPathname The full path to the .xml file in the local file system. - * @return non-zero error status if the operation failed, including SchemaImportFailed if the schema is invalid. - */ - public importSchema(schemaPathname: string): DbResult; - - /** - * Import the schema for the Functional domain. Also updates the dgn_Domain and dgn_Handler tables for the Functional domain handlers. - * @return Non-zero error status if the operation failed. - */ - public importFunctionalSchema(): DbResult; - - /** - * Get an element's properties - * @param opts Identifies the element - * @returns In case of success, the result property of the returned object will be the element's properties. - */ - public getElement(opts: string): ErrorStatusOrResult; - - /** - * Get the properties of a Model - * @param opts Identifies the model - * @returns In case of success, the result property of the returned object will be the model's properties in stringified JSON format. - */ + public getECClassMetaData(schema: string, className: string): ErrorStatusOrResult; + public getElement(opts: string): ErrorStatusOrResult; + public getElementPropertiesForDisplay(id: string): ErrorStatusOrResult; + public getIModelProps(): string; public getModel(opts: string): ErrorStatusOrResult; - - /** - * Query for the extents of a GeometricModel. - * @param options Identifies the model - * @returns In case of success, the result property of the returned object will be the model's extents (AxisAlignedBox3d) in stringified JSON format. - */ - public queryModelExtents(options: string): ErrorStatusOrResult; - - /** - * Get the properties of a tile tree - * @param id Identifies the tile tree - * @param callback Function invoked with result of the call. In case of success, the result property will be the tile tree's properties in JSON format. - */ - public getTileTree(id: string, callback: (result: ErrorStatusOrResult) => void): void; - - /** - * Get the content (geometry) of a tile as a base-64-encoded string - * @param treeId The ID of the tile tree - * @param tileId The ID of the tile - * @param callback Function invoked with result of the call. In case of success, the result property will be the binary tile content. - */ + public getMultiTxnOperationDepth(): number; + public getParentChangeSetId(): string; + public getPendingChangeSets(): ErrorStatusOrResult; + public getRedoString(): string; + public getReversedChangeSetId(): string | undefined; + public getSchema(name: string): ErrorStatusOrResult; + public getSchemaItem(schemaName: string, itemName: string): ErrorStatusOrResult; public getTileContent(treeId: string, tileId: string, callback: (result: ErrorStatusOrResult) => void): void; - - /** - * Insert an element. - * @param elemProps The element's properties, in stringified JSON format. - * @return In case of success, the result property of the returned object will be the element's ID (as a hex string) - */ + public getTileTree(id: string, callback: (result: ErrorStatusOrResult) => void): void; + public getTxnDescription(txnId: TxnIdString): string; + public getUndoString(): string; + public hasFatalTxnError(): boolean; + public hasUnsavedChanges(): boolean; + public importFunctionalSchema(): DbResult; + public importSchema(schemaPathname: string): DbResult; + public inBulkOperation(): boolean; + public insertCodeSpec(name: string, specType: number, scopeReq: number): ErrorStatusOrResult; public insertElement(elemProps: string): ErrorStatusOrResult; - - /** - * Update an element. - * @param elemProps The element's properties, in stringified JSON format. - * @return non-zero error status if the operation failed. - */ - public updateElement(elemProps: string): IModelStatus; - - /** - * Delete an element from this iModel. - * @param elemIdJson The element's Id, in stringified JSON format - * @return non-zero error status if the operation failed. - */ - public deleteElement(elemIdJson: string): IModelStatus; - - /** - * Insert an ElementAspect. - * @param aspectProps The ElementAspect's properties, in stringified JSON format. - * @return non-zero error status if the operation failed. - */ public insertElementAspect(aspectProps: string): IModelStatus; - - /** - * Delete an ElementAspect from this iModel. - * @param aspectIdJson The ElementAspect's ECInstanceId, in stringified JSON format - * @return non-zero error status if the operation failed. - */ - public deleteElementAspect(aspectIdJson: string): IModelStatus; - - /** - * Insert a LinkTableRelationship. - * @param props The linkTableRelationship's properties, in stringified JSON format. - * @return In case of success, the result property of the returned object will be the ID of the new LinkTableRelationship instance (as a hex string) - */ public insertLinkTableRelationship(props: string): ErrorStatusOrResult; - - /** - * Update a LinkTableRelationship. - * @param props The LinkTableRelationship's properties, in stringified JSON format. - * @return non-zero error status if the operation failed. - */ - public updateLinkTableRelationship(props: string): DbResult; - - /** - * Delete a LinkTableRelationship. - * @param props The LinkTableRelationship's properties, in stringified JSON format. Only classFullName and id are required. - * @return non-zero error status if the operation failed. - */ - public deleteLinkTableRelationship(props: string): DbResult; - - /** - * Insert a new CodeSpec - * @param name name of the CodeSpec - * @param specType must be one of CodeScopeSpec::Type - * @param scopeReq must be one of CodeScopeSpec::ScopeRequirement - * @return In case of success, the result property of the returned object will be the ID of the new CodeSpec instance (as a hex string) - */ - public insertCodeSpec(name: string, specType: number, scopeReq: number): ErrorStatusOrResult; - - /** - * Insert a model. - * @param modelProps The model's properties, in stringified JSON format. - * @return In case of success, the result property of the returned object will be the ID of the new Model (as a hex string) - */ public insertModel(modelProps: string): ErrorStatusOrResult; - - /** - * Update a model. - * @param modelProps The model's properties, in stringified JSON format. - * @return non-zero error status if the operation failed. - */ - public updateModel(modelProps: string): IModelStatus; - - /** - * Delete a model. - * @param modelIdJson The model's Id, in stringified JSON format - * @return non-zero error status if the operation failed. - */ - public deleteModel(modelIdJson: string): IModelStatus; - - /** - * Update the imodel project extents. - * @param newExtentsJson The new project extents in stringified JSON format - */ - public updateProjectExtents(newExtentsJson: string): void; - - /** - * Update the iModel properties see - * @param props the [IModelProps]($common) in stringified JSON - */ - public updateIModelProps(props: string): void; - - /** - * Format an element's properties, suitable for display to the user. - * @param id The element's Id, in stringified JSON format - * @param on success, the result property of the returned object will be the object's properties, in stringified JSON format. - */ - public getElementPropertiesForDisplay(id: string): ErrorStatusOrResult; - - /** - * Get information about an ECClass - * @param schema The name of the ECSchema - * @param className The name of the ECClass - * @param on success, the result property of the returned object will be an object containing the properties of the class, in stringified JSON format. - */ - public getECClassMetaData(schema: string, className: string): ErrorStatusOrResult; - - /** - * Get a SchemaItem by schema and item name - * @param schemaName The name of the ECSchema - * @param itemName The name of the SchemaItem - * @param on success, the result property of the returned object will be an object containing the schema item in stringified JSON format. - */ - public getSchemaItem(schemaName: string, itemName: string): ErrorStatusOrResult; - - /** - * Get a Schema by name - * @param name The name of the ECSchema - * @param on success, the result property of the returned object will be an object containing the schema in stringified JSON format. - */ - public getSchema(name: string): ErrorStatusOrResult; - - /** - * Add the lock, code, and other resource request that would be needed to carry out the specified operation. - * @param req The request object, which accumulates requests. - * @param elemId The ID of an existing element or the {modelid, code} properties that specify a new element. - * @param opcode The operation that will be performed on the element. - */ - public buildBriefcaseManagerResourcesRequestForElement(req: NativeBriefcaseManagerResourcesRequest, elemId: string, opcode: DbOpcode): RepositoryStatus; - - /** - * Add the lock, code, and other resource request that would be needed to carry out the specified operation. - * @param req The request object, which accumulates requests. - * @param modelId The ID of a model - * @param opcode The operation that will be performed on the model. - */ - public buildBriefcaseManagerResourcesRequestForModel(req: NativeBriefcaseManagerResourcesRequest, modelId: string, opcode: DbOpcode): RepositoryStatus; - - /** - * Add the resource request that would be needed to carry out the specified operation. - * @param req The request object, which accumulates requests. - * @param relKey Identifies a LinkTableRelationship: {classFullName, id} - * @param opcode The operation that will be performed on the LinkTableRelationships. - */ - public buildBriefcaseManagerResourcesRequestForLinkTableRelationship(req: NativeBriefcaseManagerResourcesRequest, relKey: string, opcode: DbOpcode): RepositoryStatus; - - /** - * Extract requests from the current bulk operation and append them to reqOut - * @param req The pending requests. - * @param locks Extract lock requests? - * @param codes Extract Code requests? - */ - public extractBulkResourcesRequest(req: NativeBriefcaseManagerResourcesRequest, locks: boolean, codes: boolean): void; - - /** - * Extract requests from reqIn and append them to reqOut - * @param reqOut The output request - * @param reqIn The input request - * @param locks Extract lock requests? - * @param codes Extract Code requests? - */ - public extractBriefcaseManagerResourcesRequest(reqOut: NativeBriefcaseManagerResourcesRequest, reqIn: NativeBriefcaseManagerResourcesRequest, locks: boolean, codes: boolean): void; - - /** - * Append reqIn to reqOut - * @param reqOut The request to be augmented - * @param reqIn The request to read - */ - public appendBriefcaseManagerResourcesRequest(reqOut: NativeBriefcaseManagerResourcesRequest, reqIn: NativeBriefcaseManagerResourcesRequest): void; - - /** Start bulk update mode. Valid only with the pessimistic concurrency control policy */ - public briefcaseManagerStartBulkOperation(): RepositoryStatus; - - /** End bulk update mode. This will wait for locks and codes. Valid only with the pessimistic concurrency control policy */ - public briefcaseManagerEndBulkOperation(): RepositoryStatus; - - /** Check if there is a bulk operation in progress. */ - public inBulkOperation(): boolean; - - /** - * The the pessimistic concurrency control policy. - */ - public setBriefcaseManagerPessimisticConcurrencyControlPolicy(): RepositoryStatus; - - /** Set the optimistic concurrency control policy. - * @param policy The policy to used - * @return non-zero if the policy could not be set - */ - public setBriefcaseManagerOptimisticConcurrencyControlPolicy(conflictPolicy: NativeBriefcaseManagerOnConflictPolicy): RepositoryStatus; - - /** Query the ID of the first entry in the local Txn table, if any. */ - public txnManagerQueryFirstTxnId(): NativeTxnId; - /** Query the ID of the entry in the local Txn table that comes after the specified Txn, if any. */ - public txnManagerQueryNextTxnId(txnId: NativeTxnId): NativeTxnId; - /** Query the ID of the entry in the local Txn table that comes before the specified Txn, if any. */ - public txnManagerQueryPreviousTxnId(txnId: NativeTxnId): NativeTxnId; - /** Query the ID of the most recent entry in the local Txn table, if any. */ - public txnManagerGetCurrentTxnId(): NativeTxnId; - /** Get the description of the specified Txn. */ - public txnManagerGetTxnDescription(txnId: NativeTxnId): string; - /** Check if the specified TxnId is valid. The above query functions will return an invalid ID to indicate failure. */ - public txnManagerIsTxnIdValid(txnId: NativeTxnId): boolean; - /** Check if there are un-saved changes in memory. */ - public txnManagerHasUnsavedChanges(): boolean; - - /** read the font map. */ - public readFontMap(): string; - - /** embed a font. */ - public embedFont(fontProps: string): string; - - /** query a file property. - * @param props the stringified version of the FilePropertyProps - * @param wantString true to query the string property, false for the blob property - * @returns requested value or undefined if property does not exist - */ + public isChangeCacheAttached(): boolean; + public isRedoPossible(): boolean; + public isTxnIdValid(txnId: TxnIdString): boolean; + public isUndoPossible(): boolean; + public logTxnError(fatal: boolean): void; + public openIModel(accessToken: string, projectId: GuidString, dbName: string, mode: OpenMode): DbResult; + public openIModelFile(dbName: string, mode: OpenMode): DbResult; public queryFileProperty(props: string, wantString: boolean): string | Uint8Array | undefined; - - /** save or delete a file property. - * @param props the stringified version of the FilePropertyProps - * @param value the value to save. If undefined, the file property is deleted. - * @returns 0 if property was saved (or deleted), error status otherwise - */ - public saveFileProperty(props: string, strValue: string | undefined, blobVal: Uint8Array | undefined): number; - - /** query the next available major id for the given file property. If no properties yet exist, will return 0. */ + public queryFirstTxnId(): TxnIdString; + public queryModelExtents(options: string): ErrorStatusOrResult; public queryNextAvailableFileProperty(props: string): number; - - /** - * Execute a test by name - * @param testName The name of the test to execute - * @param params A JSON string with the parameters for the test - */ - public executeTest(testName: string, params: string): string; + public queryNextTxnId(txnId: TxnIdString): TxnIdString; + public queryPreviousTxnId(txnId: TxnIdString): TxnIdString; + public readFontMap(): string; + public reinstateTxn(): IModelStatus; + public removePendingChangeSet(changeSetId: string): DbResult; + public reverseAll(): IModelStatus; + public reverseTo(txnId: TxnIdString): IModelStatus; + public reverseTxns(numOperations: number): IModelStatus; + public saveChanges(description?: string): DbResult; + public saveFileProperty(props: string, strValue: string | undefined, blobVal: Uint8Array | undefined): number; + public setAsMaster(guid?: GuidString): DbResult; + public setBriefcaseId(idValue: number): DbResult; + public setBriefcaseManagerOptimisticConcurrencyControlPolicy(conflictPolicy: NativeBriefcaseManagerOnConflictPolicy): RepositoryStatus; + public setBriefcaseManagerPessimisticConcurrencyControlPolicy(): RepositoryStatus; + public setDbGuid(guid: GuidString): DbResult; + public setIModelDb(iModelDb?: IModelDb): void; + public startCreateChangeSet(): ErrorStatusOrResult; + public updateElement(elemProps: string): IModelStatus; + public updateIModelProps(props: string): void; + public updateLinkTableRelationship(props: string): DbResult; + public updateModel(modelProps: string): IModelStatus; + public updateProjectExtents(newExtentsJson: string): void; } -/** - * The NativeECDb class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECDb implements IDisposable { constructor(); - /** - * Create a new ECDb. - * @param dbName The full path to the ECDb in the local file system - * @return non-zero error status if operation failed. - */ public createDb(dbName: string): DbResult; - - /** Open a existing ECDb. - * @param dbName The full path to the ECDb in the local file system - * @param mode The open mode - * @param upgradeProfiles If true and open mode is read/write, the file's profiles are upgraded (if necessary) - * @return non-zero error status if operation failed. - */ public openDb(dbName: string, mode: OpenMode, upgradeProfiles?: boolean): DbResult; - - /** Check to see if connection to ECDb is open or not. - * @return true if connection is open - */ public isOpen(): boolean; - - /** Check to see if connection to ECDb is open or not. - * @return true if connection was closed - */ public closeDb(): void; - - /** Dispose of the native ECDb object. */ public dispose(): void; - - /** Save changes to ecdb - * @param changesetName The name of the operation that generated these changes. If transaction tracking is enabled. - * @return non-zero error status if operation failed. - */ public saveChanges(changesetName?: string): DbResult; - - /** Abandon changes - * @return non-zero error status if operation failed. - */ public abandonChanges(): DbResult; - - /** Import ECSchema into ECDb - * @param schemaPathName Path to ECSchema file on disk. All reference schema should also be present on same path. - * @return non-zero error status if operation failed. - */ public importSchema(schemaPathName: string): DbResult; } -/** - * The NativeECSqlStatement class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECSqlStatement implements IDisposable { constructor(); - - /** - * Prepare an ECSQL statement. - * @param db The NativeDgnDb or NativeECDb object - * @param ecsql The ECSQL to prepare - * @return Returns the Zero status in case of success. Non-zero error status in case of failure. The error's message property will contain additional information. - */ public prepare(db: NativeDgnDb | NativeECDb, ecsql: string): StatusCodeWithMessage; - - /** Reset the statement to just before the first row. - * @return Returns non-zero error status in case of failure. - */ public reset(): DbResult; - - /** Dispose of the native ECSqlStatement object - call this when finished stepping a statement, but only if the statement is not shared. */ public dispose(): void; - - /** - * Gets a binder for the specified parameter. It can be used to bind any type of values to the parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @return Returns the binder for the specified parameter - */ public getBinder(param: number | string): NativeECSqlBinder; - - /** Clear the bindings of this statement. See bindValues. - * @return Returns a non-zero error status in case of failure. - */ public clearBindings(): DbResult; - - /** Step this statement to move to the next row. - * @return Returns BE_SQLITE_ROW if the step moved to a new row. Returns BE_SQLITE_DONE if the step failed because there is no next row. Another non-zero error status if step failed because of an error. - */ public step(): DbResult; - - /** Step this INSERT statement and returns the status along with the ECInstanceId of the newly inserted row. - * @return Returns BE_SQLITE_DONE if the insert was successful. Returns another non-zero error status if step failed because of an error. - */ public stepForInsert(): { status: DbResult, id: string }; - - /** - * Get the value of the specified column for the current row - * @param columnIndex Index (0-based) of the column in the ECSQL SELECT clause for which the value is to be retrieved. - * @return Returns the ECSQL value of the specified column for the current row - */ public getValue(columnIndex: number): NativeECSqlValue; - - /** - * Get the number of ECSQL columns in the result set after calling step on a SELECT statement. - * @return Returns the ECSQL value of the specified column for the current row - */ public getColumnCount(): number; } -/** - * The NativeECSqlBinder class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECSqlBinder { constructor(); - - /** Binds null to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindNull(): DbResult; - - /** Binds a BLOB, formatted as Base64 string, to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindBlob(base64String: string | Uint8Array | ArrayBuffer | SharedArrayBuffer): DbResult; - - /** Binds a Boolean to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindBoolean(val: boolean): DbResult; - - /** Binds a DateTime, formatted as ISO string, to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindDateTime(isoString: string): DbResult; - - /** Binds a double to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindDouble(val: number): DbResult; - - /** Binds an Guid, formatted as GUID string, to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindGuid(guidStr: GuidString): DbResult; - - /** Binds an Id, formatted as hexadecimal string, to the parameter represented by this binder - * @return non-zero error status in case of failure. - */ public bindId(hexStr: Id64String): DbResult; - - /** Binds an int to the parameter represented by this binder - * @param val Integral value, either as number or as decimal or hexadecimal string (for the case - * where the integer is larger than the JS accuracy threshold) - * @return non-zero error status in case of failure. - */ public bindInteger(val: number | string): DbResult; - - /** Binds a Point2d to the parameter represented by this binder. - * @return non-zero error status in case of failure. - */ public bindPoint2d(x: number, y: number): DbResult; - - /** Binds a Point3d to the parameter represented by this binder. - * @return non-zero error status in case of failure. - */ public bindPoint3d(x: number, y: number, z: number): DbResult; - - /** Binds a string to the parameter represented by this binder. - * @return non-zero error status in case of failure. - */ public bindString(val: string): DbResult; - - /** Binds a Navigation property value to the parameter represented by this binder. - * @param navIdHexStr Id of the related instance represented by the navigation property (formatted as hexadecimal string) - * @param relClassName Name of the relationship class of the navigation property (can be undefined if it is not mandatory) - * @param relClassTableSpace In case the relationship of the navigation property is persisted in an attached ECDb file, specify the table space. - * If undefined, ECDb will first look in the primary file and then in the attached ones. - * @return non-zero error status in case of failure. - */ public bindNavigation(navIdHexStr: Id64String, relClassName?: string, relClassTableSpace?: string): DbResult; - - /** Gets a binder for the specified member of a struct parameter - * @return Struct member binder. - */ public bindMember(memberName: string): NativeECSqlBinder; - - /** Adds a new array element to the array parameter and returns the binder for the new array element - * @return Binder for the new array element. - */ public addArrayElement(): NativeECSqlBinder; } -/** - * The NativeECSqlColumnInfo class that is projected by IModelJsNative. - * @remarks No need to dispose this is its native counterpart is owned by the IECSqlValue. - * @hidden - */ +/** @hidden */ export declare class NativeECSqlColumnInfo { constructor(); - - /** Gets the data type of the column. - * @returns one of the values of the enum ECSqlValueType values, defined in imodeljs-core/common. - * (enums cannot be defined in the Native) - */ public getType(): number; - - /** Gets the name of the property backing the column. - * @remarks If this column is backed by a generated property, i.e. it represents ECSQL expression, - * the access string consists of the name of the generated property. - */ public getPropertyName(): string; - - /** Gets the full access string to the corresponding ECSqlValue starting from the root class. - * @remarks If this column is backed by a generated property, i.e. it represents ECSQL expression, - * the access string consists of the ECSQL expression. - */ public getAccessString(): string; - - /** Indicates whether the column refers to a system property (e.g. id, className) backing the column. */ public isSystemProperty(): boolean; - - /** Indicates whether the column is backed by a generated property or not. For SELECT clause items that are expressions other - * than simply a reference to an ECProperty, a property is generated containing the expression name. - */ public isGeneratedProperty(): boolean; - - /** Gets the table space in which this root class is persisted. - * @remarks for classes in the primary file the table space is MAIN. For classes in attached - * files, the table space is the name by which the file was attached (see BentleyApi::BeSQLite::Db::AttachDb) - * For generated properties the table space is empty - */ public getRootClassTableSpace(): string; - - /** Gets the fully qualified name of the ECClass of the top-level ECProperty backing this column. */ public getRootClassName(): string; - - /** Gets the class alias of the root class to which the column refers to. - * @returns Returns the alias of root class the column refers to or an empty string if no class alias was specified in the select clause - */ public getRootClassAlias(): string; } /** - * The NativeECSqlValue class that is projected by IModelJsNative. * @hidden */ export declare class NativeECSqlValue { constructor(); - - /** Get information about the ECSQL SELECT clause column this value refers to. */ public getColumnInfo(): NativeECSqlColumnInfo; - public isNull(): boolean; - /** Get value as a BLOB. */ public getBlob(): Uint8Array; - /** Get value as boolean. */ public getBoolean(): boolean; - /** Get value as date time, formatted as ISO8601 string. */ public getDateTime(): string; - /** Get value as double. */ public getDouble(): number; - /** Get value as IGeometry formatted as JSON. */ public getGeometry(): string; - /** Get value as GUID, formatted as GUID string. */ public getGuid(): GuidString; - /** Get value as id, formatted as hexadecimal string. */ public getId(): Id64String; - /** If this ECSqlValue represents a class id, this method returns the fully qualified class name. */ public getClassNameForClassId(): string; - /** Get value as int. */ public getInt(): number; - /** Get value as int64. This method does not deal with JS accuracy issues of int64 values greater than 2^53. */ public getInt64(): number; - /** Get value as Point2d. */ public getPoint2d(): { x: number, y: number }; - /** Get value as Point3d. */ public getPoint3d(): { x: number, y: number, z: number }; - /** Get value as string. */ public getString(): string; - /** Get value as Navigation property value. */ public getNavigation(): { id: Id64String, relClassName?: string }; - - /** Get an iterator for iterating the struct members of this struct value. */ public getStructIterator(): NativeECSqlValueIterator; - /** Get an iterator for iterating the array elements of this array value. */ public getArrayIterator(): NativeECSqlValueIterator; } -/** - * The NativeECSqlValueIterator class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECSqlValueIterator { constructor(); - /** - * Move the iterator to the next ECSqlValue. - * @returns Returns true if the iterator now points to the next element. Returns false if the iterator reached the end. - */ public moveNext(): boolean; - /** - * Get the ECSqlValue the iterator is currently pointing to. - */ public getCurrent(): NativeECSqlValue; } -/** - * The NativeSqliteStatement class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeSqliteStatement implements IDisposable { constructor(); - - /** - * Prepare a SQLite SQL statement. - * @param db The NativeDgnDb or NativeECDb object - * @param sql The SQL to prepare - * @return Returns the Zero status in case of success. Non-zero error status in case of failure. The error's message property will contain additional information. - */ public prepare(db: NativeDgnDb | NativeECDb, sql: string): StatusCodeWithMessage; - - /** - * Indicates whether the prepared statement makes no **direct* changes to the content of the file - * or not. See [SQLite docs](https://www.sqlite.org/c3ref/stmt_readonly.html) for details. - * @return Returns True, if the statement is readonly. False otherwise. - */ public isReadonly(): boolean; - - /** Reset the statement to just before the first row. - * @return Returns non-zero error status in case of failure. - */ public reset(): DbResult; - - /** Dispose of the NativeSqliteStatement object - call this when finished stepping a statement, but only if the statement is not shared. */ public dispose(): void; - - /** Binds null to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @return non-zero error status in case of failure. - */ public bindNull(param: number | string): DbResult; - - /** Binds a BLOB to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param val BLOB value - * @return non-zero error status in case of failure. - */ public bindBlob(param: number | string, val: Uint8Array | ArrayBuffer | SharedArrayBuffer): DbResult; - - /** Binds a double to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param val Double value - * @return non-zero error status in case of failure. - */ public bindDouble(param: number | string, val: number): DbResult; - - /** Binds an integer to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param val Integral value, either as number or as decimal or hexadecimal string (for the case - * where the integer is larger than the JS accuracy threshold) - * @return non-zero error status in case of failure. - */ public bindInteger(param: number | string, val: number | string): DbResult; - - /** Binds a string to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param val String value - * @return non-zero error status in case of failure. - */ public bindString(param: number | string, val: string): DbResult; - - /** Binds an Id, formatted as hexadecimal string, to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param hexStr Id, formatted as hexadecimal string - * @return non-zero error status in case of failure. - */ public bindId(param: number | string, hexStr: Id64String): DbResult; - - /** Binds a Guid, formatted as GUID string, to the specified SQL parameter. - * @param param Index (1-based) or name (without leading colon) of the parameter. - * @param guidStr GUID value - * @return non-zero error status in case of failure. - */ public bindGuid(param: number | string, guidStr: GuidString): DbResult; - - /** Clear the bindings of this statement. - * @return Returns a non-zero error status in case of failure. - */ public clearBindings(): DbResult; - - /** Step this statement to move to the next row. - * @return Returns BE_SQLITE_ROW if the step moved to a new row. Returns BE_SQLITE_DONE if the step failed because there is no next row. Another non-zero error status if step failed because of an error. - */ public step(): DbResult; - - /** - * Get the number of SQL columns in the result set after calling step on a SELECT statement. - * @return Returns the SQL value of the specified column for the current row - */ public getColumnCount(): number; - - /** - * Get the data type of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - * @return Returns the data type of the specified column for the current row (as values of the DbValueType enum in native BeSQLite) - */ public getColumnType(columnIndex: number): number; - - /** - * Get the name of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - * @return Returns the name of the specified column for the current row - */ public getColumnName(columnIndex: number): string; - - /** - * Indicates whether the value of the specified column for the current row is null or not. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - * @return Returns true if the value of the specified column for the current row is null. false otherwise. - */ public isValueNull(columnIndex: number): boolean; - /** - * Get value as a BLOB of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueBlob(columnIndex: number): Uint8Array; - - /** Get value as double of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueDouble(columnIndex: number): number; - /** Get value as integer of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueInteger(columnIndex: number): number; - /** Get value as string of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueString(columnIndex: number): string; - /** Get value as Id, formatted as hexadecimal string of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueId(columnIndex: number): Id64String; - /** Get value as Guid, formatted as string of the specified column for the current row. - * @param columnIndex Index (0-based) of the column in the SQL SELECT clause for which the value is to be retrieved. - */ public getValueGuid(columnIndex: number): GuidString; } -/** - * Status codes used by NativeECPresentationManager APIs. - * @hidden - */ +/** @hidden */ export const enum NativeECPresentationStatus { Success = 0, Error = 1, /** Base error */ InvalidArgument = Error + 1, /** Argument is invalid */ } -/** - * The NativeECPresentationManager class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECPresentationManager implements IDisposable { constructor(); - /** - * Sets up a ruleset locater that looks for rulesets in the specified directories - * @param directories Ruleset locations - */ public setupRulesetDirectories(directories: string[]): ErrorStatusOrResult; - /** - * Sets up a list of directories to lookup for localization files - * @param directories Localization-related files' locations - */ public setupLocaleDirectories(directories: string[]): ErrorStatusOrResult; - /** - * Set user setting value. - * @param rulesetId Id of the ruleset variable is associated with - * @param variableId Id of the variable - * @param type Type of the variable - * @param value Variable value - */ public setRulesetVariableValue(rulesetId: string, variableId: string, type: string, value: any): ErrorStatusOrResult; - /** - * Set ruleset variable value - * @param rulesetId Id of the ruleset variable is associated with - * @param variableId Id of the variable - * @param type Type of the variable - */ public getRulesetVariableValue(rulesetId: string, variableId: string, type: string): ErrorStatusOrResult; - /** - * Get serialized JSON string of available rulesets array - * @param rulesetId Id of the lookup rulesets - * @return Serialized JSON array of tuples [ruleset, hash] - */ public getRulesets(rulesetId: string): ErrorStatusOrResult; - /** - * Adds ruleset that can be used by NativeECPresentationManager - * @param serializedRuleset Serialized JSON string of a ruleset to be added - * @return Hash of the ruleset - */ public addRuleset(serializedRuleset: string): ErrorStatusOrResult; - /** - * Removes a ruleset - * @param rulesetId Id of a ruleset to be removed - * @param hash Hash of the ruleset to be removed - * @return True if removal was successful - */ public removeRuleset(rulesetId: string, hash: string): ErrorStatusOrResult; - /** - * Removes all rulesets - */ public clearRulesets(): ErrorStatusOrResult; - /** - * Handles an ECPresentation manager request - * @param db The db to run the request on - * @param options Serialized JSON object that contains parameters for the request - * @param callback Callback which is called with ECPresentation result to request - */ public handleRequest(db: NativeDgnDb, options: string, callback: (result: ErrorStatusOrResult) => void): void; - /** - * Terminates the presentation manager. - */ public dispose(): void; } -/** - * Some types used by the NativeECSchemaXmlContext class. - * @hidden - */ +/** @hidden */ export declare namespace NativeECSchemaXmlContext { interface SchemaKey { name: string; @@ -1067,10 +312,7 @@ export declare namespace NativeECSchemaXmlContext { type SchemaLocaterCallback = (key: SchemaKey, matchType: SchemaMatchType) => string | undefined | void; } -/** - * The NativeECSchemaXmlContext class that is projected by IModelJsNative. - * @hidden - */ +/** @hidden */ export declare class NativeECSchemaXmlContext { constructor(); public addSchemaPath(path: string): void; @@ -1085,10 +327,7 @@ export declare class SnapRequest { public cancelSnap(): void; } -/** - * Helper class for iModel.js tests to disable native assertions - * @hidden - */ +/** @hidden */ export declare class DisableNativeAssertions implements IDisposable { constructor(); public dispose(): void; diff --git a/core/backend/src/rpc-impl/IModelReadRpcImpl.ts b/core/backend/src/rpc-impl/IModelReadRpcImpl.ts index 9d09695..e13a67f 100644 --- a/core/backend/src/rpc-impl/IModelReadRpcImpl.ts +++ b/core/backend/src/rpc-impl/IModelReadRpcImpl.ts @@ -29,9 +29,9 @@ export class IModelReadRpcImpl extends RpcInterface implements IModelReadRpcInte return OpenIModelDbMemoizer.openIModelDb(activityContext, AccessToken.fromJson(accessToken)!, iModelToken, OpenParams.fixedVersion()); } - public close(accessToken: AccessToken, iModelToken: IModelToken): Promise { + public async close(accessToken: AccessToken, iModelToken: IModelToken): Promise { const activityContext = ActivityLoggingContext.current; activityContext.enter(); - IModelDb.find(iModelToken).close(activityContext, AccessToken.fromJson(accessToken)!); + await IModelDb.find(iModelToken).close(activityContext, AccessToken.fromJson(accessToken)!); return Promise.resolve(true); } diff --git a/core/backend/src/rpc-impl/OpenIModelDbMemoizer.ts b/core/backend/src/rpc-impl/OpenIModelDbMemoizer.ts index ef695c9..ecb03b4 100644 --- a/core/backend/src/rpc-impl/OpenIModelDbMemoizer.ts +++ b/core/backend/src/rpc-impl/OpenIModelDbMemoizer.ts @@ -47,24 +47,25 @@ export class OpenIModelDbMemoizer extends PromiseMemoizer { OpenIModelDbMemoizer._openIModelDbMemoizer = new OpenIModelDbMemoizer(); const { memoize: memoizeOpenIModelDb, deleteMemoized: deleteMemoizedOpenIModelDb } = OpenIModelDbMemoizer._openIModelDbMemoizer; - const qp = memoizeOpenIModelDb(actx, accessTokenObj!, iModelToken.contextId!, iModelToken.iModelId!, openParams, iModelVersion); + const openPromise = memoizeOpenIModelDb(actx, accessTokenObj!, iModelToken.contextId!, iModelToken.iModelId!, openParams, iModelVersion); - await BeDuration.wait(50); // Wait a little before issuing a pending response - this avoids a potentially expensive round trip for the case a briefcase was already downloaded + const waitPromise = BeDuration.wait(100); // Wait a little before issuing a pending response - this avoids a potentially expensive round trip for the case a briefcase was already downloaded. + await Promise.race([openPromise, waitPromise]); // This resolves as soon as either the open is completed or the wait time has expired. Prevents waiting un-necessarily if the open has already completed. - if (qp.isPending) { + if (openPromise.isPending) { Logger.logTrace(loggingCategory, "Issuing pending status in OpenIModelDbMemoizer.openIModelDb", () => (iModelToken)); throw new RpcPendingResponse(); } deleteMemoizedOpenIModelDb(actx, accessTokenObj!, iModelToken.contextId!, iModelToken.iModelId!, openParams, iModelVersion); - if (qp.isFulfilled) { + if (openPromise.isFulfilled) { Logger.logTrace(loggingCategory, "Completed open request in OpenIModelDbMemoizer.openIModelDb", () => (iModelToken)); - return qp.result!; + return openPromise.result!; } - assert(qp.isRejected); + assert(openPromise.isRejected); Logger.logTrace(loggingCategory, "Rejected open request in OpenIModelDbMemoizer.openIModelDb", () => (iModelToken)); - throw qp.error!; + throw openPromise.error!; } } diff --git a/core/backend/src/test/IModelTestUtils.ts b/core/backend/src/test/IModelTestUtils.ts index 645322b..d5cea6a 100644 --- a/core/backend/src/test/IModelTestUtils.ts +++ b/core/backend/src/test/IModelTestUtils.ts @@ -3,19 +3,21 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import { assert } from "chai"; -import { Logger, OpenMode, Id64, Id64String, IDisposable, ActivityLoggingContext } from "@bentley/bentleyjs-core"; -import { AccessToken, Config } from "@bentley/imodeljs-clients"; -import { SubCategoryAppearance, Code, CreateIModelProps, ElementProps, RpcManager, GeometricElementProps, IModel, IModelReadRpcInterface, RelatedElement, RpcConfiguration } from "@bentley/imodeljs-common"; +import { Logger, OpenMode, Id64, Id64String, IDisposable, ActivityLoggingContext, BeEvent } from "@bentley/bentleyjs-core"; +import { AccessToken, Config, ChangeSet } from "@bentley/imodeljs-clients"; +import { SubCategoryAppearance, Code, CreateIModelProps, ElementProps, RpcManager, GeometricElementProps, IModel, IModelReadRpcInterface, RelatedElement, RpcConfiguration, CodeProps } from "@bentley/imodeljs-common"; import { IModelHostConfiguration, IModelHost, BriefcaseManager, IModelDb, DefinitionModel, Model, Element, - InformationPartitionElement, SpatialCategory, IModelJsFs, IModelJsFsStats, PhysicalPartition, PhysicalModel, NativePlatformRegistry, + InformationPartitionElement, SpatialCategory, IModelJsFs, IModelJsFsStats, PhysicalPartition, PhysicalModel, NativePlatformRegistry, SubjectOwnsPartitionElements, } from "../backend"; import { DisableNativeAssertions as NativeDisableNativeAssertions } from "../imodeljs-native-platform-api"; import { KnownTestLocations } from "./KnownTestLocations"; -import { TestIModelInfo } from "./MockAssetUtil"; import { HubUtility, UserCredentials } from "./integration/HubUtility"; -import { TestConfig } from "./TestConfig"; import * as path from "path"; +import { Schema, Schemas } from "../Schema"; +import { ElementDrivesElement, RelationshipProps } from "../Relationship"; +import { PhysicalElement } from "../Element"; +import { ClassRegistry } from "../ClassRegistry"; const actx = new ActivityLoggingContext(""); @@ -33,6 +35,33 @@ export class Timer { } } +export class TestIModelInfo { + private _name: string; + private _id: string; + private _localReadonlyPath: string; + private _localReadWritePath: string; + private _changeSets: ChangeSet[]; + + constructor(name: string) { + this._name = name; + this._id = ""; + this._localReadonlyPath = ""; + this._localReadWritePath = ""; + this._changeSets = []; + } + + get name(): string { return this._name; } + set name(name: string) { this._name = name; } + get id(): string { return this._id; } + set id(id: string) { this._id = id; } + get localReadonlyPath(): string { return this._localReadonlyPath; } + set localReadonlyPath(localReadonlyPath: string) { this._localReadonlyPath = localReadonlyPath; } + get localReadWritePath(): string { return this._localReadWritePath; } + set localReadWritePath(localReadWritePath: string) { this._localReadWritePath = localReadWritePath; } + get changeSets(): ChangeSet[] { return this._changeSets; } + set changeSets(changeSets: ChangeSet[]) { this._changeSets = changeSets; } +} + RpcConfiguration.developmentMode = true; Logger.initializeToConsole(); @@ -120,38 +149,41 @@ export class DisableNativeAssertions implements IDisposable { } } +export class TestBim extends Schema { } +export interface TestRelationshipProps extends RelationshipProps { + property1: string; +} +export class TestElementDrivesElement extends ElementDrivesElement implements TestRelationshipProps { + public property1!: string; + public static rootChanged = new BeEvent<(props: RelationshipProps, imodel: IModelDb) => void>(); + public static validateOutput = new BeEvent<(props: RelationshipProps, imodel: IModelDb) => void>(); + public static deletedDependency = new BeEvent<(props: RelationshipProps, imodel: IModelDb) => void>(); + public static onRootChanged(props: RelationshipProps, imodel: IModelDb): void { this.rootChanged.raiseEvent(props, imodel); } + public static onValidateOutput(props: RelationshipProps, imodel: IModelDb): void { this.validateOutput.raiseEvent(props, imodel); } + public static onDeletedDependency(props: RelationshipProps, imodel: IModelDb): void { this.deletedDependency.raiseEvent(props, imodel); } +} +export interface TestPhysicalObjectProps extends GeometricElementProps { + intProperty: number; +} +export class TestPhysicalObject extends PhysicalElement implements TestPhysicalObjectProps { + public intProperty!: number; + public static beforeOutputsHandled = new BeEvent<(id: Id64String, imodel: IModelDb) => void>(); + public static allInputsHandled = new BeEvent<(id: Id64String, imodel: IModelDb) => void>(); + public static onBeforeOutputsHandled(id: Id64String, imodel: IModelDb): void { this.beforeOutputsHandled.raiseEvent(id, imodel); } + public static onAllInputsHandled(id: Id64String, imodel: IModelDb): void { this.allInputsHandled.raiseEvent(id, imodel); } +} + export class IModelTestUtils { + public static async getTestModelInfo(accessToken: AccessToken, testProjectId: string, iModelName: string): Promise { + const iModelInfo = new TestIModelInfo(iModelName); + iModelInfo.id = await HubUtility.queryIModelIdByName(accessToken, testProjectId, iModelInfo.name); - // public static async createIModel(accessToken: AccessToken, projectId: string, name: string, seedFile: string) { - // try { - // const existingid = await HubUtility.queryIModelIdByName(accessToken, projectId, name); - // BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, projectId, existingid); - // } catch (_err) { - // } - // return BriefcaseManager.imodelClient.IModels().create(actx, accessToken, projectId, name, seedFile); - // } - - public static async setupIntegratedFixture(testIModels: TestIModelInfo[]): Promise { - const accessToken = await IModelTestUtils.getTestUserAccessToken(); - const testProjectId = await HubUtility.queryProjectIdByName(accessToken, TestConfig.projectName); const cacheDir = IModelHost.configuration!.briefcaseCacheDir; + iModelInfo.localReadonlyPath = path.join(cacheDir, iModelInfo.id, "readOnly"); + iModelInfo.localReadWritePath = path.join(cacheDir, iModelInfo.id, "readWrite"); - for (const iModelInfo of testIModels) { - iModelInfo.id = (await HubUtility.queryIModelIdByName(accessToken, testProjectId, iModelInfo.name)).toString(); - iModelInfo.localReadonlyPath = path.join(cacheDir, iModelInfo.id, "readOnly"); - iModelInfo.localReadWritePath = path.join(cacheDir, iModelInfo.id, "readWrite"); - - iModelInfo.changeSets = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, iModelInfo.id); - iModelInfo.changeSets.shift(); // The first change set is a schema change that was not named - - iModelInfo.localReadonlyPath = path.join(cacheDir, iModelInfo.id, "readOnly"); - iModelInfo.localReadWritePath = path.join(cacheDir, iModelInfo.id, "readWrite"); - - // Purge briefcases that are close to reaching the acquire limit - await HubUtility.purgeAcquiredBriefcases(accessToken, TestConfig.projectName, iModelInfo.name); - } - - return [accessToken, testProjectId, cacheDir]; + iModelInfo.changeSets = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, iModelInfo.id); + return iModelInfo; } public static async getTestUserAccessToken(userCredentials: any = TestUsers.regular): Promise { @@ -243,14 +275,12 @@ export class IModelTestUtils { } } - // // Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. - // - public static createAndInsertPhysicalPartition(testImodel: IModelDb, newModelCode: Code): Id64String { + public static createAndInsertPhysicalPartition(testImodel: IModelDb, newModelCode: CodeProps): Id64String { const modeledElementProps: ElementProps = { classFullName: PhysicalPartition.classFullName, iModel: testImodel, - parent: { id: IModel.rootSubjectId, relClassName: "BisCore:SubjectOwnsPartitionElements" }, + parent: new SubjectOwnsPartitionElements(IModel.rootSubjectId), model: IModel.repositoryModelId, code: newModelCode, }; @@ -258,9 +288,7 @@ export class IModelTestUtils { return testImodel.elements.insertElement(modeledElement); } - // // Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. - // public static createAndInsertPhysicalModel(testImodel: IModelDb, modeledElementRef: RelatedElement, privateModel: boolean = false): Id64String { const newModel = testImodel.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName, isPrivate: privateModel }); @@ -277,7 +305,7 @@ export class IModelTestUtils { // Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. // @return [modeledElementId, modelId] // - public static createAndInsertPhysicalPartitionAndModel(testImodel: IModelDb, newModelCode: Code, privateModel: boolean = false): Id64String[] { + public static createAndInsertPhysicalPartitionAndModel(testImodel: IModelDb, newModelCode: CodeProps, privateModel: boolean = false): Id64String[] { const eid = IModelTestUtils.createAndInsertPhysicalPartition(testImodel, newModelCode); const modeledElementRef = new RelatedElement({ id: eid }); const mid = IModelTestUtils.createAndInsertPhysicalModel(testImodel, modeledElementRef, privateModel); @@ -319,6 +347,14 @@ export class IModelTestUtils { public static startBackend() { const config = new IModelHostConfiguration(); IModelHost.startup(config); + Schemas.registerSchema(TestBim); + ClassRegistry.register(TestPhysicalObject, TestBim); + ClassRegistry.register(TestElementDrivesElement, TestBim); + } + + public static shutdownBackend() { + Schemas.unregisterSchema(TestBim.name); + IModelHost.shutdown(); } } diff --git a/core/backend/src/test/MockAssetUtil.ts b/core/backend/src/test/MockAssetUtil.ts index 71369c0..09413ea 100644 --- a/core/backend/src/test/MockAssetUtil.ts +++ b/core/backend/src/test/MockAssetUtil.ts @@ -11,8 +11,8 @@ import { BriefcaseManager, IModelHost } from "../backend"; import { AccessToken, ConnectClient, Project, IModelHubClient, WsgInstance, ECJsonTypeMap, Response, ChangeSet, HubIModel, Briefcase, SeedFile, InitializationState, - UserProfile, Version, IModelQuery, ChangeSetQuery, IModelHandler, BriefcaseHandler, - ChangeSetHandler, VersionHandler, VersionQuery, UserInfoHandler, UserInfoQuery, UserInfo, + UserInfo, Version, IModelQuery, ChangeSetQuery, IModelsHandler, BriefcaseHandler, + ChangeSetHandler, VersionHandler, VersionQuery, UserInfoHandler, UserInfoQuery, HubUserInfo, ConnectRequestQueryOptions, } from "@bentley/imodeljs-clients"; import { KnownLocations } from "../Platform"; @@ -40,8 +40,13 @@ const getTypedInstances = (typedConstructor: new () => T, /** Class to allow mocking of accessToken needed for various client operations */ export class MockAccessToken extends AccessToken { public constructor() { super(""); } - public getUserProfile(): UserProfile | undefined { - return new UserProfile("test", "user", "testuser001@mailinator.com", "596c0d8b-eac2-46a0-aa4a-b590c3314e7c", "Bentley", "fefac5b-bcad-488b-aed2-df27bffe5786", "1004144426", "US"); + public getUserInfo(): UserInfo | undefined { + const id = "596c0d8b-eac2-46a0-aa4a-b590c3314e7c"; + const email = { id: "testuser001@mailinator.com" }; + const profile = { firstName: "test", lastName: "user" }; + const organization = { id: "fefac5b-bcad-488b-aed2-df27bffe5786", name: "Bentley" }; + const featureTracking = { ultimateSite: "1004144426", usageCountryIso: "US" }; + return new UserInfo(id, email, profile, organization, featureTracking); } public toTokenString() { return ""; } } @@ -135,8 +140,8 @@ export class MockAssetUtil { cacheDir = path.normalize(path.join(KnownLocations.tmpdir, "Bentley/IModelJs/offlineCache/")); IModelHost.configuration!.briefcaseCacheDir = cacheDir; - MockAssetUtil.setupConnectClientMock(connectClientMock, assetDir); - MockAssetUtil.setupIModelHubClientMock(iModelHubClientMock, assetDir); + await MockAssetUtil.setupConnectClientMock(connectClientMock, assetDir); + await MockAssetUtil.setupIModelHubClientMock(iModelHubClientMock, assetDir); (BriefcaseManager as any)._defaultHubClient = iModelHubClientMock.object; @@ -150,7 +155,7 @@ export class MockAssetUtil { // Get test iModelIds from the mocked iModelHub client for (const iModelInfo of testIModels) { - const iModels = await iModelHubClientMock.object.IModels().get(actx, accessToken as any, testProjectId, new IModelQuery().byName(iModelInfo.name)); + const iModels = await iModelHubClientMock.object.iModels.get(actx, accessToken as any, testProjectId, new IModelQuery().byName(iModelInfo.name)); assert(iModels.length > 0, `No IModels returned from iModelHubClient mock for ${iModelInfo.name} iModel`); assert(!!iModels[0].id, `No IModelId returned for ${iModelInfo.name} iModel`); iModelInfo.id = iModels[0].id!.toString(); @@ -158,13 +163,13 @@ export class MockAssetUtil { iModelInfo.localReadWritePath = path.join(cacheDir, iModelInfo.id, "readWrite"); // getChangeSets - iModelInfo.changeSets = await iModelHubClientMock.object.ChangeSets().get(actx, accessToken as any, iModelInfo.id); + iModelInfo.changeSets = await iModelHubClientMock.object.changeSets.get(actx, accessToken as any, iModelInfo.id); iModelInfo.changeSets.shift(); // The first change set is a schema change that was not named assert.exists(iModelInfo.changeSets); // downloadChangeSets // const csetDir = path.join(cacheDir, iModelInfo.id, "csets"); - // await iModelHubClientMock.object.ChangeSets().download(iModelInfo.changeSets, csetDir); + // await iModelHubClientMock.object.changeSets.download(iModelInfo.changeSets, csetDir); } MockAssetUtil.verifyIModelInfo(testIModels); return testProjectId; @@ -177,8 +182,8 @@ export class MockAssetUtil { /** Setup functions for the ConnectClient mock */ public static async setupConnectClientMock(connectClientMock: TypeMoq.IMock, assetDir: string) { // For any parameters passed, grab the Sample Project json file from the assets folder and parse it into an instance - connectClientMock.setup((f: ConnectClient) => f.getProject(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, query: ConnectRequestQueryOptions) => { + connectClientMock.setup(async (f: ConnectClient) => f.getProject(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, query: ConnectRequestQueryOptions) => { for (const project of this._projectMap) { if (query.$filter!.toLocaleLowerCase().includes(project[1].toLocaleLowerCase())) { const assetPath = path.join(assetDir, "Projects", `${project[1]}.json`); @@ -198,21 +203,21 @@ export class MockAssetUtil { uploadSeedFileMock.object.mergedChangeSetId = ""; uploadSeedFileMock.object.initializationState = InitializationState.Successful; - const iModelHandlerMock = TypeMoq.Mock.ofType(IModelHandler); + const iModelsHandlerMock = TypeMoq.Mock.ofType(IModelsHandler); const briefcaseHandlerMock = TypeMoq.Mock.ofType(BriefcaseHandler); const changeSetHandlerMock = TypeMoq.Mock.ofType(ChangeSetHandler); const versionHandlerMock = TypeMoq.Mock.ofType(VersionHandler); const userInfoHandlerMock = TypeMoq.Mock.ofType(UserInfoHandler); // For any call with the specified iModel name, grab that iModel's json file and parse it into an instance - iModelHandlerMock.setup((f: IModelHandler) => f.create(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + iModelsHandlerMock.setup(async (f: IModelsHandler) => f.create(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyNumber())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, hubName: string, _path: string, _desc: string, + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, hubName: string, _path: string, _desc: string, _callback: ((progress: any) => void) | undefined, _timeOut: number) => { setTimeout(() => { }, 100); for (const pair of this._iModelMap) { @@ -228,10 +233,10 @@ export class MockAssetUtil { // For any call with request parameters contianing the iModel name, grab that iModel's json file // and parse it into an instance - iModelHandlerMock.setup((f: IModelHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + iModelsHandlerMock.setup(async (f: IModelsHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, query: IModelQuery) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, query: IModelQuery) => { let iModelPath: string = ""; if (query.getId()) { const testCaseName = this._iModelMap.get(query.getId()!.toString()); @@ -257,9 +262,9 @@ export class MockAssetUtil { // For any call with a specified iModelId, remove the specified iModel from the cache if it currently // resides there - iModelHandlerMock.setup((f: IModelHandler) => f.delete(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + iModelsHandlerMock.setup(async (f: IModelsHandler) => f.delete(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, iModelId: GuidString) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, _projId: string, iModelId: GuidString) => { const testCaseName = this._iModelMap.get(iModelId); if (testCaseName) { const iModelCacheDir = path.join(IModelHost.configuration!.briefcaseCacheDir, iModelId); @@ -272,9 +277,9 @@ export class MockAssetUtil { // For any call with a path containing a specified iModel name, grab the correct .bim asset and copy it // into the provided cache location - iModelHandlerMock.setup((f: IModelHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + iModelsHandlerMock.setup(async (f: IModelsHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, seedPathname: string) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, seedPathname: string) => { const iModelName = this._iModelMap.get(iModelId); if (iModelName) { const testModelPath = path.join(assetDir, iModelName, `${iModelName}.bim`); @@ -285,14 +290,14 @@ export class MockAssetUtil { body: undefined, }; return Promise.resolve(retResponse) - .then(() => Promise.resolve()); + .then(async () => Promise.resolve()); } return Promise.reject(`No matching asset found for iModel with id: ${iModelId}`); }); - briefcaseHandlerMock.setup((f: BriefcaseHandler) => f.create(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + briefcaseHandlerMock.setup(async (f: BriefcaseHandler) => f.create(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString) => { const iModelName = this._iModelMap.get(iModelId); if (iModelName) { const sampleBriefcasePath = path.join(assetDir, iModelName, `${iModelName}Briefcase.json`); @@ -303,9 +308,9 @@ export class MockAssetUtil { return Promise.reject(`No matching asset found for iModel with id: ${iModelId}`); }); - briefcaseHandlerMock.setup((f: BriefcaseHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + briefcaseHandlerMock.setup(async (f: BriefcaseHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString) => { const iModelName = this._iModelMap.get(iModelId); if (iModelName) { const sampleBriefcasePath = path.join(assetDir, iModelName, `${iModelName}Briefcase.json`); @@ -318,9 +323,9 @@ export class MockAssetUtil { // For any call with a specified iModelId, return a dummy briefcaseId. If future test cases demand so, we may // need to change this to return specific briefcaseIds - briefcaseHandlerMock.setup((f: BriefcaseHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + briefcaseHandlerMock.setup(async (f: BriefcaseHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, briefcase: Briefcase, outPath: string) => { + .returns(async (_alctx: ActivityLoggingContext, briefcase: Briefcase, outPath: string) => { const briefcaseName = briefcase.fileName!.slice(0, briefcase.fileName!.lastIndexOf(".bim")); let iModelName = ""; for (const pair of this._iModelMap) { @@ -338,18 +343,18 @@ export class MockAssetUtil { }); // Since the Hub is being mocked away, no action is necessary when deleting a briefacse - briefcaseHandlerMock.setup((f: BriefcaseHandler) => f.delete(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + briefcaseHandlerMock.setup(async (f: BriefcaseHandler) => f.delete(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyNumber())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, _iModelId: GuidString, _briefcaseId: number) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, _iModelId: GuidString, _briefcaseId: number) => { return Promise.resolve(); }); // For any call with a specified iModelId, grab the asset file with the associated changeset json objs // and parse them into instances - changeSetHandlerMock.setup((f: ChangeSetHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + changeSetHandlerMock.setup(async (f: ChangeSetHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, query: ChangeSetQuery) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, query: ChangeSetQuery) => { const iModelName = this._iModelMap.get(iModelId); if (iModelName) { const csetPath = path.join(assetDir, iModelName, `${iModelName}ChangeSets.json`); @@ -381,9 +386,9 @@ export class MockAssetUtil { // For any call with a path containing a specified iModel name, grab the associated change set files and copy them // into the provided cache location - changeSetHandlerMock.setup((f: ChangeSetHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + changeSetHandlerMock.setup(async (f: ChangeSetHandler) => f.download(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString())) - .returns((_alctx: ActivityLoggingContext, csets: ChangeSet[], outPath: string) => { + .returns(async (_alctx: ActivityLoggingContext, csets: ChangeSet[], outPath: string) => { for (const cset of csets) { const csetPath = path.join(outPath, cset.fileName!); if (!IModelJsFs.existsSync(csetPath)) @@ -395,13 +400,13 @@ export class MockAssetUtil { body: undefined, }; return Promise.resolve(retResponse) - .then(() => Promise.resolve()); + .then(async () => Promise.resolve()); }); - versionHandlerMock.setup((f: VersionHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + versionHandlerMock.setup(async (f: VersionHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, query: VersionQuery) => { + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, iModelId: GuidString, query: VersionQuery) => { const iModelName = this._iModelMap.get(iModelId); if (iModelName) { for (const versionName of this._versionNames) { @@ -417,10 +422,10 @@ export class MockAssetUtil { return Promise.reject(`No matching asset found for iModel with id: ${iModelId}`); }); - userInfoHandlerMock.setup((f: UserInfoHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + userInfoHandlerMock.setup(async (f: UserInfoHandler) => f.get(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((_alctx: ActivityLoggingContext, _tok: AccessToken, _iModelId: GuidString, _query: UserInfoQuery) => { - const user = new UserInfo(); + .returns(async (_alctx: ActivityLoggingContext, _tok: AccessToken, _iModelId: GuidString, _query: UserInfoQuery) => { + const user = new HubUserInfo(); user.firstName = "test"; user.lastName = "user"; user.email = "testuser001@mailinator.com"; @@ -428,10 +433,10 @@ export class MockAssetUtil { return Promise.resolve([user]); }); - iModelHubClientMock.setup((f: IModelHubClient) => f.IModels()).returns(() => iModelHandlerMock.object); - iModelHubClientMock.setup((f: IModelHubClient) => f.Briefcases()).returns(() => briefcaseHandlerMock.object); - iModelHubClientMock.setup((f: IModelHubClient) => f.ChangeSets()).returns(() => changeSetHandlerMock.object); - iModelHubClientMock.setup((f: IModelHubClient) => f.Versions()).returns(() => versionHandlerMock.object); - iModelHubClientMock.setup((f: IModelHubClient) => f.Users()).returns(() => userInfoHandlerMock.object); + iModelHubClientMock.setup((f: IModelHubClient) => f.iModels).returns(() => iModelsHandlerMock.object); + iModelHubClientMock.setup((f: IModelHubClient) => f.briefcases).returns(() => briefcaseHandlerMock.object); + iModelHubClientMock.setup((f: IModelHubClient) => f.changeSets).returns(() => changeSetHandlerMock.object); + iModelHubClientMock.setup((f: IModelHubClient) => f.versions).returns(() => versionHandlerMock.object); + iModelHubClientMock.setup((f: IModelHubClient) => f.users).returns(() => userInfoHandlerMock.object); } } diff --git a/core/backend/src/test/assets/TestBim.ecschema.xml b/core/backend/src/test/assets/TestBim.ecschema.xml index 7718c67..2fe5cc4 100644 --- a/core/backend/src/test/assets/TestBim.ecschema.xml +++ b/core/backend/src/test/assets/TestBim.ecschema.xml @@ -48,4 +48,18 @@ + + bis:ElementDrivesElement + + + + + + + + + + + + \ No newline at end of file diff --git a/core/backend/src/test/assets/TestFunctional.ecschema.xml b/core/backend/src/test/assets/TestFunctional.ecschema.xml index 0ab7272..0f97b52 100644 --- a/core/backend/src/test/assets/TestFunctional.ecschema.xml +++ b/core/backend/src/test/assets/TestFunctional.ecschema.xml @@ -3,6 +3,22 @@ + + + + + + + + + + Restriction1 + Restriction2 + Restriction3 + + + + func:FunctionalBreakdownElement diff --git a/core/backend/src/test/assets/TestSchema.ecschema.json b/core/backend/src/test/assets/TestSchema.ecschema.json index 2f77b99..4516080 100644 --- a/core/backend/src/test/assets/TestSchema.ecschema.json +++ b/core/backend/src/test/assets/TestSchema.ecschema.json @@ -1,1116 +1,1079 @@ { - "$schema":"https://dev.bentley.com/json_schemas/ec/31/draft-01/ecschema", - "alias":"ts", - "items":{ - "AbstractDerivedAbstract":{ - "baseClass":"TestSchema.AbstractEntityClass", - "description":"An abstract class derived from an abstract class", - "modifier":"abstract", - "schemaItemType":"EntityClass" + "$schema": "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + "alias": "ts", + "items": { + "AbstractDerivedAbstract": { + "baseClass": "TestSchema.AbstractEntityClass", + "description": "An abstract class derived from an abstract class", + "modifier": "Abstract", + "schemaItemType": "EntityClass" }, - "AbstractEntityClass":{ - "description":"Abstract class, cannot be instantiated", - "modifier":"abstract", - "properties":[ + "AbstractEntityClass": { + "description": "Abstract class, cannot be instantiated", + "modifier": "Abstract", + "properties": [ { - "name":"AbstractClassProperty1", - "propertyType":"PrimitiveProperty", - "typeName":"dateTime" + "name": "AbstractClassProperty1", + "type": "PrimitiveProperty", + "typeName": "dateTime" }, { - "direction":"backward", - "name":"FromAbstractToNormalClass", - "propertyType":"NavigationProperty", - "relationshipName":"TestSchema.NormalReferAbstractForward" + "direction": "Backward", + "name": "FromAbstractToNormalClass", + "type": "NavigationProperty", + "relationshipName": "TestSchema.NormalReferAbstractForward" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "BaseEntity":{ - "description":"Base Entity Description", - "label":"Base Entity", - "mixins":[ + "BaseEntity": { + "description": "Base Entity Description", + "label": "Base Entity", + "mixins": [ "TestSchema.MixinClass" ], - "modifier":"abstract", - "properties":[ + "modifier": "Abstract", + "properties": [ { - "name":"InheritedProperty", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "InheritedProperty", + "type": "PrimitiveProperty", + "typeName": "string" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "BaseEntityReferNormal":{ - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "BaseEntityReferNormal": { + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.BaseEntity" ], - "multiplicity":"(0..*)", - "polymorphic":true, - "roleLabel":"refers to" + "multiplicity": "(0..*)", + "polymorphic": true, + "roleLabel": "refers to" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "constraintClasses": [ "TestSchema.NormalEntityClass" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"is referred to by" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "is referred to by" } }, - "ClassCustomAttribute":{ - "appliesTo":"AnyClass", - "description":"Custom Attribute that can only be applied to classes.", - "properties":[ - { - "name":"Primitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "ClassCustomAttribute": { + "appliesTo": "AnyClass", + "description": "Custom Attribute that can only be applied to classes.", + "properties": [ + { + "name": "Primitive", + "type": "PrimitiveProperty", + "typeName": "string" } ], - "schemaItemType":"CustomAttributeClass" + "schemaItemType": "CustomAttributeClass" }, - "CustomAnyClassAttribute":{ - "appliesTo":"AnyClass", - "description":"Custom Attribute that can be applied to relationshipConstraint.", - "schemaItemType":"CustomAttributeClass" + "CustomAnyClassAttribute": { + "appliesTo": "AnyClass", + "description": "Custom Attribute that can be applied to relationshipConstraint.", + "schemaItemType": "CustomAttributeClass" }, - "CustomAnyPropertyAttribute":{ - "appliesTo":"AnyProperty", - "description":"Custom Attribute that can be applied to any of the property types.", - "schemaItemType":"CustomAttributeClass" + "CustomAnyPropertyAttribute": { + "appliesTo": "AnyProperty", + "description": "Custom Attribute that can be applied to any of the property types.", + "schemaItemType": "CustomAttributeClass" }, - "CustomArrayPropertyAttribute":{ - "appliesTo":"ArrayProperty", - "description":"Custom Attribute that can be applied to an ECArrayProperty.", - "schemaItemType":"CustomAttributeClass" + "CustomArrayPropertyAttribute": { + "appliesTo": "ArrayProperty", + "description": "Custom Attribute that can be applied to an ECArrayProperty.", + "schemaItemType": "CustomAttributeClass" }, - "CustomCustomAttributeClassAttribute":{ - "appliesTo":"CustomAttributeClass", - "description":"Custom Attribute that can be applied to an ECCustomAttributeClass.", - "schemaItemType":"CustomAttributeClass" + "CustomCustomAttributeClassAttribute": { + "appliesTo": "CustomAttributeClass", + "description": "Custom Attribute that can be applied to an ECCustomAttributeClass.", + "schemaItemType": "CustomAttributeClass" }, - "CustomEntityClassAttribute":{ - "appliesTo":"EntityClass", - "description":"Custom Attribute that can be applied to an ECEntityClass.", - "properties":[ + "CustomEntityClassAttribute": { + "appliesTo": "EntityClass", + "description": "Custom Attribute that can be applied to an ECEntityClass.", + "properties": [ { - "name":"EntityClassStringPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "EntityClassStringPrimitive", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "name":"EntityClassBinaryPrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"binary" + "name": "EntityClassBinaryPrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "binary" }, { - "name":"EntityClassDateTimePrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"dateTime" + "name": "EntityClassDateTimePrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "name":"EntityClassDoublePrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"double" + "name": "EntityClassDoublePrimitive", + "type": "PrimitiveProperty", + "typeName": "double" }, { - "name":"EntityClassIGeometryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "name": "EntityClassIGeometryPrimitive", + "type": "PrimitiveProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "name":"EntityClassIntPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "name": "EntityClassIntPrimitive", + "type": "PrimitiveProperty", + "typeName": "int" }, { - "name":"EntityClassLongPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "name": "EntityClassLongPrimitive", + "type": "PrimitiveProperty", + "typeName": "long" }, { - "name":"EntityClassPoint2dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point2d" + "name": "EntityClassPoint2dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point2d" }, { - "name":"EntityClassPoint3dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point3d" + "name": "EntityClassPoint3dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point3d" }, { - "name":"EntityClassIntEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.IntEnumeration" + "name": "EntityClassIntEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.IntEnumeration" }, { - "name":"EntityClassStringEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.StringEnumeration" + "name": "EntityClassStringEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.StringEnumeration" }, { - "name":"CAStructProperty", - "propertyType":"StructProperty", - "typeName":"TestSchema.DerivedStruct" + "name": "CAStructProperty", + "type": "StructProperty", + "typeName": "TestSchema.DerivedStruct" }, { - "minOccurs":0, - "name":"CAPrimitiveArrayProperty", - "propertyType":"PrimitiveArrayProperty", - "typeName":"string" + "minOccurs": 0, + "name": "CAPrimitiveArrayProperty", + "type": "PrimitiveArrayProperty", + "typeName": "string" }, { - "minOccurs":0, - "name":"CAStructArrayProperty", - "propertyType":"StructArrayProperty", - "typeName":"TestSchema.Struct" + "minOccurs": 0, + "name": "CAStructArrayProperty", + "type": "StructArrayProperty", + "typeName": "TestSchema.Struct" } ], - "schemaItemType":"CustomAttributeClass" + "schemaItemType": "CustomAttributeClass" }, - "CustomNavigationPropertyAttribute":{ - "appliesTo":"NavigationProperty", - "description":"Custom Attribute that can be applied to an ECNavigationProperty.", - "schemaItemType":"CustomAttributeClass" + "CustomNavigationPropertyAttribute": { + "appliesTo": "NavigationProperty", + "description": "Custom Attribute that can be applied to an ECNavigationProperty.", + "schemaItemType": "CustomAttributeClass" }, - "CustomPrimitivePropertyAttribute":{ - "appliesTo":"PrimitiveProperty", - "description":"Custom Attribute that can be applied to an ECProperty.", - "schemaItemType":"CustomAttributeClass" + "CustomPrimitivePropertyAttribute": { + "appliesTo": "PrimitiveProperty", + "description": "Custom Attribute that can be applied to an ECProperty.", + "schemaItemType": "CustomAttributeClass" }, - "CustomRelationshipClassAttribute":{ - "appliesTo":"RelationshipClass", - "description":"Custom Attribute that can be applied to an ECRelationshipClass.", - "schemaItemType":"CustomAttributeClass" + "CustomRelationshipClassAttribute": { + "appliesTo": "RelationshipClass", + "description": "Custom Attribute that can be applied to an ECRelationshipClass.", + "schemaItemType": "CustomAttributeClass" }, - "CustomRelationshipConstraintAttribute":{ - "appliesTo":"AnyRelationshipConstraint", - "description":"Custom Attribute that can be applied to relationshipConstraint.", - "schemaItemType":"CustomAttributeClass" + "CustomRelationshipConstraintAttribute": { + "appliesTo": "AnyRelationshipConstraint", + "description": "Custom Attribute that can be applied to relationshipConstraint.", + "schemaItemType": "CustomAttributeClass" }, - "CustomRelationshipConstraintAttributeSecond":{ - "appliesTo":"AnyRelationshipConstraint", - "description":"Custom Attribute that can be applied to relationshipConstraint.", - "schemaItemType":"CustomAttributeClass" + "CustomRelationshipConstraintAttributeSecond": { + "appliesTo": "AnyRelationshipConstraint", + "description": "Custom Attribute that can be applied to relationshipConstraint.", + "schemaItemType": "CustomAttributeClass" }, - "CustomStructArrayPropertyAttribute":{ - "appliesTo":"StructArrayProperty", - "description":"Custom Attribute that can be applied to an ECStructArrayProperty.", - "schemaItemType":"CustomAttributeClass" + "CustomStructArrayPropertyAttribute": { + "appliesTo": "StructArrayProperty", + "description": "Custom Attribute that can be applied to an ECStructArrayProperty.", + "schemaItemType": "CustomAttributeClass" }, - "CustomStructClassAttribute":{ - "appliesTo":"StructClass", - "description":"Custom Attribute that can be applied to an ECStructClass.", - "schemaItemType":"CustomAttributeClass" + "CustomStructClassAttribute": { + "appliesTo": "StructClass", + "description": "Custom Attribute that can be applied to an ECStructClass.", + "schemaItemType": "CustomAttributeClass" }, - "CustomStructPropertyAttribute":{ - "appliesTo":"StructProperty", - "description":"Custom Attribute that can be applied to an ECStructProperty.", - "schemaItemType":"CustomAttributeClass" + "CustomStructPropertyAttribute": { + "appliesTo": "StructProperty", + "description": "Custom Attribute that can be applied to an ECStructProperty.", + "schemaItemType": "CustomAttributeClass" }, - "DerivedAbstract":{ - "baseClass":"TestSchema.AbstractEntityClass", - "description":"A class derived from an abstract class", - "schemaItemType":"EntityClass" + "DerivedAbstract": { + "baseClass": "TestSchema.AbstractEntityClass", + "description": "A class derived from an abstract class", + "schemaItemType": "EntityClass" }, - "DerivedAbstractSecond":{ - "baseClass":"TestSchema.AbstractEntityClass", - "description":"A class derived from an abstract class", - "schemaItemType":"EntityClass" + "DerivedAbstractSecond": { + "baseClass": "TestSchema.AbstractEntityClass", + "description": "A class derived from an abstract class", + "schemaItemType": "EntityClass" }, - "DerivedMixin":{ - "appliesTo":"TestSchema.Entity", - "baseClass":"TestSchema.MixinClass", - "description":"A Mixin class derived from a Mixin class", - "properties":[ + "DerivedMixin": { + "appliesTo": "TestSchema.Entity", + "baseClass": "TestSchema.MixinClass", + "description": "A Mixin class derived from a Mixin class", + "properties": [ { - "name":"DerivedMixinStringPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "DerivedMixinStringPrimitive", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "name":"DerivedMixinBinaryPrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"binary" + "name": "DerivedMixinBinaryPrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "binary" }, { - "name":"DerivedMixinDateTimePrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"dateTime" + "name": "DerivedMixinDateTimePrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "name":"DerivedMixinDoublePrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"double" + "name": "DerivedMixinDoublePrimitive", + "type": "PrimitiveProperty", + "typeName": "double" }, { - "name":"DerivedMixinIGeometryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "name": "DerivedMixinIGeometryPrimitive", + "type": "PrimitiveProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "name":"DerivedMixinIntPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "name": "DerivedMixinIntPrimitive", + "type": "PrimitiveProperty", + "typeName": "int" }, { - "name":"DerivedMixinLongPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "name": "DerivedMixinLongPrimitive", + "type": "PrimitiveProperty", + "typeName": "long" }, { - "name":"DerivedMixinPoint2dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point2d" + "name": "DerivedMixinPoint2dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point2d" }, { - "name":"DerivedMixinPoint3dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point3d" + "name": "DerivedMixinPoint3dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point3d" } ], - "schemaItemType":"Mixin" + "schemaItemType": "Mixin" }, - "DerivedNormal":{ - "baseClass":"TestSchema.NormalEntityClass", - "description":"A class derived from a normal class", - "properties":[ - { - "name":"DerivedProperty1", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "DerivedNormal": { + "baseClass": "TestSchema.NormalEntityClass", + "description": "A class derived from a normal class", + "properties": [ + { + "name": "DerivedProperty1", + "type": "PrimitiveProperty", + "typeName": "string" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "DerivedNormalSecond":{ - "baseClass":"TestSchema.NormalEntityClass", - "description":"A class derived from a normal class", - "properties":[ - { - "name":"DerivedProperty2", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "DerivedNormalSecond": { + "baseClass": "TestSchema.NormalEntityClass", + "description": "A class derived from a normal class", + "properties": [ + { + "name": "DerivedProperty2", + "type": "PrimitiveProperty", + "typeName": "long" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "DerivedRelationshipClass":{ - "baseClass":"TestSchema.NormalReferMixinForward", - "modifier":"sealed", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "DerivedRelationshipClass": { + "baseClass": "TestSchema.NormalReferMixinForward", + "modifier": "Sealed", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.DerivedNormal" ], - "multiplicity":"(1..1)", - "polymorphic":true, - "roleLabel":"references" + "multiplicity": "(1..1)", + "polymorphic": true, + "roleLabel": "references" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "abstractConstraint":"TestSchema.MixinClass", - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "abstractConstraint": "TestSchema.MixinClass", + "constraintClasses": [ "TestSchema.DerivedMixin" ], - "multiplicity":"(0..1)", - "polymorphic":false, - "roleLabel":"is referenced by" + "multiplicity": "(0..1)", + "polymorphic": false, + "roleLabel": "is referenced by" } }, - "DerivedStruct":{ - "baseClass":"TestSchema.Struct", - "label":"A Struct derived from a Struct", - "schemaItemType":"StructClass" + "DerivedStruct": { + "baseClass": "TestSchema.Struct", + "label": "A Struct derived from a Struct", + "schemaItemType": "StructClass" }, - "Entity":{ - "baseClass":"TestSchema.BaseEntity", - "mixins":[ + "Entity": { + "baseClass": "TestSchema.BaseEntity", + "mixins": [ "TestSchema.DerivedMixin" ], - "properties":[ + "properties": [ { - "description":"A property override.", - "name":"InheritedProperty", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "description": "A property override.", + "name": "InheritedProperty", + "type": "PrimitiveProperty", + "typeName": "string" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "EntityEmbedNormalBackward":{ - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "EntityEmbedNormalBackward": { + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.Entity" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"is embedded by" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "is embedded by" }, - "strength":"embedding", - "strengthDirection":"backward", - "target":{ - "constraintClasses":[ + "strength": "Embedding", + "strengthDirection": "Backward", + "target": { + "constraintClasses": [ "TestSchema.NormalEntityClass" ], - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomRelationshipConstraintAttribute" + "className": "TestSchema.CustomRelationshipConstraintAttribute" }, { - "className":"TestSchema.CustomRelationshipConstraintAttributeSecond" + "className": "TestSchema.CustomRelationshipConstraintAttributeSecond" } ], - "multiplicity":"(1..*)", - "polymorphic":true, - "roleLabel":"embeds" + "multiplicity": "(1..*)", + "polymorphic": true, + "roleLabel": "embeds" } }, - "EntityReferDerivedNormal":{ - "baseClass":"TestSchema.BaseEntityReferNormal", - "modifier":"sealed", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "EntityReferDerivedNormal": { + "baseClass": "TestSchema.BaseEntityReferNormal", + "modifier": "Sealed", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.Entity" ], - "multiplicity":"(0..*)", - "polymorphic":true, - "roleLabel":"refers to" + "multiplicity": "(0..*)", + "polymorphic": true, + "roleLabel": "refers to" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "constraintClasses": [ "TestSchema.DerivedNormal" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"is referred to by" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "is referred to by" } }, - "EntityReferEntity":{ - "modifier":"sealed", - "properties":[ + "EntityReferEntity": { + "modifier": "Sealed", + "properties": [ { - "name":"RelationshipProperty", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "RelationshipProperty", + "type": "PrimitiveProperty", + "typeName": "string" } ], - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.Entity" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"references" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "references" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "constraintClasses": [ "TestSchema.Entity" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"is referenced by" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "is referenced by" } }, - "GeneralCustomAttribute":{ - "appliesTo":"Any", - "description":"Custom Attribute that can be applied to anything.", - "schemaItemType":"CustomAttributeClass" + "GeneralCustomAttribute": { + "appliesTo": "Any", + "description": "Custom Attribute that can be applied to anything.", + "schemaItemType": "CustomAttributeClass" }, - "IntEnumeration":{ - "backingTypeName":"int", - "description":"Int Enumeration", - "enumerators":[ + "IntEnumeration": { + "type": "int", + "description": "Int Enumeration", + "enumerators": [ { - "label":"First", - "value":1 + "name": "First", + "value": 1 }, { - "label":"Second", - "value":2 + "name": "Second", + "value": 2 }, { - "label":"Third", - "value":3 + "name": "Third", + "value": 3 } ], - "isStrict":true, - "label":"This is a display label.", - "schemaItemType":"Enumeration" + "isStrict": true, + "label": "This is a display label.", + "schemaItemType": "Enumeration" }, - "KindOfQuantity":{ - "description":"Kind of Quantity Description", - "label":"Kind of Quantity", - "persistenceUnit":{ - "format":"DefaultReal", - "unit":"CM" - }, - "precision":0.001, - "presentationUnits":[ - { - "format":"DefaultReal", - "unit":"FT" - }, - { - "format":"DefaultReal", - "unit":"IN" - } - ], - "schemaItemType":"KindOfQuantity" - }, - "MixinClass":{ - "appliesTo":"TestSchema.BaseEntity", - "description":"A Mixin is designed to avoid issues with multiple inheritance", - "properties":[ + "MixinClass": { + "appliesTo": "TestSchema.BaseEntity", + "description": "A Mixin is designed to avoid issues with multiple inheritance", + "properties": [ { - "name":"MixinStringPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "MixinStringPrimitive", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "name":"MixinBinaryPrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"binary" + "name": "MixinBinaryPrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "binary" }, { - "name":"MixinDateTimePrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"dateTime" + "name": "MixinDateTimePrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "name":"MixinDoublePrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"double" + "name": "MixinDoublePrimitive", + "type": "PrimitiveProperty", + "typeName": "double" }, { - "name":"MixinIGeometryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "name": "MixinIGeometryPrimitive", + "type": "PrimitiveProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "name":"MixinIntPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "name": "MixinIntPrimitive", + "type": "PrimitiveProperty", + "typeName": "int" }, { - "name":"MixinLongPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "name": "MixinLongPrimitive", + "type": "PrimitiveProperty", + "typeName": "long" }, { - "name":"MixinPoint2dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point2d" + "name": "MixinPoint2dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point2d" }, { - "name":"MixinPoint3dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point3d" + "name": "MixinPoint3dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point3d" }, { - "name":"MixinIntEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.IntEnumeration" + "name": "MixinIntEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.IntEnumeration" }, { - "name":"MixinStringEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.StringEnumeration" + "name": "MixinStringEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.StringEnumeration" } ], - "schemaItemType":"Mixin" + "schemaItemType": "Mixin" }, - "NormalEntityClass":{ - "customAttributes":[ + "NormalEntityClass": { + "customAttributes": [ { - "className":"TestSchema.CustomEntityClassAttribute" + "className": "TestSchema.CustomEntityClassAttribute" }, { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" } ], - "description":"Normal class with no modifier", - "properties":[ + "description": "Normal class with no modifier", + "properties": [ { - "name":"Property1", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "name": "Property1", + "type": "PrimitiveProperty", + "typeName": "int" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "NormalReferAbstractBackward":{ - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "NormalReferAbstractBackward": { + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.NormalEntityClass" ], - "multiplicity":"(1..*)", - "polymorphic":true, - "roleLabel":"is referred to by" + "multiplicity": "(1..*)", + "polymorphic": true, + "roleLabel": "is referred to by" }, - "strength":"referencing", - "strengthDirection":"backward", - "target":{ - "abstractConstraint":"TestSchema.AbstractEntityClass", - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Backward", + "target": { + "abstractConstraint": "TestSchema.AbstractEntityClass", + "constraintClasses": [ "TestSchema.AbstractEntityClass", "TestSchema.AbstractDerivedAbstract", "TestSchema.DerivedAbstract", "TestSchema.DerivedAbstractSecond" ], - "multiplicity":"(0..1)", - "polymorphic":false, - "roleLabel":"refers to" + "multiplicity": "(0..1)", + "polymorphic": false, + "roleLabel": "refers to" } }, - "NormalReferAbstractForward":{ - "customAttributes":[ + "NormalReferAbstractForward": { + "customAttributes": [ { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" }, { - "className":"TestSchema.CustomRelationshipClassAttribute" + "className": "TestSchema.CustomRelationshipClassAttribute" } ], - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "abstractConstraint":"TestSchema.NormalEntityClass", - "constraintClasses":[ + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "abstractConstraint": "TestSchema.NormalEntityClass", + "constraintClasses": [ "TestSchema.DerivedNormal", "TestSchema.NormalEntityClass", "TestSchema.SealedDerivedNormal" ], - "multiplicity":"(1..1)", - "polymorphic":true, - "roleLabel":"refers to" + "multiplicity": "(1..1)", + "polymorphic": true, + "roleLabel": "refers to" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "abstractConstraint":"TestSchema.AbstractEntityClass", - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "abstractConstraint": "TestSchema.AbstractEntityClass", + "constraintClasses": [ "TestSchema.AbstractEntityClass" ], - "multiplicity":"(0..*)", - "polymorphic":false, - "roleLabel":"is referred to by" + "multiplicity": "(0..*)", + "polymorphic": false, + "roleLabel": "is referred to by" } }, - "NormalReferMixinForward":{ - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "NormalReferMixinForward": { + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.NormalEntityClass" ], - "multiplicity":"(0..*)", - "polymorphic":true, - "roleLabel":"references" + "multiplicity": "(0..*)", + "polymorphic": true, + "roleLabel": "references" }, - "strength":"referencing", - "strengthDirection":"forward", - "target":{ - "abstractConstraint":"TestSchema.MixinClass", - "constraintClasses":[ + "strength": "Referencing", + "strengthDirection": "Forward", + "target": { + "abstractConstraint": "TestSchema.MixinClass", + "constraintClasses": [ "TestSchema.DerivedMixin" ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"is referenced by" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "is referenced by" } }, - "PropertyCollection":{ - "customAttributes":[ + "PropertyCollection": { + "customAttributes": [ { - "Primitive":"General Value on Class", - "className":"TestSchema.ClassCustomAttribute" + "Primitive": "General Value on Class", + "className": "TestSchema.ClassCustomAttribute" }, { - "className":"TestSchema.CustomAnyClassAttribute" + "className": "TestSchema.CustomAnyClassAttribute" } ], - "modifier":"sealed", - "properties":[ + "modifier": "Sealed", + "properties": [ { - "label":"Property Display Label", - "name":"PropCollectionBinaryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"binary" + "label": "Property Display Label", + "name": "PropCollectionBinaryPrimitive", + "type": "PrimitiveProperty", + "typeName": "binary" }, { - "description":"Property Description", - "name":"PropCollectionBooleanPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"boolean" + "description": "Property Description", + "name": "PropCollectionBooleanPrimitive", + "type": "PrimitiveProperty", + "typeName": "boolean" }, { - "name":"PropCollectionDateTimePrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"dateTime" + "name": "PropCollectionDateTimePrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "maxValue":9.6, - "minValue":2.3, - "name":"PropCollectionDoublePrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"double" + "maxValue": 9.6, + "minValue": 2.3, + "name": "PropCollectionDoublePrimitive", + "type": "PrimitiveProperty", + "typeName": "double" }, { - "name":"PropCollectionIGeometryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "name": "PropCollectionIGeometryPrimitive", + "type": "PrimitiveProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "kindOfQuantity":"TestSchema.KindOfQuantity", - "maxValue":10000, - "minValue":0, - "name":"PropCollectionIntPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "maxValue": 10000, + "minValue": 0, + "name": "PropCollectionIntPrimitive", + "type": "PrimitiveProperty", + "typeName": "int" }, { - "name":"PropCollectionLongPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "name": "PropCollectionLongPrimitive", + "type": "PrimitiveProperty", + "typeName": "long" }, { - "name":"PropCollectionPoint2dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point2d" + "name": "PropCollectionPoint2dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point2d" }, { - "name":"PropCollectionPoint3dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point3d" + "name": "PropCollectionPoint3dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point3d" }, { - "name":"PropCollectionStringPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "PropCollectionStringPrimitive", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "name":"IntEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.IntEnumeration" + "name": "IntEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.IntEnumeration" }, { - "name":"StringEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.StringEnumeration" + "name": "StringEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.StringEnumeration" }, { - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomPrimitivePropertyAttribute" + "className": "TestSchema.CustomPrimitivePropertyAttribute" }, { - "className":"TestSchema.CustomAnyPropertyAttribute" + "className": "TestSchema.CustomAnyPropertyAttribute" }, { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" } ], - "name":"PropertyWithCustomAttribute", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "PropertyWithCustomAttribute", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "customAttributes":[ - { - "ECExpression":"\"Primitve 10=\" & this.Primitive10", - "className":"Bentley_Standard_CustomAttributes.CalculatedECPropertySpecification" - } - ], - "name":"Calculated", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "minOccurs": 0, + "name": "LongArray", + "type": "PrimitiveArrayProperty", + "typeName": "long" }, { - "minOccurs":0, - "name":"LongArray", - "propertyType":"PrimitiveArrayProperty", - "typeName":"long" + "minOccurs": 1, + "name": "BinaryArray", + "type": "PrimitiveArrayProperty", + "typeName": "binary" }, { - "minOccurs":1, - "name":"BinaryArray", - "propertyType":"PrimitiveArrayProperty", - "typeName":"binary" - }, - { - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomArrayPropertyAttribute" + "className": "TestSchema.CustomArrayPropertyAttribute" }, { - "className":"TestSchema.CustomAnyPropertyAttribute" + "className": "TestSchema.CustomAnyPropertyAttribute" }, { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" } ], - "maxOccurs":10, - "minOccurs":0, - "name":"BooleanArray", - "propertyType":"PrimitiveArrayProperty", - "readOnly":true, - "typeName":"boolean" + "maxOccurs": 10, + "minOccurs": 0, + "name": "BooleanArray", + "type": "PrimitiveArrayProperty", + "isReadOnly": true, + "typeName": "boolean" }, { - "maxOccurs":10, - "minOccurs":1, - "name":"DateTimeArray", - "propertyType":"PrimitiveArrayProperty", - "readOnly":true, - "typeName":"dateTime" + "maxOccurs": 10, + "minOccurs": 1, + "name": "DateTimeArray", + "type": "PrimitiveArrayProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "maxOccurs":5, - "minOccurs":5, - "name":"DoubleArray", - "propertyType":"PrimitiveArrayProperty", - "typeName":"double" + "maxOccurs": 5, + "minOccurs": 5, + "name": "DoubleArray", + "type": "PrimitiveArrayProperty", + "typeName": "double" }, { - "maxOccurs":100, - "minOccurs":2, - "name":"IGeometryArray", - "propertyType":"PrimitiveArrayProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "maxOccurs": 100, + "minOccurs": 2, + "name": "IGeometryArray", + "type": "PrimitiveArrayProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "kindOfQuantity":"TestSchema.KindOfQuantity", - "minOccurs":0, - "name":"IntArray", - "propertyType":"PrimitiveArrayProperty", - "readOnly":true, - "typeName":"int" + "minOccurs": 0, + "name": "IntArray", + "type": "PrimitiveArrayProperty", + "isReadOnly": true, + "typeName": "int" }, { - "maxOccurs":3, - "minOccurs":3, - "name":"Point2dArray", - "propertyType":"PrimitiveArrayProperty", - "typeName":"point2d" + "maxOccurs": 3, + "minOccurs": 3, + "name": "Point2dArray", + "type": "PrimitiveArrayProperty", + "typeName": "point2d" }, { - "minOccurs":0, - "name":"Point3dArray", - "propertyType":"PrimitiveArrayProperty", - "readOnly":true, - "typeName":"point3d" + "minOccurs": 0, + "name": "Point3dArray", + "type": "PrimitiveArrayProperty", + "isReadOnly": true, + "typeName": "point3d" }, { - "maxOccurs":15, - "minOccurs":1, - "name":"StringArray", - "propertyType":"PrimitiveArrayProperty", - "readOnly":true, - "typeName":"string" + "maxOccurs": 15, + "minOccurs": 1, + "name": "StringArray", + "type": "PrimitiveArrayProperty", + "isReadOnly": true, + "typeName": "string" }, { - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomStructPropertyAttribute" + "className": "TestSchema.CustomStructPropertyAttribute" }, { - "className":"TestSchema.CustomAnyPropertyAttribute" + "className": "TestSchema.CustomAnyPropertyAttribute" }, { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" } ], - "name":"Struct1", - "propertyType":"StructProperty", - "typeName":"TestSchema.Struct" + "name": "Struct1", + "type": "StructProperty", + "typeName": "TestSchema.Struct" }, { - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomStructArrayPropertyAttribute" + "className": "TestSchema.CustomStructArrayPropertyAttribute" }, { - "className":"TestSchema.CustomAnyPropertyAttribute" + "className": "TestSchema.CustomAnyPropertyAttribute" }, { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" } ], - "minOccurs":0, - "name":"StructArray", - "propertyType":"StructArrayProperty", - "typeName":"TestSchema.Struct" + "minOccurs": 0, + "name": "StructArray", + "type": "StructArrayProperty", + "typeName": "TestSchema.Struct" }, { - "maxOccurs":100, - "minOccurs":1, - "name":"StructArraySecond", - "propertyType":"StructArrayProperty", - "typeName":"TestSchema.Struct" + "maxOccurs": 100, + "minOccurs": 1, + "name": "StructArraySecond", + "type": "StructArrayProperty", + "typeName": "TestSchema.Struct" }, { - "maxOccurs":4, - "minOccurs":4, - "name":"StructArrayThird", - "propertyType":"StructArrayProperty", - "typeName":"TestSchema.Struct" + "maxOccurs": 4, + "minOccurs": 4, + "name": "StructArrayThird", + "type": "StructArrayProperty", + "typeName": "TestSchema.Struct" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "SchemaCustomAttribute":{ - "appliesTo":"Schema", - "description":"Custom Attribute that can be applied to a schema.", - "schemaItemType":"CustomAttributeClass" + "SchemaCustomAttribute": { + "appliesTo": "Schema", + "description": "Custom Attribute that can be applied to a schema.", + "schemaItemType": "CustomAttributeClass" }, - "SealedDerivedAbstract":{ - "baseClass":"TestSchema.AbstractEntityClass", - "description":"A sealed class derived from an abstract class", - "modifier":"sealed", - "schemaItemType":"EntityClass" + "SealedDerivedAbstract": { + "baseClass": "TestSchema.AbstractEntityClass", + "description": "A sealed class derived from an abstract class", + "modifier": "Sealed", + "schemaItemType": "EntityClass" }, - "SealedDerivedNormal":{ - "baseClass":"TestSchema.NormalEntityClass", - "description":"A sealed class derived from a normal class", - "modifier":"sealed", - "schemaItemType":"EntityClass" + "SealedDerivedNormal": { + "baseClass": "TestSchema.NormalEntityClass", + "description": "A sealed class derived from a normal class", + "modifier": "Sealed", + "schemaItemType": "EntityClass" }, - "SealedEntityClass":{ - "description":"Instantiable, but cannot be used as base class", - "modifier":"sealed", - "properties":[ - { - "name":"SealedClassProperty1", - "propertyType":"PrimitiveProperty", - "typeName":"boolean" + "SealedEntityClass": { + "description": "Instantiable, but cannot be used as base class", + "modifier": "Sealed", + "properties": [ + { + "name": "SealedClassProperty1", + "type": "PrimitiveProperty", + "typeName": "boolean" } ], - "schemaItemType":"EntityClass" + "schemaItemType": "EntityClass" }, - "SealedHoldNormalBackward":{ - "modifier":"abstract", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "SealedHoldNormalBackward": { + "modifier": "Abstract", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.SealedEntityClass" ], - "multiplicity":"(0..1)", - "polymorphic":false, - "roleLabel":"is held by" + "multiplicity": "(0..1)", + "polymorphic": false, + "roleLabel": "is held by" }, - "strength":"holding", - "strengthDirection":"backward", - "target":{ - "constraintClasses":[ + "strength": "Holding", + "strengthDirection": "Backward", + "target": { + "constraintClasses": [ "TestSchema.NormalEntityClass" ], - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomRelationshipConstraintAttribute" + "className": "TestSchema.CustomRelationshipConstraintAttribute" }, { - "className":"TestSchema.CustomRelationshipConstraintAttributeSecond" + "className": "TestSchema.CustomRelationshipConstraintAttributeSecond" } ], - "multiplicity":"(0..1)", - "polymorphic":true, - "roleLabel":"holds" + "multiplicity": "(0..1)", + "polymorphic": true, + "roleLabel": "holds" } }, - "SealedHoldNormalForward":{ - "modifier":"sealed", - "schemaItemType":"RelationshipClass", - "source":{ - "constraintClasses":[ + "SealedHoldNormalForward": { + "modifier": "Sealed", + "schemaItemType": "RelationshipClass", + "source": { + "constraintClasses": [ "TestSchema.SealedEntityClass" ], - "multiplicity":"(1..1)", - "polymorphic":true, - "roleLabel":"holds" + "multiplicity": "(1..1)", + "polymorphic": true, + "roleLabel": "holds" }, - "strength":"holding", - "strengthDirection":"forward", - "target":{ - "abstractConstraint":"TestSchema.NormalEntityClass", - "constraintClasses":[ + "strength": "Holding", + "strengthDirection": "Forward", + "target": { + "abstractConstraint": "TestSchema.NormalEntityClass", + "constraintClasses": [ "TestSchema.NormalEntityClass", "TestSchema.DerivedNormal", "TestSchema.SealedDerivedNormal" ], - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.CustomRelationshipConstraintAttribute" + "className": "TestSchema.CustomRelationshipConstraintAttribute" }, { - "className":"TestSchema.CustomRelationshipConstraintAttributeSecond" + "className": "TestSchema.CustomRelationshipConstraintAttributeSecond" } ], - "multiplicity":"(0..*)", - "polymorphic":true, - "roleLabel":"is held by" + "multiplicity": "(0..*)", + "polymorphic": true, + "roleLabel": "is held by" } }, - "StringEnumeration":{ - "backingTypeName":"string", - "description":"String Enumeration", - "enumerators":[ + "StringEnumeration": { + "type": "string", + "description": "String Enumeration", + "enumerators": [ { - "label":"FirstSeason", - "value":"spring" + "name": "FirstSeason", + "value": "spring" }, { - "label":"SecondSeason", - "value":"summer" + "name": "SecondSeason", + "value": "summer" }, { - "label":"ThirdSeason", - "value":"fall" + "name": "ThirdSeason", + "value": "fall" }, { - "label":"FourthSeason", - "value":"winter" + "name": "FourthSeason", + "value": "winter" } ], - "isStrict":true, - "schemaItemType":"Enumeration" + "isStrict": true, + "schemaItemType": "Enumeration" }, - "Struct":{ - "customAttributes":[ + "Struct": { + "customAttributes": [ { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" }, { - "className":"TestSchema.CustomStructClassAttribute" + "className": "TestSchema.CustomStructClassAttribute" }, { - "className":"TestSchema.CustomAnyClassAttribute" + "className": "TestSchema.CustomAnyClassAttribute" } ], - "label":"Struct Class", - "properties":[ + "label": "Struct Class", + "properties": [ { - "name":"StructStringPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"string" + "name": "StructStringPrimitive", + "type": "PrimitiveProperty", + "typeName": "string" }, { - "name":"StructBinaryPrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"binary" + "name": "StructBinaryPrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "binary" }, { - "name":"StructDateTimePrimitive", - "propertyType":"PrimitiveProperty", - "readOnly":true, - "typeName":"dateTime" + "name": "StructDateTimePrimitive", + "type": "PrimitiveProperty", + "isReadOnly": true, + "typeName": "dateTime" }, { - "name":"StructDoublePrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"double" + "name": "StructDoublePrimitive", + "type": "PrimitiveProperty", + "typeName": "double" }, { - "name":"StructIGeometryPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"Bentley.Geometry.Common.IGeometry" + "name": "StructIGeometryPrimitive", + "type": "PrimitiveProperty", + "typeName": "Bentley.Geometry.Common.IGeometry" }, { - "name":"StructIntPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"int" + "name": "StructIntPrimitive", + "type": "PrimitiveProperty", + "typeName": "int" }, { - "name":"StructLongPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"long" + "name": "StructLongPrimitive", + "type": "PrimitiveProperty", + "typeName": "long" }, { - "name":"StructPoint2dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point2d" + "name": "StructPoint2dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point2d" }, { - "name":"StructPoint3dPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"point3d" + "name": "StructPoint3dPrimitive", + "type": "PrimitiveProperty", + "typeName": "point3d" }, { - "name":"StructIntEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.IntEnumeration" + "name": "StructIntEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.IntEnumeration" }, { - "name":"StructStringEnumerationPrimitive", - "propertyType":"PrimitiveProperty", - "typeName":"TestSchema.StringEnumeration" + "name": "StructStringEnumerationPrimitive", + "type": "PrimitiveProperty", + "typeName": "TestSchema.StringEnumeration" } ], - "schemaItemType":"StructClass" + "schemaItemType": "StructClass" }, - "UsingCustomAttributeClass":{ - "appliesTo":"Any", - "customAttributes":[ + "UsingCustomAttributeClass": { + "appliesTo": "Any", + "customAttributes": [ { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" }, { - "className":"TestSchema.CustomCustomAttributeClassAttribute" + "className": "TestSchema.CustomCustomAttributeClassAttribute" }, { - "className":"TestSchema.CustomAnyClassAttribute" + "className": "TestSchema.CustomAnyClassAttribute" } ], - "description":"Custom Attribute class that use an ECCustomAttributeClass", - "schemaItemType":"CustomAttributeClass" + "description": "Custom Attribute class that use an ECCustomAttributeClass", + "schemaItemType": "CustomAttributeClass" } }, - "customAttributes":[ + "customAttributes": [ { - "className":"TestSchema.GeneralCustomAttribute" + "className": "TestSchema.GeneralCustomAttribute" }, { - "className":"TestSchema.SchemaCustomAttribute" + "className": "TestSchema.SchemaCustomAttribute" } ], - "description":"Comprehensive Schema to demonstrate use of all ECSchema concepts.", - "label":"Comprehensive Schema", - "name":"TestSchema", - "references":[ - { - "name":"Bentley_Standard_CustomAttributes", - "version":"01.00.13" - }, + "description": "Comprehensive Schema to demonstrate use of all ECSchema concepts.", + "label": "Comprehensive Schema", + "name": "TestSchema", + "references": [ { - "name":"CoreCustomAttributes", - "version":"01.00.00" + "name": "CoreCustomAttributes", + "version": "01.00.01" } ], - "version":"01.00.00" + "version": "01.00.00" } \ No newline at end of file diff --git a/core/backend/src/test/assets/TestSchema.ecschema.xml b/core/backend/src/test/assets/TestSchema.ecschema.xml index d1efda5..a38c008 100644 --- a/core/backend/src/test/assets/TestSchema.ecschema.xml +++ b/core/backend/src/test/assets/TestSchema.ecschema.xml @@ -1,8 +1,9 @@ - - + - + + + @@ -32,46 +33,46 @@ - + - + - + - + - + - + - - - - + + + + - - - - - + + + + + - + - + - + BaseEntity @@ -89,7 +90,7 @@ - + Entity @@ -135,14 +136,14 @@ AbstractEntityClass - - MixinClass - + + MixinClass + - + - + @@ -155,29 +156,29 @@ - + - + - + - + Struct - + BaseEntity DerivedMixin - + - + General Value on Class - + @@ -185,7 +186,7 @@ - + @@ -194,57 +195,50 @@ - + - + - + - - - - "Primitve 10=" & this.Primitive10 - - - - + - + - + - + - + - + - + - + - + - + @@ -275,9 +269,9 @@ - + - + @@ -285,7 +279,7 @@ - + @@ -304,7 +298,7 @@ - + @@ -330,9 +324,9 @@ - + - + @@ -345,9 +339,9 @@ - + - + @@ -359,9 +353,9 @@ - + - + @@ -375,6 +369,4 @@ - diff --git a/core/backend/src/test/integration/ApplyChangeSets.test.ts b/core/backend/src/test/integration/ApplyChangeSets.test.ts index 927caba..12cd30d 100644 --- a/core/backend/src/test/integration/ApplyChangeSets.test.ts +++ b/core/backend/src/test/integration/ApplyChangeSets.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as path from "path"; import { assert } from "chai"; -import { OpenMode, ChangeSetApplyOption, ChangeSetStatus, Logger, LogLevel, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; +import { Logger, LogLevel, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; import { AccessToken } from "@bentley/imodeljs-clients"; import { IModelVersion } from "@bentley/imodeljs-common"; -import { IModelDb, ChangeSetToken, OpenParams } from "../../backend"; +import { IModelDb, OpenParams } from "../../backend"; import { IModelTestUtils } from "../IModelTestUtils"; import { HubUtility } from "./HubUtility"; import { KnownLocations } from "../../Platform"; @@ -26,41 +26,7 @@ describe("ApplyChangeSets (#integration)", () => { const testAllChangeSetOperations = async (accessToken: AccessToken, projectId: string, iModelId: GuidString) => { const iModelDir = path.join(iModelRootDir, iModelId.toString()); - - Logger.logInfo(HubUtility.logCategory, "Downloading seed file and all available change sets"); - await HubUtility.downloadIModelById(accessToken, projectId, iModelId, iModelDir); - - const seedPathname = HubUtility.getSeedPathname(iModelDir); - const iModelPathname = path.join(iModelDir, path.basename(seedPathname)); - - Logger.logInfo(HubUtility.logCategory, "Creating standalone iModel"); - HubUtility.createStandaloneIModel(iModelPathname, iModelDir); - const iModel: IModelDb = IModelDb.openStandalone(iModelPathname, OpenMode.ReadWrite); - - const changeSets: ChangeSetToken[] = HubUtility.readChangeSets(iModelDir); - - let status: ChangeSetStatus; - - // Logger.logInfo(HubUtility.logCategory, "Dumping all available change sets"); - // HubUtility.dumpStandaloneChangeSets(iModel, changeSets); - - Logger.logInfo(HubUtility.logCategory, "Merging all available change sets"); - status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Merge); - - if (status === ChangeSetStatus.Success) { - Logger.logInfo(HubUtility.logCategory, "Reversing all available change sets"); - changeSets.reverse(); - status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Reverse); - } - - if (status === ChangeSetStatus.Success) { - Logger.logInfo(HubUtility.logCategory, "Reinstating all available change sets"); - changeSets.reverse(); - status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Reinstate); - } - - iModel.closeStandalone(); - assert(status === ChangeSetStatus.Success, "Error applying change sets"); + return HubUtility.validateAllChangeSetOperations(accessToken, projectId, iModelId, iModelDir); }; const testOpen = async (accessToken: AccessToken, projectId: string, iModelId: string) => { @@ -78,22 +44,17 @@ describe("ApplyChangeSets (#integration)", () => { const accessToken = await IModelTestUtils.getTestUserAccessToken(); - let projectName = "iModelJsTest"; let iModelName = "ReadOnlyTest"; + let projectName = "iModelJsIntegrationTest"; let iModelName = "ReadOnlyTest"; let projectId = await HubUtility.queryProjectIdByName(accessToken, projectName); let iModelId = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); await testAllOperations(accessToken, projectId, iModelId); - projectName = "iModelJsTest"; iModelName = "ReadWriteTest"; - projectId = await HubUtility.queryProjectIdByName(accessToken, projectName); - iModelId = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); - await testAllOperations(accessToken, projectId, iModelId); - - projectName = "iModelJsTest"; iModelName = "NoVersionsTest"; + projectName = "iModelJsIntegrationTest"; iModelName = "ReadWriteTest"; projectId = await HubUtility.queryProjectIdByName(accessToken, projectName); iModelId = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); await testAllOperations(accessToken, projectId, iModelId); - projectName = "NodeJsTestProject"; iModelName = "TestModel"; + projectName = "iModelJsIntegrationTest"; iModelName = "NoVersionsTest"; projectId = await HubUtility.queryProjectIdByName(accessToken, projectName); iModelId = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); await testAllOperations(accessToken, projectId, iModelId); diff --git a/core/backend/src/test/integration/BriefcaseManager.test.ts b/core/backend/src/test/integration/BriefcaseManager.test.ts index 0197278..d587812 100644 --- a/core/backend/src/test/integration/BriefcaseManager.test.ts +++ b/core/backend/src/test/integration/BriefcaseManager.test.ts @@ -3,34 +3,27 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from "typemoq"; import { assert } from "chai"; import { IModelJsFs } from "../../IModelJsFs"; import { OpenMode, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; import { IModelVersion, IModelError, IModelStatus } from "@bentley/imodeljs-common"; -import { IModelTestUtils } from "../IModelTestUtils"; +import { IModelTestUtils, TestUsers, TestIModelInfo } from "../IModelTestUtils"; import { KeepBriefcase, IModelDb, OpenParams, AccessMode, ExclusiveAccessOption, Element, IModelHost, IModelHostConfiguration, BriefcaseManager, BriefcaseEntry } from "../../backend"; -import { TestIModelInfo, MockAssetUtil, MockAccessToken } from "../MockAssetUtil"; +import { AccessToken, BriefcaseQuery, Briefcase as HubBriefcase } from "@bentley/imodeljs-clients"; import { HubUtility } from "./HubUtility"; -import { AccessToken, ConnectClient, IModelHubClient, BriefcaseQuery, Briefcase as HubBriefcase } from "@bentley/imodeljs-clients"; +// import { Logger, LogLevel } from "@bentley/bentleyjs-core"; -describe.skip("BriefcaseManager", () => { - const index = process.argv.indexOf("--offline"); - const offline: boolean = process.argv[index + 1] === "mock"; +describe("BriefcaseManager (#integration)", () => { + let accessToken: AccessToken; let testProjectId: string; - const testIModels: TestIModelInfo[] = [ - new TestIModelInfo("ReadOnlyTest"), - new TestIModelInfo("ReadWriteTest"), - new TestIModelInfo("NoVersionsTest"), - ]; - const testVersionNames = ["FirstVersion", "SecondVersion", "ThirdVersion"]; - const testElementCounts = [27, 28, 29]; - - const assetDir = "./src/test/assets/_mocks_"; - let cacheDir: string = ""; - let accessToken: AccessToken = new MockAccessToken(); - const iModelHubClientMock = TypeMoq.Mock.ofType(IModelHubClient); - const connectClientMock = TypeMoq.Mock.ofType(ConnectClient); + + let readOnlyTestIModel: TestIModelInfo; + const readOnlyTestVersions = ["FirstVersion", "SecondVersion", "ThirdVersion"]; + const readOnlyTestElementCounts = [27, 28, 29]; + + let readWriteTestIModel: TestIModelInfo; + let noVersionsTestIModel: TestIModelInfo; + const actx = new ActivityLoggingContext(""); const getElementCount = (iModel: IModelDb): number => { @@ -45,30 +38,28 @@ describe.skip("BriefcaseManager", () => { assert.strictEqual(briefcase.getKey(), key, `Cached key ${key} doesn't match the current generated key ${briefcase.getKey()}`); if (briefcase.isOpen) { assert.strictEqual(briefcase.nativeDb.getParentChangeSetId(), briefcase.changeSetId, `Parent change set id of Db doesn't match what's cached in memory`); - if (briefcase.openParams!.accessMode === AccessMode.Shared) { - assert.isTrue(!briefcase.reversedChangeSetId, "Found a shared briefcase that was reversed!"); - assert.isTrue(!briefcase.nativeDb.getReversedChangeSetId(), "Found a shared briefcase that was reversed!"); - } } }); }; before(async () => { - if (offline) { - MockAssetUtil.setupMockAssets(assetDir); - testProjectId = await MockAssetUtil.setupOfflineFixture(accessToken, iModelHubClientMock, connectClientMock, assetDir, cacheDir, testIModels); - } else { - [accessToken, testProjectId, cacheDir] = await IModelTestUtils.setupIntegratedFixture(testIModels); - - // Clearing the briefcases for frontend tests here since the frontend is not setup with the CORS proxy. - await HubUtility.purgeAcquiredBriefcases(accessToken, "iModelJsTest", "ConnectionReadTest"); - } - - }); - - after(() => { - if (offline) - MockAssetUtil.tearDownOfflineFixture(); + accessToken = await HubUtility.login(TestUsers.regular); + + testProjectId = await HubUtility.queryProjectIdByName(accessToken, "iModelJsIntegrationTest"); + readOnlyTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadOnlyTest"); + noVersionsTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "NoVersionsTest"); + readWriteTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadWriteTest"); + + // Purge briefcases that are close to reaching the acquire limit + const managerAccessToken: AccessToken = await HubUtility.login(TestUsers.manager); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadOnlyTest"); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "NoVersionsTest"); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadWriteTest"); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ConnectionReadTest"); + + // Logger.initializeToConsole(); + // Logger.setLevelDefault(LogLevel.Warning); + // Logger.setLevel("Performance", LogLevel.Info); }); afterEach(() => { @@ -86,7 +77,7 @@ describe.skip("BriefcaseManager", () => { onOpenCalled = true; assert.deepEqual(accessTokenIn, accessToken); assert.equal(contextIdIn, testProjectId); - assert.equal(iModelIdIn, testIModels[0].id); + assert.equal(iModelIdIn, readOnlyTestIModel.id); assert.equal(openParams.openMode, OpenMode.Readonly); }; IModelDb.onOpen.addListener(onOpenListener); @@ -94,7 +85,7 @@ describe.skip("BriefcaseManager", () => { let onOpenedCalled: boolean = false; const onOpenedListener = (iModelDb: IModelDb) => { onOpenedCalled = true; - assert.equal(iModelDb.iModelToken.iModelId, testIModels[0].id); + assert.equal(iModelDb.iModelToken.iModelId, readOnlyTestIModel.id); }; IModelDb.onOpened.addListener(onOpenedListener); @@ -104,7 +95,7 @@ describe.skip("BriefcaseManager", () => { }; try { - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.latest()); assert.exists(iModel, "No iModel returned from call to BriefcaseManager.open"); iModel.onBeforeClose.addListener(onBeforeCloseListener); @@ -112,7 +103,7 @@ describe.skip("BriefcaseManager", () => { // Validate that the IModelDb is readonly assert(iModel.openParams.openMode === OpenMode.Readonly, "iModel not set to Readonly mode"); - const expectedChangeSetId = await IModelVersion.latest().evaluateChangeSet(actx, accessToken, testIModels[0].id, BriefcaseManager.imodelClient); + const expectedChangeSetId = await IModelVersion.latest().evaluateChangeSet(actx, accessToken, readOnlyTestIModel.id, BriefcaseManager.imodelClient); assert.strictEqual(iModel.briefcase.changeSetId, expectedChangeSetId); assert.strictEqual(iModel.iModelToken.changeSetId!, expectedChangeSetId); @@ -133,14 +124,14 @@ describe.skip("BriefcaseManager", () => { }); it("should reuse briefcases", async () => { - const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); + const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); assert.exists(iModel1, "No iModel returned from call to BriefcaseManager.open"); - const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); + const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); assert.exists(iModel2, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel1, iModel2, "previously open briefcase was expected to be shared"); - const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("SecondVersion")); + const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("SecondVersion")); assert.exists(iModel3, "No iModel returned from call to BriefcaseManager.open"); assert.notEqual(iModel3, iModel2, "opening two different versions should not cause briefcases to be shared when the older one is open"); assert.notEqual(iModel3.briefcase, iModel2.briefcase, "opening two different versions should not cause briefcases to be shared when the older one is open"); @@ -157,11 +148,11 @@ describe.skip("BriefcaseManager", () => { assert.equal(iModel3.briefcase, undefined); assert.isTrue(IModelJsFs.existsSync(pathname3)); - const iModel4: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); + const iModel4: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("FirstVersion")); assert.exists(iModel4, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel4.briefcase, briefcase2, "previously closed briefcase was expected to be shared"); - const iModel5: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("SecondVersion")); + const iModel5: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.named("SecondVersion")); assert.exists(iModel5, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel5.briefcase, briefcase3, "previously closed briefcase was expected to be shared"); @@ -174,25 +165,25 @@ describe.skip("BriefcaseManager", () => { it("should optionally reuse open briefcases for exclusive access (#integration)", async () => { // Note: Compare this with a similar test on the frontend - const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); + const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); assert.exists(iModel1, "No iModel returned from call to BriefcaseManager.open"); - const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); + const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); assert.exists(iModel2, "No iModel returned from call to BriefcaseManager.open"); - const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModel3, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel1, iModel2); assert.notEqual(iModel1.briefcase.pathname, iModel3.briefcase.pathname); - const iModel4: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); + const iModel4: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); assert.exists(iModel4, "No iModel returned from call to BriefcaseManager.open"); - const iModel5: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); + const iModel5: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(ExclusiveAccessOption.TryReuseOpenBriefcase), IModelVersion.latest()); assert.exists(iModel5, "No iModel returned from call to BriefcaseManager.open"); - const iModel6: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModel6: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModel6, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel4, iModel5); @@ -205,58 +196,48 @@ describe.skip("BriefcaseManager", () => { }); it("should not reuse exclusive read-only briefcases for read-write purposes (#integration)", async () => { - const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); assert.exists(iModel1, "No iModel returned from call to BriefcaseManager.open"); const pathname1 = iModel1.briefcase.pathname; - iModel1.close(actx, accessToken, KeepBriefcase.Yes); + await iModel1.close(actx, accessToken, KeepBriefcase.Yes); - const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(), IModelVersion.latest()); + const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(), IModelVersion.latest()); assert.exists(iModel2, "No iModel returned from call to BriefcaseManager.open"); assert.notEqual(iModel2.briefcase.pathname, pathname1); - iModel2.close(actx, accessToken, KeepBriefcase.No); + await iModel2.close(actx, accessToken, KeepBriefcase.No); - const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); assert.exists(iModel3, "No iModel returned from call to BriefcaseManager.open"); assert.equal(iModel3.briefcase.pathname, pathname1); - iModel3.close(actx, accessToken, KeepBriefcase.No); + await iModel3.close(actx, accessToken, KeepBriefcase.No); }); it("should open iModels of specific versions from the Hub", async () => { - const iModelFirstVersion: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.first()); + const iModelFirstVersion: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.first()); assert.exists(iModelFirstVersion); - assert.strictEqual(iModelFirstVersion.briefcase.changeSetId, ""); - assert.equal(+iModelFirstVersion.briefcase.nativeDb.getParentChangeSetId(), 0); - assert.isNotTrue(!!iModelFirstVersion.briefcase.reversedChangeSetId); - assert.isNotTrue(!!iModelFirstVersion.briefcase.nativeDb.getReversedChangeSetId()); + assert.strictEqual(iModelFirstVersion.briefcase.currentChangeSetId, ""); - for (const [arrayIndex, versionName] of testVersionNames.entries()) { - const iModelFromVersion = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.asOfChangeSet(testIModels[0].changeSets[arrayIndex].wsgId)); + for (const [arrayIndex, versionName] of readOnlyTestVersions.entries()) { + const iModelFromVersion = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.asOfChangeSet(readOnlyTestIModel.changeSets[arrayIndex + 1].wsgId)); assert.exists(iModelFromVersion); + assert.strictEqual(iModelFromVersion.briefcase.currentChangeSetId, readOnlyTestIModel.changeSets[arrayIndex + 1].wsgId); - assert.strictEqual(iModelFromVersion.briefcase.changeSetId, testIModels[0].changeSets[arrayIndex].wsgId); - assert.strictEqual(iModelFromVersion.briefcase.nativeDb.getParentChangeSetId(), testIModels[0].changeSets[arrayIndex].wsgId); - assert.isNotTrue(!!iModelFromVersion.briefcase.reversedChangeSetId); - assert.isNotTrue(!!iModelFromVersion.briefcase.nativeDb.getReversedChangeSetId()); - - const iModelFromChangeSet = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.named(versionName)); + const iModelFromChangeSet = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.named(versionName)); assert.exists(iModelFromChangeSet); - assert.strictEqual(iModelFromChangeSet, iModelFromVersion); - assert.strictEqual(iModelFromChangeSet.briefcase.changeSetId, testIModels[0].changeSets[arrayIndex].wsgId); - assert.strictEqual(iModelFromChangeSet.briefcase.nativeDb.getParentChangeSetId(), testIModels[0].changeSets[arrayIndex].wsgId); - assert.isNotTrue(!!iModelFromChangeSet.briefcase.reversedChangeSetId); - assert.isNotTrue(!!iModelFromChangeSet.briefcase.nativeDb.getReversedChangeSetId()); + assert.strictEqual(iModelFromChangeSet.briefcase.currentChangeSetId, readOnlyTestIModel.changeSets[arrayIndex + 1].wsgId); const elementCount = getElementCount(iModelFromVersion); - assert.equal(elementCount, testElementCounts[arrayIndex], `Count isn't what's expected for ${iModelFromVersion.briefcase.pathname}, version ${versionName}`); + assert.equal(elementCount, readOnlyTestElementCounts[arrayIndex], `Count isn't what's expected for ${iModelFromVersion.briefcase.pathname}, version ${versionName}`); await iModelFromVersion.close(actx, accessToken, KeepBriefcase.Yes); } - const iModelLatestVersion: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.latest()); + const iModelLatestVersion: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.latest()); assert.exists(iModelLatestVersion); - assert.strictEqual(iModelLatestVersion.briefcase.changeSetId, testIModels[0].changeSets[2].wsgId); - assert.strictEqual(iModelLatestVersion.briefcase.nativeDb.getParentChangeSetId(), testIModels[0].changeSets[2].wsgId); + assert.isUndefined(iModelLatestVersion.briefcase.reversedChangeSetId); + assert.strictEqual(iModelLatestVersion.briefcase.changeSetId, readOnlyTestIModel.changeSets[3].wsgId); + assert.strictEqual(iModelLatestVersion.briefcase.nativeDb.getParentChangeSetId(), readOnlyTestIModel.changeSets[3].wsgId); assert.isNotTrue(!!iModelLatestVersion.briefcase.reversedChangeSetId); await iModelFirstVersion.close(actx, accessToken, KeepBriefcase.No); @@ -264,28 +245,28 @@ describe.skip("BriefcaseManager", () => { }); it("should open an iModel with no versions", async () => { - const iModelNoVer: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[2].id, OpenParams.fixedVersion()); + const iModelNoVer: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, noVersionsTestIModel.id, OpenParams.fixedVersion()); assert.exists(iModelNoVer); - assert(iModelNoVer.iModelToken.iModelId && iModelNoVer.iModelToken.iModelId === testIModels[2].id, "Correct iModel not found"); + assert(iModelNoVer.iModelToken.iModelId && iModelNoVer.iModelToken.iModelId === noVersionsTestIModel.id, "Correct iModel not found"); }); it("should be able to pull or reverse changes only if allowed", async () => { - const secondChangeSetId = testIModels[0].changeSets[1].wsgId; + const secondChangeSetId = readOnlyTestIModel.changeSets[1].wsgId; - const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.asOfChangeSet(secondChangeSetId)); + const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.asOfChangeSet(secondChangeSetId)); assert.exists(iModelFixed); - assert.strictEqual(iModelFixed.briefcase.changeSetId, secondChangeSetId); + assert.strictEqual(iModelFixed.briefcase.currentChangeSetId, secondChangeSetId); - const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Shared), IModelVersion.asOfChangeSet(secondChangeSetId)); + const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Shared), IModelVersion.asOfChangeSet(secondChangeSetId)); assert.exists(iModelPullOnly); - assert.strictEqual(iModelPullOnly.briefcase.changeSetId, secondChangeSetId); + assert.strictEqual(iModelPullOnly.briefcase.currentChangeSetId, secondChangeSetId); assert.notStrictEqual(iModelPullOnly.briefcase.pathname, iModelFixed.briefcase.pathname, "pull only and fixed versions should not share the same briefcase"); - const thirdChangeSetId = testIModels[0].changeSets[2].wsgId; + const thirdChangeSetId = readOnlyTestIModel.changeSets[2].wsgId; await iModelPullOnly.pullAndMergeChanges(actx, accessToken, IModelVersion.asOfChangeSet(thirdChangeSetId)); - assert.strictEqual(iModelPullOnly.briefcase.changeSetId, thirdChangeSetId); + assert.strictEqual(iModelPullOnly.briefcase.currentChangeSetId, thirdChangeSetId); let exceptionThrown = false; try { @@ -294,10 +275,10 @@ describe.skip("BriefcaseManager", () => { exceptionThrown = true; } assert.isTrue(exceptionThrown); - assert.strictEqual(iModelFixed.briefcase.changeSetId, secondChangeSetId); + assert.strictEqual(iModelFixed.briefcase.currentChangeSetId, secondChangeSetId); try { - const firstChangeSetId = testIModels[0].changeSets[1].wsgId; + const firstChangeSetId = readOnlyTestIModel.changeSets[0].wsgId; await iModelFixed.reverseChanges(actx, accessToken, IModelVersion.asOfChangeSet(firstChangeSetId)); } catch (error) { exceptionThrown = true; @@ -309,14 +290,14 @@ describe.skip("BriefcaseManager", () => { }); it("should be able to edit and push only if it's allowed (#integration)", async () => { - const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); + const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); assert.exists(iModelFixed); let rootEl: Element = iModelFixed.elements.getRootSubject(); rootEl.userLabel = rootEl.userLabel + "changed"; assert.throws(() => iModelFixed.elements.updateElement(rootEl)); - const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullOnly(AccessMode.Shared), IModelVersion.latest()); + const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullOnly(AccessMode.Shared), IModelVersion.latest()); assert.exists(iModelPullOnly); rootEl = iModelPullOnly.elements.getRootSubject(); @@ -332,7 +313,7 @@ describe.skip("BriefcaseManager", () => { } assert.isTrue(exceptionThrown); - const iModelPullAndPush: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullAndPush(), IModelVersion.latest()); + const iModelPullAndPush: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullAndPush(), IModelVersion.latest()); assert.exists(iModelPullAndPush); rootEl = iModelPullAndPush.elements.getRootSubject(); @@ -347,27 +328,27 @@ describe.skip("BriefcaseManager", () => { }); it("should be able to allow exclusive access to iModels (#integration)", async () => { - const iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); + const iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); assert.exists(iModelShared); - const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.fixedVersion(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModelFixed: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.fixedVersion(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModelFixed); assert.notStrictEqual(iModelFixed.briefcase.pathname, iModelShared.briefcase.pathname); - const iModelFixed2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.fixedVersion(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModelFixed2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.fixedVersion(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModelFixed); assert.notStrictEqual(iModelFixed.briefcase.pathname, iModelFixed2.briefcase.pathname); - const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModelPullOnly: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModelPullOnly); assert.notStrictEqual(iModelPullOnly.briefcase.pathname, iModelShared.briefcase.pathname); assert.notStrictEqual(iModelPullOnly.briefcase.pathname, iModelFixed.briefcase.pathname); - const iModelPullOnly2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModelPullOnly2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModelPullOnly2); assert.notStrictEqual(iModelPullOnly2.briefcase.pathname, iModelPullOnly.briefcase.pathname); - const iModelPullAndPush: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullAndPush(), IModelVersion.latest()); + const iModelPullAndPush: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullAndPush(), IModelVersion.latest()); assert.exists(iModelPullAndPush); assert.notStrictEqual(iModelPullAndPush.briefcase.pathname, iModelShared.briefcase.pathname); assert.notStrictEqual(iModelPullAndPush.briefcase.pathname, iModelFixed.briefcase.pathname); @@ -380,11 +361,11 @@ describe.skip("BriefcaseManager", () => { }); it("should be able to reuse existing briefcases from a previous session (#integration)", async () => { - let iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); + let iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); assert.exists(iModelShared); const sharedPathname = iModelShared.briefcase.pathname; - let iModelExclusive: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(), IModelVersion.latest()); + let iModelExclusive: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(), IModelVersion.latest()); assert.exists(iModelExclusive); const exclusivePathname = iModelExclusive.briefcase.pathname; @@ -398,11 +379,11 @@ describe.skip("BriefcaseManager", () => { IModelHost.startup(); - iModelShared = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); + iModelShared = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); assert.exists(iModelShared); assert.strictEqual(iModelShared.briefcase.pathname, sharedPathname); - iModelExclusive = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(), IModelVersion.latest()); + iModelExclusive = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(), IModelVersion.latest()); assert.exists(iModelExclusive); assert.strictEqual(iModelExclusive.briefcase.pathname, exclusivePathname); @@ -413,12 +394,12 @@ describe.skip("BriefcaseManager", () => { it("should be able to gracefully error out if a bad cache dir is specified", async () => { const config = new IModelHostConfiguration(); config.briefcaseCacheDir = "\\\\blah\\blah\\blah"; - IModelHost.shutdown(); + IModelTestUtils.shutdownBackend(); IModelHost.startup(config); let exceptionThrown = false; try { - const iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); + const iModelShared: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared), IModelVersion.latest()); assert.notExists(iModelShared); } catch (error) { exceptionThrown = true; @@ -432,8 +413,8 @@ describe.skip("BriefcaseManager", () => { // The test fails matching access tokens - needs investigation. it.skip("Should track the AccessTokens that are used to open IModels (#integration)", async () => { - await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(AccessMode.Exclusive)); - assert.deepEqual(IModelDb.getAccessToken(testIModels[0].id), accessToken); + await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Exclusive)); + assert.deepEqual(IModelDb.getAccessToken(readOnlyTestIModel.id), accessToken); try { IModelDb.getAccessToken("--invalidid--"); @@ -444,19 +425,19 @@ describe.skip("BriefcaseManager", () => { }); it("should be able to reverse and reinstate changes (#integration)", async () => { - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(), IModelVersion.latest()); let arrayIndex: number; - for (arrayIndex = testVersionNames.length - 1; arrayIndex >= 0; arrayIndex--) { - await iModel.reverseChanges(actx, accessToken, IModelVersion.named(testVersionNames[arrayIndex])); - assert.equal(testElementCounts[arrayIndex], getElementCount(iModel)); + for (arrayIndex = readOnlyTestVersions.length - 1; arrayIndex >= 0; arrayIndex--) { + await iModel.reverseChanges(actx, accessToken, IModelVersion.named(readOnlyTestVersions[arrayIndex])); + assert.equal(readOnlyTestElementCounts[arrayIndex], getElementCount(iModel)); } await iModel.reverseChanges(actx, accessToken, IModelVersion.first()); - for (arrayIndex = 0; arrayIndex < testVersionNames.length; arrayIndex++) { - await iModel.reinstateChanges(actx, accessToken, IModelVersion.named(testVersionNames[arrayIndex])); - assert.equal(testElementCounts[arrayIndex], getElementCount(iModel)); + for (arrayIndex = 0; arrayIndex < readOnlyTestVersions.length; arrayIndex++) { + await iModel.reinstateChanges(actx, accessToken, IModelVersion.named(readOnlyTestVersions[arrayIndex])); + assert.equal(readOnlyTestElementCounts[arrayIndex], getElementCount(iModel)); } await iModel.reinstateChanges(actx, accessToken, IModelVersion.latest()); @@ -464,7 +445,7 @@ describe.skip("BriefcaseManager", () => { const briefcaseExistsOnHub = async (iModelId: GuidString, briefcaseId: number): Promise => { try { - const hubBriefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.Briefcases().get(actx, accessToken, iModelId, new BriefcaseQuery().byId(briefcaseId)); + const hubBriefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, iModelId, new BriefcaseQuery().byId(briefcaseId)); return (hubBriefcases.length > 0) ? true : false; } catch (e) { return false; @@ -472,25 +453,25 @@ describe.skip("BriefcaseManager", () => { }; it("should allow purging the cache and delete any acquired briefcases from the hub (#integration)", async () => { - const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModel1: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive, ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); assert.exists(iModel1, "No iModel returned from call to BriefcaseManager.open"); - const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModel2: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); const briefcaseId2: number = iModel2.briefcase.briefcaseId; - let exists = await briefcaseExistsOnHub(testIModels[0].id, briefcaseId2); + let exists = await briefcaseExistsOnHub(readOnlyTestIModel.id, briefcaseId2); assert.isTrue(exists); - const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); + const iModel3: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullAndPush(ExclusiveAccessOption.CreateNewBriefcase), IModelVersion.latest()); const briefcaseId3: number = iModel3.briefcase.briefcaseId; - exists = await briefcaseExistsOnHub(testIModels[0].id, briefcaseId3); + exists = await briefcaseExistsOnHub(readOnlyTestIModel.id, briefcaseId3); assert.isTrue(exists); await BriefcaseManager.purgeCache(actx, accessToken); - exists = await briefcaseExistsOnHub(testIModels[0].id, briefcaseId2); + exists = await briefcaseExistsOnHub(readOnlyTestIModel.id, briefcaseId2); assert.isFalse(exists); - exists = await briefcaseExistsOnHub(testIModels[0].id, briefcaseId3); + exists = await briefcaseExistsOnHub(readOnlyTestIModel.id, briefcaseId3); assert.isFalse(exists); }); diff --git a/core/backend/src/test/integration/ChangeSummary.test.ts b/core/backend/src/test/integration/ChangeSummary.test.ts index b74242d..6d6fe41 100644 --- a/core/backend/src/test/integration/ChangeSummary.test.ts +++ b/core/backend/src/test/integration/ChangeSummary.test.ts @@ -4,17 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as path from "path"; import { expect, assert } from "chai"; -import { OpenMode, DbResult, Id64String, Id64, PerfLogger, ChangeSetStatus, using, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; -import { AccessToken, ConnectClient, IModelHubClient, ChangeSet } from "@bentley/imodeljs-clients"; +import { OpenMode, DbResult, Id64String, Id64, PerfLogger, ChangeSetStatus, using, ActivityLoggingContext } from "@bentley/bentleyjs-core"; +import { AccessToken, ChangeSet } from "@bentley/imodeljs-clients"; import { IModelVersion, IModelStatus, ChangeOpCode, ChangedValueState } from "@bentley/imodeljs-common"; import { ChangeSummaryManager, ChangeSummary } from "../../ChangeSummaryManager"; import { BriefcaseManager } from "../../BriefcaseManager"; import { IModelDb, OpenParams, AccessMode } from "../../IModelDb"; -import { IModelTestUtils, DisableNativeAssertions } from "../IModelTestUtils"; +import { IModelTestUtils, DisableNativeAssertions, TestUsers, TestIModelInfo } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; import { IModelJsFs } from "../../IModelJsFs"; -import { TestIModelInfo, MockAssetUtil, MockAccessToken } from "../MockAssetUtil"; -import * as TypeMoq from "typemoq"; +import { HubUtility } from "./HubUtility"; function setupTest(iModelId: string): void { const cacheFilePath: string = BriefcaseManager.getChangeCachePathName(iModelId); @@ -22,41 +21,32 @@ function setupTest(iModelId: string): void { IModelJsFs.removeSync(cacheFilePath); } -describe.skip("ChangeSummary", () => { - const index = process.argv.indexOf("--offline"); - const offline: boolean = process.argv[index + 1] === "mock"; - let accessToken: AccessToken = new MockAccessToken(); +describe("ChangeSummary (#integration)", () => { + let accessToken: AccessToken; let testProjectId: string; - const iModelHubClientMock = TypeMoq.Mock.ofType(IModelHubClient); - const connectClientMock = TypeMoq.Mock.ofType(ConnectClient); - const testIModels: TestIModelInfo[] = [ - new TestIModelInfo("ReadOnlyTest"), - new TestIModelInfo("ReadWriteTest"), - new TestIModelInfo("NoVersionsTest"), - ]; - const assetDir = "./src/test/assets/_mocks_"; - let cacheDir: string; + + let readOnlyTestIModel: TestIModelInfo; + let readWriteTestIModel: TestIModelInfo; + const actx = new ActivityLoggingContext(""); before(async () => { - if (offline) { - await MockAssetUtil.setupMockAssets(assetDir); - testProjectId = await MockAssetUtil.setupOfflineFixture(accessToken, iModelHubClientMock, connectClientMock, assetDir, cacheDir, testIModels); - } else { - [accessToken, testProjectId, cacheDir] = await IModelTestUtils.setupIntegratedFixture(testIModels); - } - }); + accessToken = await HubUtility.login(TestUsers.regular); - after(() => { - if (offline) - MockAssetUtil.tearDownOfflineFixture(); + testProjectId = await HubUtility.queryProjectIdByName(accessToken, "iModelJsIntegrationTest"); + readOnlyTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadOnlyTest"); + readWriteTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadWriteTest"); + + // Purge briefcases that are close to reaching the acquire limit + const managerAccessToken: AccessToken = await HubUtility.login(TestUsers.manager); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadOnlyTest"); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadWriteTest"); }); it("Attach / Detach ChangeCache file to pullonly briefcase", async () => { - const testIModelId: string = testIModels[1].id; - setupTest(testIModelId); + setupTest(readWriteTestIModel.id); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullOnly(), IModelVersion.latest()); try { assert.exists(iModel); assert(iModel.openParams.openMode === OpenMode.ReadWrite); @@ -80,7 +70,7 @@ describe.skip("ChangeSummary", () => { assert.equal(row.csumcount, 0); }); - expect(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(testIModelId))); + expect(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(readWriteTestIModel.id))); ChangeSummaryManager.detachChangeCache(iModel); assert.isFalse(ChangeSummaryManager.isChangeCacheAttached(iModel)); @@ -103,10 +93,9 @@ describe.skip("ChangeSummary", () => { }); it("Attach / Detach ChangeCache file to readonly briefcase", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.latest()); assert.exists(iModel); assert(iModel.openParams.openMode === OpenMode.Readonly); try { @@ -128,7 +117,7 @@ describe.skip("ChangeSummary", () => { assert.equal(row.csumcount, 0); }); - expect(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(testIModelId))); + expect(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(readOnlyTestIModel.id))); ChangeSummaryManager.detachChangeCache(iModel); assert.isFalse(ChangeSummaryManager.isChangeCacheAttached(iModel)); @@ -151,10 +140,9 @@ describe.skip("ChangeSummary", () => { }); it("ECSqlStatementCache after detaching Changes Cache", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.latest()); assert.exists(iModel); assert(iModel.openParams.openMode === OpenMode.Readonly); try { @@ -179,10 +167,9 @@ describe.skip("ChangeSummary", () => { }); it("Attach / Detach ChangeCache file to closed imodel", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion(), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.latest()); await iModel.close(actx, accessToken); assert.exists(iModel); assert.throw(() => ChangeSummaryManager.isChangeCacheAttached(iModel)); @@ -191,10 +178,9 @@ describe.skip("ChangeSummary", () => { }); it("Extract ChangeSummaries", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); assert.exists(iModel); try { const summaryIds: Id64String[] = await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); @@ -233,15 +219,14 @@ describe.skip("ChangeSummary", () => { }); it("Extract ChangeSummary for single changeset", async () => { - const testIModelId: GuidString = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, testIModelId); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, readOnlyTestIModel.id); assert.isAtLeast(changeSets.length, 3); // extract summary for second changeset const changesetId: string = changeSets[1].wsgId; - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); try { assert.exists(iModel); await iModel.reverseChanges(actx, accessToken, IModelVersion.asOfChangeSet(changesetId)); @@ -250,7 +235,7 @@ describe.skip("ChangeSummary", () => { const summaryIds: Id64String[] = await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel, { currentVersionOnly: true }); assert.equal(summaryIds.length, 1); assert.isTrue(Id64.isValidId64(summaryIds[0])); - assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(testIModelId))); + assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(readOnlyTestIModel.id))); assert.exists(iModel); ChangeSummaryManager.attachChangeCache(iModel); assert.isTrue(ChangeSummaryManager.isChangeCacheAttached(iModel)); @@ -273,22 +258,21 @@ describe.skip("ChangeSummary", () => { }); it("Extracting ChangeSummaries for a range of changesets", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, testIModelId); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, readOnlyTestIModel.id); assert.isAtLeast(changeSets.length, 3); const startChangeSetId: string = changeSets[0].id!; const endChangeSetId: string = changeSets[1].id!; const startVersion: IModelVersion = IModelVersion.asOfChangeSet(startChangeSetId); const endVersion: IModelVersion = IModelVersion.asOfChangeSet(endChangeSetId); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(AccessMode.Exclusive), endVersion); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), endVersion); try { assert.exists(iModel); const summaryIds: Id64String[] = await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel, { startVersion }); assert.equal(summaryIds.length, 2); - assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(testIModelId))); + assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(readOnlyTestIModel.id))); assert.exists(iModel); ChangeSummaryManager.attachChangeCache(iModel); @@ -320,15 +304,14 @@ describe.skip("ChangeSummary", () => { }); it("Subsequent ChangeSummary extractions", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, testIModelId); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, readOnlyTestIModel.id); assert.isAtLeast(changeSets.length, 3); // first extraction: just first changeset const firstChangesetId: string = changeSets[0].id!; - let iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + let iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); try { assert.exists(iModel); await iModel.reverseChanges(actx, accessToken, IModelVersion.asOfChangeSet(firstChangesetId)); @@ -336,7 +319,7 @@ describe.skip("ChangeSummary", () => { // now extract change summary for that one changeset const summaryIds: Id64String[] = await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel, { currentVersionOnly: true }); assert.equal(summaryIds.length, 1); - assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(testIModelId))); + assert.isTrue(IModelJsFs.existsSync(BriefcaseManager.getChangeCachePathName(readOnlyTestIModel.id))); assert.exists(iModel); ChangeSummaryManager.attachChangeCache(iModel); @@ -358,7 +341,7 @@ describe.skip("ChangeSummary", () => { // now do second extraction for last changeset const lastChangesetId: string = changeSets[changeSets.length - 1].id!; await iModel.close(actx, accessToken); - iModel = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion(), IModelVersion.asOfChangeSet(lastChangesetId)); + iModel = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(), IModelVersion.asOfChangeSet(lastChangesetId)); // WIP not working yet until cache can be detached. // await iModel.pullAndMergeChanges(accessToken, IModelVersion.asOfChangeSet(lastChangesetId)); @@ -387,28 +370,27 @@ describe.skip("ChangeSummary", () => { }); it("Extract ChangeSummaries with invalid input", async () => { - const testIModelId: string = testIModels[0].id; - setupTest(testIModelId); + setupTest(readOnlyTestIModel.id); // extract on fixedVersion(exclusive access) iModel should fail - let iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion(AccessMode.Exclusive)); + let iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Exclusive)); try { assert.exists(iModel); - using(new DisableNativeAssertions(), async () => { + await using(new DisableNativeAssertions(), async () => { await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); }); } catch (e) { assert.isDefined(e.errorNumber); - assert.equal(e.errorNumber, ChangeSetStatus.CannotMergeIntoReadonly); + assert.equal(e.errorNumber, ChangeSetStatus.ApplyError); } finally { await iModel.close(actx, accessToken); } // extract on fixedVersion(shared access) iModel should fail - iModel = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion(AccessMode.Shared)); + iModel = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion(AccessMode.Shared)); try { assert.exists(iModel); - using(new DisableNativeAssertions(), async () => { + await using(new DisableNativeAssertions(), async () => { await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); }); } catch (e) { @@ -419,11 +401,11 @@ describe.skip("ChangeSummary", () => { } // extract on closed iModel should fail - iModel = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.fixedVersion()); + iModel = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion()); try { assert.exists(iModel); await iModel.close(actx, accessToken); - using(new DisableNativeAssertions(), async () => { + await using(new DisableNativeAssertions(), async () => { await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); }); } catch (e) { @@ -437,7 +419,7 @@ describe.skip("ChangeSummary", () => { assert.exists(iModel.briefcase); assert.isTrue(iModel.briefcase!.isStandalone); try { - using(new DisableNativeAssertions(), async () => { + await using(new DisableNativeAssertions(), async () => { await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); }); } catch (e) { @@ -449,11 +431,11 @@ describe.skip("ChangeSummary", () => { }); it("Query ChangeSummary content", async () => { - const testIModelId: string = testIModels[0].id; + const testIModelId: string = readOnlyTestIModel.id; setupTest(testIModelId); let perfLogger = new PerfLogger("IModelDb.open"); - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModelId, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly(AccessMode.Exclusive), IModelVersion.latest()); perfLogger.dispose(); try { await ChangeSummaryManager.extractChangeSummaries(actx, accessToken, iModel); @@ -477,7 +459,7 @@ describe.skip("ChangeSummary", () => { }); for (const changeSummary of changeSummaries) { - const filePath = path.join(outDir, "imodelid_" + testIModels[1].id + "_changesummaryid_" + changeSummary.id + ".changesummary.json"); + const filePath = path.join(outDir, "imodelid_" + readWriteTestIModel.id + "_changesummaryid_" + changeSummary.id + ".changesummary.json"); if (IModelJsFs.existsSync(filePath)) IModelJsFs.unlinkSync(filePath); diff --git a/core/backend/src/test/integration/DebugHubIssues.test.ts b/core/backend/src/test/integration/DebugHubIssues.test.ts index cf09188..af39fc9 100644 --- a/core/backend/src/test/integration/DebugHubIssues.test.ts +++ b/core/backend/src/test/integration/DebugHubIssues.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as path from "path"; import { assert } from "chai"; -import { OpenMode, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; +import { OpenMode, ActivityLoggingContext, GuidString, PerfLogger } from "@bentley/bentleyjs-core"; import { AccessToken, Config } from "@bentley/imodeljs-clients"; -import { IModelVersion } from "@bentley/imodeljs-common"; -import { IModelDb, OpenParams, IModelHost, IModelHostConfiguration } from "../../backend"; +import { IModel, IModelVersion } from "@bentley/imodeljs-common"; +import { IModelDb, OpenParams, IModelHost, IModelHostConfiguration, PhysicalModel } from "../../backend"; import { IModelTestUtils, TestUsers } from "../IModelTestUtils"; import { HubUtility } from "./HubUtility"; -import { IModelWriter } from "./IModelWriter"; import { IModelJsFs } from "../../IModelJsFs"; import { BriefcaseManager } from "../../BriefcaseManager"; +import { Logger, LogLevel } from "@bentley/bentleyjs-core"; // Useful utilities to download/upload test cases from/to the iModel Hub describe.skip("DebugHubIssues (#integration)", () => { @@ -21,7 +21,135 @@ describe.skip("DebugHubIssues (#integration)", () => { const actx = new ActivityLoggingContext(""); before(async () => { - accessToken = await HubUtility.login(TestUsers.super); + accessToken = await HubUtility.login(TestUsers.manager); + + Logger.initializeToConsole(); + Logger.setLevelDefault(LogLevel.Warning); + // Logger.setLevel("Performance", LogLevel.Info); + // Logger.setLevel("imodeljs-backend.BriefcaseManager", LogLevel.Trace); + // Logger.setLevel("imodeljs-backend.OpenIModelDb", LogLevel.Trace); + // Logger.setLevel("imodeljs-clients.Clients", LogLevel.Trace); + // Logger.setLevel("imodeljs-clients.imodelhub", LogLevel.Trace); + // Logger.setLevel("imodeljs-clients.Url", LogLevel.Trace); + }); + + it.skip("should be able to upload required test files to the Hub", async () => { + const projectName = "iModelJsIntegrationTest"; + + let iModelName = "ReadOnlyTest"; + let iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.pushIModelAndChangeSets(accessToken, projectName, iModelDir); + + iModelName = "ReadWriteTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.pushIModelAndChangeSets(accessToken, projectName, iModelDir); + + iModelName = "NoVersionsTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.pushIModelAndChangeSets(accessToken, projectName, iModelDir); + + iModelName = "ConnectionReadTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.pushIModelAndChangeSets(accessToken, projectName, iModelDir); + }); + + it.skip("should be able to download and backup required test files from the Hub", async () => { + const projectName = "iModelJsIntegrationTest"; + + let iModelName = "ReadOnlyTest"; + let iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + + iModelName = "ReadWriteTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + + iModelName = "NoVersionsTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + + iModelName = "ConnectionReadTest"; + iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + }); + + it.skip("should be able to open ReadOnlyTest model", async () => { + const projectName = "iModelJsTest"; + const iModelName = "ReadOnlyTest"; + + const myProjectId = await HubUtility.queryProjectIdByName(accessToken, projectName); + const myIModelId = await HubUtility.queryIModelIdByName(accessToken, myProjectId, iModelName); + + const iModel: IModelDb = await IModelDb.open(actx, accessToken, myProjectId, myIModelId.toString(), OpenParams.fixedVersion()); + assert.exists(iModel); + assert(iModel.openParams.openMode === OpenMode.Readonly); + + await iModel.close(actx, accessToken); + }); + + it.skip("should be able to validate change set operations", async () => { + const projectName = "iModelJsTest"; + const iModelName = "ReadOnlyTest"; + const myProjectId = await HubUtility.queryProjectIdByName(accessToken, projectName); + const myIModelId = await HubUtility.queryIModelIdByName(accessToken, myProjectId, iModelName); + + const link = `https://connect-imodelweb.bentley.com/imodeljs/?projectId=${myProjectId}&iModelId=${myIModelId}`; + console.log(`ProjectName: ${projectName}, iModelName: ${iModelName}, URL Link: ${link}`); // tslint:disable-line:no-console + + const iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.validateAllChangeSetOperations(accessToken, myProjectId, myIModelId, iModelDir); + }); + + it.skip("should be able to download the seed files, change sets, for UKRail_EWR2 (EWR_2E) model", async () => { + const projectName = "UKRail_EWR2"; + const iModelName = "EWR_2E"; + + const iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + }); + + it.skip("should be able to download the seed files, change sets, for 1MWCCN01 - North Project (SECTION_08_IM01) model", async () => { + const projectName = "1MWCCN01 - North Project"; + const iModelName = "SECTION_08_IM01"; + + const iModelDir = path.join(iModelRootDir, iModelName); + await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + }); + + it.skip("should be able to open UKRail_EWR2 (EWR_2E) model", async () => { + const projectName = "UKRail_EWR2"; + const iModelName = "EWR_2E"; + + const myProjectId = await HubUtility.queryProjectIdByName(accessToken, projectName); + const myIModelId = await HubUtility.queryIModelIdByName(accessToken, myProjectId, iModelName); + + const perfLogger = new PerfLogger("EWR_2E"); + + const iModel: IModelDb = await IModelDb.open(actx, accessToken, myProjectId, myIModelId.toString(), OpenParams.fixedVersion()); + assert.exists(iModel); + assert(iModel.openParams.openMode === OpenMode.Readonly); + + perfLogger.dispose(); + + await iModel.close(actx, accessToken); + }); + + it.skip("should be able to open 1MWCCN01 - North Project (SECTION_08_IM01) model", async () => { + const projectName = "1MWCCN01 - North Project"; + const iModelName = "SECTION_08_IM01"; + + const myProjectId = await HubUtility.queryProjectIdByName(accessToken, projectName); + const myIModelId = await HubUtility.queryIModelIdByName(accessToken, myProjectId, iModelName); + + const perfLogger = new PerfLogger("SECTION_08_IM01"); + + const iModel: IModelDb = await IModelDb.open(actx, accessToken, myProjectId, myIModelId.toString(), OpenParams.fixedVersion()); + assert.exists(iModel); + assert(iModel.openParams.openMode === OpenMode.Readonly); + + perfLogger.dispose(); + + await iModel.close(actx, accessToken); }); it.skip("create a test case on the Hub with a named version from a standalone iModel", async () => { @@ -36,36 +164,20 @@ describe.skip("DebugHubIssues (#integration)", () => { assert(!!iModelDb); // Create and upload a dummy change set to the Hub - const modelId = IModelWriter.insertPhysicalModel(iModelDb, "DummyTestModel"); + const modelId = PhysicalModel.insert(iModelDb, IModel.rootSubjectId, "DummyTestModel"); assert(!!modelId); iModelDb.saveChanges("Dummy change set"); await iModelDb.pushChanges(actx, accessToken!); // Create a named version on the just uploaded change set const changeSetId: string = await IModelVersion.latest().evaluateChangeSet(actx, accessToken, iModelId.toString(), BriefcaseManager.imodelClient); - await BriefcaseManager.imodelClient.Versions().create(actx, accessToken, iModelId, changeSetId, "DummyVersion", "Just a dummy version for testing with web navigator"); - }); - - it.skip("should be able to download the seed files, change sets, for any iModel on the Hub", async () => { - const projectName = "NodeJsTestProject"; - const iModelName = "TestModel"; - - const iModelDir = path.join(iModelRootDir, iModelName); - await HubUtility.downloadIModelByName(accessToken, projectName, iModelName, iModelDir); + await BriefcaseManager.imodelClient.versions.create(actx, accessToken, iModelId, changeSetId, "DummyVersion", "Just a dummy version for testing with web navigator"); }); it.skip("should be able to delete any iModel on the Hub", async () => { await HubUtility.deleteIModel(accessToken, "NodeJsTestProject", "TestModel"); }); - it.skip("should be able to upload seed files, change sets, for any iModel on the Hub", async () => { - const projectName = "iModelJsTest"; - const iModelName = "ReadWriteTest"; - - const iModelDir = path.join(iModelRootDir, iModelName); - await HubUtility.pushIModelAndChangeSets(accessToken, projectName, iModelDir); - }); - it.skip("should be able to open any iModel on the Hub", async () => { const projectName = "NodeJsTestProject"; const iModelName = "TestModel"; @@ -77,7 +189,7 @@ describe.skip("DebugHubIssues (#integration)", () => { assert.exists(iModel); assert(iModel.openParams.openMode === OpenMode.Readonly); - iModel.close(actx, accessToken); + await iModel.close(actx, accessToken); }); it.skip("should be able to create a change set from a standalone iModel)", async () => { @@ -102,7 +214,7 @@ describe.skip("DebugHubIssues (#integration)", () => { assert.exists(iModel); assert(iModel.openParams.openMode === OpenMode.Readonly); - iModel.close(actx, accessToken); + await iModel.close(actx, accessToken); }); it.skip("should be able to download the seed files, change sets, for any iModel on the Hub in PROD", async () => { diff --git a/core/backend/src/test/integration/HubUtility.ts b/core/backend/src/test/integration/HubUtility.ts index 848bbd8..30b7084 100644 --- a/core/backend/src/test/integration/HubUtility.ts +++ b/core/backend/src/test/integration/HubUtility.ts @@ -2,9 +2,9 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { AuthorizationToken, AccessToken, ImsActiveSecureTokenClient, ImsDelegationSecureTokenClient, UserProfile, IModelHubClient } from "@bentley/imodeljs-clients"; +import { AuthorizationToken, AccessToken, ImsActiveSecureTokenClient, ImsDelegationSecureTokenClient, HubUserInfo, IModelHubClient } from "@bentley/imodeljs-clients"; import { HubIModel, Project, IModelQuery, ChangeSet, ChangeSetQuery, Briefcase as HubBriefcase, ChangesType } from "@bentley/imodeljs-clients"; -import { ChangeSetApplyOption, OpenMode, ChangeSetStatus, Logger, assert, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; +import { ChangeSetApplyOption, OpenMode, ChangeSetStatus, Logger, assert, ActivityLoggingContext, GuidString, PerfLogger } from "@bentley/bentleyjs-core"; import { IModelJsFs, ChangeSetToken, BriefcaseManager, BriefcaseId, IModelDb } from "../../backend"; import * as path from "path"; @@ -118,14 +118,21 @@ export class HubUtility { const query = new ChangeSetQuery(); query.selectDownloadUrl(); - const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.ChangeSets().get(actx, accessToken, iModelId, query); + let perfLogger = new PerfLogger("HubUtility.downloadChangeSets -> Get ChangeSet Infos"); + const changeSets: ChangeSet[] = await BriefcaseManager.imodelClient.changeSets.get(actx, accessToken, iModelId, query); + perfLogger.dispose(); if (changeSets.length === 0) return new Array(); - await BriefcaseManager.imodelClient.ChangeSets().download(actx, changeSets, changeSetsPath); + perfLogger = new PerfLogger("HubUtility.downloadChangeSets -> Download ChangeSets"); + await BriefcaseManager.imodelClient.changeSets.download(actx, changeSets, changeSetsPath); + perfLogger.dispose(); return changeSets; } + /** Download an IModel's seed files and change sets from the Hub. + * A standard hierarchy of folders is created below the supplied downloadDir + */ public static async downloadIModelById(accessToken: AccessToken, projectId: string, iModelId: GuidString, downloadDir: string): Promise { // Recreate the download folder if necessary if (IModelJsFs.existsSync(downloadDir)) @@ -143,7 +150,9 @@ export class HubUtility { // Download the seed file const seedPathname = path.join(downloadDir, "seed", iModel.name!.concat(".bim")); - await BriefcaseManager.imodelClient.IModels().download(actx, accessToken, iModelId, seedPathname); + const perfLogger = new PerfLogger("HubUtility.downloadIModelById -> Download Seed File"); + await BriefcaseManager.imodelClient.iModels.download(actx, accessToken, iModelId, seedPathname); + perfLogger.dispose(); // Download the change sets const changeSetDir = path.join(downloadDir, "changeSets//"); @@ -154,7 +163,9 @@ export class HubUtility { IModelJsFs.writeFileSync(changeSetsJsonPathname, changeSetsJsonStr); } - /** Download an IModel's seed files and change sets from the Hub */ + /** Download an IModel's seed files and change sets from the Hub. + * A standard hierarchy of folders is created below the supplied downloadDir + */ public static async downloadIModelByName(accessToken: AccessToken, projectName: string, iModelName: string, downloadDir: string): Promise { const projectId: string = await HubUtility.queryProjectIdByName(accessToken, projectName); @@ -173,7 +184,48 @@ export class HubUtility { const projectId: string = await HubUtility.queryProjectIdByName(accessToken, projectName); const iModelId: GuidString = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, projectId, iModelId); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, projectId, iModelId); + } + + /** Validate all change set operations by downloading seed files & change sets, creating a standalone iModel, + * merging the change sets, reversing them, and finally reinstating them. The method also logs the necessary performance + * metrics with these operations. + */ + public static async validateAllChangeSetOperations(accessToken: AccessToken, projectId: string, iModelId: GuidString, iModelDir: string) { + Logger.logInfo(HubUtility.logCategory, "Downloading seed file and all available change sets"); + await HubUtility.downloadIModelById(accessToken, projectId, iModelId, iModelDir); + + const seedPathname = HubUtility.getSeedPathname(iModelDir); + const iModelPathname = path.join(iModelDir, path.basename(seedPathname)); + + Logger.logInfo(HubUtility.logCategory, "Creating standalone iModel"); + HubUtility.createStandaloneIModel(iModelPathname, iModelDir); + const iModel: IModelDb = IModelDb.openStandalone(iModelPathname, OpenMode.ReadWrite); + + const changeSets: ChangeSetToken[] = HubUtility.readChangeSets(iModelDir); + + let status: ChangeSetStatus; + + // Logger.logInfo(HubUtility.logCategory, "Dumping all available change sets"); + // HubUtility.dumpStandaloneChangeSets(iModel, changeSets); + + Logger.logInfo(HubUtility.logCategory, "Merging all available change sets"); + status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Merge); + + if (status === ChangeSetStatus.Success) { + Logger.logInfo(HubUtility.logCategory, "Reversing all available change sets"); + changeSets.reverse(); + status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Reverse); + } + + if (status === ChangeSetStatus.Success) { + Logger.logInfo(HubUtility.logCategory, "Reinstating all available change sets"); + changeSets.reverse(); + status = HubUtility.applyStandaloneChangeSets(iModel, changeSets, ChangeSetApplyOption.Reinstate); + } + + iModel.closeStandalone(); + assert(status === ChangeSetStatus.Success, "Error applying change sets"); } public static getSeedPathname(iModelDir: string) { @@ -193,23 +245,23 @@ export class HubUtility { const iModelName = path.basename(pathname, ".bim"); let iModel: HubIModel | undefined = await HubUtility.queryIModelByName(accessToken, projectId, iModelName); if (iModel) { - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, projectId, iModel.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, projectId, iModel.id!); } // Upload a new iModel - iModel = await BriefcaseManager.imodelClient.IModels().create(actx, accessToken, projectId, iModelName, pathname, "", undefined, 2 * 60 * 1000); + iModel = await BriefcaseManager.imodelClient.iModels.create(actx, accessToken, projectId, iModelName, pathname, "", undefined, 2 * 60 * 1000); return iModel.id!; } /** Upload an IModel's seed files and change sets to the hub - * @hidden + * It's assumed that the uploadDir contains a standard hierarchy of seed files and change sets. */ public static async pushIModelAndChangeSets(accessToken: AccessToken, projectName: string, uploadDir: string): Promise { const projectId: string = await HubUtility.queryProjectIdByName(accessToken, projectName); const seedPathname = HubUtility.getSeedPathname(uploadDir); const iModelId = await HubUtility.pushIModel(accessToken, projectId, seedPathname); - const briefcase: HubBriefcase = await BriefcaseManager.imodelClient.Briefcases().create(actx, accessToken, iModelId); + const briefcase: HubBriefcase = await BriefcaseManager.imodelClient.briefcases.create(actx, accessToken, iModelId); if (!briefcase) { return Promise.reject(`Could not acquire a briefcase for the iModel ${iModelId}`); } @@ -232,10 +284,11 @@ export class HubUtility { changeSet.id = changeSetJson.id; changeSet.parentId = changeSetJson.parentId; changeSet.fileSize = changeSetJson.fileSize; + changeSet.changesType = changeSetJson.changesType; changeSet.seedFileId = briefcase.fileId; changeSet.briefcaseId = briefcase.briefcaseId; - await BriefcaseManager.imodelClient.ChangeSets().create(actx, accessToken, iModelId, changeSet, changeSetPathname); + await BriefcaseManager.imodelClient.changeSets.create(actx, accessToken, iModelId, changeSet, changeSetPathname); } return iModelId; @@ -248,13 +301,13 @@ export class HubUtility { const projectId: string = await HubUtility.queryProjectIdByName(accessToken, projectName); const iModelId: GuidString = await HubUtility.queryIModelIdByName(accessToken, projectId, iModelName); - const briefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.Briefcases().get(actx, accessToken, iModelId); + const briefcases: HubBriefcase[] = await BriefcaseManager.imodelClient.briefcases.get(actx, accessToken, iModelId); if (briefcases.length > acquireThreshold) { Logger.logInfo(HubUtility.logCategory, `Reached limit of maximum number of briefcases for ${projectName}:${iModelName}. Purging all briefcases.`); const promises = new Array>(); briefcases.forEach((briefcase: HubBriefcase) => { - promises.push(BriefcaseManager.imodelClient.Briefcases().delete(actx, accessToken, iModelId, briefcase.briefcaseId!)); + promises.push(BriefcaseManager.imodelClient.briefcases.delete(actx, accessToken, iModelId, briefcase.briefcaseId!)); }); await Promise.all(promises); } @@ -300,6 +353,8 @@ export class HubUtility { /** Applies change sets one by one (for debugging) */ public static applyStandaloneChangeSets(iModel: IModelDb, changeSets: ChangeSetToken[], applyOption: ChangeSetApplyOption): ChangeSetStatus { + const perfLogger = new PerfLogger(`Applying change sets for operation ${ChangeSetApplyOption[applyOption]}`); + // Apply change sets one by one to debug any issues for (const changeSet of changeSets) { const tempChangeSets = [changeSet]; @@ -314,6 +369,7 @@ export class HubUtility { return status; } + perfLogger.dispose(); return ChangeSetStatus.Success; } @@ -327,8 +383,8 @@ export class HubUtility { } class ImsUserMgr { - public async authorizeUser(_actx: ActivityLoggingContext, _userProfile: UserProfile | undefined, userCredentials: any): Promise { - return await doImsLogin(userCredentials); + public async authorizeUser(_actx: ActivityLoggingContext, _userInfo: HubUserInfo | undefined, userCredentials: any): Promise { + return doImsLogin(userCredentials); } } @@ -347,15 +403,15 @@ class TestIModelHubProject { } public async createIModel(_actx: ActivityLoggingContext, accessToken: AccessToken, projectId: string, params: any): Promise { const client = this.iModelHubClient; - return client.IModels().create(actx, accessToken, projectId, params.name, params.seedFile, params.description, params.tracker); + return client.iModels.create(actx, accessToken, projectId, params.name, params.seedFile, params.description, params.tracker); } - public deleteIModel(_actx: ActivityLoggingContext, accessToken: AccessToken, projectId: string, iModelId: GuidString): Promise { + public async deleteIModel(_actx: ActivityLoggingContext, accessToken: AccessToken, projectId: string, iModelId: GuidString): Promise { const client = this.iModelHubClient; - return client.IModels().delete(actx, accessToken, projectId, iModelId); + return client.iModels.delete(actx, accessToken, projectId, iModelId); } public async queryIModels(_actx: ActivityLoggingContext, accessToken: AccessToken, projectId: string, query: IModelQuery | undefined): Promise { const client = this.iModelHubClient; - return client.IModels().get(actx, accessToken, projectId, query); + return client.iModels.get(actx, accessToken, projectId, query); } } diff --git a/core/backend/src/test/integration/IModelWrite.test.ts b/core/backend/src/test/integration/IModelWrite.test.ts index 4d42ade..5a91867 100644 --- a/core/backend/src/test/integration/IModelWrite.test.ts +++ b/core/backend/src/test/integration/IModelWrite.test.ts @@ -6,14 +6,12 @@ import { expect, assert } from "chai"; import { Id64String, DbOpcode, DbResult, ActivityLoggingContext } from "@bentley/bentleyjs-core"; import { IModelVersion, SubCategoryAppearance, IModel } from "@bentley/imodeljs-common"; -import { IModelTestUtils, TestUsers, Timer } from "../IModelTestUtils"; +import { IModelTestUtils, TestUsers, Timer, TestIModelInfo } from "../IModelTestUtils"; import { IModelJsFs } from "../../IModelJsFs"; import { KeepBriefcase, IModelDb, OpenParams, Element, DictionaryModel, BriefcaseManager, SqliteStatement, SqliteValue, SqliteValueType } from "../../backend"; import { ConcurrencyControl } from "../../ConcurrencyControl"; -import { TestIModelInfo, MockAccessToken, MockAssetUtil } from "../MockAssetUtil"; -import { AccessToken, CodeState, HubIModel, HubCode, IModelQuery, MultiCode, ConnectClient, IModelHubClient } from "@bentley/imodeljs-clients"; - -import * as TypeMoq from "typemoq"; +import { AccessToken, CodeState, HubIModel, HubCode, IModelQuery, MultiCode } from "@bentley/imodeljs-clients"; +import { HubUtility } from "./HubUtility"; const actx = new ActivityLoggingContext(""); @@ -39,29 +37,26 @@ export async function createNewModelAndCategory(rwIModel: IModelDb, accessToken: return { modelId, spatialCategoryId }; } -describe.skip("IModelWriteTest", () => { - const index = process.argv.indexOf("--offline"); - const offline: boolean = process.argv[index + 1] === "mock"; +describe("IModelWriteTest (#integration)", () => { + let accessToken: AccessToken; let testProjectId: string; - const testIModels: TestIModelInfo[] = [ - new TestIModelInfo("ReadOnlyTest"), - new TestIModelInfo("ReadWriteTest"), - new TestIModelInfo("NoVersionsTest"), - ]; - - const assetDir = "./src/test/assets/_mocks_"; - let cacheDir: string = ""; - let accessToken: AccessToken = new MockAccessToken(); - const iModelHubClientMock = TypeMoq.Mock.ofType(IModelHubClient); - const connectClientMock = TypeMoq.Mock.ofType(ConnectClient); + let writeTestProjectId: string; + let readOnlyTestIModel: TestIModelInfo; + let readWriteTestIModel: TestIModelInfo; before(async () => { - if (offline) { - MockAssetUtil.setupMockAssets(assetDir); - testProjectId = await MockAssetUtil.setupOfflineFixture(accessToken, iModelHubClientMock, connectClientMock, assetDir, cacheDir, testIModels); - } else { - [accessToken, testProjectId, cacheDir] = await IModelTestUtils.setupIntegratedFixture(testIModels); - } + accessToken = await HubUtility.login(TestUsers.manager); + + testProjectId = await HubUtility.queryProjectIdByName(accessToken, "iModelJsIntegrationTest"); + readOnlyTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadOnlyTest"); + readWriteTestIModel = await IModelTestUtils.getTestModelInfo(accessToken, testProjectId, "ReadWriteTest"); + + writeTestProjectId = await HubUtility.queryProjectIdByName(accessToken, "iModelJsTest"); + + // Purge briefcases that are close to reaching the acquire limit + const managerAccessToken: AccessToken = await HubUtility.login(TestUsers.manager); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadOnlyTest"); + await HubUtility.purgeAcquiredBriefcases(managerAccessToken, "iModelJsIntegrationTest", "ReadWriteTest"); }); it("test change-merging scenarios in optimistic concurrency mode (#integration)", async () => { @@ -69,9 +64,9 @@ describe.skip("IModelWriteTest", () => { const secondUser = await IModelTestUtils.getTestUserAccessToken(TestUsers.superManager); const neutralObserverUser = await IModelTestUtils.getTestUserAccessToken(TestUsers.manager); - const firstIModel: IModelDb = await IModelDb.open(actx, firstUser, testProjectId, testIModels[1].id, OpenParams.pullAndPush()); - const secondIModel: IModelDb = await IModelDb.open(actx, secondUser, testProjectId, testIModels[1].id, OpenParams.pullAndPush()); - const neutralObserverIModel: IModelDb = await IModelDb.open(actx, neutralObserverUser, testProjectId, testIModels[1].id, OpenParams.pullOnly()); + const firstIModel: IModelDb = await IModelDb.open(actx, firstUser, testProjectId, readWriteTestIModel.id, OpenParams.pullAndPush()); + const secondIModel: IModelDb = await IModelDb.open(actx, secondUser, testProjectId, readWriteTestIModel.id, OpenParams.pullAndPush()); + const neutralObserverIModel: IModelDb = await IModelDb.open(actx, neutralObserverUser, testProjectId, readWriteTestIModel.id, OpenParams.pullOnly()); assert.notEqual(firstIModel, secondIModel); // Set up optimistic concurrency. Note the defaults are: @@ -197,7 +192,7 @@ describe.skip("IModelWriteTest", () => { // Does not work with mocks it.skip("should build concurrency control request", async () => { - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[1].id, OpenParams.pullAndPush()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readWriteTestIModel.id, OpenParams.pullAndPush()); const el: Element = iModel.elements.getRootSubject(); el.buildConcurrencyControlRequest(DbOpcode.Update); // make a list of the locks, etc. that will be needed to update this element @@ -216,27 +211,26 @@ describe.skip("IModelWriteTest", () => { let timer = new Timer("delete iModels"); // Delete any existing iModels with the same name as the read-write test iModel const iModelName = "CodesPushTest"; - const iModels: HubIModel[] = await BriefcaseManager.imodelClient.IModels().get(actx, adminAccessToken, testProjectId, new IModelQuery().byName(iModelName)); + const iModels: HubIModel[] = await BriefcaseManager.imodelClient.iModels.get(actx, adminAccessToken, writeTestProjectId, new IModelQuery().byName(iModelName)); for (const iModelTemp of iModels) { - await BriefcaseManager.imodelClient.IModels().delete(actx, adminAccessToken, testProjectId, iModelTemp.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, adminAccessToken, writeTestProjectId, iModelTemp.id!); } timer.end(); // Create a new iModel on the Hub (by uploading a seed file) timer = new Timer("create iModel"); - const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, testProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); + const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, writeTestProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); const rwIModelId = rwIModel.iModelToken.iModelId; assert.isNotEmpty(rwIModelId); timer.end(); timer = new Timer("querying codes"); - const initialCodes = await BriefcaseManager.imodelClient.Codes().get(actx, adminAccessToken, rwIModelId!); + const initialCodes = await BriefcaseManager.imodelClient.codes.get(actx, adminAccessToken, rwIModelId!); timer.end(); timer = new Timer("make local changes"); - let newModelId: Id64String; const code = IModelTestUtils.getUniqueModelCode(rwIModel, "newPhysicalModel"); - [, newModelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, code, true); + IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, code, true); rwIModel.saveChanges("inserted generic objects"); timer.end(); @@ -253,7 +247,7 @@ describe.skip("IModelWriteTest", () => { timer.end(); timer = new Timer("querying codes"); - const codes = await BriefcaseManager.imodelClient.Codes().get(actx, adminAccessToken, rwIModelId!); + const codes = await BriefcaseManager.imodelClient.codes.get(actx, adminAccessToken, rwIModelId!); timer.end(); expect(codes.length > initialCodes.length); }); @@ -263,36 +257,35 @@ describe.skip("IModelWriteTest", () => { let timer = new Timer("delete iModels"); // Delete any existing iModels with the same name as the read-write test iModel const iModelName = "CodesConflictTest"; - const iModels: HubIModel[] = await BriefcaseManager.imodelClient.IModels().get(actx, adminAccessToken, testProjectId, new IModelQuery().byName(iModelName)); + const iModels: HubIModel[] = await BriefcaseManager.imodelClient.iModels.get(actx, adminAccessToken, writeTestProjectId, new IModelQuery().byName(iModelName)); for (const iModelTemp of iModels) { - await BriefcaseManager.imodelClient.IModels().delete(actx, adminAccessToken, testProjectId, iModelTemp.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, adminAccessToken, writeTestProjectId, iModelTemp.id!); } timer.end(); // Create a new iModel on the Hub (by uploading a seed file) timer = new Timer("create iModel"); - const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, testProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); + const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, writeTestProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); const rwIModelId = rwIModel.iModelToken.iModelId; assert.isNotEmpty(rwIModelId); timer.end(); const code = IModelTestUtils.getUniqueModelCode(rwIModel, "newPhysicalModel"); - const otherBriefcase = await BriefcaseManager.imodelClient.Briefcases().create(actx, adminAccessToken, rwIModelId!); + const otherBriefcase = await BriefcaseManager.imodelClient.briefcases.create(actx, adminAccessToken, rwIModelId!); const hubCode = new HubCode(); hubCode.value = code.value; hubCode.codeSpecId = code.spec; hubCode.codeScope = code.scope; hubCode.briefcaseId = otherBriefcase.briefcaseId; hubCode.state = CodeState.Reserved; - await BriefcaseManager.imodelClient.Codes().update(actx, adminAccessToken, rwIModelId!, [hubCode]); + await BriefcaseManager.imodelClient.codes.update(actx, adminAccessToken, rwIModelId!, [hubCode]); timer = new Timer("querying codes"); - const initialCodes = await BriefcaseManager.imodelClient.Codes().get(actx, adminAccessToken, rwIModelId!); + const initialCodes = await BriefcaseManager.imodelClient.codes.get(actx, adminAccessToken, rwIModelId!); timer.end(); timer = new Timer("make local changes"); - let newModelId: Id64String; - [, newModelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, code, true); + IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, code, true); rwIModel.saveChanges("inserted generic objects"); timer.end(); @@ -309,7 +302,7 @@ describe.skip("IModelWriteTest", () => { timer.end(); timer = new Timer("querying codes"); - const codes = await BriefcaseManager.imodelClient.Codes().get(actx, accessToken, rwIModelId!); + const codes = await BriefcaseManager.imodelClient.codes.get(actx, accessToken, rwIModelId!); timer.end(); expect(codes.length === initialCodes.length); expect(codes[0].state === CodeState.Reserved); @@ -321,15 +314,15 @@ describe.skip("IModelWriteTest", () => { let timer = new Timer("delete iModels"); // Delete any existing iModels with the same name as the OptimisticConcurrencyTest iModel const iModelName = "OptimisticConcurrencyTest"; - const iModels: HubIModel[] = await BriefcaseManager.imodelClient.IModels().get(actx, adminAccessToken, testProjectId, new IModelQuery().byName(iModelName)); + const iModels: HubIModel[] = await BriefcaseManager.imodelClient.iModels.get(actx, adminAccessToken, writeTestProjectId, new IModelQuery().byName(iModelName)); for (const iModelTemp of iModels) { - await BriefcaseManager.imodelClient.IModels().delete(actx, adminAccessToken, testProjectId, iModelTemp.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, adminAccessToken, writeTestProjectId, iModelTemp.id!); } timer.end(); // Create a new iModel on the Hub (by uploading a seed file) timer = new Timer("create iModel"); - const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, testProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); + const rwIModel: IModelDb = await IModelDb.create(actx, adminAccessToken, writeTestProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); const rwIModelId = rwIModel.iModelToken.iModelId; assert.isNotEmpty(rwIModelId); timer.end(); @@ -425,7 +418,7 @@ describe.skip("IModelWriteTest", () => { timer.end(); // Open a readonly copy of the iModel - const roIModel: IModelDb = await IModelDb.open(actx, adminAccessToken, testProjectId, rwIModelId!, OpenParams.fixedVersion(), IModelVersion.latest()); + const roIModel: IModelDb = await IModelDb.open(actx, adminAccessToken, writeTestProjectId, rwIModelId!, OpenParams.fixedVersion(), IModelVersion.latest()); assert.exists(roIModel); await rwIModel.close(actx, adminAccessToken, KeepBriefcase.No); @@ -433,7 +426,7 @@ describe.skip("IModelWriteTest", () => { }); it("Run plain SQL against pull-only connection", async () => { - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.pullOnly()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.pullOnly()); try { iModel.withPreparedSqliteStatement("CREATE TABLE Test(Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Code INTEGER)", (stmt: SqliteStatement) => { assert.equal(stmt.step(), DbResult.BE_SQLITE_DONE); @@ -505,7 +498,7 @@ describe.skip("IModelWriteTest", () => { }); it("Run plain SQL against readonly connection", async () => { - const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, testIModels[0].id, OpenParams.fixedVersion()); + const iModel: IModelDb = await IModelDb.open(actx, accessToken, testProjectId, readOnlyTestIModel.id, OpenParams.fixedVersion()); iModel.withPreparedSqliteStatement("SELECT Name,StrData FROM be_Prop WHERE Namespace='ec_Db'", (stmt: SqliteStatement) => { let rowCount: number = 0; diff --git a/core/backend/src/test/integration/IModelWriter.ts b/core/backend/src/test/integration/IModelWriter.ts index 97cff7a..e2ab1ca 100644 --- a/core/backend/src/test/integration/IModelWriter.ts +++ b/core/backend/src/test/integration/IModelWriter.ts @@ -2,119 +2,11 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { Id64String, Id64 } from "@bentley/bentleyjs-core"; -import { Box, Point3d, Vector3d, XYZProps } from "@bentley/geometry-core"; -import { - SubCategoryAppearance, CategorySelectorProps, CategoryProps, CodeScopeSpec, CodeSpec, ColorDef, DefinitionElementProps, - GeometryStreamBuilder, GeometryStreamProps, IModel, InformationPartitionElementProps, ModelSelectorProps, SpatialViewDefinitionProps, BisCodeSpec, -} from "@bentley/imodeljs-common"; -import { - CategorySelector, DisplayStyle3d, IModelDb, ModelSelector, OrthographicViewDefinition, PhysicalModel, PhysicalPartition, - SpatialCategory, ViewDefinition, -} from "../../backend"; +import { Box, Point3d, Vector3d } from "@bentley/geometry-core"; +import { GeometryStreamBuilder, GeometryStreamProps } from "@bentley/imodeljs-common"; export class IModelWriter { - /** Insert a CodeSpec */ - public static insertCodeSpec(iModelDb: IModelDb, name: string, scopeType: CodeScopeSpec.Type): Id64String { - const codeSpec = new CodeSpec(iModelDb, Id64.invalid, name, scopeType); - iModelDb.codeSpecs.insert(codeSpec); - return codeSpec.id; - } - - /** Insert a PhysicalModel */ - public static insertPhysicalModel(iModelDb: IModelDb, modelName: string): Id64String { - const partitionProps: InformationPartitionElementProps = { - classFullName: PhysicalPartition.classFullName, - model: IModel.repositoryModelId, - parent: { - id: IModel.rootSubjectId, - relClassName: "BisCore:SubjectOwnsPartitionElements", - }, - code: PhysicalPartition.createCode(iModelDb, IModel.rootSubjectId, modelName), - }; - const partitionId: Id64String = iModelDb.elements.insertElement(partitionProps); - const model: PhysicalModel = iModelDb.models.createModel({ - classFullName: PhysicalModel.classFullName, - modeledElement: { id: partitionId }, - }) as PhysicalModel; - return iModelDb.models.insertModel(model); - } - - /** Insert a SpatialCategory */ - public static insertSpatialCategory(iModelDb: IModelDb, modelId: Id64String, name: string, color: ColorDef): Id64String { - const categoryProps: CategoryProps = { - classFullName: SpatialCategory.classFullName, - model: modelId, - code: SpatialCategory.createCode(iModelDb, modelId, name), - isPrivate: false, - }; - const categoryId: Id64String = iModelDb.elements.insertElement(categoryProps); - const category: SpatialCategory = iModelDb.elements.getElement(categoryId) as SpatialCategory; - category.setDefaultAppearance(new SubCategoryAppearance({ color })); - iModelDb.elements.updateElement(category); - return categoryId; - } - - /** Insert a ModelSelector which is used to select which Models are displayed by a ViewDefinition. */ - public static insertModelSelector(iModelDb: IModelDb, modelId: Id64String, models: string[]): Id64String { - const modelSelectorProps: ModelSelectorProps = { - classFullName: ModelSelector.classFullName, - model: modelId, - code: { spec: BisCodeSpec.modelSelector, scope: modelId }, - models, - }; - return iModelDb.elements.insertElement(modelSelectorProps); - } - - /** Insert a CategorySelector which is used to select which categories are displayed by a ViewDefinition. */ - public static insertCategorySelector(iModelDb: IModelDb, modelId: Id64String, categories: string[]): Id64String { - const categorySelectorProps: CategorySelectorProps = { - classFullName: CategorySelector.classFullName, - code: { spec: BisCodeSpec.categorySelector, scope: modelId }, - model: modelId, - categories, - }; - return iModelDb.elements.insertElement(categorySelectorProps); - } - - /** Insert a DisplayStyle3d for use by a ViewDefinition. */ - public static insertDisplayStyle3d(iModelDb: IModelDb, modelId: Id64String): Id64String { - const displayStyleProps: DefinitionElementProps = { - classFullName: DisplayStyle3d.classFullName, - model: modelId, - code: { spec: BisCodeSpec.displayStyle, scope: modelId }, - isPrivate: false, - }; - return iModelDb.elements.insertElement(displayStyleProps); - } - - /** Insert an OrthographicViewDefinition */ - public static insertOrthographicViewDefinition( - iModelDb: IModelDb, - modelId: Id64String, - viewName: string, - modelSelectorId: Id64String, - categorySelectorId: Id64String, - displayStyleId: Id64String, - origin: XYZProps, - extents: XYZProps, - ): Id64String { - const viewDefinitionProps: SpatialViewDefinitionProps = { - classFullName: OrthographicViewDefinition.classFullName, - model: modelId, - code: ViewDefinition.createCode(iModelDb, modelId, viewName), - modelSelectorId, - categorySelectorId, - displayStyleId, - origin, - extents, - cameraOn: false, - camera: { eye: [0, 0, 0], lens: 0, focusDist: 0 }, // not used when cameraOn === false - }; - return iModelDb.elements.insertElement(viewDefinitionProps); - } - /** Create a geometry stream containing a box */ public static createBox(size: Point3d): GeometryStreamProps { const geometryStreamBuilder = new GeometryStreamBuilder(); diff --git a/core/backend/src/test/integration/OpenMemoizer.test.ts b/core/backend/src/test/integration/OpenMemoizer.test.ts index 9f618e9..fa8be62 100644 --- a/core/backend/src/test/integration/OpenMemoizer.test.ts +++ b/core/backend/src/test/integration/OpenMemoizer.test.ts @@ -10,7 +10,6 @@ import { OpenParams } from "../../backend"; import { OpenIModelDbMemoizer } from "../../rpc-impl/OpenIModelDbMemoizer"; import { IModelTestUtils } from "../IModelTestUtils"; import { HubUtility } from "./HubUtility"; -import { TestConfig } from "../TestConfig"; import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; describe("OpenIModelDbMemoizer (#integration)", () => { @@ -20,11 +19,11 @@ describe("OpenIModelDbMemoizer (#integration)", () => { const { memoize: memoizeOpenIModelDb, deleteMemoized: deleteMemoizedOpenIModelDb } = new OpenIModelDbMemoizer(); - const pause = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); before(async () => { accessToken = await IModelTestUtils.getTestUserAccessToken(); - testProjectId = await HubUtility.queryProjectIdByName(accessToken, TestConfig.projectName); + testProjectId = await HubUtility.queryProjectIdByName(accessToken, "iModelJsIntegrationTest"); }); it("should be able to memoize and deleteMemoized open IModelDb calls", async () => { diff --git a/core/backend/src/test/integration/PushRetry.test.ts b/core/backend/src/test/integration/PushRetry.test.ts index c95b916..caa2d7d 100644 --- a/core/backend/src/test/integration/PushRetry.test.ts +++ b/core/backend/src/test/integration/PushRetry.test.ts @@ -26,7 +26,7 @@ describe("PushRetry", () => { let testIModel: IModelDb; const testPushUtility: TestPushUtility = new TestPushUtility(); const iModelName = "PushRetryTest"; - const pause = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const actx = new ActivityLoggingContext(""); before(async () => { @@ -130,14 +130,14 @@ describe("PushRetry", () => { let actualVersionCount: number = 0; // Subscribe to change set and version events - const changeSetSubscription = await BriefcaseManager.imodelClient.Events().Subscriptions().create(actx, accessToken, testIModelId, ["ChangeSetPostPushEvent"]); - const deleteChangeSetListener = BriefcaseManager.imodelClient.Events().createListener(actx, async () => accessToken, changeSetSubscription.wsgId, testIModelId, async (_receivedEvent: ChangeSetPostPushEvent) => { + const changeSetSubscription = await BriefcaseManager.imodelClient.events.subscriptions.create(actx, accessToken, testIModelId, ["ChangeSetPostPushEvent"]); + const deleteChangeSetListener = BriefcaseManager.imodelClient.events.createListener(actx, async () => accessToken, changeSetSubscription.wsgId, testIModelId, async (_receivedEvent: ChangeSetPostPushEvent) => { actualChangeSetCount++; }); - const namedVersionSubscription = await BriefcaseManager.imodelClient.Events().Subscriptions().create(actx, accessToken, testIModelId, ["VersionEvent"]); - const deleteNamedVersionListener = BriefcaseManager.imodelClient.Events().createListener(actx, async () => accessToken, namedVersionSubscription.wsgId, testIModelId, async (receivedEvent: NamedVersionCreatedEvent) => { + const namedVersionSubscription = await BriefcaseManager.imodelClient.events.subscriptions.create(actx, accessToken, testIModelId, ["VersionEvent"]); + const deleteNamedVersionListener = BriefcaseManager.imodelClient.events.createListener(actx, async () => accessToken, namedVersionSubscription.wsgId, testIModelId, async (receivedEvent: NamedVersionCreatedEvent) => { actualVersionCount++; - extractChangeSummary(receivedEvent.changeSetId!); + extractChangeSummary(receivedEvent.changeSetId!); // tslint:disable-line:no-floating-promises }); // Start pushing change sets and versions @@ -152,9 +152,9 @@ describe("PushRetry", () => { }); it.skip("should retry to push changes (#integration)", async () => { - const iModels: HubIModel[] = await BriefcaseManager.imodelClient.IModels().get(actx, accessToken, testProjectId, new IModelQuery().byName(iModelName)); + const iModels: HubIModel[] = await BriefcaseManager.imodelClient.iModels.get(actx, accessToken, testProjectId, new IModelQuery().byName(iModelName)); for (const iModelTemp of iModels) { - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, testProjectId, iModelTemp.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, testProjectId, iModelTemp.id!); } const pushRetryIModel: IModelDb = await IModelDb.create(actx, accessToken, testProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); @@ -189,13 +189,13 @@ describe("PushRetry", () => { await pushRetryIModel.pushChanges(actx, accessToken); ResponseBuilder.clearMocks(); - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, testProjectId, pushRetryIModelId!); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, testProjectId, pushRetryIModelId!); }); it.skip("should fail to push and not retry again (#integration)", async () => { - const iModels: HubIModel[] = await BriefcaseManager.imodelClient.IModels().get(actx, accessToken, testProjectId, new IModelQuery().byName(iModelName)); + const iModels: HubIModel[] = await BriefcaseManager.imodelClient.iModels.get(actx, accessToken, testProjectId, new IModelQuery().byName(iModelName)); for (const iModelTemp of iModels) { - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, testProjectId, iModelTemp.id!); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, testProjectId, iModelTemp.id!); } const pushRetryIModel: IModelDb = await IModelDb.create(actx, accessToken, testProjectId, iModelName, { rootSubject: { name: "TestSubject" } }); @@ -219,7 +219,7 @@ describe("PushRetry", () => { assert.equal(error.name, "UnknownPushError"); } ResponseBuilder.clearMocks(); - await BriefcaseManager.imodelClient.IModels().delete(actx, accessToken, testProjectId, pushRetryIModelId!); + await BriefcaseManager.imodelClient.iModels.delete(actx, accessToken, testProjectId, pushRetryIModelId!); }); }); diff --git a/core/backend/src/test/integration/TestPushUtility.ts b/core/backend/src/test/integration/TestPushUtility.ts index 74c8af4..f0ae434 100644 --- a/core/backend/src/test/integration/TestPushUtility.ts +++ b/core/backend/src/test/integration/TestPushUtility.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { Id64String, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; -import { Point3d, YawPitchRollAngles } from "@bentley/geometry-core"; +import { Point3d, Range3d, YawPitchRollAngles } from "@bentley/geometry-core"; import { AccessToken } from "@bentley/imodeljs-clients"; import { IModelVersion, CodeScopeSpec, Code, ColorDef, IModel, GeometricElement3dProps, AxisAlignedBox3d } from "@bentley/imodeljs-common"; -import { IModelDb, OpenParams, BriefcaseManager } from "../../backend"; +import { IModelDb, OpenParams, BriefcaseManager, CategorySelector, DisplayStyle3d, ModelSelector, OrthographicViewDefinition, PhysicalModel, SpatialCategory } from "../../backend"; import * as path from "path"; import * as fs from "fs"; import { IModelWriter } from "./IModelWriter"; import { HubUtility, UserCredentials } from "./HubUtility"; import { TestUsers } from "../IModelTestUtils"; -const pause = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const actx = new ActivityLoggingContext(""); export class TestPushUtility { @@ -66,17 +66,17 @@ export class TestPushUtility { this._iModelDb = IModelDb.createStandalone(pathname, { rootSubject: { name: this.iModelName! } }); const definitionModelId: Id64String = IModel.dictionaryId; - this._physicalModelId = IModelWriter.insertPhysicalModel(this._iModelDb, "TestModel"); - this._codeSpecId = IModelWriter.insertCodeSpec(this._iModelDb, "TestCodeSpec", CodeScopeSpec.Type.Model); - this._categoryId = IModelWriter.insertSpatialCategory(this._iModelDb, definitionModelId, "TestCategory", new ColorDef("blanchedAlmond")); + this._physicalModelId = PhysicalModel.insert(this._iModelDb, IModel.rootSubjectId, "TestModel"); + this._codeSpecId = this._iModelDb.codeSpecs.insert("TestCodeSpec", CodeScopeSpec.Type.Model); + this._categoryId = SpatialCategory.insert(this._iModelDb, definitionModelId, "TestCategory", { color: new ColorDef("blanchedAlmond") }); // Insert a ViewDefinition for the PhysicalModel - const modelSelectorId: Id64String = IModelWriter.insertModelSelector(this._iModelDb, definitionModelId, [this._physicalModelId.toString()]); - const categorySelectorId: Id64String = IModelWriter.insertCategorySelector(this._iModelDb, definitionModelId, [this._categoryId.toString()]); - const displayStyleId: Id64String = IModelWriter.insertDisplayStyle3d(this._iModelDb, definitionModelId); - const physicalViewOrigin = new Point3d(0, 0, 0); - const physicalViewExtents = new Point3d(50, 50, 50); - IModelWriter.insertOrthographicViewDefinition(this._iModelDb, definitionModelId, "Physical View", modelSelectorId, categorySelectorId, displayStyleId, physicalViewOrigin, physicalViewExtents); + const viewName = "Physical View"; + const modelSelectorId: Id64String = ModelSelector.insert(this._iModelDb, definitionModelId, viewName, [this._physicalModelId]); + const categorySelectorId: Id64String = CategorySelector.insert(this._iModelDb, definitionModelId, viewName, [this._categoryId]); + const displayStyleId: Id64String = DisplayStyle3d.insert(this._iModelDb, definitionModelId, viewName); + const viewRange = new Range3d(0, 0, 0, 50, 50, 50); + OrthographicViewDefinition.insert(this._iModelDb, definitionModelId, viewName, modelSelectorId, categorySelectorId, displayStyleId, viewRange); this._iModelDb.updateProjectExtents(new AxisAlignedBox3d(new Point3d(-1000, -1000, -1000), new Point3d(1000, 1000, 1000))); @@ -187,7 +187,7 @@ export class TestPushUtility { private async createNamedVersion() { const changeSetId: string = await IModelVersion.latest().evaluateChangeSet(actx, this._accessToken!, this._iModelId!.toString(), BriefcaseManager.imodelClient); - await BriefcaseManager.imodelClient.Versions().create(actx, this._accessToken!, this._iModelId!, changeSetId, TestPushUtility.getVersionName(this._currentLevel)); + await BriefcaseManager.imodelClient.versions.create(actx, this._accessToken!, this._iModelId!, changeSetId, TestPushUtility.getVersionName(this._currentLevel)); } } diff --git a/core/backend/src/test/standalone/ClassRegistry.test.ts b/core/backend/src/test/standalone/ClassRegistry.test.ts index 39769f0..e1422f1 100644 --- a/core/backend/src/test/standalone/ClassRegistry.test.ts +++ b/core/backend/src/test/standalone/ClassRegistry.test.ts @@ -66,9 +66,9 @@ describe("Class Registry", () => { } }); - it("should verify Entity metadata with both base class and mixin properties", () => { + it("should verify Entity metadata with both base class and mixin properties", async () => { const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestDomain.ecschema.xml"); - imodel.importSchema(actx, schemaPathname); // will throw an exception if import fails + await imodel.importSchema(actx, schemaPathname); // will throw an exception if import fails const testDomainClass = imodel.getMetaData("TestDomain:TestDomainClass"); // will throw on failure diff --git a/core/backend/src/test/standalone/ECSchemaXmlContext.test.ts b/core/backend/src/test/standalone/ECSchemaXmlContext.test.ts index 5ae16bb..98d2f66 100644 --- a/core/backend/src/test/standalone/ECSchemaXmlContext.test.ts +++ b/core/backend/src/test/standalone/ECSchemaXmlContext.test.ts @@ -9,7 +9,7 @@ import { KnownTestLocations } from "../KnownTestLocations"; describe("ECSchemaXmlContext", () => { - it.skip("should be able to convert schema XML to JSON", () => { + it("should be able to convert schema XML to JSON", () => { const testSchemaXmlPath = path.join(KnownTestLocations.assetsDir, "TestSchema.ecschema.xml"); const testSchemaJsonPath = path.join(KnownTestLocations.assetsDir, "TestSchema.ecschema.json"); const expectedTestSchemaJson = require(testSchemaJsonPath); diff --git a/core/backend/src/test/standalone/FunctionalDomain.test.ts b/core/backend/src/test/standalone/FunctionalDomain.test.ts index 0c023a0..d48c02a 100644 --- a/core/backend/src/test/standalone/FunctionalDomain.test.ts +++ b/core/backend/src/test/standalone/FunctionalDomain.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { assert } from "chai"; import * as path from "path"; -import { ActivityLoggingContext, Guid, Id64String, Id64 } from "@bentley/bentleyjs-core"; -// import { Logger, LogLevel } from "@bentley/bentleyjs-core"; -import { Code, CodeSpec, CodeScopeSpec, FunctionalElementProps, IModel, InformationPartitionElementProps } from "@bentley/imodeljs-common"; -import { BriefcaseManager, Functional, FunctionalModel, FunctionalPartition, IModelDb } from "../../backend"; +import { ActivityLoggingContext, DbResult, Guid, Id64String, Id64 } from "@bentley/bentleyjs-core"; +import { Logger } from "@bentley/bentleyjs-core"; +import { Code, CodeSpec, CodeScopeSpec, FunctionalElementProps, IModel } from "@bentley/imodeljs-common"; +import { BriefcaseManager, ECSqlStatement, Functional, FunctionalModel, IModelDb, SqliteStatement } from "../../backend"; import { IModelTestUtils } from "../IModelTestUtils"; describe("Functional Domain", () => { @@ -16,6 +16,7 @@ describe("Functional Domain", () => { before(() => { // Logger.initializeToConsole(); // Logger.setLevelDefault(LogLevel.Warning); + // Logger.setLevel("FunctionalDomain.test", LogLevel.Info); // Logger.setLevel("imodeljs-addon", LogLevel.Warning); // Logger.setLevel("imodeljs-backend", LogLevel.Warning); // Logger.setLevel("DgnCore", LogLevel.Warning); @@ -35,34 +36,31 @@ describe("Functional Domain", () => { // Import the Functional schema await Functional.importSchema(activityLoggingContext, iModelDb); Functional.registerSchema(); + + let commits = 0; + let committed = 0; + const dropCommit = iModelDb.txns.onCommit.addListener(() => commits++); + const dropCommitted = iModelDb.txns.onCommitted.addListener(() => committed++); iModelDb.saveChanges("Import Functional schema"); + assert.equal(commits, 1); + assert.equal(committed, 1); + dropCommit(); + dropCommitted(); + BriefcaseManager.createStandaloneChangeSet(iModelDb.briefcase); // importSchema below will fail if this is not called to flush local changes await iModelDb.importSchema(activityLoggingContext, path.join(__dirname, "../assets/TestFunctional.ecschema.xml")); + iModelDb.saveChanges("Import TestFunctional schema"); + assert.equal(commits, 1); + assert.equal(committed, 1); const codeSpec = new CodeSpec(iModelDb, Id64.invalid, "Test Functional Elements", CodeScopeSpec.Type.Model); iModelDb.codeSpecs.insert(codeSpec); assert.isTrue(Id64.isValidId64(codeSpec.id)); - // Create and populate a FunctionalModel - const partitionProps: InformationPartitionElementProps = { - classFullName: FunctionalPartition.classFullName, - model: IModel.repositoryModelId, - parent: { - id: IModel.rootSubjectId, - relClassName: "BisCore:SubjectOwnsPartitionElements", - }, - code: FunctionalPartition.createCode(iModelDb, IModel.rootSubjectId, "Test Functional Model"), - }; - const partitionId: Id64String = iModelDb.elements.insertElement(partitionProps); - assert.isTrue(Id64.isValidId64(partitionId)); - const model: FunctionalModel = iModelDb.models.createModel({ - classFullName: FunctionalModel.classFullName, - modeledElement: { id: partitionId }, - }) as FunctionalModel; - const modelId: Id64String = iModelDb.models.insertModel(model); + const modelId: Id64String = FunctionalModel.insert(iModelDb, IModel.rootSubjectId, "Test Functional Model"); assert.isTrue(Id64.isValidId64(modelId)); const breakdownProps: FunctionalElementProps = { @@ -82,6 +80,29 @@ describe("Functional Domain", () => { assert.isTrue(Id64.isValidId64(componentId)); iModelDb.saveChanges("Insert Functional elements"); + + iModelDb.withPreparedStatement("SELECT ECInstanceId AS id FROM ECDbMeta.ECSchemaDef WHERE Name='TestFunctional' LIMIT 1", (schemaStatement: ECSqlStatement) => { + while (DbResult.BE_SQLITE_ROW === schemaStatement.step()) { + const schemaRow: any = schemaStatement.getRow(); + Logger.logInfo("FunctionalDomain.test", `${schemaRow.id}`); + iModelDb.withPreparedStatement("SELECT ECInstanceId AS id FROM ECDbMeta.ECClassDef WHERE ECClassDef.Schema.Id=? AND Name='PlaceholderForSchemaHasBehavior' LIMIT 1", (classStatement: ECSqlStatement) => { + classStatement.bindId(1, schemaRow.id); + while (DbResult.BE_SQLITE_ROW === classStatement.step()) { + const classRow: any = classStatement.getRow(); + Logger.logInfo("FunctionalDomain.test", `${classRow.id}`); + iModelDb.withPreparedSqliteStatement("SELECT Id AS id, Instance AS xml FROM ec_CustomAttribute WHERE ClassId=? AND ContainerId=?", (customAttributeStatement: SqliteStatement) => { + customAttributeStatement.bindValue(1, { id: classRow.id }); + customAttributeStatement.bindValue(2, { id: schemaRow.id }); + while (DbResult.BE_SQLITE_ROW === customAttributeStatement.step()) { + const customAttributeRow: any = customAttributeStatement.getRow(); + Logger.logInfo("FunctionalDomain.test", `${customAttributeRow.id}, ${customAttributeRow.xml}`); + } + }); + } + }); + } + }); + iModelDb.closeStandalone(); }); }); diff --git a/core/backend/src/test/standalone/GenericDomain.test.ts b/core/backend/src/test/standalone/GenericDomain.test.ts index 0423533..94ca05d 100644 --- a/core/backend/src/test/standalone/GenericDomain.test.ts +++ b/core/backend/src/test/standalone/GenericDomain.test.ts @@ -6,7 +6,7 @@ import { assert } from "chai"; import { Guid, Id64String, Id64 } from "@bentley/bentleyjs-core"; // import { Logger, LogLevel } from "@bentley/bentleyjs-core"; import { CategoryProps, Code, GeometricElement3dProps, ElementProps, IModel, InformationPartitionElementProps } from "@bentley/imodeljs-common"; -import { Generic, GroupInformationPartition, Group, GroupModel, IModelDb, PhysicalModel, PhysicalObject, PhysicalPartition, SpatialCategory } from "../../backend"; +import { Generic, GroupInformationPartition, Group, GroupModel, IModelDb, PhysicalModel, PhysicalObject, PhysicalPartition, SpatialCategory, SubjectOwnsPartitionElements } from "../../backend"; import { IModelTestUtils } from "../IModelTestUtils"; describe("Generic Domain", () => { @@ -48,10 +48,7 @@ describe("Generic Domain", () => { const physicalPartitionProps: InformationPartitionElementProps = { classFullName: PhysicalPartition.classFullName, model: IModel.repositoryModelId, - parent: { - id: IModel.rootSubjectId, - relClassName: "BisCore:SubjectOwnsPartitionElements", - }, + parent: new SubjectOwnsPartitionElements(IModel.rootSubjectId), code: PhysicalPartition.createCode(iModelDb, IModel.rootSubjectId, "Test Physical Model"), }; const physicalPartitionId: Id64String = iModelDb.elements.insertElement(physicalPartitionProps); @@ -79,10 +76,7 @@ describe("Generic Domain", () => { const groupPartitionProps: InformationPartitionElementProps = { classFullName: GroupInformationPartition.classFullName, model: IModel.repositoryModelId, - parent: { - id: IModel.rootSubjectId, - relClassName: "BisCore:SubjectOwnsPartitionElements", - }, + parent: new SubjectOwnsPartitionElements(IModel.rootSubjectId), code: GroupInformationPartition.createCode(iModelDb, IModel.rootSubjectId, "Test Group Model"), }; const groupPartitionId: Id64String = iModelDb.elements.insertElement(groupPartitionProps); diff --git a/core/backend/src/test/standalone/GeometryStream.test.ts b/core/backend/src/test/standalone/GeometryStream.test.ts index 37946fc..23f1781 100644 --- a/core/backend/src/test/standalone/GeometryStream.test.ts +++ b/core/backend/src/test/standalone/GeometryStream.test.ts @@ -6,7 +6,7 @@ import { assert } from "chai"; import { Point3d, YawPitchRollAngles, Arc3d, LineSegment3d, LineString3d, Loop, Transform, Angle, Point2d, Geometry } from "@bentley/geometry-core"; import { Id64String, Id64 } from "@bentley/bentleyjs-core"; import { - Code, GeometricElement3dProps, GeometryPartProps, IModel, GeometryStreamBuilder, GeometryStreamIterator, TextString, TextStringProps, LinePixels, FontProps, FontType, FillDisplay, GeometryParams, LineStyle, ColorDef, BackgroundFill, Gradient, AreaPattern, ColorByName, BRepEntity, + Code, GeometricElement3dProps, GeometryPartProps, IModel, GeometryStreamBuilder, GeometryStreamIterator, TextString, TextStringProps, LinePixels, FontProps, FontType, FillDisplay, GeometryParams, LineStyle, ColorDef, BackgroundFill, Gradient, AreaPattern, ColorByName, BRepEntity, GeometryStreamProps, } from "@bentley/imodeljs-common"; import { IModelTestUtils } from "../IModelTestUtils"; import { GeometryPart, IModelDb, LineStyleDefinition, Platform } from "../../backend"; @@ -78,10 +78,10 @@ describe("GeometryStream", () => { imodel.saveChanges(); // Extract and test value returned... - const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); + const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); assert.isDefined(value.geom); - const itNextCheck = new GeometryStreamIterator(value.geom, value.category); + const itNextCheck = new GeometryStreamIterator(value.geom!, value.category); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); assert.isFalse(itNextCheck.next().done); @@ -93,7 +93,7 @@ describe("GeometryStream", () => { assert.isTrue(itNextCheck.next().done); const lsStylesUsed: Id64String[] = []; - const it = new GeometryStreamIterator(value.geom, value.category); + const it = new GeometryStreamIterator(value.geom!, value.category); for (const entry of it) { assert.isDefined(entry.geometryQuery); lsStylesUsed.push(entry.geomParams.styleInfo ? entry.geomParams.styleInfo.styleId : Id64.invalid); @@ -169,12 +169,12 @@ describe("GeometryStream", () => { imodel.saveChanges(); // Extract and test value returned... - const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); + const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); assert.isDefined(value.geom); const stylesUsed: Id64String[] = []; const widthsUsed: number[] = []; - const it = new GeometryStreamIterator(value.geom, value.category); + const it = new GeometryStreamIterator(value.geom!, value.category); for (const entry of it) { assert.isDefined(entry.geometryQuery); assert.isDefined(entry.geomParams.styleInfo); @@ -589,7 +589,7 @@ describe("GeometryStream", () => { const seedElement = imodel.elements.getElement("0x1d"); assert.exists(seedElement); assert.isTrue(seedElement.federationGuid! === "18eb4650-b074-414f-b961-d9cfaa6c8746"); - assert.isTrue(0 === imodel.getFontMap().fonts.size); // file currently contains no fonts... + assert.isTrue(0 === imodel.fontMap.fonts.size); // file currently contains no fonts... let fontProps: FontProps = { id: 0, type: FontType.TrueType, name: "Arial" }; try { @@ -601,8 +601,8 @@ describe("GeometryStream", () => { return; // failure expected if not windows, skip remainder of test... } - assert.isTrue(0 !== imodel.getFontMap().fonts.size); - const foundFont = imodel.getFontMap().getFont("Arial"); + assert.isTrue(0 !== imodel.fontMap.fonts.size); + const foundFont = imodel.fontMap.getFont("Arial"); assert.isTrue(foundFont && foundFont.id === fontProps.id); const testOrigin = Point3d.create(5, 10, 0); @@ -762,6 +762,59 @@ describe("GeometryStream", () => { } }); + it("create GeometricElement3d wireformat appearance check", async () => { + // Set up element to be placed in iModel + const seedElement = imodel.elements.getElement("0x1d"); + assert.exists(seedElement); + assert.isTrue(seedElement.federationGuid! === "18eb4650-b074-414f-b961-d9cfaa6c8746"); + + const builder = new GeometryStreamBuilder(); + const params = new GeometryParams(seedElement.category); + const shape = Loop.create(LineString3d.create(Point3d.create(0, 0, 0), Point3d.create(1, 0, 0), Point3d.create(1, 1, 0), Point3d.create(0, 1, 0), Point3d.create(0, 0, 0))); + + params.fillDisplay = FillDisplay.ByView; + builder.appendGeometryParamsChange(params); + builder.appendGeometry(shape); + + const elementProps: GeometricElement3dProps = { + classFullName: "Generic:PhysicalObject", + iModel: imodel, + model: seedElement.model, + category: seedElement.category, + code: Code.createEmpty(), + userLabel: "UserLabel-" + 1, + geom: builder.geometryStream, + }; + + const newId = imodel.elements.insertElement(elementProps); + assert.isTrue(Id64.isValidId64(newId)); + imodel.saveChanges(); + + // Extract and test value returned... + const value = imodel.elements.getElementProps({ id: newId, wantGeometry: true }); + assert.isDefined(value.geom); + + const itLocal = new GeometryStreamIterator(value.geom, value.category); + for (const entry of itLocal) { + assert.isDefined(entry.geometryQuery); + assert.isTrue(FillDisplay.ByView === entry.geomParams.fillDisplay); + } + + const geometryStream: GeometryStreamProps = []; + + geometryStream.push({ appearance: {} }); // Native ToJson should add appearance entry with no defined values for this case... + geometryStream.push({ fill: { display: FillDisplay.ByView } }); + geometryStream.push({ loop: [{ lineString: [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 0]] }] }); + + const fromBuilder = JSON.stringify(builder.geometryStream); + const fromElProps = JSON.stringify(value.geom); + const fromScratch = JSON.stringify(geometryStream); + + assert.isTrue(undefined !== builder.geometryStream[0].appearance && builder.geometryStream[0].appearance.subCategory === IModel.getDefaultSubCategoryId(value.category)); // Ensure default sub-category is specified... + assert.isTrue(fromElProps !== fromBuilder); // Should not match, default sub-category should not be persisted... + assert.isTrue(fromElProps === fromScratch); + }); + it("create GeometricElement3d from world coordinate brep data", async () => { // Currently only have parasolid for windows... if ("win32" !== Platform.platformName) diff --git a/core/backend/src/test/standalone/IModel.test.ts b/core/backend/src/test/standalone/IModel.test.ts index 48ef64f..cf4411b 100644 --- a/core/backend/src/test/standalone/IModel.test.ts +++ b/core/backend/src/test/standalone/IModel.test.ts @@ -2,52 +2,25 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ +import { ActivityLoggingContext, BeEvent, DbResult, Guid, Id64, Id64String, OpenMode } from "@bentley/bentleyjs-core"; +import { Angle, Matrix4d, Point3d, Range3d, Transform } from "@bentley/geometry-core"; +import { AccessToken } from "@bentley/imodeljs-clients"; +import { + AxisAlignedBox3d, Code, CodeScopeSpec, CodeSpec, ColorByName, EntityMetaData, EntityProps, FilePropertyProps, FontMap, + FontType, GeometricElementProps, IModel, IModelError, IModelStatus, PrimitiveTypeCode, RelatedElement, SubCategoryAppearance, + ViewDefinitionProps, DisplayStyleSettingsProps, ColorDef, ViewFlags, RenderMode, DisplayStyleProps, BisCodeSpec, +} from "@bentley/imodeljs-common"; import { assert, expect } from "chai"; import * as path from "path"; -import { DbResult, Guid, Id64String, Id64, BeEvent, OpenMode, ActivityLoggingContext } from "@bentley/bentleyjs-core"; -import { Point3d, Transform, Range3d, Angle, Matrix4d } from "@bentley/geometry-core"; import { - ClassRegistry, - BisCore, - Element, - GeometricElement2d, - GeometricElement3d, - GeometricModel, - InformationPartitionElement, - DefinitionPartition, - LinkPartition, - PhysicalPartition, - GroupInformationPartition, - DocumentPartition, - Subject, - ElementPropertyFormatter, - IModelDb, - ECSqlStatement, - SqliteStatement, - SqliteValue, - SqliteValueType, - Entity, - Model, - DictionaryModel, - Category, - SubCategory, - SpatialCategory, - ElementGroupsMembers, - LightLocation, - PhysicalModel, - AutoPushEventType, - AutoPush, - AutoPushState, - AutoPushEventHandler, - ViewDefinition, + AutoPush, AutoPushEventHandler, AutoPushEventType, AutoPushState, BisCore, Category, ClassRegistry, DefinitionPartition, + DictionaryModel, DocumentPartition, ECSqlStatement, Element, ElementGroupsMembers, ElementPropertyFormatter, Entity, + GeometricElement2d, GeometricElement3d, GeometricModel, GroupInformationPartition, IModelDb, InformationPartitionElement, + LightLocation, LinkPartition, Model, PhysicalModel, PhysicalPartition, SpatialCategory, SqliteStatement, SqliteValue, + SqliteValueType, SubCategory, Subject, ViewDefinition, DisplayStyle3d, ElementDrivesElement, PhysicalObject, } from "../../backend"; -import { - GeometricElementProps, Code, CodeSpec, CodeScopeSpec, EntityProps, IModelError, IModelStatus, ModelProps, ViewDefinitionProps, - AxisAlignedBox3d, SubCategoryAppearance, IModel, FontType, FontMap, ColorByName, FilePropertyProps, RelatedElement, EntityMetaData, PrimitiveTypeCode, -} from "@bentley/imodeljs-common"; import { IModelTestUtils } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; -import { AccessToken } from "@bentley/imodeljs-clients"; let lastPushTimeMillis = 0; let lastAutoPushEventType: AutoPushEventType | undefined; @@ -62,12 +35,15 @@ describe("iModel", () => { let imodel5: IModelDb; const actx = new ActivityLoggingContext(""); - before(() => { + before(async () => { imodel1 = IModelTestUtils.openIModel("test.bim"); imodel2 = IModelTestUtils.openIModel("CompatibilityTestSeed.bim"); imodel3 = IModelTestUtils.openIModel("GetSetAutoHandledStructProperties.bim"); imodel4 = IModelTestUtils.openIModel("GetSetAutoHandledArrayProperties.bim"); imodel5 = IModelTestUtils.openIModel("mirukuru.ibim"); + + const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); + await imodel1.importSchema(actx, schemaPathname); // will throw an exception if import fails }); after(() => { @@ -113,7 +89,7 @@ describe("iModel", () => { }); it("FontMap", () => { - const fonts1 = imodel1.getFontMap(); + const fonts1 = imodel1.fontMap; assert.equal(fonts1.fonts.size, 4, "font map size should be 4"); assert.equal(FontType.TrueType, fonts1.getFont(1)!.type, "get font 1 type is TrueType"); assert.equal("Arial", fonts1.getFont(1)!.name, "get Font 1 name"); @@ -221,7 +197,50 @@ describe("iModel", () => { const elementId: Id64String = imodel2.elements.insertElement(element); assert.isTrue(Id64.isValidId64(elementId)); } + }); + + it("should insert a DisplayStyle", () => { + const model = imodel2.models.getModel(IModel.dictionaryId) as DictionaryModel; + expect(model).not.to.be.undefined; + + const settings: DisplayStyleSettingsProps = { + backgroundColor: ColorDef.blue, + viewflags: ViewFlags.fromJSON({ + renderMode: RenderMode.SolidFill, + }), + }; + + const props: DisplayStyleProps = { + classFullName: DisplayStyle3d.classFullName, + model: IModel.dictionaryId, + code: { spec: BisCodeSpec.displayStyle, scope: IModel.dictionaryId }, + isPrivate: false, + jsonProperties: { + styles: settings, + }, + }; + + const styleId = imodel2.elements.insertElement(props); + let style = imodel2.elements.getElement(styleId); + expect(style instanceof DisplayStyle3d).to.be.true; + + expect(style.settings.viewFlags.renderMode).to.equal(RenderMode.SolidFill); + expect(style.settings.backgroundColor.equals(ColorDef.blue)).to.be.true; + + const newFlags = style.settings.viewFlags.clone(); + newFlags.renderMode = RenderMode.SmoothShade; + style.settings.viewFlags = newFlags; + style.settings.backgroundColor = ColorDef.red; + style.settings.monochromeColor = ColorDef.green; + expect(style.jsonProperties.styles.viewflags.renderMode).to.equal(RenderMode.SmoothShade); + imodel2.elements.updateElement(style.toJSON()); + style = imodel2.elements.getElement(styleId); + expect(style instanceof DisplayStyle3d).to.be.true; + + expect(style.settings.viewFlags.renderMode).to.equal(RenderMode.SmoothShade); + expect(style.settings.backgroundColor.equals(ColorDef.red)).to.be.true; + expect(style.settings.monochromeColor.equals(ColorDef.green)).to.be.true; }); it("should have a valid root subject element", () => { @@ -570,28 +589,6 @@ describe("iModel", () => { } }); - it("should set auto-handled number property with value of zero", () => { - const testImodel: IModelDb = imodel1; - try { - testImodel.getMetaData("TestBim:TestPhysicalObject"); - } catch (err) { - const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); - testImodel.importSchema(actx, schemaPathname); // will throw an exception if import fails - assert.isDefined(testImodel.getMetaData("TestBim:TestPhysicalObject"), "TestPhysicalObject is present"); - } - - const props: GeometricElementProps = { - classFullName: "TestBim:TestPhysicalObject", - model: "0", - category: "0", - code: Code.createEmpty(), - intProperty: 0, - }; - - const element = testImodel.elements.createElement(props); - assert.equal(element.intProperty, 0, "int property should be zero"); - }); - function checkElementMetaData(obj: EntityMetaData) { assert.isNotNull(obj); assert.equal(obj.ecclass, Element.classFullName); @@ -838,16 +835,9 @@ describe("iModel", () => { const worldToView = Matrix4d.createIdentity(); const response = await imodel2.requestSnap(actx, "0x222", { testPoint: { x: 1, y: 2, z: 3 }, closePoint: { x: 1, y: 2, z: 3 }, id: "0x111", worldToView: worldToView.toJSON() }); assert.isDefined(response.status); - - // make sure we can read native asset files. - const sprite = IModelDb.loadNativeAsset("decorators/dgncore/SnapNone.png"); - assert.isDefined(sprite); }); - it("should import schemas", () => { - const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); - imodel1.importSchema(actx, schemaPathname); // will throw an exception if import fails - + it("should import schemas", async () => { const classMetaData = imodel1.getMetaData("TestBim:TestDocument"); // will throw on failure assert.isDefined(classMetaData.properties.testDocumentProperty); assert.isTrue(classMetaData.properties.testDocumentProperty.primitiveType === PrimitiveTypeCode.Integer); @@ -869,28 +859,19 @@ describe("iModel", () => { assert.deepEqual(newModelPersist.modeledElement.id, modeledElementId); // Update the model - const changedModelProps: ModelProps = Object.assign({}, newModelPersist); - changedModelProps.isPrivate = false; - testImodel.models.updateModel(changedModelProps); + newModelPersist.isPrivate = false; + testImodel.models.updateModel(newModelPersist); // ... and check that it updated the model in the db - const newModelPersist2: Model = testImodel.models.getModel(newModelId); + const newModelPersist2 = testImodel.models.getModel(newModelId); assert.isFalse(newModelPersist2.isPrivate); // Delete the model - testImodel.models.deleteModel(newModelPersist); - try { - assert.fail(); - } catch (err) { - // this is expected - } + testImodel.models.deleteModel(newModelId); }); - it("should create model with custom relationship to modeled element", () => { - const testBimName = "should-create-models-with-custom-relationship.bim"; - let testImodel: IModelDb = IModelTestUtils.openIModel("test.bim", { copyFilename: testBimName }); + it("should create model with custom relationship to modeled element", async () => { + const testImodel = imodel1; - const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); - testImodel.importSchema(actx, schemaPathname); // will throw an exception if import fails assert.isDefined(testImodel.getMetaData("TestBim:TestModelModelsElement"), "TestModelModelsElement is expected to be defined in TestBim.ecschema.xml"); let newModelId1: Id64String; @@ -912,9 +893,6 @@ describe("iModel", () => { relClassName2 = newModel2.modeledElement.relClassName; } - IModelTestUtils.closeIModel(testImodel); - testImodel = IModelTestUtils.openIModelFromOut(testBimName); - const model1 = testImodel.models.getModel(newModelId1); const model2 = testImodel.models.getModel(newModelId2); @@ -926,40 +904,42 @@ describe("iModel", () => { }); it("should create link table relationship instances", () => { - const testImodel: IModelDb = imodel1; + const testImodel = imodel1; + const elements = testImodel.elements; - // Create a new physical model - let newModelId: Id64String; - [, newModelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(testImodel, Code.createEmpty(), true); + testImodel.nativeDb.enableTxnTesting(); - // Find or create a SpatialCategory - const dictionary = testImodel.models.getModel(IModel.dictionaryId) as DictionaryModel; - let spatialCategoryId: Id64String | undefined = SpatialCategory.queryCategoryIdByName(dictionary.iModel, dictionary.id, "MySpatialCategory"); - if (undefined === spatialCategoryId) { - spatialCategoryId = IModelTestUtils.createAndInsertSpatialCategory(dictionary, "MySpatialCategory", new SubCategoryAppearance({ color: ColorByName.darkRed })); + // Create a new physical model + const newModelId = PhysicalModel.insert(testImodel, IModel.rootSubjectId, "TestModel"); - const updated = testImodel.elements.getElement(IModelDb.getDefaultSubCategoryId(spatialCategoryId)) as SubCategory; - assert.equal(updated.appearance.color.tbgr, ColorByName.darkRed, "SubCategory appearance should be updated"); - } + // create a SpatialCategory + const spatialCategoryId = SpatialCategory.insert(testImodel, IModel.dictionaryId, "MySpatialCategory", new SubCategoryAppearance({ color: ColorByName.darkRed })); // Create a couple of physical elements. - const id0 = testImodel.elements.insertElement(IModelTestUtils.createPhysicalObject(testImodel, newModelId, spatialCategoryId)); - const id1 = testImodel.elements.insertElement(IModelTestUtils.createPhysicalObject(testImodel, newModelId, spatialCategoryId)); - const id2 = testImodel.elements.insertElement(IModelTestUtils.createPhysicalObject(testImodel, newModelId, spatialCategoryId)); + const elementProps: GeometricElementProps = { + classFullName: PhysicalObject.classFullName, + iModel: testImodel, + model: newModelId, + category: spatialCategoryId, + code: Code.createEmpty(), + }; - const geometricModel = testImodel.models.getModel(newModelId) as GeometricModel; + const id0 = elements.insertElement(elementProps); + const id1 = elements.insertElement(elementProps); + const id2 = elements.insertElement(elementProps); + + const geometricModel = testImodel.models.getModel(newModelId); assert.throws(() => geometricModel.queryExtents()); // no geometry // Create grouping relationships from 0 to 1 and from 0 to 2 - const r1: ElementGroupsMembers = ElementGroupsMembers.create(testImodel, id0, id1); - r1.memberPriority = 1; - testImodel.linkTableRelationships.insertInstance(r1); - const r2: ElementGroupsMembers = ElementGroupsMembers.create(testImodel, id0, id2); - testImodel.linkTableRelationships.insertInstance(r2); + const r1 = ElementGroupsMembers.create(testImodel, id0, id1, 1); + r1.insert(); + const r2 = ElementGroupsMembers.create(testImodel, id0, id2); + r2.insert(); // Look up by id - const g1 = testImodel.linkTableRelationships.getInstance(ElementGroupsMembers.classFullName, r1.id) as ElementGroupsMembers; - const g2 = testImodel.linkTableRelationships.getInstance(ElementGroupsMembers.classFullName, r2.id) as ElementGroupsMembers; + const g1 = ElementGroupsMembers.getInstance(testImodel, r1.id); + const g2 = ElementGroupsMembers.getInstance(testImodel, r2.id); assert.deepEqual(g1.id, r1.id); assert.equal(g1.classFullName, ElementGroupsMembers.classFullName); @@ -969,36 +949,37 @@ describe("iModel", () => { assert.equal(g2.memberPriority, 0, "g2.memberPriority"); // The memberPriority parameter defaults to 0 in ElementGroupsMembers.create // Look up by source and target - const g1byst: ElementGroupsMembers = testImodel.linkTableRelationships.getInstance(ElementGroupsMembers.classFullName, { sourceId: r1.sourceId, targetId: r1.targetId }) as ElementGroupsMembers; + const g1byst = ElementGroupsMembers.getInstance(testImodel, { sourceId: r1.sourceId, targetId: r1.targetId }); assert.deepEqual(g1byst, g1); - // TODO: Do an ECSQL query to verify that 0->1 and 0->2 relationships can be found - // Update relationship instance property r1.memberPriority = 2; - testImodel.linkTableRelationships.updateInstance(r1); + r1.update(); - const g11: ElementGroupsMembers = testImodel.linkTableRelationships.getInstance(ElementGroupsMembers.classFullName, r1.id) as ElementGroupsMembers; + const g11 = ElementGroupsMembers.getInstance(testImodel, r1.id); assert.equal(g11.memberPriority, 2, "g11.memberPriority"); + testImodel.saveChanges("step 1"); // Delete relationship instance property - testImodel.linkTableRelationships.deleteInstance(r1); + g11.delete(); + testImodel.saveChanges("step 2"); + assert.throws(() => ElementGroupsMembers.getInstance(testImodel, r1.id), IModelError); + + const d0 = elements.insertElement(elementProps); + const d1 = elements.insertElement(elementProps); + const ede1 = ElementDrivesElement.create(testImodel, d0, d1, 0); + ede1.insert(); + testImodel.saveChanges("step 3"); - // TODO: Do an ECSQL query to verify that 0->1 is gone but 0->2 is still there. - testImodel.saveChanges(""); + ede1.delete(); + testImodel.saveChanges("step 4"); }); - it("should set EC properties of various types", () => { + it("should set EC properties of various types", async () => { const testImodel = imodel1; - try { - testImodel.getMetaData("TestBim:TestPhysicalObject"); - } catch (err) { - const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); - testImodel.importSchema(actx, schemaPathname); // will throw an exception if import fails - assert.isTrue(testImodel.getMetaData("TestBim:TestPhysicalObject") !== undefined); - } + testImodel.getMetaData("TestBim:TestPhysicalObject"); // Create a new physical model let newModelId: Id64String; @@ -1136,10 +1117,10 @@ describe("iModel", () => { }); let callbackcount = 0; - testPromise.then(() => { + testPromise.then(() => { // tslint:disable-line:no-floating-promises ++callbackcount; }); - testPromise.then(() => { + testPromise.then(() => { // tslint:disable-line:no-floating-promises ++callbackcount; }); @@ -1220,7 +1201,7 @@ describe("iModel", () => { assert.equal(lastAutoPushEventType, AutoPushEventType.PushFinished, "event handler should have been called"); // Just verify that this doesn't blow up. - autoPush.reserveCodes(); + await autoPush.reserveCodes(); // Now turn on auto-schedule and verify that we get a few auto-pushes lastPushTimeMillis = 0; diff --git a/core/backend/src/test/standalone/IModelImporter.test.ts b/core/backend/src/test/standalone/IModelImporter.test.ts new file mode 100644 index 0000000..b209a88 --- /dev/null +++ b/core/backend/src/test/standalone/IModelImporter.test.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import * as path from "path"; +import { assert } from "chai"; +import { Id64, Id64String } from "@bentley/bentleyjs-core"; +import { Range2d, Range3d } from "@bentley/geometry-core"; +import { CodeScopeSpec, ColorDef, GeometricElement2dProps, IModel, SubCategoryAppearance, Code } from "@bentley/imodeljs-common"; +import { + CategorySelector, DefinitionModel, DisplayStyle2d, DisplayStyle3d, DocumentListModel, Drawing, DrawingCategory, DrawingGraphic, DrawingGraphicRepresentsElement, DrawingViewDefinition, + IModelDb, IModelJsFs, ModelSelector, OrthographicViewDefinition, PhysicalModel, SpatialCategory, SubCategory, Subject, +} from "../../backend"; +import { KnownTestLocations } from "../KnownTestLocations"; + +class TestImporter { + public readonly iModelDb: IModelDb; + + public constructor() { + const outputDir = KnownTestLocations.outputDir; + if (!IModelJsFs.existsSync(outputDir)) + IModelJsFs.mkdirSync(outputDir); + const outputFile: string = path.join(outputDir, "TestImporter.bim"); + if (IModelJsFs.existsSync(outputFile)) + IModelJsFs.removeSync(outputFile); + this.iModelDb = IModelDb.createStandalone(outputFile, { rootSubject: { name: "TestImporter" } }); + assert.isTrue(IModelJsFs.existsSync(outputFile)); + } + + public import(): void { + const codeSpecId: Id64String = this.iModelDb.codeSpecs.insert("CodeSpec", CodeScopeSpec.Type.Model); + assert.isTrue(Id64.isValidId64(codeSpecId)); + const subjectId: Id64String = Subject.insert(this.iModelDb, IModel.rootSubjectId, "Subject", "Subject description"); + assert.isTrue(Id64.isValidId64(subjectId)); + const definitionModelId: Id64String = DefinitionModel.insert(this.iModelDb, subjectId, "Definition"); + assert.isTrue(Id64.isValidId64(definitionModelId)); + const physicalModelId: Id64String = PhysicalModel.insert(this.iModelDb, subjectId, "Physical"); + assert.isTrue(Id64.isValidId64(physicalModelId)); + const documentListModelId: Id64String = DocumentListModel.insert(this.iModelDb, subjectId, "Document"); + assert.isTrue(Id64.isValidId64(documentListModelId)); + const drawingId: Id64String = Drawing.insert(this.iModelDb, documentListModelId, "Drawing"); + assert.isTrue(Id64.isValidId64(drawingId)); + const modelSelectorId: Id64String = ModelSelector.insert(this.iModelDb, definitionModelId, "PhysicalModels", [physicalModelId]); + assert.isTrue(Id64.isValidId64(modelSelectorId)); + const spatialCategoryId: Id64String = SpatialCategory.insert(this.iModelDb, definitionModelId, "SpatialCategory", { color: ColorDef.red }); + assert.isTrue(Id64.isValidId64(spatialCategoryId)); + const subCategoryId: Id64String = SubCategory.insert(this.iModelDb, spatialCategoryId, "SubCategory", { color: ColorDef.blue }); + assert.isTrue(Id64.isValidId64(subCategoryId)); + const drawingCategoryId: Id64String = DrawingCategory.insert(this.iModelDb, definitionModelId, "DrawingCategory", new SubCategoryAppearance()); + assert.isTrue(Id64.isValidId64(drawingCategoryId)); + const drawingGraphicProps: GeometricElement2dProps = { + classFullName: DrawingGraphic.classFullName, + model: drawingId, + category: drawingCategoryId, + code: Code.createEmpty(), + userLabel: "DrawingGraphic", + }; + const drawingGraphicId: Id64String = this.iModelDb.elements.insertElement(drawingGraphicProps); + assert.isTrue(Id64.isValidId64(drawingGraphicId)); + const drawingGraphicRepresentsId: Id64String = DrawingGraphicRepresentsElement.insert(this.iModelDb, drawingGraphicId, drawingId); // WIP: drawingId as targetId is bogus + assert.isTrue(Id64.isValidId64(drawingGraphicRepresentsId)); + const spatialCategorySelectorId: Id64String = CategorySelector.insert(this.iModelDb, definitionModelId, "SpatialCategories", [spatialCategoryId]); + assert.isTrue(Id64.isValidId64(spatialCategorySelectorId)); + const drawingCategorySelectorId: Id64String = CategorySelector.insert(this.iModelDb, definitionModelId, "DrawingCategories", [drawingCategoryId]); + assert.isTrue(Id64.isValidId64(drawingCategorySelectorId)); + const displayStyle2dId: Id64String = DisplayStyle2d.insert(this.iModelDb, definitionModelId, "DisplayStyle2d"); + assert.isTrue(Id64.isValidId64(displayStyle2dId)); + const displayStyle3dId: Id64String = DisplayStyle3d.insert(this.iModelDb, definitionModelId, "DisplayStyle3d"); + assert.isTrue(Id64.isValidId64(displayStyle3dId)); + const viewRange = new Range3d(0, 0, 0, 100, 100, 20); + const viewId: Id64String = OrthographicViewDefinition.insert(this.iModelDb, definitionModelId, "Orthographic View", modelSelectorId, spatialCategorySelectorId, displayStyle3dId, viewRange); + assert.isTrue(Id64.isValidId64(viewId)); + this.iModelDb.views.setDefaultViewId(viewId); + const drawingViewRange = new Range2d(0, 0, 100, 100); + const drawingViewId: Id64String = DrawingViewDefinition.insert(this.iModelDb, definitionModelId, "Drawing View", drawingId, drawingCategorySelectorId, displayStyle2dId, drawingViewRange); + assert.isTrue(Id64.isValidId64(drawingViewId)); + this.iModelDb.saveChanges(); + } +} + +describe("IModelImporter", () => { + it("should import", async () => { + const importer = new TestImporter(); + assert.isDefined(importer); + importer.import(); + }); +}); diff --git a/core/backend/src/test/standalone/PromiseMemoizer.test.ts b/core/backend/src/test/standalone/PromiseMemoizer.test.ts index 8172eb2..741235b 100644 --- a/core/backend/src/test/standalone/PromiseMemoizer.test.ts +++ b/core/backend/src/test/standalone/PromiseMemoizer.test.ts @@ -16,7 +16,7 @@ describe.skip("PromiseMemoizer", () => { let accessTokenRegular: AccessToken; let accessTokenManager: AccessToken; - const pause = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const pause = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const generateTestFunctionKey = (accessToken: AccessToken, contextId: string, iModelId: string, openParams: OpenParams, version: IModelVersion): string => { return `${accessToken.toTokenString()}:${contextId}:${iModelId}:${JSON.stringify(openParams)}:${JSON.stringify(version)}`; diff --git a/core/backend/src/test/standalone/TxnManager.test.ts b/core/backend/src/test/standalone/TxnManager.test.ts new file mode 100644 index 0000000..022a8bd --- /dev/null +++ b/core/backend/src/test/standalone/TxnManager.test.ts @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import { ActivityLoggingContext, IModelStatus } from "@bentley/bentleyjs-core"; +import { assert } from "chai"; +import * as path from "path"; +import { IModelTestUtils, TestElementDrivesElement, TestPhysicalObject, TestPhysicalObjectProps } from "../IModelTestUtils"; +import { KnownTestLocations } from "../KnownTestLocations"; +import { IModelDb, SpatialCategory, TxnAction, PhysicalModel } from "../../backend"; +import { Code, IModel, SubCategoryAppearance, ColorByName, IModelError } from "@bentley/imodeljs-common"; + +describe("TxnManager", () => { + let imodel: IModelDb; + let props: TestPhysicalObjectProps; + + const actx = new ActivityLoggingContext(""); + + before(async () => { + imodel = IModelTestUtils.openIModel("test.bim"); + const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml"); + await imodel.importSchema(actx, schemaPathname); // will throw an exception if import fails + + props = { + classFullName: "TestBim:TestPhysicalObject", + model: PhysicalModel.insert(imodel, IModel.rootSubjectId, "TestModel"), + category: SpatialCategory.insert(imodel, IModel.dictionaryId, "MySpatialCategory", new SubCategoryAppearance({ color: ColorByName.darkRed })), + code: Code.createEmpty(), + intProperty: 100, + }; + imodel.saveChanges("schema change"); + imodel.nativeDb.enableTxnTesting(); + }); + after(() => IModelTestUtils.closeIModel(imodel)); + + it("Undo/Redo", () => { + assert.isDefined(imodel.getMetaData("TestBim:TestPhysicalObject"), "TestPhysicalObject is present"); + + const txns = imodel.txns; + assert.isFalse(txns.hasPendingTxns); + + const change1Msg = "change 1"; + const change2Msg = "change 2"; + let beforeUndo = 0; + let afterUndo = 0; + let undoAction = TxnAction.None; + + txns.onBeforeUndoRedo.addListener(() => afterUndo++); + txns.onAfterUndoRedo.addListener((action) => { beforeUndo++; undoAction = action; }); + + let elementId = imodel.elements.insertElement(props); + assert.isFalse(txns.isRedoPossible); + assert.isFalse(txns.isUndoPossible); + assert.isTrue(txns.hasUnsavedChanges); + assert.isFalse(txns.hasPendingTxns); + + imodel.saveChanges(change1Msg); + assert.isFalse(txns.hasUnsavedChanges); + assert.isTrue(txns.hasPendingTxns); + assert.isTrue(txns.hasLocalChanges); + + let element = imodel.elements.getElement(elementId); + assert.equal(element.intProperty, 100, "int property should be 100"); + + assert.isTrue(txns.isUndoPossible); // we have an undoable Txn, but nothing undone. + assert.equal(change1Msg, txns.getUndoString()); + assert.equal(IModelStatus.Success, txns.reverseSingleTxn()); + assert.isTrue(txns.isRedoPossible); + assert.equal(change1Msg, txns.getRedoString()); + assert.equal(beforeUndo, 1); + assert.equal(afterUndo, 1); + assert.equal(undoAction, TxnAction.Reverse); + + assert.throws(() => imodel.elements.getElement(elementId), IModelError); + assert.equal(IModelStatus.Success, txns.reinstateTxn()); + assert.isTrue(txns.isUndoPossible); + assert.isFalse(txns.isRedoPossible); + assert.equal(beforeUndo, 2); + assert.equal(afterUndo, 2); + assert.equal(undoAction, TxnAction.Reinstate); + + element = imodel.elements.getElement(elementId); + element.intProperty = 200; + element.update(); + + imodel.saveChanges(change2Msg); + element = imodel.elements.getElement(elementId); + assert.equal(element.intProperty, 200, "int property should be 200"); + assert.equal(txns.getTxnDescription(txns.queryPreviousTxnId(txns.getCurrentTxnId())), change2Msg); + + assert.equal(IModelStatus.Success, txns.reverseSingleTxn()); + element = imodel.elements.getElement(elementId); + assert.equal(element.intProperty, 100, "int property should be 100"); + + // make sure abandon changes works. + element.delete(); + assert.throws(() => imodel.elements.getElement(elementId), IModelError); + imodel.abandonChanges(); // + element = imodel.elements.getElement(elementId); // should be back now. + imodel.elements.insertElement(props); // create a new element + imodel.saveChanges(change2Msg); + + elementId = imodel.elements.insertElement(props); // create a new element + assert.isTrue(txns.hasUnsavedChanges); + assert.equal(IModelStatus.Success, txns.reverseSingleTxn()); + assert.isFalse(txns.hasUnsavedChanges); + assert.throws(() => imodel.elements.getElement(elementId), IModelError); // reversing a txn with pending uncommitted changes should abandon them. + assert.equal(IModelStatus.Success, txns.reinstateTxn()); + assert.throws(() => imodel.elements.getElement(elementId), IModelError); // doesn't come back, wasn't committed + + // verify multi-txn operations are undone/redone together + const el1 = imodel.elements.insertElement(props); + imodel.saveChanges("step 1"); + txns.beginMultiTxnOperation(); + assert.equal(1, txns.getMultiTxnOperationDepth()); + const el2 = imodel.elements.insertElement(props); + imodel.saveChanges("step 2"); + const el3 = imodel.elements.insertElement(props); + imodel.saveChanges("step 3"); + txns.endMultiTxnOperation(); + assert.equal(0, txns.getMultiTxnOperationDepth()); + assert.equal(IModelStatus.Success, txns.reverseSingleTxn()); + assert.throws(() => imodel.elements.getElement(el2), IModelError); + assert.throws(() => imodel.elements.getElement(el3), IModelError); + imodel.elements.getElement(el1); + assert.equal(IModelStatus.Success, txns.reverseSingleTxn()); + assert.throws(() => imodel.elements.getElement(el1), IModelError); + assert.equal(IModelStatus.Success, txns.reinstateTxn()); + assert.throws(() => imodel.elements.getElement(el2), IModelError); + assert.throws(() => imodel.elements.getElement(el3), IModelError); + imodel.elements.getElement(el1); + assert.equal(IModelStatus.Success, txns.reinstateTxn()); + imodel.elements.getElement(el1); + imodel.elements.getElement(el2); + imodel.elements.getElement(el3); + + assert.equal(IModelStatus.Success, txns.cancelTo(txns.queryFirstTxnId())); + assert.isFalse(txns.hasUnsavedChanges); + assert.isFalse(txns.hasPendingTxns); + assert.isFalse(txns.hasLocalChanges); + }); + + it("Element drives element events", () => { + const el1 = imodel.elements.insertElement(props); + const el2 = imodel.elements.insertElement(props); + const ede = TestElementDrivesElement.create(imodel, el1, el2); + ede.property1 = "test ede"; + ede.insert(); + let beforeOutputsHandled = 0; + let allInputsHandled = 0; + let rootChanged = 0; + let validateOutput = 0; + let deletedDependency = 0; + TestElementDrivesElement.deletedDependency.addListener((evProps) => { + assert.equal(evProps.sourceId, el1); + assert.equal(evProps.targetId, el2); + ++deletedDependency; + }); + TestElementDrivesElement.rootChanged.addListener((evProps, im) => { + const ede2 = im.relationships.getInstance(evProps.classFullName, evProps.id!); + assert.equal(ede2.property1, ede.property1); + assert.equal(evProps.sourceId, el1); + assert.equal(evProps.targetId, el2); + ++rootChanged; + }); + TestElementDrivesElement.validateOutput.addListener((_props) => ++validateOutput); + TestPhysicalObject.beforeOutputsHandled.addListener((id, im) => { + const e1 = im.elements.getElement(id); + assert.equal(e1.intProperty, props.intProperty); + assert.equal(id, el1); + ++beforeOutputsHandled; + }); + TestPhysicalObject.allInputsHandled.addListener((id, im) => { + const e2 = im.elements.getElement(id); + assert.equal(e2.intProperty, props.intProperty); + assert.equal(id, el2); + ++allInputsHandled; + }); + + imodel.saveChanges("step 1"); + assert.equal(1, beforeOutputsHandled); + assert.equal(1, allInputsHandled); + assert.equal(1, rootChanged); + assert.equal(0, validateOutput); + assert.equal(0, deletedDependency); + + const element2 = imodel.elements.getElement(el2); + element2.update(); + imodel.saveChanges("step 2"); + assert.equal(2, beforeOutputsHandled); + assert.equal(2, allInputsHandled); + assert.equal(2, rootChanged); + assert.equal(0, validateOutput); + assert.equal(0, deletedDependency); + }); + +}); diff --git a/core/bentley/CHANGELOG.json b/core/bentley/CHANGELOG.json index f1f310b..b14a713 100644 --- a/core/bentley/CHANGELOG.json +++ b/core/bentley/CHANGELOG.json @@ -1,6 +1,84 @@ { "name": "@bentley/bentleyjs-core", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/bentleyjs-core_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": {} + }, + { + "version": "0.170.0", + "tag": "@bentley/bentleyjs-core_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Change Logger.ParseLogLevel() to use camelCased method naming, Logger.parseLogLevel()." + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/bentleyjs-core_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": { + "none": [ + { + "comment": "Remove PromiseUtil class" + } + ] + } + }, + { + "version": "0.168.0", + "tag": "@bentley/bentleyjs-core_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/bentleyjs-core_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "changes to debug utilities. " + }, + { + "comment": "Fluentd Bunnyan Logger added" + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/bentleyjs-core_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/bentleyjs-core_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/bentleyjs-core_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "OIDC related enhancments (WIP)." + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/bentleyjs-core_v0.163.0", diff --git a/core/bentley/CHANGELOG.md b/core/bentley/CHANGELOG.md index 88b9b5f..92a588a 100644 --- a/core/bentley/CHANGELOG.md +++ b/core/bentley/CHANGELOG.md @@ -1,6 +1,56 @@ # Change Log - @bentley/bentleyjs-core -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +*Version update only* + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Change Logger.ParseLogLevel() to use camelCased method naming, Logger.parseLogLevel(). + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +### Updates + +- Remove PromiseUtil class + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- changes to debug utilities. +- Fluentd Bunnyan Logger added + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- OIDC related enhancments (WIP). +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/bentley/package.json b/core/bentley/package.json index 1214bfa..40bad7f 100644 --- a/core/bentley/package.json +++ b/core/bentley/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/bentleyjs-core", - "version": "0.163.0", + "version": "0.171.0", "description": "Bentley JavaScript core components", "main": "lib/bentleyjs-core.js", "typings": "lib/bentleyjs-core", @@ -28,7 +28,7 @@ }, "dependencies": {}, "devDependencies": { - "@bentley/build-tools": "0.163.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/mocha": "^5.2.5", "@types/node": "10.10.3", @@ -38,7 +38,7 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0", + "typescript": "~3.1.0", "ts-node": "^7.0.1", "nyc": "^13.0.1", "source-map-support": "^0.5.6" diff --git a/core/bentley/src/BeSQLite.ts b/core/bentley/src/BeSQLite.ts index b451ab7..a9cf4bb 100644 --- a/core/bentley/src/BeSQLite.ts +++ b/core/bentley/src/BeSQLite.ts @@ -172,7 +172,7 @@ export const enum DbResult { /** * Options that specify how to apply ChangeSets. */ -export const enum ChangeSetApplyOption { +export enum ChangeSetApplyOption { /** ChangeSet won't be used. */ None = 0, /** ChangeSet will be merged into the Db */ diff --git a/core/bentley/src/BentleyError.ts b/core/bentley/src/BentleyError.ts index a78ad4e..9f24564 100644 --- a/core/bentley/src/BentleyError.ts +++ b/core/bentley/src/BentleyError.ts @@ -294,6 +294,13 @@ export enum IModelHubStatus { FileNotFound = IMODELHUBERROR_REQUESTERRORBASE + 6, } +// iModelHub Services Errors +export enum AuthStatus { + Success = 0, + AUTHSTATUS_BASE = 0x20000, + Error = AUTHSTATUS_BASE, +} + /** When you want to associate an explanatory message with an error status value. */ export interface StatusCodeWithMessage { status: ErrorCodeType; @@ -615,6 +622,9 @@ export class BentleyError extends Error { case IModelHubStatus.FileHandlerNotSet: return "File handler is not set"; case IModelHubStatus.FileNotFound: return "File not found"; + // errors returned from authorization + case AuthStatus.Error: return "Authorization error"; + // Unexpected cases case IModelStatus.Success: case DbResult.BE_SQLITE_OK: diff --git a/core/bentley/src/Disposable.ts b/core/bentley/src/Disposable.ts index 0be0a82..36fa516 100644 --- a/core/bentley/src/Disposable.ts +++ b/core/bentley/src/Disposable.ts @@ -68,7 +68,7 @@ export function disposeArray(list?: IDisposable[]): undefined { * of this function is equal to return value of func. If func throws, this function also throws (after * disposing the resource). */ -export function using(resources: TDisposable | TDisposable[], func: (...resources: TDisposable[]) => TResult): TResult { +export function using(resources: TDisposable | TDisposable[], func: (...resources: TArg[]) => TResult): TResult { if (!Array.isArray(resources)) return using([resources], func); diff --git a/core/bentley/src/FluentdBunyanLoggerConfig.ts b/core/bentley/src/FluentdBunyanLoggerConfig.ts new file mode 100644 index 0000000..f799004 --- /dev/null +++ b/core/bentley/src/FluentdBunyanLoggerConfig.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Logging */ + +// tslint:disable-next-line:no-var-requires +const bunyan = require("bunyan"); +import { FluentdLoggerStream, IFluentdConfig } from "./FluentdLoggerStream"; +import { BentleyError, IModelStatus } from "./BentleyError"; + +/** Helps to configure the bentleyjs-core Logger to use fluentd and seq. + * Note: The app must depend on the bunyan, request and request-promise packages. + */ +export class FluentdBunyanLoggerConfig { + /** Create a bunyan logger that streams to fluentd + * ``` + * BunyanLoggerConfig.logToBunyan(FluentdBunyanLoggerConfig.createBunyanFluentdLogger(fluentdConfig)); + * ``` + * See [[BunyanLoggerConfig.logToBunyan]] + */ + public static createBunyanFluentdLogger(fluentdConfig: IFluentdConfig, loggerName: string): any { + if (fluentdConfig === undefined) { + fluentdConfig = {}; + } + const params: IFluentdConfig = {}; + params.fluentdHost = (fluentdConfig.fluentdHost || "http://localhost"); + params.fluentdPort = (fluentdConfig.fluentdPort || 9880); + params.fluentdTimeout = (fluentdConfig.fluentdTimeout || 1500); + params.seqServerUrl = (fluentdConfig.seqServerUrl || "http://localhost"); + params.seqServerPort = (fluentdConfig.seqServerPort || 5341); + params.seqApiKey = (fluentdConfig.seqApiKey || "InvalidApiKey"); + + // nb: Define only one bunyan stream! Otherwise, we will get logging messages coming out multiple times, once for each stream. (https://github.com/trentm/node-bunyan/issues/334) + // this one stream must accept messages at all levels. That is why we set it to "trace". That is just its lower limit. + // const tracelevel: any = "trace"; + + return bunyan.createLogger({ + name: loggerName, + streams: [ + { stream: new FluentdLoggerStream(params), level: 10 }, + ], + }); + } + + /** Check that the specified object is a valid SeqConfig. This is useful when reading a config from a .json file. */ + public static validateProps(fluentdConfig: any): void { + const validProps: string[] = ["host", "port"]; + for (const prop of Object.keys(fluentdConfig)) { + if (!validProps.includes(prop)) { + throw new BentleyError(IModelStatus.BadArg, "unrecognized fluentdConfig property: " + prop); + } + } + } +} diff --git a/core/bentley/src/FluentdLoggerStream.ts b/core/bentley/src/FluentdLoggerStream.ts new file mode 100644 index 0000000..d47b4b2 --- /dev/null +++ b/core/bentley/src/FluentdLoggerStream.ts @@ -0,0 +1,136 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Logging */ + +import { Writable } from "stream"; +import * as domain from "domain"; + +// tslint:disable-next-line:no-var-requires +const bunyan = require("bunyan"); +// tslint:disable-next-line:no-var-requires +const post = require("request-promise"); + +export interface GenericPost { + postasync(config: any, jsonbody: any): Promise; +} + +/** fluentd logging server configuration. */ +export interface IFluentdConfig { + /** The URL of the fluentd server to connect to. Defaults to localhost. */ + fluentdHost?: string; + /** The port of the fluentd server. Defaults to 9880. */ + fluentdPort?: number; + /** fluentd server request timeout. Defaults to 1500. */ + fluentdTimeout?: number; + /** The URL of the seq server to send logs. Defaults to localhost. */ + seqServerUrl?: string; + /** The port of the seq server. Defaults to 5341. */ + seqServerPort?: number; + /** The API Key to use when sending logs to Seq */ + seqApiKey?: string; +} + +export class PostFluentd implements GenericPost { + private generateOptions(config: IFluentdConfig, jsonbody: any): any { + const customHeaders: any = {}; + customHeaders["content-type"] = "application/json"; + customHeaders["seq-server"] = config.seqServerUrl; + customHeaders["seq-apikey"] = config.seqApiKey; + customHeaders["seq-port"] = config.seqServerPort; + // TODO: Handle SEQ_PORT (on fluentd side as well) and use kabab case instead of snake case. + return { + uri: config.fluentdHost + ":" + config.fluentdPort + "/seqlogging", + body: jsonbody, + headers: JSON.parse(JSON.stringify(customHeaders)), + resolveWithFullResponse: true, + timeout: config.fluentdTimeout, + }; + } + public async postasync(config: any, jsonbody: any): Promise { + const response = await post(this.generateOptions(config, jsonbody)); + return response.statusCode || -1; + } +} + +export class FluentdLoggerStream extends Writable { + private _fluentdParams: IFluentdConfig; + constructor(fluentdParams: IFluentdConfig) { + super(); + this._fluentdParams = fluentdParams; + } + + private mapLevelToString(level: any): any { + let response: string; + switch (level) { + case bunyan.TRACE: { + response = "Trace"; + break; + } + case bunyan.DEBUG: { + response = "Debug"; + break; + } + case bunyan.INFO: { + response = "Information"; + break; + } + case bunyan.WARN: { + response = "Warning"; + break; + } + case bunyan.ERROR: { + response = "Error"; + break; + } + case bunyan.FATAL: { + response = "Fatal"; + break; + } + default: { + response = "Information"; + } + } + return response; + } + + // tslint:disable-next-line:naming-convention + public _writev(chunks: Array<{ chunk: any, encoding: string }>, callback: (err?: Error) => void): void { + for (const entry of chunks) { + this._write(entry.chunk, entry.encoding, callback); + } + } + + // tslint:disable-next-line:naming-convention + public _write(chunk: any, encoding: string, callback: (err?: Error) => void): void { + // we create a domain to catch errors from the socket. Major errors like CONNECTION not made is sent to bunyan + const fluentdDomain: domain.Domain = domain.create(); + fluentdDomain.on("error", (errEvent: Error) => { + this.emit("error", new Error(`Fluentd domain error -- , ${errEvent.message}`)); + callback(errEvent); + }); + + fluentdDomain.run(() => { + // generate a valid json as body + let packet: any; + try { + packet = JSON.parse(chunk); + if (packet.hasOwnProperty("level")) { + packet.level = this.mapLevelToString(chunk.level); + } + packet = JSON.stringify(packet); + } catch (error) { + this.emit("error", new Error(`Error: ${error}, Encoding: ${encoding}`)); + packet = JSON.stringify(chunk); + } + + // Post to fluentd -- async + const poster = new PostFluentd(); + Promise.resolve(poster.postasync(this._fluentdParams, packet)) + .then((res) => { if (res === -1 || res !== 200) { throw new Error("invalid response from fluentd"); } }) + .catch((err) => { this.emit("error", new Error(`Fluentd post error -- ${err.message}`)); }); + callback(); + }); + } +} diff --git a/core/bentley/src/Id.ts b/core/bentley/src/Id.ts index 9d382ea..43999ce 100644 --- a/core/bentley/src/Id.ts +++ b/core/bentley/src/Id.ts @@ -5,14 +5,14 @@ /** @module Ids */ /** - * A string containing a well-formed string representation of an [[Id64]]. + * A string containing a well-formed string representation of an [Id64]($bentleyjs-core). * * See [Working with Ids]($docs/learning/common/Id64.md). */ export type Id64String = string; /** - * A string containing a well-formed string representation of a [[Guid]]. + * A string containing a well-formed string representation of a [Guid]($bentleyjs-core). */ export type GuidString = string; @@ -37,10 +37,7 @@ function isLowerCaseNonZeroHexDigit(str: string, index: number) { function isLowerCaseHexDigit(str: string, index: number, allowZero: boolean = true): boolean { const charCode = str.charCodeAt(index); const minDecimalDigit = allowZero ? 0x30 : 0x31; // '0' or '1'... - if (charCode >= minDecimalDigit && charCode <= 0x39) // ...to '9' - return true; - else - return charCode >= 0x61 && charCode <= 0x66; // 'a' to 'f' + return (charCode >= minDecimalDigit && charCode <= 0x39) || (charCode >= 0x61 && charCode <= 0x66); // '0'-'9, 'a' -'f' } function isValidHexString(id: string, startIndex: number, len: number) { @@ -114,7 +111,7 @@ export namespace Id64 { * For a description of "well-formed", see [Working with Ids]($docs/learning/common/Id64.md). */ export function fromString(val: string): Id64String { - // NB: Yes, we must check the run-time type... + // NB: in case this is called from JavaScript, we must check the run-time type... if (typeof val !== "string") return invalid; @@ -218,11 +215,11 @@ export namespace Id64 { if (arg instanceof Set) return arg; - const ids = new Set(); + const ids = new Set(); if (typeof arg === "string") ids.add(arg); else if (Array.isArray(arg)) { - arg.forEach((id) => { + arg.forEach((id: Id64String) => { if (typeof id === "string") ids.add(id); }); @@ -238,18 +235,17 @@ export namespace Id64 { * @param id A well-formed Id string. * @returns true if the Id represents a transient Id. * @note This method assumes the input is a well-formed Id string. - * @see [[isTransientId64]] + * @see [[Id64.isTransientId64]] * @see [[TransientIdSequence]] */ export function isTransient(id: Id64String): boolean { // A transient Id is of the format "0xffffffxxxxxxxxxx" where the leading 6 digits indicate an invalid briefcase Id. - const str = id.toString(); - return 18 === str.length && str.startsWith("0xffffff"); + return 18 === id.length && id.startsWith("0xffffff"); } /** Determine if the input is a well-formed [[Id64String]] and represents a transient Id. - * @see [[isTransient]] - * @see [[isId64]] + * @see [[Id64.isTransient]] + * @see [[Id64.isId64]] * @see [[TransientIdSequence]] */ export function isTransientId64(id: string): boolean { @@ -259,7 +255,7 @@ export namespace Id64 { /** Determine if the input is a well-formed [[Id64String]]. * * For a description of "well-formed", see [Working with Ids]($docs/learning/common/Id64.md). - * @see [[isValidId64]] + * @see [[Id64.isValidId64]] */ export function isId64(id: string): boolean { const len = id.length; @@ -303,27 +299,21 @@ export namespace Id64 { /** Returns true if the input is not equal to the representation of an invalid Id. * @note This method assumes the input is a well-formed Id string. - * @see [[isInvalid]] - * @see [[isValidId64]] + * @see [[Id64.isInvalid]] + * @see [[Id64.isValidId64]] */ - export function isValid(id: Id64String): boolean { - return Id64.invalid !== id; - } + export function isValid(id: Id64String): boolean { return Id64.invalid !== id; } /** Returns true if the input is a well-formed [[Id64String]] representing a valid Id. - * @see [[isValid]] - * @see [[isId64]] + * @see [[Id64.isValid]] + * @see [[Id64.isId64]] */ - export function isValidId64(id: string): boolean { - return Id64.invalid !== id && Id64.isId64(id); - } + export function isValidId64(id: string): boolean { return Id64.invalid !== id && Id64.isId64(id); } /** Returns true if the input is a well-formed [[Id64String]] representing an invalid Id. - * @see [[isValid]] + * @see [[Id64.isValid]] */ - export function isInvalid(id: Id64String): boolean { - return Id64.invalid === id; - } + export function isInvalid(id: Id64String): boolean { return Id64.invalid === id; } } /** @@ -347,7 +337,7 @@ export class TransientIdSequence { export namespace Guid { const uuidPattern = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); - /** determine whether the input string is "guid-like". That is, it follows the 8-4-4-4-12 pattern. This does not enforce + /** Determine whether the input string is "guid-like". That is, it follows the 8-4-4-4-12 pattern. This does not enforce * that the string is actually in valid UUID format. */ export function isGuid(value: string): boolean { return uuidPattern.test(value); } diff --git a/core/bentley/src/Logger.ts b/core/bentley/src/Logger.ts index 8ba6ff7..552d791 100644 --- a/core/bentley/src/Logger.ts +++ b/core/bentley/src/Logger.ts @@ -117,7 +117,7 @@ export class Logger { } /** Interpret a string as the name of a LogLevel */ - public static ParseLogLevel(str: string): LogLevel { + public static parseLogLevel(str: string): LogLevel { switch (str.toUpperCase()) { case "EXCEPTION": return LogLevel.Error; case "FATAL": return LogLevel.Error; @@ -134,11 +134,11 @@ export class Logger { public static configureLevels(cfg: LoggerLevelsConfig) { Logger.validateProps(cfg); if (cfg.defaultLevel !== undefined) { - this.setLevelDefault(Logger.ParseLogLevel(cfg.defaultLevel)); + this.setLevelDefault(Logger.parseLogLevel(cfg.defaultLevel)); } if (cfg.categoryLevels !== undefined) { for (const cl of cfg.categoryLevels) { - this.setLevel(cl.category, Logger.ParseLogLevel(cl.logLevel)); + this.setLevel(cl.category, Logger.parseLogLevel(cl.logLevel)); } } } diff --git a/core/bentley/src/PromiseUtil.ts b/core/bentley/src/PromiseUtil.ts deleted file mode 100644 index 46aaa29..0000000 --- a/core/bentley/src/PromiseUtil.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -/** @module Utils */ - -export class BusyError extends Error { - public static check(err: Error) { - if (err instanceof BusyError || err.message === "abort") { - // console.log("Busy error: " + err.message); - } else { - // console.log("re-throwing from BusyError"); - throw err; // unrecognized exception - } - } -} - -/** - * An object that returns a Promise when you call [[init]], but supplies a way to abort the promise if it is no longer relevant. - * When you call abort, the promise will be rejected. You must supply a [[run]] method to the constructor that - * creates the real Promise for the underlying action. Notice that to use this class there are really two - * Promises involved that are chained together. That makes this class less efficient than just using a Promise directly. - */ -class PromiseWithAbort { - /** Method to abort the Promise created by [[init]] while it is outstanding. The promise will be rejected. */ - public abort!: () => void; - private _resolve!: (val: any) => void; - - /** Create a PromiseWithAbort. After this call you must call [[init]] to create the underlying Promise. - * @param _run The method that creates the underlying Promise. - * @param _args An array of args to be passed to run when [[start]] is called. - */ - constructor(private _run: (...args: any[]) => Promise, private _args: any[]) { } - - /** Create a Promise that is chained to the underlying Promise, but is connected to the abort method. */ - public init(msg: string): Promise { return new Promise((resolve, reject) => { this.abort = () => reject(new BusyError(msg)); this._resolve = resolve; }); } - - /** Call the [[run]] method supplied to the ctor to start the underlying Promise. */ - public start() { this._run(this._args).then((val) => this._resolve(val)); } -} - -/** - * Orchestrator of a one-at-a-time Promise. This concept is useful only for *replaceable* operations (that is, operations where subsequent requests replace and obviate - * the need for previous requests) over slow HTTP connections. In that case, without this class, the stream of requests can overwhelm the connection, and cause the HTTP - * request queue to grow such that the delay to service new requests is unbounded. - * - * With this class, we issue the initial request immediately. When the second request arrives before the first one completes, it becomes *pending*. If subsequent - * requests arrive with a pending request, the current pending request is *aborted* (its Promise is rejected) and the new request becomes pending. - * When the active request completes, the pending request (if present) is started. In this manner there will only ever be one outstanding HTTP request for this type - * of operation, but the last request will always eventually complete. - * - */ -export class OneAtATimePromise { - private _active?: PromiseWithAbort; - private _pending?: PromiseWithAbort; - - /** Ctor for OneAtATimePromise. - * @param _msg A message to be passed to the constructor of [[BusyError]] when pending requests are aborted. - * @param _run The method that performs an action that creates the Promise. - */ - constructor(private _msg: string, private _run: (...args: any[]) => Promise, private _allowPending = true) { } - - /** Add a new request to this OneAtATimePromise. The request will only run when no other outstanding requests are active. - * @note Callers of this method *must* handle BusyError exceptions. - */ - public async addRequest(...args: any[]) { - const entry = new PromiseWithAbort(this._run, args); // create an "abortable promise" object - const promise = entry.init(this._msg); // create the Promise from PromiseWithAbort. Note: this must be called before we call start. - - if (this._active !== undefined) { // is there an active request? - if (this._pending) // yes. If there is also a pending request, this one replaces it and previous one is aborted - this._pending.abort(); // rejects previous call to this method, throwing BusyError. - if (this._allowPending) - this._pending = entry; - } else { - this._active = entry; // this is the first request, start it. - entry.start(); - } - - await promise; // wait until we're finally completed - this._active = this._pending; // see if there's a pending request waiting - this._pending = undefined; // clear pending - if (this._active) - this._active.start(); // now start the pending request - return promise; // return fulfilled promise - } -} diff --git a/core/bentley/src/bentleyjs-core.ts b/core/bentley/src/bentleyjs-core.ts index 2a3d8d9..a8f7d15 100644 --- a/core/bentley/src/bentleyjs-core.ts +++ b/core/bentley/src/bentleyjs-core.ts @@ -15,7 +15,6 @@ export * from "./JsonUtils"; export * from "./Logger"; export * from "./ActivityLoggingContext"; export * from "./LRUMap"; -export * from "./PromiseUtil"; export * from "./SortedArray"; export * from "./StringUtils"; export * from "./Time"; diff --git a/core/bentley/src/test/Disposable.test.ts b/core/bentley/src/test/Disposable.test.ts index 30898dc..005d779 100644 --- a/core/bentley/src/test/Disposable.test.ts +++ b/core/bentley/src/test/Disposable.test.ts @@ -51,7 +51,7 @@ describe("Disposable", () => { const disposable = new CallbackDisposable(() => { disposed = true; }); - await using(disposable, () => { + await using(disposable, async () => { return new Promise((resolve: () => void, _reject: () => void) => { setTimeout(() => { resolve(); @@ -67,7 +67,7 @@ describe("Disposable", () => { const disposable = new CallbackDisposable(() => { disposed = true; }); - const result = using(disposable, () => { + const result = using(disposable, async () => { return new Promise((_resolve: () => void, reject: () => void) => { setTimeout(() => { reject(); diff --git a/core/clients-backend/CHANGELOG.json b/core/clients-backend/CHANGELOG.json index 586e1de..2959852 100644 --- a/core/clients-backend/CHANGELOG.json +++ b/core/clients-backend/CHANGELOG.json @@ -1,6 +1,63 @@ { "name": "@bentley/imodeljs-clients-backend", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-clients-backend_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": {} + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-clients-backend_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": {} + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-clients-backend_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-clients-backend_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-clients-backend_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": {} + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-clients-backend_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-clients-backend_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-clients-backend_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "OIDC related enhancments (WIP)." + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-clients-backend_v0.163.0", diff --git a/core/clients-backend/CHANGELOG.md b/core/clients-backend/CHANGELOG.md index 879613a..cc2117c 100644 --- a/core/clients-backend/CHANGELOG.md +++ b/core/clients-backend/CHANGELOG.md @@ -1,6 +1,49 @@ # Change Log - @bentley/imodeljs-clients-backend -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +*Version update only* + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +*Version update only* + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +*Version update only* + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- OIDC related enhancments (WIP). +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/clients-backend/package.json b/core/clients-backend/package.json index 2fb9264..4dbb183 100644 --- a/core/clients-backend/package.json +++ b/core/clients-backend/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-clients-backend", - "version": "0.163.0", + "version": "0.171.0", "description": "Clients for various Bentley Services used by iModel.js at the backend", "main": "lib/index.js", "typings": "lib/index", @@ -32,16 +32,16 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/config-loader": "0.163.0", - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", + "@bentley/config-loader": "0.171.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/mocha": "^5.2.5", "@types/nock": "^9.1.2", @@ -56,12 +56,13 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0", + "typescript": "~3.1.0", "webpack": "^4.16.4" }, "dependencies": { - "@bentley/imodeljs-clients": "0.163.0", - "openid-client": "^2.3.1" + "@bentley/imodeljs-clients": "0.171.0", + "openid-client": "^2.3.1", + "@openid/appauth": "^1.1.1" }, "nyc": { "nycrc-path": "./node_modules/@bentley/build-tools/.nycrc" diff --git a/core/clients-backend/src/index.ts b/core/clients-backend/src/index.ts index c27b0fd..6c9b329 100644 --- a/core/clients-backend/src/index.ts +++ b/core/clients-backend/src/index.ts @@ -2,6 +2,4 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -export * from "./OidcBackendClient"; -export * from "./OidcAgentClient"; -export * from "./OidcDelegationClient"; +export * from "./oidc"; diff --git a/core/clients-backend/src/OidcAgentClient.ts b/core/clients-backend/src/oidc/OidcAgentClient.ts similarity index 100% rename from core/clients-backend/src/OidcAgentClient.ts rename to core/clients-backend/src/oidc/OidcAgentClient.ts diff --git a/core/clients-backend/src/OidcBackendClient.ts b/core/clients-backend/src/oidc/OidcBackendClient.ts similarity index 83% rename from core/clients-backend/src/OidcBackendClient.ts rename to core/clients-backend/src/oidc/OidcBackendClient.ts index 53be95b..086b2af 100644 --- a/core/clients-backend/src/OidcBackendClient.ts +++ b/core/clients-backend/src/oidc/OidcBackendClient.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { OidcClient, AccessToken, UserProfile, IncludePrefix } from "@bentley/imodeljs-clients"; -import { Issuer, Client as OpenIdClient, ClientConfiguration, GrantParams, TokenSet, UserInfo } from "openid-client"; +import { OidcClient, AccessToken, IncludePrefix, UserInfo } from "@bentley/imodeljs-clients"; +import { Issuer, Client as OpenIdClient, ClientConfiguration, GrantParams, TokenSet, UserInfo as OpenIdUserInfo } from "openid-client"; import { ActivityLoggingContext, BentleyStatus, BentleyError } from "@bentley/bentleyjs-core"; /** Client configuration to create OIDC/OAuth tokens for backend applications */ @@ -63,15 +63,11 @@ export abstract class OidcBackendClient extends OidcClient { return this._client; } - private createUserProfile(userInfo: UserInfo): UserProfile { - return new UserProfile(userInfo.given_name, userInfo.family_name, userInfo.email!, userInfo.sub, userInfo.org_name!, userInfo.org!, userInfo.ultimate_site!, userInfo.usage_country_iso!); - } - - private createToken(tokenSet: TokenSet, userInfo: UserInfo): AccessToken { + private createToken(tokenSet: TokenSet, openIdUserInfo: OpenIdUserInfo): AccessToken { const startsAt: Date = new Date(tokenSet.expires_at - tokenSet.expires_in); const expiresAt: Date = new Date(tokenSet.expires_at); - const userProfile = this.createUserProfile(userInfo); - return AccessToken.fromJsonWebTokenString(tokenSet.access_token, userProfile, startsAt, expiresAt); + const userInfo = UserInfo.fromJson(openIdUserInfo); + return AccessToken.fromJsonWebTokenString(tokenSet.access_token, startsAt, expiresAt, userInfo); } protected async exchangeToken(actx: ActivityLoggingContext, grantParams: GrantParams): Promise { @@ -83,7 +79,7 @@ export abstract class OidcBackendClient extends OidcClient { const client = await this.getClient(actx); const tokenSet: TokenSet = await client.grant(grantParams); - const userInfo: UserInfo = await client.userinfo(tokenSet.access_token); + const userInfo: OpenIdUserInfo = await client.userinfo(tokenSet.access_token); return this.createToken(tokenSet, userInfo); } @@ -100,7 +96,9 @@ export abstract class OidcBackendClient extends OidcClient { const client = await this.getClient(actx); const tokenSet: TokenSet = await client.refresh(jwt.toTokenString(IncludePrefix.No)!); - const userInfo: UserInfo = await client.userinfo(tokenSet.access_token); + const userInfo: OpenIdUserInfo = await client.userinfo(tokenSet.access_token); return this.createToken(tokenSet, userInfo); } } + +// const userInfo: UserInfo = await client.userinfo(tokenSet.access_token); diff --git a/core/clients-backend/src/OidcDelegationClient.ts b/core/clients-backend/src/oidc/OidcDelegationClient.ts similarity index 100% rename from core/clients-backend/src/OidcDelegationClient.ts rename to core/clients-backend/src/oidc/OidcDelegationClient.ts diff --git a/core/clients-backend/src/oidc/OidcDeviceClient.ts b/core/clients-backend/src/oidc/OidcDeviceClient.ts new file mode 100644 index 0000000..e878283 --- /dev/null +++ b/core/clients-backend/src/oidc/OidcDeviceClient.ts @@ -0,0 +1,216 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { BeEvent, ActivityLoggingContext, BentleyError, AuthStatus, Logger, assert } from "@bentley/bentleyjs-core"; +import { AccessToken, OidcClient, IOidcFrontendClient, UserInfo, OidcFrontendClientConfiguration } from "@bentley/imodeljs-clients"; +import { + AuthorizationRequest, AuthorizationRequestJson, AuthorizationNotifier, AuthorizationServiceConfiguration, + AuthorizationResponse, AuthorizationError, RevokeTokenRequest, + GRANT_TYPE_AUTHORIZATION_CODE, GRANT_TYPE_REFRESH_TOKEN, + TokenRequest, TokenRequestJson, BaseTokenRequestHandler, TokenRequestHandler, TokenResponse, +} from "@openid/appauth"; +import { NodeBasedHandler, NodeRequestor } from "@openid/appauth/built/node_support"; +import { StringMap } from "@openid/appauth/built/types"; + +const loggingCategory = "imodeljs-clients-device.OidcDeviceClient"; + +export class OidcDeviceClient extends OidcClient implements IOidcFrontendClient { + private _clientConfiguration: OidcFrontendClientConfiguration; + private _notifier: AuthorizationNotifier; + private _authorizationHandler: NodeBasedHandler; + private _tokenHandler: TokenRequestHandler; + private _configuration: AuthorizationServiceConfiguration | undefined; + private _tokenResponse: TokenResponse | undefined; + private _nodeRequestor = new NodeRequestor(); // the Node.js based HTTP client + private _actx?: ActivityLoggingContext; + + /** Event called when the user's sign-in state changes - this may be due to calls to signIn(), signOut() or simply because the token expired */ + public readonly onUserStateChanged = new BeEvent<(token: AccessToken | undefined) => void>(); + + public constructor(clientConfiguration: OidcFrontendClientConfiguration) { + super(); + this._clientConfiguration = clientConfiguration; + this._notifier = new AuthorizationNotifier(); + this._authorizationHandler = new NodeBasedHandler(); + + this._tokenHandler = new BaseTokenRequestHandler(this._nodeRequestor); + // set notifier to deliver responses + + this._authorizationHandler.setAuthorizationNotifier(this._notifier); + + const redirectUrl = new URL(this._clientConfiguration.redirectUri); + this._authorizationHandler.httpServerPort = +redirectUrl.port; + + // set a listener to listen for authorization responses + this._notifier.setAuthorizationListener(this.authorizationListener); + } + + /** Used to initialize the client - must be awaited before any other methods are called */ + public async initialize(actx: ActivityLoggingContext): Promise { + const url = await this.getUrl(actx); + this._configuration = await AuthorizationServiceConfiguration.fetchFromIssuer( + url, + this._nodeRequestor, + ); + Logger.logTrace(loggingCategory, "Initialized service configuration", () => ({ configuration: this._configuration })); + } + + public getIsSignedIn(): boolean { + return !!this._tokenResponse && this._tokenResponse.isValid(); + } + + /** Called to start the sign-in process. Subscribe to onUserStateChanged to be notified when sign-in completes */ + public signIn(actx: ActivityLoggingContext): void { + if (this.getIsSignedIn()) + return; + this.makeAuthorizationRequest(actx); + } + + /** Called to start the sign-out process. Subscribe to onUserStateChanged to be notified when sign-out completes */ + public signOut(actx: ActivityLoggingContext): void { + if (!this.getIsSignedIn()) + return; + + this.makeRevokeTokenRequest(actx); // tslint:disable-line:no-floating-promises + } + + /** Returns a promise that resolves to the AccessToken if signed in, and undefined otherwise. The token is refreshed if it's possible and necessary. */ + public async getAccessToken(actx: ActivityLoggingContext): Promise { + actx.enter(); + if (!this._tokenResponse) + throw new BentleyError(AuthStatus.Error, "Not signed In. First call signIn()", Logger.logError, loggingCategory); + + if (!this._tokenResponse.isValid()) { + await this.makeAccessTokenRequest(actx); + assert(this._tokenResponse.isValid()); + } + + const request = new Request(this._configuration!.userInfoEndpoint!, { + headers: new Headers({ Authorization: `Bearer ${this._tokenResponse.accessToken}` }), + method: "GET", + cache: "no-cache", + }); + + const result = await fetch(request); + const user = await result.json(); + const userInfo = UserInfo.fromJson(user); + const startsAt: Date = new Date(this._tokenResponse.issuedAt); + const expiresAt: Date = new Date(this._tokenResponse.issuedAt + this._tokenResponse.expiresIn!); + const accessToken = AccessToken.fromJsonWebTokenString(this._tokenResponse.accessToken, startsAt, expiresAt, userInfo); + return Promise.resolve(accessToken); + } + + /** Disposes the resources held by this client */ + public dispose(): void { + } + + private async authorizationListener(authRequest: AuthorizationRequest, authResponse: AuthorizationResponse | null, authError: AuthorizationError | null) { + assert(!!this._actx); + const actx: ActivityLoggingContext = this._actx!; + this._actx = undefined; + actx.enter(); + + Logger.logTrace(loggingCategory, "Authorization listener invoked", () => ({ authRequest, authResponse, authError })); + if (authError || !authResponse) + throw new BentleyError(AuthStatus.Error, "Authorization error", Logger.logError, loggingCategory, () => authError); + + await this.makeRefreshTokenRequest(this._actx!, authResponse.code, authRequest); + actx.enter(); + + const accessToken: AccessToken | undefined = await this.getAccessToken(this._actx!); + actx.enter(); + + Logger.logTrace(loggingCategory, "Authorization completed, and issued access token"); + this.onUserStateChanged.raiseEvent(accessToken); + } + + private makeAuthorizationRequest(actx: ActivityLoggingContext) { + actx.enter(); + if (!this._configuration) + throw new BentleyError(AuthStatus.Error, "Not initialized. First call initialize()", Logger.logError, loggingCategory); + + const reqJson: AuthorizationRequestJson = { + response_type: AuthorizationRequest.RESPONSE_TYPE_CODE, + client_id: this._clientConfiguration.clientId, + redirect_uri: this._clientConfiguration.redirectUri, + scope: this._clientConfiguration.scope || "openid email profile organization feature_tracking imodelhub context-registry-service imodeljs-router offline_access", + extras: { prompt: "consent", access_type: "offline" }, + }; + const request = new AuthorizationRequest( + reqJson, + undefined /* state */, + true, /* usePkce */ + ); + + Logger.logTrace(loggingCategory, "Making authorization request", () => ({ configuration: this._configuration, request })); + this._actx = actx; + this._authorizationHandler.performAuthorizationRequest( + this._configuration, + request, + ); + } + + /** Make a request for a refresh token starting with the authorization code */ + private async makeRefreshTokenRequest(actx: ActivityLoggingContext, authCode: string, authRequest: AuthorizationRequest): Promise { + actx.enter(); + if (!this._configuration) + throw new BentleyError(AuthStatus.Error, "Not initialized. First call initialize()", Logger.logError, loggingCategory); + + let extras: StringMap | undefined; + if (authRequest && authRequest.internal) { + extras = {}; + extras.code_verifier = authRequest.internal.code_verifier; + } + + const tokenRequestJson: TokenRequestJson = { + grant_type: GRANT_TYPE_AUTHORIZATION_CODE, + code: authCode, + redirect_uri: this._clientConfiguration.redirectUri, + client_id: this._clientConfiguration.clientId, + extras, + }; + + const request = new TokenRequest(tokenRequestJson); + this._tokenResponse = await this._tokenHandler.performTokenRequest(this._configuration, request); + return this._tokenResponse; + } + + private async makeAccessTokenRequest(actx: ActivityLoggingContext): Promise { + actx.enter(); + if (!this._configuration) + throw new BentleyError(AuthStatus.Error, "Not initialized. First call initialize()", Logger.logError, loggingCategory); + if (!this._tokenResponse) + throw new BentleyError(AuthStatus.Error, "Missing refresh token. First call signIn() and ensure it's successful", Logger.logError, loggingCategory); + + const tokenRequestJson: TokenRequestJson = { + grant_type: GRANT_TYPE_REFRESH_TOKEN, + refresh_token: this._tokenResponse.refreshToken, + redirect_uri: this._clientConfiguration.redirectUri, + client_id: this._clientConfiguration.clientId, + }; + + const request = new TokenRequest(tokenRequestJson); + this._tokenResponse = await this._tokenHandler.performTokenRequest(this._configuration, request); + actx.enter(); + + return this._tokenResponse; + } + + private async makeRevokeTokenRequest(actx: ActivityLoggingContext): Promise { + actx.enter(); + if (!this._configuration) + throw new BentleyError(AuthStatus.Error, "Not initialized. First call initialize()", Logger.logError, loggingCategory); + if (!this._tokenResponse) + throw new BentleyError(AuthStatus.Error, "Missing refresh token. First call signIn() and ensure it's successful", Logger.logError, loggingCategory); + + const request = new RevokeTokenRequest({ token: this._tokenResponse.refreshToken! }); + await this._tokenHandler.performRevokeTokenRequest(this._configuration, request); + actx.enter(); + + this._tokenResponse = undefined; + Logger.logTrace(loggingCategory, "Authorization revoked, and removed access token"); + this.onUserStateChanged.raiseEvent(undefined); + } +} diff --git a/core/clients-backend/src/oidc/index.ts b/core/clients-backend/src/oidc/index.ts new file mode 100644 index 0000000..3095106 --- /dev/null +++ b/core/clients-backend/src/oidc/index.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +export * from "./OidcBackendClient"; +export * from "./OidcAgentClient"; +export * from "./OidcDelegationClient"; +export * from "./OidcDeviceClient"; diff --git a/core/clients-backend/src/test/OidcBackendClient.test.ts b/core/clients-backend/src/test/OidcBackendClient.test.ts index f64b504..bb8bbfa 100644 --- a/core/clients-backend/src/test/OidcBackendClient.test.ts +++ b/core/clients-backend/src/test/OidcBackendClient.test.ts @@ -42,7 +42,7 @@ describe("OidcBackendClient (#integration)", () => { const getIModelId = async (accessToken: AccessToken, iModelName: string, projectId: string): Promise => { const hubClient = new IModelHubClient(); - const imodel: HubIModel = (await hubClient.IModels().get(actx, accessToken, projectId, new IModelQuery().byName(testIModelName)))[0]; + const imodel: HubIModel = (await hubClient.iModels.get(actx, accessToken, projectId, new IModelQuery().byName(testIModelName)))[0]; chai.expect(imodel.name).to.be.equal(iModelName); return imodel.wsgId; }; diff --git a/core/clients/CHANGELOG.json b/core/clients/CHANGELOG.json index 5df0458..ab42b65 100644 --- a/core/clients/CHANGELOG.json +++ b/core/clients/CHANGELOG.json @@ -1,6 +1,102 @@ { "name": "@bentley/imodeljs-clients", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-clients_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "Use property getters instead of methods for IModelClient" + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-clients_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Fixed floating promises in iModelHub client" + }, + { + "comment": "Fix for integration tests" + }, + { + "comment": "Use property getters instead of methods for IModelClient" + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-clients_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-clients_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-clients_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Updated iModel Hub Client so iModel Base Handler is injectable. Now Http Request Options can be sepecified for the iModelHubClient" + }, + { + "comment": "Added IModelHubClient.IModel, removed IModelQuery.primary(), use IModelHubClient.IModel.Get instead" + }, + { + "comment": "Simplified download stream" + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-clients_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-clients_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-clients_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Deprecated dev-cors-proxy-server and use of it. " + }, + { + "comment": "Removed PropertySerializer used by ECJsonTypeMap." + }, + { + "comment": "OIDC related enhancments (WIP)." + }, + { + "comment": "Fixed more integration tests. " + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-clients_v0.163.0", diff --git a/core/clients/CHANGELOG.md b/core/clients/CHANGELOG.md index 465ac4f..2331779 100644 --- a/core/clients/CHANGELOG.md +++ b/core/clients/CHANGELOG.md @@ -1,6 +1,62 @@ # Change Log - @bentley/imodeljs-clients -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- Use property getters instead of methods for IModelClient + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Fixed floating promises in iModelHub client +- Fix for integration tests +- Use property getters instead of methods for IModelClient + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Updated iModel Hub Client so iModel Base Handler is injectable. Now Http Request Options can be sepecified for the iModelHubClient +- Added IModelHubClient.IModel, removed IModelQuery.primary(), use IModelHubClient.IModel.Get instead +- Simplified download stream + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Deprecated dev-cors-proxy-server and use of it. +- Removed PropertySerializer used by ECJsonTypeMap. +- OIDC related enhancments (WIP). +- Fixed more integration tests. +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/clients/package.json b/core/clients/package.json index a05c958..ea820e6 100644 --- a/core/clients/package.json +++ b/core/clients/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-clients", - "version": "0.163.0", + "version": "0.171.0", "description": "Clients for various Bentley Services used by iModel.js", "main": "lib/index.js", "typings": "lib/index", @@ -17,12 +17,12 @@ "cover:integration": "npm run copy:test-assets && nyc --report-dir ./lib/test/coverage/integration npm run test:integration", "docs": "node ./node_modules/@bentley/build-tools/scripts/docs.js --source=./src --includes=../../generated-docs/extract --json=../../generated-docs/core/imodeljs-clients/file.json --tsIndexFile=index.ts --onlyJson %TYPEDOC_THEME%", "lint": "tslint --project . 1>&2", - "pretest": "rimraf \"./lib/test/iModelClientsTests.log\" && npm run copy:test-assets", - "test": "npm run pretest && node ./node_modules/@bentley/build-tools/scripts/test.js --offline=\"mock\" --grep=\"#integration|URL Whitelist Validator\" --invert", - "test:online": "npm run pretest && node ./node_modules/@bentley/build-tools/scripts/test.js --grep=\"URL Whitelist Validator\" --invert", - "test:integration": "npm run test:online & npm run test:url", + "pretest": "rimraf \"./lib/test/*.log\" && npm run copy:test-assets", + "test": "npm run pretest && node ./node_modules/@bentley/build-tools/scripts/test.js --offline=\"mock\" --grep=\"#integration|iModelHub URL Whitelist Validator\" --invert", + "test:online": "npm run pretest && node ./node_modules/@bentley/build-tools/scripts/test.js --grep=\"iModelHub URL Whitelist Validator\" --invert", + "test:integration": "npm run test:online && npm run test:url", "test:settings": "npm run pretest && node ./node_modules/@bentley/build-tools/scripts/test.js --grep Setting", - "test:url": "node ./node_modules/@bentley/build-tools/scripts/test.js --grep=\"URL Whitelist Validator\"", + "test:url": "node ./node_modules/@bentley/build-tools/scripts/test.js --grep=\"iModelHub URL Whitelist Validator\"", "watch": "npm run docs && bmsWatch --src ./lib/docs/json --destination ./public" }, "repository": { @@ -40,16 +40,16 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/config-loader": "0.163.0", - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", + "@bentley/config-loader": "0.171.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/deep-assign": "^0.1.0", "@types/fs-extra": "^4.0.7", @@ -70,7 +70,7 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0", + "typescript": "~3.1.0", "webpack": "^4.16.4" }, "dependencies": { diff --git a/core/clients/src/BIMReviewShareClient.ts b/core/clients/src/BIMReviewShareClient.ts index 9d543c9..e304759 100644 --- a/core/clients/src/BIMReviewShareClient.ts +++ b/core/clients/src/BIMReviewShareClient.ts @@ -249,7 +249,7 @@ export class BIMReviewShareClient extends WsgClient { * @param projectId Id of the project to get the instances from * @param module Name of the module (e.g. 'DataViz') */ - public getContentInstances(alctx: ActivityLoggingContext, token: AccessToken, projectId: string, module: string, owner?: string) { + public async getContentInstances(alctx: ActivityLoggingContext, token: AccessToken, projectId: string, module: string, owner?: string) { const queryOptions: RequestQueryOptions = { $filter: `ProjectId+eq+'${projectId}'+and+Module+eq+'${module}'` + (owner ? `+and+owner+eq+'${owner}'` : ``), }; @@ -266,7 +266,7 @@ export class BIMReviewShareClient extends WsgClient { * @param instanceId Instance Id of the Content * @param queryOptions Query options for filtering */ - public getContentInstance(alctx: ActivityLoggingContext, token: AccessToken, projectId: string, moduleName: string, instanceId: string, queryOptions?: RequestQueryOptions) { + public async getContentInstance(alctx: ActivityLoggingContext, token: AccessToken, projectId: string, moduleName: string, instanceId: string, queryOptions?: RequestQueryOptions) { const url = `/Repositories/ContentPlugin--default/ContentSchema/Content/${projectId}${moduleName}${instanceId}`; return this.getInstances(alctx, Content, token, url, queryOptions); } @@ -278,7 +278,7 @@ export class BIMReviewShareClient extends WsgClient { * @param content Content instance * @param options WsgRequestOptions optional */ - public deleteContentInstance(alctx: ActivityLoggingContext, token: AccessToken, content: Content, options?: WsgRequestOptions) { + public async deleteContentInstance(alctx: ActivityLoggingContext, token: AccessToken, content: Content, options?: WsgRequestOptions) { const url = `/Repositories/ContentPlugin--default/ContentSchema/Content/${content.wsgId}`; return this.deleteInstance(alctx, token, url, content, options); } @@ -290,7 +290,7 @@ export class BIMReviewShareClient extends WsgClient { * @param instanceId Instance Id of the Content instance * @param queryOptions Query options for filtering */ - public getContentData(alctx: ActivityLoggingContext, token: AccessToken, instanceId: string, queryOptions?: RequestQueryOptions) { + public async getContentData(alctx: ActivityLoggingContext, token: AccessToken, instanceId: string, queryOptions?: RequestQueryOptions) { const url = `/Repositories/ContentPlugin--default/ContentSchema/Content/${instanceId}/$file`; return this.getBlob(alctx, token, url, queryOptions); } diff --git a/core/clients/src/Client.ts b/core/clients/src/Client.ts index 01d3c40..ac0994b 100644 --- a/core/clients/src/Client.ts +++ b/core/clients/src/Client.ts @@ -21,7 +21,7 @@ export class DefaultRequestOptionsProvider { constructor() { this._defaultOptions = { method: "GET", - useCorsProxy: true, + useCorsProxy: false, }; } @@ -85,11 +85,11 @@ export abstract class Client { const urlDiscoveryClient: UrlDiscoveryClient = new UrlDiscoveryClient(); const searchKey: string = this.getUrlSearchKey(); return urlDiscoveryClient.discoverUrl(alctx, searchKey, undefined) - .then((url: string): Promise => { + .then(async (url: string): Promise => { this._url = url; return Promise.resolve(this._url); // TODO: On the server this really needs a lifetime!! }) - .catch((): Promise => { + .catch(async (): Promise => { return Promise.reject(`Failed to discover URL for service identified by "${searchKey}"`); }); } diff --git a/core/clients/src/ConnectClients.ts b/core/clients/src/ConnectClients.ts index 4186827..0bcb1e2 100644 --- a/core/clients/src/ConnectClients.ts +++ b/core/clients/src/ConnectClients.ts @@ -156,11 +156,11 @@ export class RbacClient extends WsgClient { * @returns Resolves to an array of projects. */ public async getProjects(alctx: ActivityLoggingContext, token: AccessToken, queryOptions?: RbacRequestQueryOptions): Promise { - const userProfile = token.getUserProfile(); - if (!userProfile) + const userInfo = token.getUserInfo(); + if (!userInfo) return Promise.reject(new Error("Invalid access token")); - const url: string = "/Repositories/BentleyCONNECT--Main/RBAC/User/" + userProfile.userId + "/Project"; + const url: string = "/Repositories/BentleyCONNECT--Main/RBAC/User/" + userInfo.id + "/Project"; return this.getInstances(alctx, RbacProject, token, url, queryOptions); } @@ -183,11 +183,11 @@ export class RbacClient extends WsgClient { */ public async getIModelHubPermissions(alctx: ActivityLoggingContext, token: AccessToken, projectId: string): Promise { alctx.enter(); - const userProfile = token.getUserProfile(); - if (!userProfile) + const userInfo = token.getUserInfo(); + if (!userInfo) return Promise.reject(new Error("Invalid access token")); - const relativeUrlPath: string = "/Repositories/BentleyCONNECT--Main/RBAC/User/" + userProfile.userId + "/Project"; + const relativeUrlPath: string = "/Repositories/BentleyCONNECT--Main/RBAC/User/" + userInfo.id + "/Project"; const url: string = await this.getUrl(alctx) + relativeUrlPath; alctx.enter(); diff --git a/core/clients/src/ECJsonTypeMap.ts b/core/clients/src/ECJsonTypeMap.ts index 007f1ad..49d432a 100644 --- a/core/clients/src/ECJsonTypeMap.ts +++ b/core/clients/src/ECJsonTypeMap.ts @@ -156,19 +156,13 @@ */ // @todo Update example with property type conversions once that's available. -import { Id64, Logger, assert } from "@bentley/bentleyjs-core"; +import { Logger, assert } from "@bentley/bentleyjs-core"; export type ConstructorType = new () => any; const loggingCategory = "ECJson"; const className = "className"; -export interface PropertySerializer { - serialize: (value: any) => any; - - deserialize: (value: any) => any; -} - export interface ClassKeyMapInfo { /** The key of the JSON property that stores the schema name - e.g., set to"schemaName" in the case of JSON consumed/supplied by WSG */ schemaPropertyName?: string; @@ -179,7 +173,7 @@ export interface ClassKeyMapInfo { } class PropertyEntry { - constructor(public readonly typedPropertyName: string, public propertyAccessString: string, public propertySerializer?: PropertySerializer) { + constructor(public readonly typedPropertyName: string, public propertyAccessString: string) { } } @@ -199,14 +193,14 @@ class ApplicationEntry { } /** Adds a new entry for a mapped property */ - public addProperty(typedPropertyName: string, propertyAccessString: string, propertySerializer?: PropertySerializer): void { + public addProperty(typedPropertyName: string, propertyAccessString: string): void { let propertyEntry = this.getPropertyByAccessString(propertyAccessString); if (propertyEntry) { const err = `The ECProperty ${propertyAccessString} has already been mapped to another TypeScript property ${propertyEntry.typedPropertyName}`; throw new Error(err); } - propertyEntry = new PropertyEntry(typedPropertyName, propertyAccessString, propertySerializer); + propertyEntry = new PropertyEntry(typedPropertyName, propertyAccessString); this.propertiesByAccessString.set(propertyAccessString, propertyEntry); } } @@ -294,7 +288,7 @@ export class ECJsonTypeMap { } /** Adds a new entry for a mapped property */ - private static addProperty(typedPropertyName: string, typedConstructor: ConstructorType, applicationKey: string, propertyAccessString: string, propertySerializer?: PropertySerializer) { + private static addProperty(typedPropertyName: string, typedConstructor: ConstructorType, applicationKey: string, propertyAccessString: string) { let classEntry: ClassEntry | undefined = ECJsonTypeMap.getClassByType(typedConstructor); if (!classEntry) classEntry = ECJsonTypeMap.addClassPlaceholder(typedConstructor); @@ -303,7 +297,7 @@ export class ECJsonTypeMap { if (!applicationEntry) applicationEntry = classEntry.addApplication(applicationKey); - applicationEntry.addProperty(typedPropertyName, propertyAccessString, propertySerializer); + applicationEntry.addProperty(typedPropertyName, propertyAccessString); } /** Create a typed instance from an untyped JSON ECInstance */ @@ -388,8 +382,7 @@ export class ECJsonTypeMap { i++; } } - if (propertyEntry.propertySerializer) - ecValue = propertyEntry.propertySerializer.deserialize(ecValue); + typedInstance[propertyEntry.typedPropertyName] = ecValue; }); @@ -463,8 +456,6 @@ export class ECJsonTypeMap { let value = {}; if (isLastPart) { value = typedValue; - if (propertyEntry.propertySerializer) - value = propertyEntry.propertySerializer.serialize(value); } untypedInstanceCursor[accessString] = value; } @@ -519,8 +510,6 @@ export class ECJsonTypeMap { let value = {}; if (isLastPart) { value = typedValue; - if (propertyEntry.propertySerializer) - value = propertyEntry.propertySerializer.serialize(value); } untypedInstanceCursor[accessString][relationshipCount] = value; } @@ -533,8 +522,6 @@ export class ECJsonTypeMap { let value = {}; if (isLastPart) { value = typedValue; - if (propertyEntry.propertySerializer) - value = propertyEntry.propertySerializer.serialize(value); } untypedInstanceCursor[accessString] = value; } @@ -567,42 +554,13 @@ export class ECJsonTypeMap { * @param applicationKey Identifies the application for which the mapping is specified. e.g., "ecdb", "wsg", etc. * @param propertyAccessString Access string for the ECProperty */ - public static propertyToJson(applicationKey: string, propertyAccessString: string, propertySerializer?: PropertySerializer) { + public static propertyToJson(applicationKey: string, propertyAccessString: string) { return (object: any, propertyKey: string): void => { - ECJsonTypeMap.addProperty(propertyKey, object.constructor as ConstructorType, applicationKey.toLowerCase(), propertyAccessString, propertySerializer); + ECJsonTypeMap.addProperty(propertyKey, object.constructor as ConstructorType, applicationKey.toLowerCase(), propertyAccessString); }; } } -export class GuidSerializer implements PropertySerializer { - public serialize(value: any): any { - if (typeof value === "string") - return value; - - return undefined; - } - - public deserialize(value: any): any { - if (typeof value !== "string") - return undefined; - return value.trim(); - } -} - -export class Id64Serializer implements PropertySerializer { - public serialize(value: any): any { - if (typeof value === "string") - return value; - return undefined; - } - - public deserialize(value: any): any { - if (typeof value !== "string") - return undefined; - return Id64.fromString(value); - } -} - /** Base class for all typed instances mapped to ECInstance-s in an ECDb */ export abstract class ECInstance { @ECJsonTypeMap.propertyToJson("ecdb", "id") diff --git a/core/clients/src/FormDataManagementClient.ts b/core/clients/src/FormDataManagementClient.ts index 5b910b8..99e132b 100644 --- a/core/clients/src/FormDataManagementClient.ts +++ b/core/clients/src/FormDataManagementClient.ts @@ -132,7 +132,7 @@ export class FormDataManagementClient extends WsgClient { * @param filter [optional] can either supply a string or an array of property names and values which are then unioned together as a string and inserted into the url to exclude definitions * @param format either xml or json, this corresponds to the actual form definition which will be returned a string property, but can be parse into the format specified */ - public getFormDefinitions(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, filter?: string | Array<{ name: string, value: string }>, format: "json" | "xml" = "json"): Promise { + public async getFormDefinitions(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, filter?: string | Array<{ name: string, value: string }>, format: "json" | "xml" = "json"): Promise { let url = `/Repositories/Bentley.Forms--${projectId}/Forms_EC_Mapping/FormDefinition`; if (filter !== undefined) { @@ -151,7 +151,7 @@ export class FormDataManagementClient extends WsgClient { * @param alctx the ActivityLoggingContext to track this request by * @param projectId the project associated with the form definitions */ - public getRiskIssueFormDefinitions(token: AccessToken, alctx: ActivityLoggingContext, projectId: string): Promise { + public async getRiskIssueFormDefinitions(token: AccessToken, alctx: ActivityLoggingContext, projectId: string): Promise { const filters = [ { name: "Classification", value: "Risk" }, { name: "Discipline", value: "Issue" }, @@ -172,7 +172,7 @@ export class FormDataManagementClient extends WsgClient { * @param top the maximum number of instances to return * @param filter [optional] can either supply a string or an array of property names and values which are then unioned together as a string and inserted into the url to exclude data instances */ - public getFormData(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, className: string, skip: number = 0, top: number = 50, filter?: string | Array<{ name: string, value: string }>): Promise { + public async getFormData(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, className: string, skip: number = 0, top: number = 50, filter?: string | Array<{ name: string, value: string }>): Promise { let url = `/Repositories/Bentley.Forms--${projectId}/DynamicSchema/${className}?$select=*,Forms_EC_Mapping.Form.*&$skip=${skip}&$top=${top}`; if (filter !== undefined) { @@ -207,7 +207,7 @@ export class FormDataManagementClient extends WsgClient { * @param skip the starting index of instances to fetch (accomodates paging to reduce the request payload) * @param top the maximum number of instances to return */ - public getRiskIssueFormData(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, iModelId: string, className: string = "Issue", skip: number = 0, top: number = 50): Promise { + public async getRiskIssueFormData(token: AccessToken, alctx: ActivityLoggingContext, projectId: string, iModelId: string, className: string = "Issue", skip: number = 0, top: number = 50): Promise { const filters = [ { name: "_Classification", value: "Risk" }, { name: "_Discipline", value: "Issue" }, @@ -225,7 +225,7 @@ export class FormDataManagementClient extends WsgClient { * @param className the name of the class that owns the properties for which each instance binds data to (note this is dynamic, ie a user can create/destroy classes) * @param instanceId [optional] if provided the data is used to update an existing form data instance, otherwise a new instance is created */ - public postFormData(token: AccessToken, alctx: ActivityLoggingContext, formData: FormInstanceData, projectId: string, className: string = "Issue", instanceId?: string): Promise { + public async postFormData(token: AccessToken, alctx: ActivityLoggingContext, formData: FormInstanceData, projectId: string, className: string = "Issue", instanceId?: string): Promise { formData.changeState = instanceId === undefined ? "new" : "modified"; formData.formDataDirection = "forward"; formData.formSchemaName = "Forms_EC_Mapping"; @@ -250,7 +250,7 @@ export class FormDataManagementClient extends WsgClient { * @param className the name of the class that owns the properties for which each instance binds data to (note this is dynamic, ie a user can create/destroy classes) * @param instanceId [optional] if provided the data is used to update an existing form data instance, otherwise a new instance is created */ - public postRiskIssueFormData(token: AccessToken, alctx: ActivityLoggingContext, properties: any, projectId: string, iModelId: string, elementId: string, formId: string, className: string = "Issue", instanceId?: string): Promise { + public async postRiskIssueFormData(token: AccessToken, alctx: ActivityLoggingContext, properties: any, projectId: string, iModelId: string, elementId: string, formId: string, className: string = "Issue", instanceId?: string): Promise { const formData = new FormInstanceData(); formData.formId = formId; formData.properties = properties; diff --git a/core/clients/src/IModelBank/IModelBankDummyAuthorizationClient.ts b/core/clients/src/IModelBank/IModelBankDummyAuthorizationClient.ts index 996cb40..fb2508e 100644 --- a/core/clients/src/IModelBank/IModelBankDummyAuthorizationClient.ts +++ b/core/clients/src/IModelBank/IModelBankDummyAuthorizationClient.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import { AccessToken } from "../Token"; -import { UserProfile } from "../UserProfile"; +import { UserInfo } from "../UserInfo"; import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; import { IModelAuthorizationClient } from "../IModelCloudEnvironment"; @@ -11,11 +11,11 @@ import { IModelAuthorizationClient } from "../IModelCloudEnvironment"; * be able to tolerate this dummy token. */ export class IModelBankDummyAuthorizationClient implements IModelAuthorizationClient { - public authorizeUser(_actx: ActivityLoggingContext, userProfile: UserProfile | undefined, userCredentials: any): Promise { - if (!userProfile) - userProfile = { email: userCredentials.email, userId: "", firstName: "", lastName: "", organization: "", ultimateSite: "", organizationId: "", usageCountryIso: "" }; + public async authorizeUser(_actx: ActivityLoggingContext, userInfo: UserInfo | undefined, userCredentials: any): Promise { + if (!userInfo) + userInfo = { id: "", email: { id: userCredentials.email }, profile: { name: "", firstName: "", lastName: "" } }; const foreignAccessTokenWrapper: any = {}; - foreignAccessTokenWrapper[AccessToken.foreignProjectAccessTokenJsonProperty] = { userProfile }; + foreignAccessTokenWrapper[AccessToken.foreignProjectAccessTokenJsonProperty] = { userInfo }; return Promise.resolve(AccessToken.fromForeignProjectAccessTokenJson(JSON.stringify(foreignAccessTokenWrapper))!); } } diff --git a/core/clients/src/IModelBank/IModelBankFileSystemContextClient.ts b/core/clients/src/IModelBank/IModelBankFileSystemContextClient.ts index c19a2f0..1e390fc 100644 --- a/core/clients/src/IModelBank/IModelBankFileSystemContextClient.ts +++ b/core/clients/src/IModelBank/IModelBankFileSystemContextClient.ts @@ -77,7 +77,7 @@ export class IModelBankFileSystemContextClient implements ContextManagerClient { body, }; - return request(alctx, url, options).then(() => Promise.resolve()); + return request(alctx, url, options).then(async () => Promise.resolve()); } public async deleteContext(alctx: ActivityLoggingContext, accessToken: AccessToken, contextId: string): Promise { @@ -91,6 +91,6 @@ export class IModelBankFileSystemContextClient implements ContextManagerClient { headers: { authorization: accessToken.toTokenString() }, }; - return request(alctx, url, options).then(() => Promise.resolve()); + return request(alctx, url, options).then(async () => Promise.resolve()); } } diff --git a/core/clients/src/IModelClient.ts b/core/clients/src/IModelClient.ts index a342bb1..9276dfb 100644 --- a/core/clients/src/IModelClient.ts +++ b/core/clients/src/IModelClient.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModels */ import { FileHandler } from "./FileHandler"; -import { BriefcaseHandler, IModelHandler, ChangeSetHandler, LockHandler, CodeHandler, UserInfoHandler, VersionHandler, EventHandler } from "./imodelhub/index"; +import { BriefcaseHandler, IModelsHandler, IModelHandler, ChangeSetHandler, LockHandler, CodeHandler, UserInfoHandler, VersionHandler, EventHandler } from "./imodelhub/index"; import { ThumbnailHandler } from "./imodelhub/Thumbnails"; import { GlobalEventHandler } from "./imodelhub/GlobalEvents"; import { IModelBaseHandler } from "./imodelhub/BaseHandler"; @@ -38,78 +38,86 @@ export abstract class IModelClient { /** * Get the handler for [[HubIModel]] instances. + * @note Use [[IModelHubClient.IModel]] for the preferred single iModel per [[Project]] workflow. */ - public IModels(): IModelHandler { - return new IModelHandler(this._handler, this._fileHandler); + public get iModels(): IModelsHandler { + return new IModelsHandler(this._handler, this._fileHandler); + } + + /** + * Get the handler for [[HubIModel]]. + */ + public get iModel(): IModelHandler { + return new IModelHandler(new IModelsHandler(this._handler, this._fileHandler)); } /** * Get the handler for [[Briefcase]]s. */ - public Briefcases(): BriefcaseHandler { + public get briefcases(): BriefcaseHandler { return new BriefcaseHandler(this._handler, this._fileHandler); } /** * Get the handler for [[ChangeSet]]s. */ - public ChangeSets(): ChangeSetHandler { + public get changeSets(): ChangeSetHandler { return new ChangeSetHandler(this._handler, this._fileHandler); } /** * Get the handler for [[Lock]]s. */ - public Locks(): LockHandler { + public get locks(): LockHandler { return new LockHandler(this._handler); } /** * Get the handler for [Code]($common)s. */ - public Codes(): CodeHandler { + public get codes(): CodeHandler { return new CodeHandler(this._handler); } /** * Get the handler for [[UserInfo]]. */ - public Users(): UserInfoHandler { + public get users(): UserInfoHandler { return new UserInfoHandler(this._handler); } /** * Get the handler for [[Version]]s. */ - public Versions(): VersionHandler { + public get versions(): VersionHandler { return new VersionHandler(this._handler); } /** * Get the handler for [[Thumbnail]]s. */ - public Thumbnails(): ThumbnailHandler { + public get thumbnails(): ThumbnailHandler { return new ThumbnailHandler(this._handler); } /** * Get the handler for [[IModelHubEvent]]s. */ - public Events(): EventHandler { + public get events(): EventHandler { return new EventHandler(this._handler); } /** * Get the handler for [[IModelHubGlobalEvent]]s. */ - public GlobalEvents(): GlobalEventHandler { + public get globalEvents(): GlobalEventHandler { return new GlobalEventHandler(this._handler); } /** * Get the [CustomRequestOptions]($clients) object for controlling future request options. */ - public RequestOptions(): CustomRequestOptions { + public get requestOptions(): CustomRequestOptions { return this._handler.getCustomRequestOptions(); } } diff --git a/core/clients/src/IModelCloudEnvironment.ts b/core/clients/src/IModelCloudEnvironment.ts index e3f1994..3d6b1a5 100644 --- a/core/clients/src/IModelCloudEnvironment.ts +++ b/core/clients/src/IModelCloudEnvironment.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModels */ import { AccessToken } from "./Token"; -import { UserProfile } from "./UserProfile"; +import { UserInfo } from "./UserInfo"; import { Project } from "./ConnectClients"; import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; @@ -15,7 +15,7 @@ export interface ContextManagerClient { /** @hidden User-authorization service. */ export interface IModelAuthorizationClient { - authorizeUser(alctx: ActivityLoggingContext, userProfile: UserProfile | undefined, userCredentials: any): Promise; + authorizeUser(alctx: ActivityLoggingContext, userInfo: UserInfo | undefined, userCredentials: any): Promise; } /** @hidden All of the services that a frontend or other client app needs to find and access iModels. */ diff --git a/core/clients/src/ImsClients.ts b/core/clients/src/ImsClients.ts index 771da14..842524f 100644 --- a/core/clients/src/ImsClients.ts +++ b/core/clients/src/ImsClients.ts @@ -69,7 +69,7 @@ export class ImsActiveSecureTokenClient extends Client { protected async setupOptionDefaults(options: RequestOptions): Promise { await super.setupOptionDefaults(options); - options.useCorsProxy = true; + options.useCorsProxy = false; } /** @@ -98,7 +98,7 @@ export class ImsActiveSecureTokenClient extends Client { await this.setupOptionDefaults(options); return request(alctx, url, options) - .then((res: Response): Promise => { + .then(async (res: Response): Promise => { if (!res.body.RequestedSecurityToken) return Promise.reject(new Error("Authorization token not in expected format " + JSON.stringify(res))); @@ -136,7 +136,7 @@ export class ImsDelegationSecureTokenClient extends Client { protected async setupOptionDefaults(options: RequestOptions): Promise { await super.setupOptionDefaults(options); - options.useCorsProxy = true; + options.useCorsProxy = false; } /** @@ -172,7 +172,7 @@ export class ImsDelegationSecureTokenClient extends Client { await this.setupOptionDefaults(options); return request(alctx, url, options) - .then((res: Response): Promise => { + .then(async (res: Response): Promise => { if (!res.body.RequestedSecurityToken) return Promise.reject(new Error("Authorization token not in expected format " + JSON.stringify(res))); diff --git a/core/clients/src/RealityDataServicesClient.ts b/core/clients/src/RealityDataServicesClient.ts index e140aec..4ec4c89 100644 --- a/core/clients/src/RealityDataServicesClient.ts +++ b/core/clients/src/RealityDataServicesClient.ts @@ -354,7 +354,7 @@ export class RealityDataServicesClient extends WsgClient { if (root.includes("/")) root = root.split("/")[root.split("/").length - 1]; - return (await this.getModelData(alctx, token, projectId, tilesId, root)); + return this.getModelData(alctx, token, projectId, tilesId, root); } /** diff --git a/core/clients/src/Request.ts b/core/clients/src/Request.ts index 1e98597..85574ab 100644 --- a/core/clients/src/Request.ts +++ b/core/clients/src/Request.ts @@ -352,7 +352,7 @@ export async function request(alctx: ActivityLoggingContext, url: string, option * superagent, but may eventually switch to JavaScript's fetch library. */ return sareq - .then((response: sarequest.Response) => { + .then(async (response: sarequest.Response) => { const retResponse: Response = { body: response.body, header: response.header, @@ -360,7 +360,7 @@ export async function request(alctx: ActivityLoggingContext, url: string, option }; return Promise.resolve(retResponse); }) - .catch((error: any) => { + .catch(async (error: any) => { const parsedError = errorCallback(error); return Promise.reject(parsedError); }); diff --git a/core/clients/src/SettingsClient.ts b/core/clients/src/SettingsClient.ts index 6f9d606..d98fb07 100644 --- a/core/clients/src/SettingsClient.ts +++ b/core/clients/src/SettingsClient.ts @@ -30,7 +30,7 @@ export class ConnectSettingsClient extends Client implements SettingsAdmin { const baseUrl: string = await this.getUrl(alctx); const imsClient = new ImsDelegationSecureTokenClient(); - return await imsClient.getToken(alctx, authSamlToken, baseUrl); + return imsClient.getToken(alctx, authSamlToken, baseUrl); } /** @@ -110,9 +110,9 @@ export class ConnectSettingsClient extends Client implements SettingsAdmin { const urlOptions: string = this.getUrlOptions(settingNamespace, settingName, userSpecific, applicationSpecific, projectId, iModelId); const url: string = baseUrl.concat(urlOptions); - return request(alctx, url, options).then((_response: Response): Promise => { + return request(alctx, url, options).then(async (_response: Response): Promise => { return Promise.resolve(new SettingsResult(SettingsStatus.Success)); - }, (response: Response): Promise => { + }, async (response: Response): Promise => { if ((response.status < 200) || (response.status > 299)) return Promise.resolve(this.formErrorResponse(response)); return Promise.resolve(new SettingsResult(SettingsStatus.UnknownError, "Unexpected Status " + JSON.stringify(response))); @@ -133,9 +133,9 @@ export class ConnectSettingsClient extends Client implements SettingsAdmin { const urlOptions: string = this.getUrlOptions(settingNamespace, settingName, userSpecific, applicationSpecific, projectId, iModelId); const url: string = baseUrl.concat(urlOptions); - return request(alctx, url, options).then((response: Response): Promise => { + return request(alctx, url, options).then(async (response: Response): Promise => { return Promise.resolve(new SettingsResult(SettingsStatus.Success, undefined, response.body.properties)); - }, (response: Response): Promise => { + }, async (response: Response): Promise => { if ((response.status < 200) || (response.status > 299)) return Promise.resolve(this.formErrorResponse(response)); return Promise.resolve(new SettingsResult(SettingsStatus.UnknownError, "Unexpected Status " + JSON.stringify(response))); @@ -156,9 +156,9 @@ export class ConnectSettingsClient extends Client implements SettingsAdmin { const urlOptions: string = this.getUrlOptions(settingNamespace, settingName, userSpecific, applicationSpecific, projectId, iModelId); const url: string = baseUrl.concat(urlOptions); - return request(alctx, url, options).then((_response: Response): Promise => { + return request(alctx, url, options).then(async (_response: Response): Promise => { return Promise.resolve(new SettingsResult(SettingsStatus.Success)); - }, (response: Response): Promise => { + }, async (response: Response): Promise => { if ((response.status < 200) || (response.status > 299)) return Promise.resolve(this.formErrorResponse(response)); else diff --git a/core/clients/src/Token.ts b/core/clients/src/Token.ts index 59ee173..3e0cee7 100644 --- a/core/clients/src/Token.ts +++ b/core/clients/src/Token.ts @@ -6,7 +6,7 @@ import * as xpath from "xpath"; import { DOMParser } from "xmldom"; -import { UserProfile } from "./UserProfile"; +import { UserInfo } from "./UserInfo"; import { Base64 } from "js-base64"; import { BentleyError, BentleyStatus } from "@bentley/bentleyjs-core"; @@ -19,7 +19,7 @@ export enum IncludePrefix { export abstract class Token { protected _samlAssertion: string; - protected _userProfile?: UserProfile; + protected _userInfo?: UserInfo; protected _startsAt?: Date; protected _expiresAt?: Date; protected _x509Certificate?: string; @@ -32,8 +32,8 @@ export abstract class Token { return this._samlAssertion; } - public getUserProfile(): UserProfile | undefined { - return this._userProfile; + public getUserInfo(): UserInfo | undefined { + return this._userInfo; } public getExpiresAt(): Date | undefined { @@ -65,21 +65,29 @@ export abstract class Token { select("/saml:Assertion/saml:AttributeStatement/saml:Attribute[@AttributeName='" + attributeName + "']/saml:AttributeValue/text()", dom).toString(); - this._userProfile = { + const id = extractAttribute("userid"); + const email = { + id: extractAttribute("emailaddress"), + }; + const profile = { + name: extractAttribute("name"), firstName: extractAttribute("givenname"), lastName: extractAttribute("surname"), - email: extractAttribute("emailaddress"), - userId: extractAttribute("userid"), - organization: extractAttribute("organization"), - organizationId: extractAttribute("organizationid"), + }; + const organization = { + id: extractAttribute("organizationid"), + name: extractAttribute("organization"), + }; + const featureTracking = { ultimateSite: extractAttribute("ultimatesite"), usageCountryIso: extractAttribute("usagecountryiso"), }; + this._userInfo = new UserInfo(id, email, profile, organization, featureTracking); this._startsAt = new Date(startsAtStr); this._expiresAt = new Date(expiresAtStr); - return !!this._x509Certificate && !!this._startsAt && !!this._expiresAt && !!this._userProfile; + return !!this._x509Certificate && !!this._startsAt && !!this._expiresAt; } } @@ -129,7 +137,7 @@ export class AccessToken extends Token { if (props[this.foreignProjectAccessTokenJsonProperty] === undefined) return undefined; const tok = new AccessToken(foreignJsonStr); - tok._userProfile = props[this.foreignProjectAccessTokenJsonProperty].userProfile; + tok._userInfo = props[this.foreignProjectAccessTokenJsonProperty].userInfo; return tok; } @@ -155,12 +163,12 @@ export class AccessToken extends Token { } /** Create an AccessToken from a JWT token for OIDC workflows */ - public static fromJsonWebTokenString(jwt: string, userProfile: UserProfile, startsAt: Date, expiresAt: Date): AccessToken { + public static fromJsonWebTokenString(jwt: string, startsAt: Date, expiresAt: Date, userInfo: UserInfo): AccessToken { const token = new AccessToken(""); token._jwt = jwt; - token._userProfile = userProfile; token._startsAt = startsAt; token._expiresAt = expiresAt; + token._userInfo = userInfo; return token; } @@ -176,10 +184,8 @@ export class AccessToken extends Token { } public static fromJson(jsonObj: any): AccessToken | undefined { - if (jsonObj._jwt) { - const userProfile = UserProfile.fromJson(jsonObj._userProfile); - return AccessToken.fromJsonWebTokenString(jsonObj._jwt, userProfile, jsonObj._startsAt, jsonObj._expiresAt); - } + if (jsonObj._jwt) + return AccessToken.fromJsonWebTokenString(jsonObj._jwt, jsonObj._startsAt, jsonObj._expiresAt, jsonObj._userInfo); const foreignTok = AccessToken.fromForeignProjectAccessTokenJson(jsonObj._samlAssertion); if (foreignTok !== undefined) diff --git a/core/clients/src/UserInfo.ts b/core/clients/src/UserInfo.ts new file mode 100644 index 0000000..29dc6e1 --- /dev/null +++ b/core/clients/src/UserInfo.ts @@ -0,0 +1,35 @@ + +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Authentication */ + +/** Information on the authenticated user. */ +export class UserInfo { + constructor( + /** Id of the user */ + public id: string, + + /** Email details */ + public email?: { id: string, isVerified?: boolean }, + + /** Profile of the user */ + public profile?: { firstName: string, lastName: string, name?: string, preferredUserName?: string }, + + /** Organization the user belongs to */ + public organization?: { id: string, name: string }, + + /** Feature tracking information associated with the user */ + public featureTracking?: { ultimateSite: string, usageCountryIso: string }, + ) { } + + public static fromJson(jsonObj: any): UserInfo { + const id: string = jsonObj.sub; + const email: any = jsonObj.email ? { id: jsonObj.email, isVerified: jsonObj.email_verified } : undefined; + const profile: any = jsonObj.name ? { name: jsonObj.name, preferredUserName: jsonObj.preferred_username, firstName: jsonObj.given_name, lastName: jsonObj.family_name } : undefined; + const organization: any = jsonObj.org ? { id: jsonObj.org, name: jsonObj.org_name } : undefined; + const featureTracking: any = jsonObj.feature_tracking ? { ultimateSite: jsonObj.ultimate_site, usageCountryIso: jsonObj.usage_country_iso } : undefined; + return new UserInfo(id, email, profile, organization, featureTracking); + } +} diff --git a/core/clients/src/UserProfile.ts b/core/clients/src/UserProfile.ts deleted file mode 100644 index 029df95..0000000 --- a/core/clients/src/UserProfile.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -/** @module Authentication */ - -/** Profile of the authenticated user. */ -export class UserProfile { - constructor( - public firstName: string, - public lastName: string, - public email: string, - public userId: string, - public organization: string, - public organizationId: string, - public ultimateSite: string, - public usageCountryIso: string, - ) { } - - public static fromJson(jsonObj: any): UserProfile { - return new UserProfile(jsonObj.firstName, jsonObj.lastName, jsonObj.email, jsonObj.userId, jsonObj.organization, jsonObj.organizationId, jsonObj.ultimateSite, jsonObj.usageCountryIso); - } -} diff --git a/core/clients/src/WsgClient.ts b/core/clients/src/WsgClient.ts index 61209ef..f393959 100644 --- a/core/clients/src/WsgClient.ts +++ b/core/clients/src/WsgClient.ts @@ -243,7 +243,7 @@ export abstract class WsgClient extends Client { } return super.getUrl(alctx) - .then((url: string): Promise => { + .then(async (url: string): Promise => { this._url = url; if (!excludeApiVersion) { this._url += "/" + this.apiVersion; @@ -279,7 +279,7 @@ export abstract class WsgClient extends Client { options.body.requestOptions = requestOptions; } await this.setupOptionDefaults(options); - return request(alctx, url, options).then(() => Promise.resolve()); + return request(alctx, url, options).then(async () => Promise.resolve()); } /** diff --git a/core/clients/src/imodelhub/AzureFileHandler.ts b/core/clients/src/imodelhub/AzureFileHandler.ts index c31a504..d122e23 100644 --- a/core/clients/src/imodelhub/AzureFileHandler.ts +++ b/core/clients/src/imodelhub/AzureFileHandler.ts @@ -11,7 +11,7 @@ import { ArgumentCheck } from "./Errors"; import * as fs from "fs"; import * as path from "path"; import * as https from "https"; -import { Writable } from "stream"; +import { Transform, TransformCallback } from "stream"; import * as sareq from "superagent"; const loggingCategory = "imodeljs-clients.imodelhub"; @@ -19,50 +19,35 @@ const loggingCategory = "imodeljs-clients.imodelhub"; /** * Stream that buffers writing to file. */ -class BufferedStream extends Writable { +class BufferedStream extends Transform { private _buffer?: Buffer; - private _highWaterMark: number; - private _stream: fs.WriteStream; - public constructor(outputPath: string, highWaterMark: number) { + private _threshold: number; + public constructor(threshold: number) { super(); - this._stream = fs.createWriteStream(outputPath, { encoding: "binary" }); - this._highWaterMark = highWaterMark; - } - - private flush(callback: (err?: Error) => void) { - if (this._buffer) { - this._stream.write(this._buffer, () => { - this._buffer = undefined; - callback(); - }); - } else { - callback(); - } + this._threshold = threshold; } // override - public _write(chunk: any, chunkEncoding: string, callback: (err?: Error) => void) { // tslint:disable-line - if (chunkEncoding !== "buffer" && chunkEncoding !== "binary") - throw new TypeError(`Encoding '${chunkEncoding}' is not supported.`); + public _transform(chunk: any, encoding: string, callback: TransformCallback): void { // tslint:disable-line + if (encoding !== "buffer" && encoding !== "binary") + throw new TypeError(`Encoding '${encoding}' is not supported.`); if (!this._buffer) { this._buffer = new Buffer("", "binary"); } this._buffer = Buffer.concat([this._buffer, chunk]); - if (this._buffer.length > this._highWaterMark) { - this.flush(callback); + if (this._buffer.length > this._threshold) { + callback(undefined, this._buffer); + this._buffer = undefined; return; + } else { + callback(); } - callback(); - } - // override - public _final(callback: (err?: Error) => void) { // tslint:disable-line - this.flush(callback); - this._stream.close(); } - get bytesWritten(): number { - return this._stream.bytesWritten; + // override + public _flush(callback: TransformCallback): void { // tslint:disable-line + callback(undefined, this._buffer); } } @@ -72,17 +57,14 @@ class BufferedStream extends Writable { export class AzureFileHandler implements FileHandler { /** @hidden */ public agent: https.Agent; - private _bufferedDownload = false; - private _highWaterMark: number; + private _threshold: number; /** * Constructor for AzureFileHandler. - * @param bufferedDownload Set true, if writing to files should be buffered. - * @param highWaterMark Minimum chunk size in bytes for a single file write. + * @param threshold Minimum chunk size in bytes for a single file write. */ - constructor(bufferedDownload = true, highWaterMark = 1000000) { - this._bufferedDownload = bufferedDownload; - this._highWaterMark = highWaterMark; + constructor(threshold = 1000000) { + this._threshold = threshold; } /** Create a directory, recursively setting up the path as necessary. */ @@ -117,22 +99,14 @@ export class AzureFileHandler implements FileHandler { AzureFileHandler.makeDirectoryRecursive(path.dirname(downloadToPathname)); - let outputStream: Writable; - let bytesWritten: () => number; - if (this._bufferedDownload) { - const bufferedStream = new BufferedStream(downloadToPathname, this._highWaterMark); - outputStream = bufferedStream; - bytesWritten = () => bufferedStream.bytesWritten; - } else { - const fileStream = fs.createWriteStream(downloadToPathname, "binary"); - outputStream = fileStream; - bytesWritten = () => fileStream.bytesWritten; - } + const bufferedStream = new BufferedStream(this._threshold); + const fileStream = fs.createWriteStream(downloadToPathname, "binary"); + const bytesWritten: () => number = () => fileStream.bytesWritten; if (progressCallback) { - outputStream.on("drain", () => { + fileStream.on("drain", () => { progressCallback({ loaded: bytesWritten(), total: fileSize, percent: fileSize ? bytesWritten() / fileSize : 0 }); }); - outputStream.on("finish", () => { + fileStream.on("finish", () => { progressCallback({ loaded: bytesWritten(), total: fileSize, percent: fileSize ? bytesWritten() / fileSize : 0 }); }); } @@ -145,7 +119,8 @@ export class AzureFileHandler implements FileHandler { .timeout({ response: 10000, }) - .pipe(outputStream) + .pipe(bufferedStream) + .pipe(fileStream) .on("error", (error: any) => { const parsedError = ResponseError.parse(error); reject(parsedError); diff --git a/core/clients/src/imodelhub/BaseHandler.ts b/core/clients/src/imodelhub/BaseHandler.ts index 4d1dba8..922dc59 100644 --- a/core/clients/src/imodelhub/BaseHandler.ts +++ b/core/clients/src/imodelhub/BaseHandler.ts @@ -103,7 +103,7 @@ export class IModelBaseHandler extends WsgClient { * Get the URL of the service. This method attempts to discover and cache the URL from the URL Discovery Service. If not found uses the default URL provided by client implementations. Note that for consistency sake, the URL is stripped of any trailing "/" * @returns URL for the service */ - public getUrl(alctx: ActivityLoggingContext): Promise { + public async getUrl(alctx: ActivityLoggingContext): Promise { return super.getUrl(alctx); } @@ -123,7 +123,7 @@ export class IModelBaseHandler extends WsgClient { * @param relativeUrlPath Relative path to the REST resource. * @returns Promise resolves after successfully deleting REST resource at the specified path. */ - public delete(alctx: ActivityLoggingContext, token: AccessToken, relativeUrlPath: string): Promise { + public async delete(alctx: ActivityLoggingContext, token: AccessToken, relativeUrlPath: string): Promise { return super.delete(alctx, token, relativeUrlPath); } @@ -135,7 +135,7 @@ export class IModelBaseHandler extends WsgClient { * @param requestOptions WSG options for the request. * @returns Promise resolves after successfully deleting instance. */ - public deleteInstance(alctx: ActivityLoggingContext, token: AccessToken, relativeUrlPath: string, instance?: T, requestOptions?: WsgRequestOptions): Promise { + public async deleteInstance(alctx: ActivityLoggingContext, token: AccessToken, relativeUrlPath: string, instance?: T, requestOptions?: WsgRequestOptions): Promise { if (this._customRequestOptions.isSet) { if (!requestOptions) { requestOptions = {}; @@ -154,7 +154,7 @@ export class IModelBaseHandler extends WsgClient { * @param requestOptions WSG options for the request. * @returns The posted instance that's returned back from the server. */ - public postInstance(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, instance: T, requestOptions?: WsgRequestOptions): Promise { + public async postInstance(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, instance: T, requestOptions?: WsgRequestOptions): Promise { if (this._customRequestOptions.isSet) { if (!requestOptions) { requestOptions = {}; @@ -173,7 +173,7 @@ export class IModelBaseHandler extends WsgClient { * @param requestOptions WSG options for the request. * @returns The posted instances that's returned back from the server. */ - public postInstances(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, instances: T[], requestOptions?: WsgRequestOptions): Promise { + public async postInstances(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, instances: T[], requestOptions?: WsgRequestOptions): Promise { return super.postInstances(alctx, typedConstructor, token, relativeUrlPath, instances, requestOptions); } @@ -185,7 +185,7 @@ export class IModelBaseHandler extends WsgClient { * @param queryOptions Query options. * @returns Array of strongly typed instances. */ - public getInstances(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, queryOptions?: RequestQueryOptions): Promise { + public async getInstances(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, queryOptions?: RequestQueryOptions): Promise { return super.getInstances(alctx, typedConstructor, token, relativeUrlPath, queryOptions); } @@ -197,7 +197,7 @@ export class IModelBaseHandler extends WsgClient { * @param queryOptions Query options. * @returns Array of strongly typed instances. */ - public postQuery(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, queryOptions: RequestQueryOptions): Promise { + public async postQuery(alctx: ActivityLoggingContext, typedConstructor: new () => T, token: AccessToken, relativeUrlPath: string, queryOptions: RequestQueryOptions): Promise { return super.postQuery(alctx, typedConstructor, token, relativeUrlPath, queryOptions); } diff --git a/core/clients/src/imodelhub/Briefcases.ts b/core/clients/src/imodelhub/Briefcases.ts index 4fc1fe7..cc4fe6e 100644 --- a/core/clients/src/imodelhub/Briefcases.ts +++ b/core/clients/src/imodelhub/Briefcases.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModelHub */ -import { ECJsonTypeMap, WsgInstance, GuidSerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { IModelHubClientError } from "./Errors"; import { AccessToken } from "../Token"; import { Logger, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -45,7 +45,7 @@ export class Briefcase extends WsgInstance { public fileSize?: string; /** FileId of the master file. See [BriefcaseEntry.fileId]($backend). */ - @ECJsonTypeMap.propertyToJson("wsg", "properties.FileId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.FileId") public fileId?: GuidString; /** Id of the briefcase. See [BriefcaseId]($backend) */ @@ -84,7 +84,7 @@ export class Briefcase extends WsgInstance { @ECJsonTypeMap.propertyToJson("ecdb", "lastAccessedAt") public lastAccessedAt?: Date; - @ECJsonTypeMap.propertyToJson("ecdb", "iModelId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("ecdb", "iModelId") public iModelId?: GuidString; } diff --git a/core/clients/src/imodelhub/ChangeSets.ts b/core/clients/src/imodelhub/ChangeSets.ts index b51c889..4f52eea 100644 --- a/core/clients/src/imodelhub/ChangeSets.ts +++ b/core/clients/src/imodelhub/ChangeSets.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModelHub */ -import { ECJsonTypeMap, WsgInstance, GuidSerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { IModelHubClientError, ArgumentCheck } from "./Errors"; import { AccessToken } from "../Token"; import { Logger, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -53,7 +53,7 @@ export class ChangeSet extends WsgInstance { public parentId?: string; /** Id of the file that this ChangeSet belongs to. It has to be set during the push. See [IModelDb.getGuid]($backend). */ - @ECJsonTypeMap.propertyToJson("wsg", "properties.SeedFileId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.SeedFileId") public seedFileId?: GuidString; /** Id of the [[Briefcase]] that pushed this ChangeSet. It has to be set during the push. */ diff --git a/core/clients/src/imodelhub/Client.ts b/core/clients/src/imodelhub/Client.ts index 4838fc1..c0f18ae 100644 --- a/core/clients/src/imodelhub/Client.ts +++ b/core/clients/src/imodelhub/Client.ts @@ -18,9 +18,10 @@ export class IModelHubClient extends IModelClient { /** * Create an instance of IModelHubClient. * @param fileHandler File handler to handle file upload/download and file system operations. See [[AzureFileHandler]]. + * @param iModelBaseHandler WSG Client for iModel Hub operations. See [[IModelBaseHandler]] */ - public constructor(fileHandler?: FileHandler) { - super(new IModelBaseHandler(), fileHandler); + public constructor(fileHandler?: FileHandler, iModelBaseHandler: IModelBaseHandler = new IModelBaseHandler()) { + super(iModelBaseHandler, fileHandler); } /** diff --git a/core/clients/src/imodelhub/Codes.ts b/core/clients/src/imodelhub/Codes.ts index 8ae10f6..1eb642d 100644 --- a/core/clients/src/imodelhub/Codes.ts +++ b/core/clients/src/imodelhub/Codes.ts @@ -6,7 +6,7 @@ import * as deepAssign from "deep-assign"; -import { ECJsonTypeMap, WsgInstance, Id64Serializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { ResponseError } from "./../Request"; import { WsgRequestOptions } from "./../WsgClient"; @@ -36,7 +36,7 @@ export enum CodeState { /** Base class for [Code]($common)s. */ export class CodeBase extends WsgInstance { /** Code specification Id. */ - @ECJsonTypeMap.propertyToJson("wsg", "properties.CodeSpecId", new Id64Serializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.CodeSpecId") public codeSpecId?: Id64String; /** Code scope. */ @@ -403,7 +403,7 @@ export class CodeHandler { } /** Get handler for querying [[CodeSequence]]s. */ - public Sequences(): CodeSequenceHandler { + public get sequences(): CodeSequenceHandler { return new CodeSequenceHandler(this._handler); } @@ -514,7 +514,7 @@ export class CodeHandler { ArgumentCheck.nonEmptyArray("codes", codes); updateOptions = updateOptions || {}; - this.setupOptionDefaults(updateOptions); + await this.setupOptionDefaults(updateOptions); const result: HubCode[] = []; let conflictError: ConflictingCodesError | undefined; diff --git a/core/clients/src/imodelhub/Events.ts b/core/clients/src/imodelhub/Events.ts index 5207416..1cf0cd9 100644 --- a/core/clients/src/imodelhub/Events.ts +++ b/core/clients/src/imodelhub/Events.ts @@ -365,7 +365,7 @@ export class EventHandler extends EventBaseHandler { /** * Get a handler for managing [[EventSubscription]]s. */ - public Subscriptions(): EventSubscriptionHandler { + public get subscriptions(): EventSubscriptionHandler { if (!this._subscriptionHandler) { this._subscriptionHandler = new EventSubscriptionHandler(this._handler); } @@ -434,7 +434,7 @@ export class EventHandler extends EventBaseHandler { ArgumentCheck.defined("baseAddress", baseAddress); ArgumentCheck.validGuid("subscriptionId", subscriptionId); - const options = this.getEventRequestOptions(GetEventOperationToRequestType.GetDestructive, sasToken, timeout); + const options = await this.getEventRequestOptions(GetEventOperationToRequestType.GetDestructive, sasToken, timeout); const result = await request(alctx, this.getEventUrl(baseAddress, subscriptionId, timeout), options); alctx.enter(); @@ -466,9 +466,9 @@ export class EventHandler extends EventBaseHandler { const subscription = new ListenerSubscription(); subscription.authenticationCallback = authenticationCallback; - subscription.getEvent = (sasToken: string, baseAddress: string, id: string, timeout?: number) => + subscription.getEvent = async (sasToken: string, baseAddress: string, id: string, timeout?: number) => this.getEvent(alctx, sasToken, baseAddress, id, timeout); - subscription.getSASToken = (token: AccessToken) => this.getSASToken(alctx, token, imodelId); + subscription.getSASToken = async (token: AccessToken) => this.getSASToken(alctx, token, imodelId); subscription.id = subscriptionId; return EventListener.create(subscription, listener); } diff --git a/core/clients/src/imodelhub/EventsBase.ts b/core/clients/src/imodelhub/EventsBase.ts index d29ece8..5e9fbd9 100644 --- a/core/clients/src/imodelhub/EventsBase.ts +++ b/core/clients/src/imodelhub/EventsBase.ts @@ -66,7 +66,7 @@ export abstract class IModelHubBaseEvent { */ public async delete(alctx: ActivityLoggingContext): Promise { if (this._handler && this._lockUrl && this._sasToken) { - const options = getEventBaseOperationRequestOptions(this._handler, ModifyEventOperationToRequestType.Delete, this._sasToken); + const options = await getEventBaseOperationRequestOptions(this._handler, ModifyEventOperationToRequestType.Delete, this._sasToken); const result = await request(alctx, this._lockUrl, options); if (result.status === 200) @@ -98,7 +98,7 @@ export enum GetEventOperationToRequestType { * @param requestTimeout Timeout for the request. * @return Event if it exists. */ -export function getEventBaseOperationRequestOptions(handler: IModelBaseHandler, method: string, sasToken: string, requestTimeout?: number): RequestOptions { +export async function getEventBaseOperationRequestOptions(handler: IModelBaseHandler, method: string, sasToken: string, requestTimeout?: number): Promise { const options: RequestOptions = { method, headers: { authorization: sasToken }, @@ -110,7 +110,7 @@ export function getEventBaseOperationRequestOptions(handler: IModelBaseHandler, if (requestTimeout) options.timeout = requestTimeout * 1500; - new DefaultRequestOptionsProvider().assignOptions(options); + await new DefaultRequestOptionsProvider().assignOptions(options); return options; } @@ -160,8 +160,8 @@ export abstract class EventBaseHandler { * @return Event if it exists. * @hidden */ - protected getEventRequestOptions(operation: GetEventOperationToRequestType, sasToken: string, requestTimeout?: number): RequestOptions { - const options = getEventBaseOperationRequestOptions(this._handler, operation, sasToken, requestTimeout); + protected async getEventRequestOptions(operation: GetEventOperationToRequestType, sasToken: string, requestTimeout?: number): Promise { + const options = await getEventBaseOperationRequestOptions(this._handler, operation, sasToken, requestTimeout); this.setServiceBusOptions(options); @@ -193,7 +193,7 @@ export class EventListener { existingSubscription = subscription; existingSubscription.listeners = new BeEvent<(event: IModelHubBaseEvent) => void>(); deleteListener = subscription.listeners.addListener(listener); - this.getEvents(subscription); + this.getEvents(subscription); // tslint:disable-line:no-floating-promises } else { deleteListener = subscription.listeners.addListener(listener); } diff --git a/core/clients/src/imodelhub/GlobalEvents.ts b/core/clients/src/imodelhub/GlobalEvents.ts index 2b421b1..77f8d4c 100644 --- a/core/clients/src/imodelhub/GlobalEvents.ts +++ b/core/clients/src/imodelhub/GlobalEvents.ts @@ -283,7 +283,7 @@ export class GlobalEventHandler extends EventBaseHandler { /** * Get a handler for managing [[GlobalEventSubscription]]s. */ - public Subscriptions(): GlobalEventSubscriptionHandler { + public get subscriptions(): GlobalEventSubscriptionHandler { if (!this._subscriptionHandler) { this._subscriptionHandler = new GlobalEventSubscriptionHandler(this._handler); } @@ -352,9 +352,9 @@ export class GlobalEventHandler extends EventBaseHandler { let options: RequestOptions; if (getOperation === GetEventOperationType.Destructive) - options = this.getEventRequestOptions(GetEventOperationToRequestType.GetDestructive, sasToken, timeout); + options = await this.getEventRequestOptions(GetEventOperationToRequestType.GetDestructive, sasToken, timeout); else if (getOperation === GetEventOperationType.Peek) - options = this.getEventRequestOptions(GetEventOperationToRequestType.GetPeek, sasToken, timeout); + options = await this.getEventRequestOptions(GetEventOperationToRequestType.GetPeek, sasToken, timeout); else // Unknown operation type. return undefined; @@ -384,9 +384,9 @@ export class GlobalEventHandler extends EventBaseHandler { ArgumentCheck.defined("subscriptionInstanceId", subscriptionInstanceId); const subscription = new ListenerSubscription(); subscription.authenticationCallback = authenticationCallback; - subscription.getEvent = (sasToken: string, baseAddress: string, id: string, timeout?: number) => + subscription.getEvent = async (sasToken: string, baseAddress: string, id: string, timeout?: number) => this.getEvent(alctx, sasToken, baseAddress, id, timeout); - subscription.getSASToken = (token: AccessToken) => this.getSASToken(alctx, token); + subscription.getSASToken = async (token: AccessToken) => this.getSASToken(alctx, token); subscription.id = subscriptionInstanceId; return EventListener.create(subscription, listener); } diff --git a/core/clients/src/imodelhub/Locks.ts b/core/clients/src/imodelhub/Locks.ts index e4c2c86..d588c8d 100644 --- a/core/clients/src/imodelhub/Locks.ts +++ b/core/clients/src/imodelhub/Locks.ts @@ -6,11 +6,11 @@ import * as deepAssign from "deep-assign"; -import { ECJsonTypeMap, WsgInstance, Id64Serializer, PropertySerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { ResponseError } from "./../Request"; import { AccessToken } from "../Token"; -import { Logger, IModelHubStatus, ActivityLoggingContext, Id64, Id64String, GuidString } from "@bentley/bentleyjs-core"; +import { Logger, IModelHubStatus, ActivityLoggingContext, Id64String, GuidString } from "@bentley/bentleyjs-core"; import { AggregateResponseError, Query } from "./index"; import { IModelHubError, ArgumentCheck } from "./Errors"; import { IModelBaseHandler } from "./BaseHandler"; @@ -184,32 +184,10 @@ export class LockBase extends WsgInstance { @ECJsonTypeMap.classToJson("wsg", "iModelScope.Lock", { schemaPropertyName: "schemaName", classPropertyName: "className" }) export class Lock extends LockBase { /** Id of the locked object. */ - @ECJsonTypeMap.propertyToJson("wsg", "properties.ObjectId", new Id64Serializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.ObjectId") public objectId?: Id64String; } -class Id64ArraySerializer implements PropertySerializer { - public serialize(value: any): any { - if (!(value instanceof Array)) - return undefined; - return value.map((v) => { - if (typeof v === "string") - return v; - return undefined; - }); - } - - public deserialize(value: any): any { - if (!(value instanceof Array)) - return undefined; - return value.map((v) => { - if (typeof v === "string") - return Id64.fromString(v); - return undefined; - }); - } -} - /** * MultiLock * Data about locks grouped by BriefcaseId, LockLevel and LockType. @@ -217,7 +195,7 @@ class Id64ArraySerializer implements PropertySerializer { */ @ECJsonTypeMap.classToJson("wsg", "iModelScope.MultiLock", { schemaPropertyName: "schemaName", classPropertyName: "className" }) export class MultiLock extends LockBase { - @ECJsonTypeMap.propertyToJson("wsg", "properties.ObjectIds", new Id64ArraySerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.ObjectIds") public objectIds?: Id64String[]; } @@ -485,7 +463,7 @@ export class LockHandler { ArgumentCheck.nonEmptyArray("locks", locks); updateOptions = updateOptions || {}; - this.setupOptionDefaults(updateOptions); + await this.setupOptionDefaults(updateOptions); const result: Lock[] = []; let conflictError: ConflictingLocksError | undefined; diff --git a/core/clients/src/imodelhub/Thumbnails.ts b/core/clients/src/imodelhub/Thumbnails.ts index 79a36aa..9ec4e63 100644 --- a/core/clients/src/imodelhub/Thumbnails.ts +++ b/core/clients/src/imodelhub/Thumbnails.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModelHub */ -import { ECJsonTypeMap, WsgInstance, GuidSerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { request, RequestOptions } from "./../Request"; import { AccessToken } from "../Token"; @@ -20,7 +20,7 @@ export type ThumbnailSize = "Small" | "Large"; /** Base class for Thumbnails. */ export abstract class Thumbnail extends WsgInstance { - @ECJsonTypeMap.propertyToJson("wsg", "instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "instanceId") public id?: GuidString; } @@ -197,7 +197,7 @@ export class ThumbnailHandler { ArgumentCheck.validGuid("imodelId", imodelId); if (this.isTipThumbnail(thumbnail)) { - return await this.downloadTipThumbnail(alctx, token, thumbnail.projectId, imodelId, thumbnail.size); + return this.downloadTipThumbnail(alctx, token, thumbnail.projectId, imodelId, thumbnail.size); } const size: ThumbnailSize = thumbnail instanceof SmallThumbnail ? "Small" : "Large"; diff --git a/core/clients/src/imodelhub/Users.ts b/core/clients/src/imodelhub/Users.ts index cdb9779..129aa32 100644 --- a/core/clients/src/imodelhub/Users.ts +++ b/core/clients/src/imodelhub/Users.ts @@ -16,7 +16,7 @@ const loggingCategory = "imodeljs-clients.imodelhub"; /** Information about the user, allowing to identify them based on their id. */ @ECJsonTypeMap.classToJson("wsg", "iModelScope.UserInfo", { schemaPropertyName: "schemaName", classPropertyName: "className" }) -export class UserInfo extends WsgInstance { +export class HubUserInfo extends WsgInstance { /** Id of the user. */ @ECJsonTypeMap.propertyToJson("wsg", "properties.Id") public id?: string; @@ -36,7 +36,7 @@ export class UserInfo extends WsgInstance { /** Statistics of user created and owned instances on the iModel. */ @ECJsonTypeMap.classToJson("wsg", "iModelScope.UserInfo", { schemaPropertyName: "schemaName", classPropertyName: "className" }) -export class UserStatistics extends UserInfo { +export class UserStatistics extends HubUserInfo { /** Number of [[Briefcase]]s the user currently owns. */ @ECJsonTypeMap.propertyToJson("wsg", "relationshipInstances[HasStatistics].relatedInstance[Statistics].properties.BriefcasesCount") public briefcasesCount?: number; @@ -208,7 +208,7 @@ export class UserStatisticsHandler { } /** - * Query object for getting [[UserInfo]]. You can use this to modify the [[UserInfoHandler.get]] results. + * Query object for getting [[HubUserInfo]]. You can use this to modify the [[UserInfoHandler.get]] results. */ export class UserInfoQuery extends Query { private _queriedByIds = false; @@ -266,7 +266,7 @@ export class UserInfoQuery extends Query { } /** - * Handler for querying [[UserInfo]]. Use [[IModelClient.Users]] to get an instance of this class. + * Handler for querying [[HubUserInfo]]. Use [[IModelClient.Users]] to get an instance of this class. */ export class UserInfoHandler { private _handler: IModelBaseHandler; @@ -283,7 +283,7 @@ export class UserInfoHandler { /** * Get the handler for querying [[UserStatistics]]. */ - public Statistics(): UserStatisticsHandler { + public get statistics(): UserStatisticsHandler { return new UserStatisticsHandler(this._handler); } @@ -304,17 +304,17 @@ export class UserInfoHandler { * @param query Optional query object to filter the queried users or select different data from them. * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) */ - public async get(alctx: ActivityLoggingContext, token: AccessToken, imodelId: GuidString, query: UserInfoQuery = new UserInfoQuery()): Promise { + public async get(alctx: ActivityLoggingContext, token: AccessToken, imodelId: GuidString, query: UserInfoQuery = new UserInfoQuery()): Promise { alctx.enter(); Logger.logInfo(loggingCategory, `Querying users for iModel ${imodelId}`); ArgumentCheck.defined("token", token); ArgumentCheck.validGuid("imodelId", imodelId); - let users: UserInfo[]; + let users: HubUserInfo[]; if (query.isQueriedByIds) { - users = await this._handler.postQuery(alctx, UserInfo, token, this.getRelativeUrl(imodelId, query.getId()), query.getQueryOptions()); + users = await this._handler.postQuery(alctx, HubUserInfo, token, this.getRelativeUrl(imodelId, query.getId()), query.getQueryOptions()); } else { - users = await this._handler.getInstances(alctx, UserInfo, token, this.getRelativeUrl(imodelId, query.getId()), query.getQueryOptions()); + users = await this._handler.getInstances(alctx, HubUserInfo, token, this.getRelativeUrl(imodelId, query.getId()), query.getQueryOptions()); } alctx.enter(); Logger.logTrace(loggingCategory, `Queried users for iModel ${imodelId}`); diff --git a/core/clients/src/imodelhub/Versions.ts b/core/clients/src/imodelhub/Versions.ts index 679e912..a4c8685 100644 --- a/core/clients/src/imodelhub/Versions.ts +++ b/core/clients/src/imodelhub/Versions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModelHub */ -import { ECJsonTypeMap, WsgInstance, GuidSerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { AccessToken } from "../Token"; import { Logger, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -20,7 +20,7 @@ const loggingCategory = "imodeljs-clients.imodelhub"; */ @ECJsonTypeMap.classToJson("wsg", "iModelScope.Version", { schemaPropertyName: "schemaName", classPropertyName: "className" }) export class Version extends WsgInstance { - @ECJsonTypeMap.propertyToJson("wsg", "instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "instanceId") public id?: GuidString; /** Description of the named Version. */ @@ -44,11 +44,11 @@ export class Version extends WsgInstance { public changeSetId?: string; /** Id of the [[SmallThumbnail]] of the named Version. */ - @ECJsonTypeMap.propertyToJson("wsg", "relationshipInstances[HasThumbnail].relatedInstance[SmallThumbnail].instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "relationshipInstances[HasThumbnail].relatedInstance[SmallThumbnail].instanceId") public smallThumbnailId?: GuidString; /** Id of the [[LargeThumbnail]] of the named Version. */ - @ECJsonTypeMap.propertyToJson("wsg", "relationshipInstances[HasThumbnail].relatedInstance[LargeThumbnail].instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "relationshipInstances[HasThumbnail].relatedInstance[LargeThumbnail].instanceId") public largeThumbnailId?: GuidString; } diff --git a/core/clients/src/imodelhub/iModels.ts b/core/clients/src/imodelhub/iModels.ts index 46927e7..991ae34 100644 --- a/core/clients/src/imodelhub/iModels.ts +++ b/core/clients/src/imodelhub/iModels.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module iModelHub */ -import { ECJsonTypeMap, WsgInstance, GuidSerializer } from "./../ECJsonTypeMap"; +import { ECJsonTypeMap, WsgInstance } from "./../ECJsonTypeMap"; import { IModelHubClientError, IModelHubError, ArgumentCheck } from "./Errors"; import { InstanceIdQuery, addSelectFileAccessKey } from "./Query"; import { AccessToken } from "../Token"; @@ -23,7 +23,7 @@ const loggingCategory = "imodeljs-clients.imodelhub"; @ECJsonTypeMap.classToJson("wsg", "ProjectScope.iModel", { schemaPropertyName: "schemaName", classPropertyName: "className" }) export class HubIModel extends WsgInstance { /** Id of the iModel. */ - @ECJsonTypeMap.propertyToJson("wsg", "instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "instanceId") public id?: GuidString; /** Description of the iModel. */ @@ -74,7 +74,7 @@ export enum InitializationState { @ECJsonTypeMap.classToJson("wsg", "iModelScope.SeedFile", { schemaPropertyName: "schemaName", classPropertyName: "className" }) export class SeedFile extends WsgInstance { /** Id of the iModel. */ - @ECJsonTypeMap.propertyToJson("wsg", "instanceId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "instanceId") public id?: GuidString; @ECJsonTypeMap.propertyToJson("wsg", "properties.FileName") @@ -86,7 +86,7 @@ export class SeedFile extends WsgInstance { @ECJsonTypeMap.propertyToJson("wsg", "properties.FileSize") public fileSize?: string; - @ECJsonTypeMap.propertyToJson("wsg", "properties.FileId", new GuidSerializer()) + @ECJsonTypeMap.propertyToJson("wsg", "properties.FileId") public fileId?: GuidString; @ECJsonTypeMap.propertyToJson("wsg", "properties.Index") @@ -223,7 +223,7 @@ class SeedFileHandler { } /** - * Query object for getting [[HubIModel]] instances. You can use this to modify the [[IModelHandler.get]] results. + * Query object for getting [[HubIModel]] instances. You can use this to modify the [[IModelsHandler.get]] results. */ export class IModelQuery extends InstanceIdQuery { /** @@ -237,31 +237,22 @@ export class IModelQuery extends InstanceIdQuery { this.addFilter(`Name+eq+'${name}'`); return this; } - - /** - * Query [[Project]] primary iModel. - * @returns This query. - */ - public primary() { - this.resetQueryOptions(); - - this.orderBy("CreatedDate+asc").top(1); - return this; - } } /** * Handler for managing [[HubIModel]] instances. Use [[IModelHubClient.IModels]] to get an instance of this handler. + * @note Use [[IModelHubClient.IModel]] for the preferred single iModel per [[Project]] workflow. */ -export class IModelHandler { +export class IModelsHandler { private _handler: IModelBaseHandler; private _fileHandler?: FileHandler; private _seedFileHandler: SeedFileHandler; /** - * Constructor for IModelHandler. Should use @see IModelClient instead of directly constructing this. + * Constructor for IModelsHandler. Should use @see IModelClient instead of directly constructing this. * @param handler Handler for WSG requests. * @param fileHandler Handler for file system. + * @note Use [[IModelHubClient.IModel]] for the preferred single iModel per [[Project]] workflow. * @hidden */ constructor(handler: IModelBaseHandler, fileHandler?: FileHandler) { @@ -532,3 +523,136 @@ export class IModelHandler { Logger.logTrace(loggingCategory, `Downloading seed file for iModel ${imodelId}`); } } + +/** + * Handler for managing [[HubIModel]] instance. Use [[IModelHubClient.IModel]] to get an instance of this handler. + * @note Use [[IModelHubClient.IModels]] if multiple iModels per [[Project]] are supported. + */ +export class IModelHandler { + private _handler: IModelsHandler; + + /** + * Constructor for IModelHandler. Should use @see IModelClient instead of directly constructing this. + * @param handler Handler for managing [[HubIModel]] instances. + * @note Use [[IModelHubClient.IModels]] if multiple iModels per [[Project]] are supported. + * @hidden + */ + constructor(handler: IModelsHandler) { + this._handler = handler; + } + + /** + * Get iModel that belong to the specified [[Project]]. + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @returns [[HubIModel]] instances that match the query. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelDoesNotExist]$(bentley) if iModel does not exist. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async get(alctx: ActivityLoggingContext, token: AccessToken, contextId: string): Promise { + alctx.enter(); + + Logger.logInfo(loggingCategory, `Querying iModel in project ${contextId}`); + const query = new IModelQuery().orderBy("CreatedDate+asc").top(1); + const imodels = await this._handler.get(alctx, token, contextId, query); + + if (imodels.length < 1) + return Promise.reject(new IModelHubError(IModelHubStatus.iModelDoesNotExist)); + + return imodels[0]; + } + + /** + * Delete an iModel from a [[Project]]. This method is not supported in iModelBank. + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelDoesNotExist]$(bentley) if iModel does not exist. + * @throws [[IModelHubError]] with [IModelHubStatus.UserDoesNotHavePermission]($bentley) if the user does not have DeleteiModel permission. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async delete(alctx: ActivityLoggingContext, token: AccessToken, contextId: string): Promise { + const imodel = await this.get(alctx, token, contextId); + await this._handler.delete(alctx, token, contextId, imodel.id!); + } + + /** + * Get the [[InitializationState]] for the specified iModel. See [iModel creation]($docs/learning/iModelHub/iModels/CreateiModel.md). + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @returns State of the seed file initialization. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelDoesNotExist]$(bentley) if iModel does not exist. + * @throws [[IModelHubError]] with [IModelHubStatus.FileDoesNotExist]($bentley) if the seed file was not found. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async getInitializationState(alctx: ActivityLoggingContext, token: AccessToken, contextId: string): Promise { + const imodel = await this.get(alctx, token, contextId); + return this._handler.getInitializationState(alctx, token, imodel.id!); + } + + /** + * Create an iModel from given seed file. In most cases [IModelDb.create]($backend) should be used instead. See [iModel creation]($docs/learning/iModelHub/iModels/CreateiModel.md). + * + * This method does not work on browsers. If iModel creation fails before finishing file upload, partially created iModel is deleted. This method is not supported in iModelBank. + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @param name Name of the iModel on the Hub. + * @param description Description of the iModel on the Hub. + * @param progressCallback Callback for tracking progress. + * @param timeOutInMiliseconds Time to wait for iModel initialization. + * @throws [[IModelHubError]] with [IModelHubStatus.UserDoesNotHavePermission]($bentley) if the user does not have CreateiModel permission. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async create(alctx: ActivityLoggingContext, token: AccessToken, contextId: string, name: string, pathName: string, + description?: string, progressCallback?: (progress: ProgressInfo) => void, + timeOutInMilliseconds: number = 120000): Promise { + let imodelExists = true; + try { + await this.get(alctx, token, contextId); + } catch (err) { + if (err instanceof IModelHubError && err.errorNumber === IModelHubStatus.iModelDoesNotExist) + imodelExists = false; + else + throw err; + } + + if (imodelExists) + return Promise.reject(new IModelHubError(IModelHubStatus.iModelAlreadyExists)); + + return this._handler.create(alctx, token, contextId, name, pathName, description, progressCallback, timeOutInMilliseconds); + } + + /** + * Update iModel's name and/or description + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @param imodel iModel to update. See [[HubIModel]]. + * @throws [[IModelHubError]] with [IModelHubStatus.UserDoesNotHavePermission]($bentley) if the user does not have CreateiModel permission. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelDoesNotExist]$(bentley) if iModel does not exist. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelIsNotInitialized]$(bentley) if iModel is not initialized. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelAlreadyExists]$(bentley) if iModel with specified name already exists. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async update(alctx: ActivityLoggingContext, token: AccessToken, contextId: string, imodel: HubIModel): Promise { + return this._handler.update(alctx, token, contextId, imodel); + } + + /** + * Method to download the seed file for iModel. This will download the original seed file, that was uploaded when creating iModel. To download a file that was updated with ChangeSets on iModelHub, see [[BriefcaseHandler.download]]. + * @param alctx Activity logging context + * @param token Delegation token of the authorized user. + * @param contextId Id for the iModel's context. For iModelHub it should be the id of the connect [[Project]]. + * @param downloadToPathname Directory where the seed file should be downloaded. + * @param progressCallback Callback for tracking progress. + * @throws [[IModelHubError]] with [IModelHubStatus.iModelDoesNotExist]$(bentley) if iModel does not exist. + * @throws [Common iModelHub errors]($docs/learning/iModelHub/CommonErrors) + */ + public async download(alctx: ActivityLoggingContext, token: AccessToken, contextId: string, downloadToPathname: string, progressCallback?: (progress: ProgressInfo) => void): Promise { + const imodel = await this.get(alctx, token, contextId); + await this._handler.download(alctx, token, imodel.id!, downloadToPathname, progressCallback); + } +} diff --git a/core/clients/src/index.ts b/core/clients/src/index.ts index 8a86449..107cbfc 100644 --- a/core/clients/src/index.ts +++ b/core/clients/src/index.ts @@ -6,7 +6,7 @@ export * from "./ECJsonTypeMap"; export * from "./Client"; export * from "./Config"; export * from "./Token"; -export * from "./UserProfile"; +export * from "./UserInfo"; export * from "./ConnectClients"; export * from "./WsgClient"; export * from "./FileHandler"; diff --git a/core/clients/src/oidc/OidcClient.ts b/core/clients/src/oidc/OidcClient.ts index 279ddb5..5941402 100644 --- a/core/clients/src/oidc/OidcClient.ts +++ b/core/clients/src/oidc/OidcClient.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Client } from "../Client"; -export class OidcClient extends Client { +export abstract class OidcClient extends Client { public static readonly searchKey: string = "IMSOpenID"; public constructor() { diff --git a/core/clients/src/oidc/OidcFrontendClient.ts b/core/clients/src/oidc/OidcFrontendClient.ts index 51188c0..c8427a9 100644 --- a/core/clients/src/oidc/OidcFrontendClient.ts +++ b/core/clients/src/oidc/OidcFrontendClient.ts @@ -1,44 +1,46 @@ + /*--------------------------------------------------------------------------------------------- * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; -import { UserProfile } from "../UserProfile"; +import { ActivityLoggingContext, IDisposable, BeEvent } from "@bentley/bentleyjs-core"; import { AccessToken } from "../Token"; -import { OidcClient } from "./OidcClient"; -import { UserManagerSettings, User } from "oidc-client"; -/** Client configuration to generate OIDC/OAuth tokens for frontend or browser applications */ +/** Interface to implement a typical frontend client */ +export interface IOidcFrontendClient extends IDisposable { + /** Used to initialize the client - must be awaited before any other methods are called */ + initialize(actx: ActivityLoggingContext): Promise; + + /** Called to start the sign-in process. Subscribe to onUserStateChanged to be notified when sign-in completes */ + signIn(actx: ActivityLoggingContext): void; + + /** Called to start the sign-out process. Subscribe to onUserStateChanged to be notified when sign-out completes */ + signOut(actx: ActivityLoggingContext): void; + + /** Returns a promise that resolves to the AccessToken if signed in, and undefined otherwise. The token is refreshed if it's possible and necessary. */ + getAccessToken(actx: ActivityLoggingContext): Promise; + + /** Event called when the user's sign-in state changes - this may be due to calls to signIn(), signOut() or simply because the token expired */ + readonly onUserStateChanged: BeEvent<(token: AccessToken | undefined) => void>; +} + +/** Client configuration to generate OIDC/OAuth tokens for browser, desktop and mobile applications */ export interface OidcFrontendClientConfiguration { /** Client application's identifier as registered with the Bentley IMS OIDC/OAuth2 provider. */ clientId: string; - /** Upon login, the client application receives a response from the Bentley IMS OIDC/OAuth2 provider at this URI */ + /** + * Upon signing in, the client application receives a response from the Bentley IMS OIDC/OAuth2 provider at this URI + * For mobile/desktop applications, must be `http://127.0.0.1:${redirectPort}` + */ redirectUri: string; -} - -/** Utility to generate OIDC/OAuth tokens for backend applications */ -export class OidcFrontendClient extends OidcClient { - public constructor(private _configuration: OidcFrontendClientConfiguration) { - super(); - } - - public async getUserManagerSettings(actx: ActivityLoggingContext, scope?: string): Promise { - const userManagerSettings: UserManagerSettings = { - authority: await this.getUrl(actx), - client_id: this._configuration.clientId, - redirect_uri: this._configuration.redirectUri, - silent_redirect_uri: this._configuration.redirectUri, - automaticSilentRenew: true, - response_type: "id_token token", - scope: scope || "openid email profile organization feature_tracking imodelhub context-registry-service imodeljs-router", - }; - return userManagerSettings; - } - - public static createAccessToken(user: User): AccessToken { - const startsAt: Date = new Date(user.expires_at - user.expires_in!); - const expiresAt: Date = new Date(user.expires_at); - const userProfile = new UserProfile(user.profile.given_name, user.profile.family_name, user.profile.email!, user.profile.sub, user.profile.org_name!, user.profile.org!, user.profile.ultimate_site!, user.profile.usage_country_iso!); - return AccessToken.fromJsonWebTokenString(user.access_token, userProfile, startsAt, expiresAt); - } + /** + * Upon signing out, the client application receives a response from the Bentley IMS OIDC/OAuth2 provider at this URI + * Not specified/used in the case of mobile/desktop applications + */ + postSignoutRedirectUri?: string; + /** + * Optional scope that requests access to various resources. If omitted, a default is setup to access the resources typically required, + * including the iModelHub + */ + scope?: string; } diff --git a/core/clients/src/test/BIMReviewShareClient.test.ts b/core/clients/src/test/BIMReviewShareClient.test.ts index 0e181f1..2a3da02 100644 --- a/core/clients/src/test/BIMReviewShareClient.test.ts +++ b/core/clients/src/test/BIMReviewShareClient.test.ts @@ -6,19 +6,19 @@ import * as chai from "chai"; import { AuthorizationToken, AccessToken } from "../Token"; import { TestConfig } from "./TestConfig"; import { BIMReviewShareClient, Content } from "../BIMReviewShareClient"; -import { ActivityLoggingContext, Guid } from "@bentley/bentleyjs-core"; +import { ActivityLoggingContext, Guid, GuidString } from "@bentley/bentleyjs-core"; describe("BIMReviewShareClient", () => { let accessToken: AccessToken; let actx: ActivityLoggingContext; const bimReviewShareClient: BIMReviewShareClient = new BIMReviewShareClient(); - let projectId: string = ""; + let projectId: GuidString = ""; const moduleName = "BIMREVIEWSHARE_TEST_SavedViewsModule"; before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - actx = new ActivityLoggingContext(""); + actx = new ActivityLoggingContext(Guid.createValue()); if (TestConfig.enableMocks) return; @@ -26,8 +26,7 @@ describe("BIMReviewShareClient", () => { const authToken: AuthorizationToken = await TestConfig.login(); accessToken = await bimReviewShareClient.getAccessToken(actx, authToken); - const testCase = await TestConfig.queryTestCase(accessToken, TestConfig.projectName); - projectId = testCase.project.wsgId; + projectId = (await TestConfig.queryProject(accessToken, TestConfig.projectName)).wsgId; // Try to pre-emptively clean-up instances that may have stayed in the service after a failed run of this test try { @@ -43,8 +42,8 @@ describe("BIMReviewShareClient", () => { it("should be able to post, retrieve, update and delete Content instance and data (#integration)", async function (this: Mocha.ITestCallbackContext) { // Test with fabricated "User ID" - const userGuidTest = Guid.createValue(); - const userGuidTest2 = Guid.createValue(); + const userGuidTest: GuidString = Guid.createValue(); + const userGuidTest2: GuidString = Guid.createValue(); // Test data to post in content's blob data let testData = { varA: 1, varB: 2, str1: "Test1", str2: "Test2" }; // Post content using a randomly generated user GUIDs diff --git a/core/clients/src/test/ConnectClients.test.ts b/core/clients/src/test/ConnectClients.test.ts index f2be6a7..eca26dd 100644 --- a/core/clients/src/test/ConnectClients.test.ts +++ b/core/clients/src/test/ConnectClients.test.ts @@ -108,7 +108,7 @@ describe("RbacClient (#integration)", () => { chai.expect(!!project); // Get the user ID we are using that should exist in the returned users - const currentUserId = accessToken.getUserProfile()!.userId; + const currentUserId = accessToken.getUserInfo()!.id; // Get users const users: RbacUser[] = await rbacClient.getUsers(actx, accessToken!, project.wsgId); diff --git a/core/clients/src/test/ImsClients.test.ts b/core/clients/src/test/ImsClients.test.ts index 9255994..ba0711f 100644 --- a/core/clients/src/test/ImsClients.test.ts +++ b/core/clients/src/test/ImsClients.test.ts @@ -5,7 +5,7 @@ import * as chai from "chai"; import { AuthorizationToken, AccessToken } from "../Token"; import { ImsActiveSecureTokenClient, ImsDelegationSecureTokenClient } from "../ImsClients"; -import { UserProfile } from "../UserProfile"; +import { UserInfo } from "../UserInfo"; import { TestUsers, UserCredentials } from "./TestConfig"; import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; @@ -38,10 +38,10 @@ describe("ImsFederatedAuthenticationClient", () => { chai.expect(tokenStr!.startsWith("X509 access_token=")); chai.expect(tokenStr!.length > 1000); - const userProfile: UserProfile | undefined = authToken!.getUserProfile(); - chai.assert(!!userProfile); + const userInfo: UserInfo | undefined = authToken!.getUserInfo(); + chai.assert(!!userInfo); - chai.expect(userProfile!.email.toLowerCase() === TestUsers.regular.email.toLowerCase()); + chai.expect(userInfo!.email!.id.toLowerCase() === TestUsers.regular.email.toLowerCase()); } }); diff --git a/core/clients/src/test/RealityDataServicesClient.test.ts b/core/clients/src/test/RealityDataServicesClient.test.ts index 43a6504..5161d3c 100644 --- a/core/clients/src/test/RealityDataServicesClient.test.ts +++ b/core/clients/src/test/RealityDataServicesClient.test.ts @@ -29,7 +29,7 @@ describe.skip("RealityDataServicesClient", () => { accessToken = await realityDataServiceClient.getAccessToken(actx, authToken); const imodelHubToken = await imodelHubClient.getAccessToken(actx, authToken); - const versions: Version[] = await imodelHubClient.Versions().get(actx, imodelHubToken, iModelId); + const versions: Version[] = await imodelHubClient.versions.get(actx, imodelHubToken, iModelId); chai.expect(versions); versionId = versions[0].wsgId; chai.expect(versionId); diff --git a/core/clients/src/test/SettingsClient.test.ts b/core/clients/src/test/SettingsClient.test.ts index e04b305..b73fd5b 100644 --- a/core/clients/src/test/SettingsClient.test.ts +++ b/core/clients/src/test/SettingsClient.test.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import * as chai from "chai"; -import { ConnectClient } from "../ConnectClients"; import { ConnectSettingsClient } from "../SettingsClient"; import { SettingsStatus, SettingsResult } from "../SettingsAdmin"; import { AuthorizationToken, AccessToken } from "../Token"; import { TestConfig, TestUsers } from "./TestConfig"; -import { ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; +import { ActivityLoggingContext, GuidString, Guid } from "@bentley/bentleyjs-core"; // compare simple arrays function arraysEqual(array1: any, array2: any) { @@ -27,29 +26,22 @@ function arraysEqual(array1: any, array2: any) { chai.should(); -describe.skip("ConnectSettingsClient-User", () => { +describe("ConnectSettingsClient-User", () => { let accessToken: AccessToken; - let authToken: AuthorizationToken; - let projectId: string; - let iModelId: string; - let connectClient: ConnectClient; + let projectId: GuidString; + let iModelId: GuidString; let settingsClient: ConnectSettingsClient; - const actx = new ActivityLoggingContext(""); + const actx = new ActivityLoggingContext(Guid.createValue()); before(async function (this: Mocha.IHookCallbackContext) { - connectClient = new ConnectClient(); settingsClient = new ConnectSettingsClient("1001"); - authToken = await TestConfig.login(); - accessToken = await connectClient.getAccessToken(actx, authToken); - - const { project, iModel } = await TestConfig.queryTestCase(accessToken, TestConfig.projectName, "test"); - - projectId = project.wsgId; - chai.expect(projectId); - - iModelId = iModel!.wsgId; - chai.expect(iModelId); + const authToken: AuthorizationToken = await TestConfig.login(); + accessToken = await settingsClient.getAccessToken(actx, authToken); + projectId = (await TestConfig.queryProject(accessToken, TestConfig.projectName)).wsgId; + chai.assert.isDefined(projectId); + iModelId = (await TestConfig.queryIModel(accessToken, projectId)).wsgId; + chai.assert.isDefined(iModelId); }); // Application User Setting @@ -60,15 +52,15 @@ describe.skip("ConnectSettingsClient-User", () => { const appUserSetting = { appString: "application User String", appNumber: 7, appArray: [1, 2, 3, 4] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppUser", authToken, true); + const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppUser", accessToken, true); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appUserSetting, "TestSettings", "AppUser", authToken, true); + const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appUserSetting, "TestSettings", "AppUser", accessToken, true); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppUser", authToken, true); + const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppUser", accessToken, true); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.appString).equals(appUserSetting.appString); @@ -79,9 +71,9 @@ describe.skip("ConnectSettingsClient-User", () => { appUserSetting.appString = "new Application User String"; appUserSetting.appNumber = 8; appUserSetting.appArray.splice(2, 1); // is now 1, 2, 4 - const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appUserSetting, "TestSettings", "AppUser", authToken, true); + const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appUserSetting, "TestSettings", "AppUser", accessToken, true); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppUser", authToken, true); + const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppUser", accessToken, true); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.appString).equals(appUserSetting.appString); @@ -98,15 +90,15 @@ describe.skip("ConnectSettingsClient-User", () => { const appProjectUserSetting = { appString: "application/Project User String", appNumber: 213, appArray: [10, 20, 30, 40, 50] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppProjectUser", authToken, true, projectId); + const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppProjectUser", accessToken, true, projectId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appProjectUserSetting, "TestSettings", "AppProjectUser", authToken, true, projectId); + const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appProjectUserSetting, "TestSettings", "AppProjectUser", accessToken, true, projectId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppProjectUser", authToken, true, projectId); + const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppProjectUser", accessToken, true, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.appString).equals(appProjectUserSetting.appString); @@ -117,9 +109,9 @@ describe.skip("ConnectSettingsClient-User", () => { appProjectUserSetting.appString = "new Application Project User String"; appProjectUserSetting.appNumber = 8; appProjectUserSetting.appArray.splice(2, 1); - const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appProjectUserSetting, "TestSettings", "AppProjectUser", authToken, true, projectId); + const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appProjectUserSetting, "TestSettings", "AppProjectUser", accessToken, true, projectId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppProjectUser", authToken, true, projectId); + const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppProjectUser", accessToken, true, projectId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.appString).equals(appProjectUserSetting.appString); @@ -136,15 +128,15 @@ describe.skip("ConnectSettingsClient-User", () => { const appIModelUserSetting = { appString: "application/iModel User String", appNumber: 41556, appArray: [1, 2, 3, 5, 8, 13, 21, 34] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppIModelUser", authToken, true, projectId, iModelId); + const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "AppIModelUser", accessToken, true, projectId, iModelId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appIModelUserSetting, "TestSettings", "AppIModelUser", authToken, true, projectId, iModelId); + const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, appIModelUserSetting, "TestSettings", "AppIModelUser", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppIModelUser", authToken, true, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppIModelUser", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.appString).equals(appIModelUserSetting.appString); @@ -155,9 +147,9 @@ describe.skip("ConnectSettingsClient-User", () => { appIModelUserSetting.appString = "new Application User iModel String"; appIModelUserSetting.appNumber = 32757; appIModelUserSetting.appArray.splice(3, 2); - const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appIModelUserSetting, "TestSettings", "AppIModelUser", authToken, true, projectId, iModelId); + const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, appIModelUserSetting, "TestSettings", "AppIModelUser", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppIModelUser", authToken, true, projectId, iModelId); + const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "AppIModelUser", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.appString).equals(appIModelUserSetting.appString); @@ -173,15 +165,15 @@ describe.skip("ConnectSettingsClient-User", () => { const projectUserSetting = { projString: "Project User String", projNumber: 213, projArray: [1, 3, 5, 7, 11, 13, 17] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "ProjectUser", authToken, false, projectId); + const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "ProjectUser", accessToken, false, projectId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, projectUserSetting, "TestSettings", "ProjectUser", authToken, false, projectId); + const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, projectUserSetting, "TestSettings", "ProjectUser", accessToken, false, projectId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "ProjectUser", authToken, false, projectId); + const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "ProjectUser", accessToken, false, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.projString).equals(projectUserSetting.projString); @@ -192,9 +184,9 @@ describe.skip("ConnectSettingsClient-User", () => { projectUserSetting.projString = "new Project User String"; projectUserSetting.projNumber = 8; projectUserSetting.projArray.splice(2, 2); - const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, projectUserSetting, "TestSettings", "ProjectUser", authToken, false, projectId); + const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, projectUserSetting, "TestSettings", "ProjectUser", accessToken, false, projectId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "ProjectUser", authToken, false, projectId); + const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "ProjectUser", accessToken, false, projectId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.projString).equals(projectUserSetting.projString); @@ -211,15 +203,15 @@ describe.skip("ConnectSettingsClient-User", () => { const iModelUserSetting = { iModelString: "iModel User String", iModelNumber: 723, iModelArray: [99, 98, 97, 96, 95] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "IModelUser", authToken, false, projectId, iModelId); + const deleteResult: SettingsResult = await settingsClient.deleteUserSetting(actx, "TestSettings", "IModelUser", accessToken, false, projectId, iModelId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, iModelUserSetting, "TestSettings", "IModelUser", authToken, false, projectId, iModelId); + const saveResult: SettingsResult = await settingsClient.saveUserSetting(actx, iModelUserSetting, "TestSettings", "IModelUser", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "IModelUser", authToken, false, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "IModelUser", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.iModelString).equals(iModelUserSetting.iModelString); @@ -230,9 +222,9 @@ describe.skip("ConnectSettingsClient-User", () => { iModelUserSetting.iModelString = "new iModel User String"; iModelUserSetting.iModelNumber = 327; iModelUserSetting.iModelArray.splice(2, 2); - const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, iModelUserSetting, "TestSettings", "IModelUser", authToken, false, projectId, iModelId); + const saveResult2: SettingsResult = await settingsClient.saveUserSetting(actx, iModelUserSetting, "TestSettings", "IModelUser", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "IModelUser", authToken, false, projectId, iModelId); + const getResult2: SettingsResult = await settingsClient.getUserSetting(actx, "TestSettings", "IModelUser", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.iModelString).equals(iModelUserSetting.iModelString); @@ -243,32 +235,25 @@ describe.skip("ConnectSettingsClient-User", () => { }); -describe.skip("ConnectSettingsClient-Administrator", () => { +describe("ConnectSettingsClient-Administrator", () => { let accessToken: AccessToken; - let authToken: AuthorizationToken; - let projectId: string; - let iModelId: string; - let connectClient: ConnectClient; + let projectId: GuidString; + let iModelId: GuidString; let settingsClient: ConnectSettingsClient; - const actx = new ActivityLoggingContext(""); + const actx = new ActivityLoggingContext(Guid.createValue()); before(async function (this: Mocha.IHookCallbackContext) { if (TestConfig.enableMocks) return; - connectClient = new ConnectClient(); settingsClient = new ConnectSettingsClient("1001"); - authToken = await TestConfig.login(TestUsers.super); - accessToken = await connectClient.getAccessToken(actx, authToken); - - const { project, iModel } = await TestConfig.queryTestCase(accessToken, TestConfig.projectName, "test"); - - projectId = project.wsgId; - chai.expect(projectId); - - iModelId = iModel!.wsgId; - chai.expect(iModelId); + const authToken: AuthorizationToken = await TestConfig.login(TestUsers.super); + accessToken = await settingsClient.getAccessToken(actx, authToken); + projectId = (await TestConfig.queryProject(accessToken, TestConfig.projectName)).wsgId; + chai.assert.isDefined(projectId); + iModelId = (await TestConfig.queryIModel(accessToken, projectId)).wsgId; + chai.assert.isDefined(iModelId); }); // Application Setting @@ -279,15 +264,15 @@ describe.skip("ConnectSettingsClient-Administrator", () => { const appSetting = { appString: "application String", appNumber: 112, appArray: [101, 102, 103, 104] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppSetting", authToken, true); + const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppSetting", accessToken, true); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveSetting(actx, appSetting, "TestSettings", "AppSetting", authToken, true); + const saveResult: SettingsResult = await settingsClient.saveSetting(actx, appSetting, "TestSettings", "AppSetting", accessToken, true); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", authToken, true); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", accessToken, true); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.appString).equals(appSetting.appString); @@ -297,9 +282,9 @@ describe.skip("ConnectSettingsClient-Administrator", () => { // change the value of an existing setting appSetting.appString = "new Application String"; appSetting.appArray.splice(2, 1); - const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, appSetting, "TestSettings", "AppSetting", authToken, true); + const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, appSetting, "TestSettings", "AppSetting", accessToken, true); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", authToken, true); + const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", accessToken, true); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.appString).equals(appSetting.appString); @@ -316,15 +301,15 @@ describe.skip("ConnectSettingsClient-Administrator", () => { const projectAppSetting = { projAppString: "project Application String", projAppNumber: 592, projAppArray: [2101, 2102, 2103, 2104] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveSetting(actx, projectAppSetting, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const saveResult: SettingsResult = await settingsClient.saveSetting(actx, projectAppSetting, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.projAppString).equals(projectAppSetting.projAppString); @@ -335,9 +320,9 @@ describe.skip("ConnectSettingsClient-Administrator", () => { projectAppSetting.projAppString = "new Project Application String"; projectAppSetting.projAppNumber = 1578; projectAppSetting.projAppArray.splice(2, 1); - const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, projectAppSetting, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, projectAppSetting, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.projAppString).equals(projectAppSetting.projAppString); @@ -354,15 +339,15 @@ describe.skip("ConnectSettingsClient-Administrator", () => { const iModelAppSetting = { iModelAppString: "iModel Application String", iModelAppNumber: 592, iModelAppArray: [3211, 3212, 3213, 3214, 3215] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveSetting(actx, iModelAppSetting, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const saveResult: SettingsResult = await settingsClient.saveSetting(actx, iModelAppSetting, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.iModelAppString).equals(iModelAppSetting.iModelAppString); @@ -373,9 +358,9 @@ describe.skip("ConnectSettingsClient-Administrator", () => { iModelAppSetting.iModelAppString = "new IModel Application String"; iModelAppSetting.iModelAppNumber = 1578; iModelAppSetting.iModelAppArray.splice(2, 1); - const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, iModelAppSetting, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, iModelAppSetting, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.iModelAppString).equals(iModelAppSetting.iModelAppString); @@ -392,15 +377,15 @@ describe.skip("ConnectSettingsClient-Administrator", () => { const projectSetting = { projString: "project String", projNumber: 592, projArray: [8765, 4321, 9876, 5432, 1987] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "ProjectSettings", authToken, false, projectId); + const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveSetting(actx, projectSetting, "TestSettings", "ProjectSettings", authToken, false, projectId); + const saveResult: SettingsResult = await settingsClient.saveSetting(actx, projectSetting, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", authToken, false, projectId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.projString).equals(projectSetting.projString); @@ -411,9 +396,9 @@ describe.skip("ConnectSettingsClient-Administrator", () => { projectSetting.projString = "new Project String"; projectSetting.projNumber = 1578; projectSetting.projArray.splice(2, 1); - const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, projectSetting, "TestSettings", "ProjectSettings", authToken, false, projectId); + const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, projectSetting, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", authToken, false, projectId); + const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.projString).equals(projectSetting.projString); @@ -430,15 +415,15 @@ describe.skip("ConnectSettingsClient-Administrator", () => { const iModelSetting = { iModelString: "iModel String", iModelNumber: 592, iModelArray: [33482, 29385, 99742, 32195, 99475] }; // start by deleting the setting we're going to create. - const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const deleteResult: SettingsResult = await settingsClient.deleteSetting(actx, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert((SettingsStatus.Success === deleteResult.status) || (SettingsStatus.SettingNotFound === deleteResult.status), "Delete should work or give SettingNotFound"); // save a new setting (deleted above, so we know it's new) - const saveResult: SettingsResult = await settingsClient.saveSetting(actx, iModelSetting, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const saveResult: SettingsResult = await settingsClient.saveSetting(actx, iModelSetting, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult.status, "Save should work"); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.iModelString).equals(iModelSetting.iModelString); @@ -449,9 +434,9 @@ describe.skip("ConnectSettingsClient-Administrator", () => { iModelSetting.iModelString = "new IModel Application String"; iModelSetting.iModelNumber = 1578; iModelSetting.iModelArray.splice(3, 1); - const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, iModelSetting, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const saveResult2: SettingsResult = await settingsClient.saveSetting(actx, iModelSetting, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === saveResult2.status, "Second save should work"); - const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const getResult2: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult2.status, "Retrieval should work"); chai.assert(getResult2.setting, "Setting should be returned"); chai.expect(getResult2.setting.iModelString).equals(iModelSetting.iModelString); @@ -464,28 +449,20 @@ describe.skip("ConnectSettingsClient-Administrator", () => { describe("Reading non-user settings from ordinary user", () => { let accessToken: AccessToken; - let authToken: AuthorizationToken; - let projectId: string; + let projectId: GuidString; let iModelId: GuidString; - let connectClient: ConnectClient; let settingsClient: ConnectSettingsClient; - const actx = new ActivityLoggingContext(""); + const actx = new ActivityLoggingContext(Guid.createValue()); before(async function (this: Mocha.IHookCallbackContext) { - connectClient = new ConnectClient(); settingsClient = new ConnectSettingsClient("1001"); - authToken = await TestConfig.login(); - accessToken = await connectClient.getAccessToken(actx, authToken); - - const { project, iModel } = await TestConfig.queryTestCase(accessToken, TestConfig.projectName, "test"); - - projectId = project.wsgId; - chai.expect(projectId); + const authToken: AuthorizationToken = await TestConfig.login(); + accessToken = await settingsClient.getAccessToken(actx, authToken); - chai.expect(iModel); - chai.expect(iModel!.wsgId); - iModelId = iModel!.wsgId; - chai.expect(iModelId); + projectId = (await TestConfig.queryProject(accessToken, TestConfig.projectName)).wsgId; + chai.assert.isDefined(projectId); + iModelId = (await TestConfig.queryIModel(accessToken, projectId)).wsgId; + chai.assert.isDefined(iModelId); }); // Application Setting @@ -493,7 +470,7 @@ describe("Reading non-user settings from ordinary user", () => { if (TestConfig.enableMocks) this.skip(); - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", authToken, true); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppSetting", accessToken, true); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.appString).equals("new Application String"); @@ -505,7 +482,7 @@ describe("Reading non-user settings from ordinary user", () => { this.skip(); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", authToken, true, projectId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppProjectSetting", accessToken, true, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.projAppString).equals("new Project Application String"); @@ -517,7 +494,7 @@ describe("Reading non-user settings from ordinary user", () => { this.skip(); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", authToken, true, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "AppIModelSettings", accessToken, true, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.iModelAppString).equals("new IModel Application String"); @@ -529,7 +506,7 @@ describe("Reading non-user settings from ordinary user", () => { this.skip(); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", authToken, false, projectId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "ProjectSettings", accessToken, false, projectId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.projString).equals("new Project String"); @@ -541,7 +518,7 @@ describe("Reading non-user settings from ordinary user", () => { this.skip(); // read back the result. - const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", authToken, false, projectId, iModelId); + const getResult: SettingsResult = await settingsClient.getSetting(actx, "TestSettings", "IModelSettings", accessToken, false, projectId, iModelId); chai.assert(SettingsStatus.Success === getResult.status, "Retrieval should work"); chai.assert(getResult.setting, "Setting should be returned"); chai.expect(getResult.setting.iModelString).equals("new IModel Application String"); diff --git a/core/clients/src/test/TestConfig.ts b/core/clients/src/test/TestConfig.ts index 40e8a75..04a5ca6 100644 --- a/core/clients/src/test/TestConfig.ts +++ b/core/clients/src/test/TestConfig.ts @@ -4,33 +4,42 @@ *--------------------------------------------------------------------------------------------*/ import { ImsActiveSecureTokenClient } from "../ImsClients"; import { AuthorizationToken, AccessToken } from "../Token"; -import { Version, HubIModel, VersionQuery, IModelQuery } from "../imodelhub"; +import { HubIModel } from "../imodelhub"; import { IModelHubClient, IModelClient } from ".."; import { ConnectClient, Project } from "../ConnectClients"; import { expect } from "chai"; -import { loggingCategoryFullUrl } from "../Request"; import * as fs from "fs"; +import * as path from "path"; -import { Logger, LogLevel, ActivityLoggingContext } from "@bentley/bentleyjs-core"; +import { Logger, LogLevel, ActivityLoggingContext, GuidString, Guid } from "@bentley/bentleyjs-core"; import { Config } from "../Config"; import { IModelJsConfig } from "@bentley/config-loader/lib/IModelJsConfig"; +import { loggingCategoryFullUrl } from "../Request"; + IModelJsConfig.init(true /* suppress exception */, false /* suppress error message */, Config.App); -export const whitelistPath = "./lib/test/assets/whitelist.txt"; -export const logPath = "./lib/test/iModelClientsTests.log"; +const actx = new ActivityLoggingContext(Guid.createValue()); -const fileStream = fs.createWriteStream(logPath, { flags: "a" }); -const actx = new ActivityLoggingContext(""); +const logFileStream = fs.createWriteStream(path.join(__dirname, "./iModelClientsTests.log"), { flags: "a" }); + +// The Request URLs are captured separate. The log file is used by the Hub URL whitelist validation. +export const urlLogPath = path.join(__dirname, "./requesturls.log"); +const urlLogFileStream = fs.createWriteStream(urlLogPath, { flags: "a" }); +console.log("URL Log file created at: " + urlLogPath); + +function logFunction(logLevel: string, category: string, message: string) { + if (category === loggingCategoryFullUrl) + urlLogFileStream.write(message + "\n"); + else + logFileStream.write(logLevel + "|" + category + "|" + message + "\n"); +} // Initialize logger to file Logger.initialize( - (category: string, message: string): void => { fileStream.write("Error |" + category + " | " + message + "\n"); }, - (category: string, message: string): void => { fileStream.write("Warning |" + category + " | " + message + "\n"); }, - (category: string, message: string): void => { fileStream.write("Info |" + category + " | " + message + "\n"); }, - (category: string, message: string): void => { fileStream.write("Trace |" + category + " | " + message + "\n"); }); - -// Log at minimum the full url category, so url validator test can execute -Logger.setLevel(loggingCategoryFullUrl, LogLevel.Trace); + (category: string, message: string): void => { logFunction("Error", category, message); }, + (category: string, message: string): void => { logFunction("Warning", category, message); }, + (category: string, message: string): void => { logFunction("Info", category, message); }, + (category: string, message: string): void => { logFunction("Trace", category, message); }); // Note: Turn this off unless really necessary - it causes Error messages on the // console with the existing suite of tests, and this is quite misleading, @@ -41,6 +50,9 @@ if (!!loggingConfigFile) { Logger.configureLevels(require(loggingConfigFile)); } +// log all request URLs as this will be the input to the Hub URL whitelist test +Logger.setLevel(loggingCategoryFullUrl, LogLevel.Trace); + /** Credentials for test users */ export interface UserCredentials { email: string; @@ -56,7 +68,7 @@ function isOfflineSet(): boolean { */ export class TestConfig { /** Name of project used by most tests */ - public static readonly projectName: string = "NodeJsTestProject"; + public static readonly projectName: string = "iModelJsTest"; public static readonly enableMocks: boolean = isOfflineSet(); /** Login the specified user and return the AuthorizationToken */ @@ -70,31 +82,27 @@ export class TestConfig { return authToken; } - /** Query for the test file from connect/hub */ - public static async queryTestCase(accessToken: AccessToken, projectName: string, iModelName?: string, versionName?: string): Promise<{ project: Project, iModel?: HubIModel, version?: Version }> { + public static async queryProject(accessToken: AccessToken, projectName: string): Promise { const connectClient = new ConnectClient(); - const imodelHubClient: IModelClient = new IModelHubClient(); const project: Project | undefined = await connectClient.getProject(actx, accessToken, { $select: "*", $filter: `Name+eq+'${projectName}'`, }); - expect(project); - - let iModel: HubIModel | undefined = undefined; // tslint:disable-line:no-unnecessary-initializer - let version: Version | undefined = undefined; // tslint:disable-line:no-unnecessary-initializer - if (iModelName) { - const iModels = await imodelHubClient.IModels().get(actx, accessToken, project.wsgId, new IModelQuery().byName(iModelName)); - expect(iModels.length === 1); - iModel = iModels[0]; - - if (versionName) { - version = (await imodelHubClient.Versions().get(actx, accessToken, iModel.id!, new VersionQuery().byName(versionName)))[0]; - expect(version); - } - } - - return { project, iModel, version }; + if (!project || !project.wsgId) + throw new Error(`Project ${projectName} not found for user ${!accessToken.getUserInfo() ? "n/a" : accessToken.getUserInfo()!.email}.`); + + return project; + } + + public static async queryIModel(accessToken: AccessToken, projectId: GuidString): Promise { + const imodelHubClient: IModelClient = new IModelHubClient(); + + const iModel: HubIModel = await imodelHubClient.iModel.get(actx, accessToken, projectId); + if (!iModel || !iModel.wsgId) + throw new Error(`Primary iModel not found for project ${projectId} for user ${!accessToken.getUserInfo() ? "n/a" : accessToken.getUserInfo()!.email}.`); + + return iModel; } } diff --git a/core/clients/src/test/UrlValidator.test.ts b/core/clients/src/test/UrlValidator.test.ts deleted file mode 100644 index e7e8796..0000000 --- a/core/clients/src/test/UrlValidator.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as chai from "chai"; -import * as fs from "fs"; - -import { whitelistPath, logPath } from "./TestConfig"; - -const whitelistViolationDetails = "If this is caused by a necessary API change, update the whitelist and notify iModelBank of the updates. " + - "If the whitelist violation is unintentional, modify your changes to use existing API functionality."; - -describe("URL Whitelist Validator", () => { - let logURLs: string[] = []; - let whitelistURLs: string[] = []; - - it("should load the whitelist URLs", () => { - const data = fs.readFileSync(whitelistPath, "utf8"); - // Split into line array - whitelistURLs = data.split(/\r?\n/); - // Assert length > 0 - chai.expect(whitelistURLs.length, `No whitelist URLs found in ${whitelistPath}`).to.be.above(0); - }); - - it("should load the log URLs", () => { - const data = fs.readFileSync(logPath, "utf8"); - // Split into line array - logURLs = data.split(/\r?\n/); - // Assert length > 0 - chai.expect(logURLs.length, `No log URLs found in ${logPath}`).to.be.above(0); - }); - - it("should only use whitelisted URLs", () => { - logURLs.forEach((url) => { - if (url !== "") { - chai.expect(whitelistURLs.indexOf(url), `The URL "${url}" is not whitelisted.\n${whitelistViolationDetails}`).to.be.above(-1); - } - }); - }); -}); diff --git a/core/clients/src/test/imodelhub/Briefcases.test.ts b/core/clients/src/test/imodelhub/Briefcases.test.ts index 515051e..da7358f 100644 --- a/core/clients/src/test/imodelhub/Briefcases.test.ts +++ b/core/clients/src/test/imodelhub/Briefcases.test.ts @@ -11,8 +11,7 @@ import { AccessToken } from "../../"; import { IModelHubClient, Briefcase, BriefcaseQuery, IModelHubClientError, } from "../../"; -import { AzureFileHandler } from "../../imodelhub/AzureFileHandler"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; import { IModelHubStatus, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -59,24 +58,24 @@ describe("iModelHub BriefcaseHandler", () => { before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); iModelClient = utils.getDefaultClient(); if (!TestConfig.enableMocks) { - const briefcases = await iModelClient.Briefcases().get(actx, accessToken, imodelId); + const briefcases = await iModelClient.briefcases.get(actx, accessToken, imodelId); let briefcasesCount = briefcases.length; if (briefcasesCount > 19) { // Ensure that tests can still acquire briefcases for (const briefcase of briefcases) { - await iModelClient.Briefcases().delete(actx, accessToken, imodelId, briefcase.briefcaseId!); + await iModelClient.briefcases.delete(actx, accessToken, imodelId, briefcase.briefcaseId!); } briefcasesCount = 0; } // Ensure that at least one briefcase is available for querying if (briefcasesCount === 0) { - const briefcase = await iModelClient.Briefcases().create(actx, accessToken, imodelId); + const briefcase = await iModelClient.briefcases.create(actx, accessToken, imodelId); briefcaseId = briefcase.briefcaseId!; } else { briefcaseId = briefcases[0].briefcaseId!; @@ -96,14 +95,14 @@ describe("iModelHub BriefcaseHandler", () => { it("should acquire a briefcase", async () => { utils.mockCreateBriefcase(imodelId, 3); - const briefcase = await iModelClient.Briefcases().create(actx, accessToken, imodelId); + const briefcase = await iModelClient.briefcases.create(actx, accessToken, imodelId); chai.expect(briefcase.briefcaseId).to.be.greaterThan(1); acquiredBriefcaseId = briefcase.briefcaseId!; }); it("should get all briefcases ", async () => { utils.mockGetBriefcase(imodelId, utils.generateBriefcase(2), utils.generateBriefcase(3)); - const briefcases = await iModelClient.Briefcases().get(actx, accessToken, imodelId); + const briefcases = await iModelClient.briefcases.get(actx, accessToken, imodelId); chai.expect(briefcases.length).to.be.greaterThan(0); for (const briefcase of briefcases) { @@ -114,13 +113,13 @@ describe("iModelHub BriefcaseHandler", () => { it("should delete a briefcase", async () => { utils.mockGetBriefcase(imodelId, utils.generateBriefcase(2), utils.generateBriefcase(acquiredBriefcaseId)); - const originalBriefcaseCount = (await iModelClient.Briefcases().get(actx, accessToken, imodelId)).length; + const originalBriefcaseCount = (await iModelClient.briefcases.get(actx, accessToken, imodelId)).length; mockDeleteBriefcase(imodelId, acquiredBriefcaseId); - await iModelClient.Briefcases().delete(actx, accessToken, imodelId, acquiredBriefcaseId); + await iModelClient.briefcases.delete(actx, accessToken, imodelId, acquiredBriefcaseId); utils.mockGetBriefcase(imodelId, utils.generateBriefcase(2)); - const briefcaseCount = (await iModelClient.Briefcases().get(actx, accessToken, imodelId)).length; + const briefcaseCount = (await iModelClient.briefcases.get(actx, accessToken, imodelId)).length; chai.expect(briefcaseCount).to.be.lessThan(originalBriefcaseCount); }); @@ -128,7 +127,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should fail getting an invalid briefcase", async () => { let error: any; try { - await iModelClient.Briefcases().get(actx, accessToken, imodelId, new BriefcaseQuery().byId(-1)); + await iModelClient.briefcases.get(actx, accessToken, imodelId, new BriefcaseQuery().byId(-1)); } catch (err) { error = err; } @@ -139,7 +138,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should get information on a briefcase by id", async () => { mockGetBriefcaseById(imodelId, utils.generateBriefcase(briefcaseId)); - const briefcase: Briefcase = (await iModelClient.Briefcases().get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId)))[0]; + const briefcase: Briefcase = (await iModelClient.briefcases.get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId)))[0]; chai.expect(briefcase.briefcaseId).to.be.equal(briefcaseId); chai.expect(briefcase.downloadUrl).to.be.equal(undefined); chai.assert(briefcase.iModelId); @@ -149,7 +148,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should fail deleting an invalid briefcase", async () => { let error: any; try { - await iModelClient.Briefcases().delete(actx, accessToken, imodelId, -1); + await iModelClient.briefcases.delete(actx, accessToken, imodelId, -1); } catch (err) { error = err; } @@ -160,7 +159,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should get the download URL for a Briefcase", async () => { mockGetBriefcaseWithDownloadUrl(imodelId, utils.generateBriefcase(briefcaseId)); - const briefcase: Briefcase = (await iModelClient.Briefcases().get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId).selectDownloadUrl()))[0]; + const briefcase: Briefcase = (await iModelClient.briefcases.get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId).selectDownloadUrl()))[0]; chai.expect(briefcase.briefcaseId).to.be.equal(briefcaseId); chai.assert(briefcase.fileName); chai.expect(briefcase.fileName!.length).to.be.greaterThan(0); @@ -170,7 +169,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should download a Briefcase", async () => { mockGetBriefcaseWithDownloadUrl(imodelId, utils.generateBriefcase(briefcaseId)); - const briefcase: Briefcase = (await iModelClient.Briefcases().get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId).selectDownloadUrl()))[0]; + const briefcase: Briefcase = (await iModelClient.briefcases.get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId).selectDownloadUrl()))[0]; chai.assert(briefcase.downloadUrl); const fileName: string = briefcase.fileName!; @@ -179,24 +178,7 @@ describe("iModelHub BriefcaseHandler", () => { utils.mockFileResponse(); const progressTracker = new utils.ProgressTracker(); - await iModelClient.Briefcases().download(actx, briefcase, downloadToPathname, progressTracker.track()); - progressTracker.check(); - fs.existsSync(downloadToPathname).should.be.equal(true); - }); - - it("should download a Briefcase without buffered write", async () => { - mockGetBriefcaseWithDownloadUrl(imodelId, utils.generateBriefcase(briefcaseId)); - const briefcase: Briefcase = (await iModelClient.Briefcases().get(actx, accessToken, imodelId, new BriefcaseQuery().byId(briefcaseId).selectDownloadUrl()))[0]; - chai.assert(briefcase.downloadUrl); - - const fileName: string = briefcase.fileName!; - const downloadToPathname: string = path.join(utils.workDir, fileName); - - utils.mockFileResponse(); - - const progressTracker = new utils.ProgressTracker(); - const client = new IModelHubClient(new AzureFileHandler(false)); - await client.Briefcases().download(actx, briefcase, downloadToPathname, progressTracker.track()); + await iModelClient.briefcases.download(actx, briefcase, downloadToPathname, progressTracker.track()); progressTracker.check(); fs.existsSync(downloadToPathname).should.be.equal(true); }); @@ -209,7 +191,7 @@ describe("iModelHub BriefcaseHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateError("NoServerLicense"), 1, undefined, undefined, 409); let error; try { - (await iModelClient.Briefcases().get(actx, accessToken, imodelId)); + (await iModelClient.briefcases.get(actx, accessToken, imodelId)); } catch (err) { error = err; } @@ -226,7 +208,7 @@ describe("iModelHub BriefcaseHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateError(undefined, "ServerError"), 5, undefined, undefined, 500); let error; try { - (await iModelClient.Briefcases().get(actx, accessToken, imodelId)); + (await iModelClient.briefcases.get(actx, accessToken, imodelId)); } catch (err) { error = err; } @@ -238,7 +220,7 @@ describe("iModelHub BriefcaseHandler", () => { let error: IModelHubClientError | undefined; const invalidClient = new IModelHubClient(); try { - await invalidClient.Briefcases().download(actx, new Briefcase(), utils.workDir); + await invalidClient.briefcases.download(actx, new Briefcase(), utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -250,7 +232,7 @@ describe("iModelHub BriefcaseHandler", () => { it("should fail downloading briefcase with no file url", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.Briefcases().download(actx, new Briefcase(), utils.workDir); + await iModelClient.briefcases.download(actx, new Briefcase(), utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; diff --git a/core/clients/src/test/imodelhub/ChangeSet.test.ts b/core/clients/src/test/imodelhub/ChangeSet.test.ts index b54119a..2491e27 100644 --- a/core/clients/src/test/imodelhub/ChangeSet.test.ts +++ b/core/clients/src/test/imodelhub/ChangeSet.test.ts @@ -7,7 +7,7 @@ import * as fs from "fs"; import * as path from "path"; import * as deepAssign from "deep-assign"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { AccessToken, IModelClient } from "../../"; import { @@ -72,12 +72,12 @@ describe("iModelHub ChangeSetHandler", () => { before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); iModelClient = utils.getDefaultClient(); if (!TestConfig.enableMocks) { - const changeSetCount = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId)).length; + const changeSetCount = (await iModelClient.changeSets.get(actx, accessToken, imodelId)).length; if (changeSetCount > 9) { // Recreate iModel if can not create any new changesets await utils.createIModel(accessToken, imodelName, undefined, true); @@ -90,12 +90,12 @@ describe("iModelHub ChangeSetHandler", () => { await utils.createChangeSets(accessToken, imodelId, briefcase, 0, 3); if (!TestConfig.enableMocks) { - const changesets = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId)); + const changesets = (await iModelClient.changeSets.get(actx, accessToken, imodelId)); // Ensure that at least one lock exists await utils.createLocks(accessToken, imodelId, briefcase, 1, 2, 2, changesets[0].id, changesets[0].index); // Create named versions - utils.createVersions(accessToken, imodelId, [changesets[0].id!, changesets[1].id!, changesets[2].id!], + await utils.createVersions(accessToken, imodelId, [changesets[0].id!, changesets[1].id!, changesets[2].id!], ["Version 1", "Version 2", "Version 3"]); } @@ -112,14 +112,14 @@ describe("iModelHub ChangeSetHandler", () => { const mockChangeSets = utils.getMockChangeSets(briefcase); utils.mockGetChangeSet(imodelId, false, undefined, mockChangeSets[0], mockChangeSets[1]); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId); const index = changeSets.length; const filePath = utils.getMockChangeSetPath(index, mockChangeSets[index].id!); mockCreateChangeSet(imodelId, mockChangeSets[2]); const progressTracker = new utils.ProgressTracker(); - const newChangeSet = await iModelClient.ChangeSets().create(actx, accessToken, imodelId, mockChangeSets[index], filePath, progressTracker.track()); + const newChangeSet = await iModelClient.changeSets.create(actx, accessToken, imodelId, mockChangeSets[index], filePath, progressTracker.track()); chai.assert(newChangeSet); progressTracker.check(); @@ -129,7 +129,7 @@ describe("iModelHub ChangeSetHandler", () => { const mockedChangeSets = utils.getMockChangeSets(briefcase).slice(0, 3); utils.mockGetChangeSet(imodelId, true, undefined, ...mockedChangeSets); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(1); let i = 0; @@ -142,7 +142,7 @@ describe("iModelHub ChangeSetHandler", () => { const downloadUrl: string = changeSet.downloadUrl!; chai.assert(downloadUrl.startsWith("https://")); - const changeSet2: ChangeSet = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().byId(changeSet.id!)))[0]; + const changeSet2: ChangeSet = (await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().byId(changeSet.id!)))[0]; chai.expect(changeSet.id!).to.be.equal(changeSet2.id!); chai.expect(changeSet.index).to.be.equal(changeSet2.index); @@ -155,19 +155,19 @@ describe("iModelHub ChangeSetHandler", () => { `?$filter=${followingChangesetBackwardChangesetId}+eq+%27${lastButOneId}%27`); ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateGetResponse(mockedChangeSets[changeSets.length - 2])); } - const followingChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().fromId(lastButOneId)); + const followingChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().fromId(lastButOneId)); chai.expect(followingChangeSets.length).to.be.equal(1); }); it("should download ChangeSets", async () => { utils.mockGetChangeSet(imodelId, true, undefined, utils.generateChangeSet(), utils.generateChangeSet()); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); const downloadChangeSetsToPath: string = path.join(utils.workDir, imodelId.toString()); utils.mockFileResponse(2); const progressTracker = new utils.ProgressTracker(); - await iModelClient.ChangeSets().download(actx, changeSets, downloadChangeSetsToPath, progressTracker.track()); + await iModelClient.changeSets.download(actx, changeSets, downloadChangeSetsToPath, progressTracker.track()); fs.existsSync(downloadChangeSetsToPath).should.be.equal(true); progressTracker.check(); for (const changeSet of changeSets) { @@ -181,7 +181,7 @@ describe("iModelHub ChangeSetHandler", () => { it("should get ChangeSets skipping the first one", async () => { const mockChangeSets = utils.getMockChangeSets(briefcase); utils.mockGetChangeSet(imodelId, false, "?$skip=1", mockChangeSets[2]); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().skip(1)); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().skip(1)); chai.assert(changeSets); chai.expect(parseInt(changeSets[0].index!, 10)).to.be.greaterThan(1); }); @@ -189,13 +189,13 @@ describe("iModelHub ChangeSetHandler", () => { it("should get latest ChangeSets", async () => { const mockChangeSets = utils.getMockChangeSets(briefcase); utils.mockGetChangeSet(imodelId, false, "?$orderby=Index+desc&$top=2", mockChangeSets[2], mockChangeSets[1]); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().latest().top(2)); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().latest().top(2)); chai.assert(changeSets); chai.expect(changeSets.length).to.be.equal(2); chai.expect(parseInt(changeSets[0].index!, 10)).to.be.greaterThan(parseInt(changeSets[1].index!, 10)); utils.mockGetChangeSet(imodelId, false, "?$orderby=Index+desc&$top=2", mockChangeSets[2], mockChangeSets[1]); - const changeSets2: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().orderBy("Index+desc").top(2)); + const changeSets2: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().orderBy("Index+desc").top(2)); chai.assert(changeSets); chai.expect(changeSets).to.be.deep.equal(changeSets2); }); @@ -203,7 +203,7 @@ describe("iModelHub ChangeSetHandler", () => { it("should fail getting a ChangeSet by invalid id", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().byId("InvalidId")); + await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().byId("InvalidId")); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -215,12 +215,12 @@ describe("iModelHub ChangeSetHandler", () => { it("should fail downloading ChangeSets with no file handler", async () => { const mockChangeSets = utils.getMockChangeSets(briefcase); utils.mockGetChangeSet(imodelId, false, "?$orderby=Index+desc&$top=1", mockChangeSets[2]); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().latest().top(1)); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().latest().top(1)); let error: IModelHubClientError | undefined; const invalidClient = new IModelHubClient(); try { - await invalidClient.ChangeSets().download(actx, changeSets, utils.workDir); + await invalidClient.changeSets.download(actx, changeSets, utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -232,7 +232,7 @@ describe("iModelHub ChangeSetHandler", () => { it("should fail downloading ChangeSets with no file url", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.ChangeSets().download(actx, [new ChangeSet()], utils.workDir); + await iModelClient.changeSets.download(actx, [new ChangeSet()], utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -245,7 +245,7 @@ describe("iModelHub ChangeSetHandler", () => { let error: IModelHubClientError | undefined; const invalidClient = new IModelHubClient(); try { - await invalidClient.ChangeSets().create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir); + await invalidClient.changeSets.create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -257,7 +257,7 @@ describe("iModelHub ChangeSetHandler", () => { it("should fail creating a ChangeSet with no file", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.ChangeSets().create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir + "InvalidChangeSet.cs"); + await iModelClient.changeSets.create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir + "InvalidChangeSet.cs"); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -269,7 +269,7 @@ describe("iModelHub ChangeSetHandler", () => { it("should fail creating a ChangeSet with directory path", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.ChangeSets().create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir); + await iModelClient.changeSets.create(actx, accessToken, imodelId, new ChangeSet(), utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -293,10 +293,10 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateGetArrayResponse([mockedChangeSets[1], mockedChangeSets[2]])); } - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().betweenChangeSets(changeSets[0].id!, changeSets[2].id)); chai.expect(selectedChangeSets.length).to.be.equal(2); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[1].id); @@ -317,10 +317,10 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateGetArrayResponse([mockedChangeSets[0], mockedChangeSets[1], mockedChangeSets[2]])); } - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().betweenChangeSets(changeSets[2].id!)); chai.expect(selectedChangeSets.length).to.be.equal(3); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[0].id); @@ -346,13 +346,13 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.generateGetResponse(mockedChangeSets[0])); } - const versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId); + const versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId); chai.assert(versions); chai.expect(versions.length).to.be.greaterThan(0); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().getVersionChangeSets(versions[versions.length - 1].id!)); chai.expect(selectedChangeSets.length).to.be.equal(1); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[0].id); @@ -374,13 +374,13 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.generateGetArrayResponse([mockedChangeSets[2], mockedChangeSets[1]])); } - const versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId); + const versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId); chai.assert(versions); chai.expect(versions.length).to.be.greaterThan(1); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().afterVersion(versions[versions.length - 2].id!)); chai.expect(selectedChangeSets.length).to.be.greaterThan(1); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[2].id); @@ -406,13 +406,13 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.generateGetArrayResponse([mockedChangeSets[1], mockedChangeSets[2]])); } - const versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId); + const versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId); chai.assert(versions); chai.expect(versions.length).to.be.greaterThan(1); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().betweenVersions(versions[0].id!, versions[2].id!)); chai.expect(selectedChangeSets.length).to.be.equal(2); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[1].id); @@ -441,13 +441,13 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.generateGetResponse(mockedChangeSets[1])); } - const versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId); + const versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId); chai.assert(versions); chai.expect(versions.length).to.be.greaterThan(0); - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(2); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().betweenVersionAndChangeSet(versions[versions.length - 1].id!, changeSets[1].id!)); chai.expect(selectedChangeSets.length).to.be.equal(1); chai.expect(selectedChangeSets[0].id).to.be.equal(changeSets[1].id); @@ -466,10 +466,10 @@ describe("iModelHub ChangeSetHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, ResponseBuilder.generateGetResponse(mockedChangeSets[0])); } - const changeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); + const changeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().selectDownloadUrl()); chai.expect(changeSets.length).to.be.greaterThan(0); - const selectedChangeSets: ChangeSet[] = await iModelClient.ChangeSets().get(actx, accessToken, imodelId, + const selectedChangeSets: ChangeSet[] = await iModelClient.changeSets.get(actx, accessToken, imodelId, new ChangeSetQuery().bySeedFileId(changeSets[0].seedFileId!)); chai.expect(selectedChangeSets.length).to.be.greaterThan(0); selectedChangeSets.forEach((cs: ChangeSet) => { diff --git a/core/clients/src/test/imodelhub/Codes.test.ts b/core/clients/src/test/imodelhub/Codes.test.ts index bae0466..6b96d79 100644 --- a/core/clients/src/test/imodelhub/Codes.test.ts +++ b/core/clients/src/test/imodelhub/Codes.test.ts @@ -13,7 +13,7 @@ import { } from "../../"; import { ResponseBuilder } from "../ResponseBuilder"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { IModelHubStatus, ActivityLoggingContext, Id64, GuidString } from "@bentley/bentleyjs-core"; chai.should(); @@ -42,7 +42,7 @@ describe("iModelHub CodeHandler", () => { before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); iModelClient = utils.getDefaultClient(); @@ -61,7 +61,7 @@ describe("iModelHub CodeHandler", () => { utils.mockUpdateCodes(imodelId, code1, code2); - const result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code1, code2]); + const result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code1, code2]); chai.assert(result); chai.expect(result.length).to.be.equal(2); result.forEach((value: HubCode) => chai.expect(value.state).to.be.equal(CodeState.Reserved)); @@ -75,7 +75,7 @@ describe("iModelHub CodeHandler", () => { utils.mockUpdateCodes(imodelId, code1, code2, code3); - const result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code1, code2, code3]); + const result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code1, code2, code3]); chai.assert(result); chai.expect(result.length).to.be.equal(3); result.forEach((value: HubCode) => chai.expect(value.state).to.be.equal(CodeState.Reserved)); @@ -90,7 +90,7 @@ describe("iModelHub CodeHandler", () => { let receivedError: Error | undefined; try { - await iModelClient.Codes().update(alctx, accessToken, imodelId, [code2, code3, code4], { codesPerRequest: 1 }); + await iModelClient.codes.update(alctx, accessToken, imodelId, [code2, code3, code4], { codesPerRequest: 1 }); } catch (error) { receivedError = error; } @@ -106,7 +106,7 @@ describe("iModelHub CodeHandler", () => { utils.mockUpdateCodes(imodelId, code1, code2, code3); - const result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code1, code2, code3]); + const result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code1, code2, code3]); chai.assert(result); chai.expect(result.length).to.be.equal(3); result.forEach((value: HubCode) => chai.expect(value.state).to.be.equal(CodeState.Reserved)); @@ -121,7 +121,7 @@ describe("iModelHub CodeHandler", () => { let receivedError: ConflictingCodesError | undefined; try { - await iModelClient.Codes().update(alctx, accessToken, imodelId, [code2, code3, code4], + await iModelClient.codes.update(alctx, accessToken, imodelId, [code2, code3, code4], { deniedCodes: true, codesPerRequest: 1, continueOnConflict: true }); } catch (error) { chai.expect(error).to.be.instanceof(ConflictingCodesError); @@ -137,7 +137,7 @@ describe("iModelHub CodeHandler", () => { it("should update code multiple times", async () => { let code = utils.randomCode(briefcaseId); utils.mockUpdateCodes(imodelId, code); - let result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code]); + let result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code]); chai.assert(result); chai.expect(result.length).to.be.equal(1); @@ -148,7 +148,7 @@ describe("iModelHub CodeHandler", () => { code.briefcaseId = briefcaseId; code.changeState = "new"; utils.mockUpdateCodes(imodelId, code); - result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code]); + result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code]); chai.assert(result); chai.expect(result.length).to.be.equal(1); @@ -159,7 +159,7 @@ describe("iModelHub CodeHandler", () => { code.briefcaseId = briefcaseId; code.changeState = "new"; utils.mockUpdateCodes(imodelId, code); - result = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code]); + result = await iModelClient.codes.update(alctx, accessToken, imodelId, [code]); chai.assert(result); chai.expect(result.length).to.be.equal(1); @@ -169,7 +169,7 @@ describe("iModelHub CodeHandler", () => { it("should get codes", async () => { utils.mockGetCodes(imodelId, "", utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId); chai.assert(codes); chai.expect(codes).length.to.be.greaterThan(0); }); @@ -179,7 +179,7 @@ describe("iModelHub CodeHandler", () => { this.skip(); const query = new CodeQuery().select("Values"); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); codes.forEach((code) => { chai.assert(code.value); chai.assert(!code.codeScope); @@ -194,7 +194,7 @@ describe("iModelHub CodeHandler", () => { utils.mockGetCodes(imodelId, filter, utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)); const query = new CodeQuery().byBriefcaseId(briefcaseId); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(codes); chai.expect(codes).length.to.be.greaterThan(0); codes.forEach((code) => chai.expect(code.briefcaseId).to.be.equal(briefcaseId)); @@ -206,7 +206,7 @@ describe("iModelHub CodeHandler", () => { utils.mockGetCodes(imodelId, filter, utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)); const query = new CodeQuery().byCodeSpecId(codeSpecId); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(codes); chai.expect(codes).length.to.be.greaterThan(0); codes.forEach((code) => chai.expect(code.codeSpecId!.toString().toUpperCase()).to.be.equal(codeSpecId.toString().toUpperCase())); @@ -219,12 +219,12 @@ describe("iModelHub CodeHandler", () => { utils.mockGetCodes(imodelId, "?$filter=" + filter, ...codes); const query1 = new CodeQuery().byBriefcaseId(briefcaseId).byCodeSpecId(codeSpecId); - const queriedCodes1 = await iModelClient.Codes().get(alctx, accessToken, imodelId, query1); + const queriedCodes1 = await iModelClient.codes.get(alctx, accessToken, imodelId, query1); chai.assert(queriedCodes1); utils.mockGetCodes(imodelId, "?$filter=" + filter, ...codes); const query2 = new CodeQuery().filter(filter); - const queriedCodes2 = await iModelClient.Codes().get(alctx, accessToken, imodelId, query2); + const queriedCodes2 = await iModelClient.codes.get(alctx, accessToken, imodelId, query2); chai.assert(queriedCodes2); chai.expect(queriedCodes1).to.be.deep.equal(queriedCodes2); @@ -236,7 +236,7 @@ describe("iModelHub CodeHandler", () => { utils.mockGetCodes(imodelId, filter, utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)); const query = new CodeQuery().byCodeScope(codeScope); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(codes); chai.expect(codes).length.to.be.greaterThan(0); codes.forEach((code) => chai.expect(code.codeScope).to.be.equal(codeScope)); @@ -246,12 +246,12 @@ describe("iModelHub CodeHandler", () => { const mockedCodes = [utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)]; utils.mockGetCodes(imodelId, "", ...mockedCodes); - let existingCodes = await iModelClient.Codes().get(alctx, accessToken, imodelId); + let existingCodes = await iModelClient.codes.get(alctx, accessToken, imodelId); existingCodes = existingCodes.slice(0, 2); utils.mockGetCodes(imodelId, undefined, ...mockedCodes); const query = new CodeQuery().byCodes(existingCodes); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(codes); chai.expect(codes.length).to.be.greaterThan(0); chai.expect(codes.length).to.be.equal(existingCodes.length); @@ -264,14 +264,14 @@ describe("iModelHub CodeHandler", () => { const filter = `?$filter=BriefcaseId+eq+${briefcaseId}`; utils.mockGetCodes(imodelId, filter, utils.randomCode(briefcaseId), utils.randomCode(briefcaseId)); const query = new CodeQuery().byBriefcaseId(briefcaseId); - let codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + let codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.expect(codes.length).to.be.greaterThan(0); utils.mockDeleteAllCodes(imodelId, briefcaseId); - await iModelClient.Codes().deleteAll(alctx, accessToken, imodelId, briefcaseId); + await iModelClient.codes.deleteAll(alctx, accessToken, imodelId, briefcaseId); utils.mockGetCodes(imodelId, filter); - codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.expect(codes.length).to.be.equal(0); }); @@ -285,7 +285,7 @@ describe("iModelHub CodeHandler", () => { utils.mockGetCodes(imodelId, filter, ...mockedCodes); } const query = new CodeQuery().unavailableCodes(briefcaseId); - const codes = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const codes = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(codes); chai.expect(codes.length).to.be.greaterThan(0); codes.forEach((code: HubCode) => { @@ -320,7 +320,7 @@ describe("iModelHub CodeHandler", () => { it("should fail deleting all codes with invalid briefcase id", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.Codes().deleteAll(alctx, accessToken, imodelId, 0); + await iModelClient.codes.deleteAll(alctx, accessToken, imodelId, 0); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -357,7 +357,7 @@ describe("iModelHub CodeSequenceHandler", () => { if (TestConfig.enableMocks) this.skip(); - accessToken = await utils.login(); + accessToken = await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); iModelClient = utils.getDefaultClient(); @@ -368,21 +368,21 @@ describe("iModelHub CodeSequenceHandler", () => { it("should acquire code with next available index value", async () => { // Get next value in sequence const sequence = createTestSequence(CodeSequenceType.NextAvailable); - const sequenceResult = await iModelClient.Codes().Sequences().get(alctx, accessToken, imodelId, sequence); + const sequenceResult = await iModelClient.codes.sequences.get(alctx, accessToken, imodelId, sequence); chai.assert(sequenceResult); // Try to acquire Code with this value const code = utils.randomCode(briefcaseId); code.value = formatSequenceValue(sequenceResult); code.state = CodeState.Used; - const reserveResult = await iModelClient.Codes().update(alctx, accessToken, imodelId, [code]); + const reserveResult = await iModelClient.codes.update(alctx, accessToken, imodelId, [code]); chai.assert(reserveResult); }); it("should query a code with largest used index value", async () => { // Get next value in sequence const sequence = createTestSequence(CodeSequenceType.LargestUsed); - const sequenceResult = await iModelClient.Codes().Sequences().get(alctx, accessToken, imodelId, sequence); + const sequenceResult = await iModelClient.codes.sequences.get(alctx, accessToken, imodelId, sequence); chai.assert(sequenceResult); // Try to acquire Code with this value @@ -390,7 +390,7 @@ describe("iModelHub CodeSequenceHandler", () => { code.value = formatSequenceValue(sequenceResult); code.state = CodeState.Used; const query = new CodeQuery().byCodes([code]); - const queryResult = await iModelClient.Codes().get(alctx, accessToken, imodelId, query); + const queryResult = await iModelClient.codes.get(alctx, accessToken, imodelId, query); chai.assert(queryResult); chai.expect(queryResult.length).to.be.gt(0); chai.expect(queryResult[0].value).to.be.equal(code.value); diff --git a/core/clients/src/test/imodelhub/Events.test.ts b/core/clients/src/test/imodelhub/Events.test.ts index 07d5bc2..14f0acf 100644 --- a/core/clients/src/test/imodelhub/Events.test.ts +++ b/core/clients/src/test/imodelhub/Events.test.ts @@ -14,7 +14,7 @@ import { EventSubscription, EventSAS, EventType, IModelHubEvent, } from "../../"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import { LockLevel, LockType } from "../../imodelhub"; @@ -102,7 +102,7 @@ describe("iModelHub EventHandler", () => { before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); briefcaseId = (await utils.getBriefcases(accessToken, imodelId, 1))[0].briefcaseId!; @@ -116,7 +116,7 @@ describe("iModelHub EventHandler", () => { const eventTypes: EventType[] = ["CodeEvent"]; mockCreateEventSubscription(imodelId, eventTypes); - subscription = await imodelHubClient.Events().Subscriptions().create(alctx, accessToken, imodelId, eventTypes); + subscription = await imodelHubClient.events.subscriptions.create(alctx, accessToken, imodelId, eventTypes); chai.assert(subscription); chai.expect(subscription.eventTypes).to.be.deep.equal(eventTypes); }); @@ -127,7 +127,7 @@ describe("iModelHub EventHandler", () => { let error; try { - await imodelHubClient.Events().getSASToken(alctx, new utils.MockAccessToken(), imodelId); + await imodelHubClient.events.getSASToken(alctx, new utils.MockAccessToken(), imodelId); } catch (err) { error = err; } @@ -137,7 +137,7 @@ describe("iModelHub EventHandler", () => { it("should get SAS token", async () => { mockGetEventSASToken(imodelId); - sasToken = await imodelHubClient.Events().getSASToken(alctx, accessToken, imodelId); + sasToken = await imodelHubClient.events.getSASToken(alctx, accessToken, imodelId); chai.assert(sasToken); }); @@ -145,7 +145,7 @@ describe("iModelHub EventHandler", () => { mockGetEvent(imodelId, subscription.wsgId, {}, undefined, undefined, 401); let error; try { - await imodelHubClient.Events().getEvent(alctx, "InvalidSASToken", sasToken.baseAddress!, subscription.wsgId); + await imodelHubClient.events.getEvent(alctx, "InvalidSASToken", sasToken.baseAddress!, subscription.wsgId); } catch (err) { error = err; } @@ -157,7 +157,7 @@ describe("iModelHub EventHandler", () => { mockGetEvent(imodelId, "", {}, undefined, undefined, 404); let error; try { - await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, ""); + await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, ""); } catch (err) { error = err; } @@ -167,7 +167,7 @@ describe("iModelHub EventHandler", () => { it("should return undefined when no event is available", async () => { mockGetEvent(imodelId, subscription.wsgId, {}, undefined, undefined, 204); - const result = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const result = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(result).to.be.equal(undefined); }); @@ -175,7 +175,7 @@ describe("iModelHub EventHandler", () => { const timeout = !TestConfig.enableMocks ? 10 : 1; mockGetEvent(imodelId, subscription.wsgId, {}, undefined, 1, 204, 1); - const result = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, timeout); + const result = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, timeout); chai.expect(result).to.be.equal(undefined); }); @@ -186,7 +186,7 @@ describe("iModelHub EventHandler", () => { mockGetEvent(imodelId, subscription.wsgId, {}, undefined, 1, 204, 20000); let error; try { - await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, 1); + await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId, 1); } catch (err) { error = err; } @@ -197,13 +197,13 @@ describe("iModelHub EventHandler", () => { it("should receive code event", async () => { const codes = [utils.randomCode(briefcaseId)]; if (!TestConfig.enableMocks) { - await imodelHubClient.Codes().update(alctx, accessToken, imodelId, codes); + await imodelHubClient.codes.update(alctx, accessToken, imodelId, codes); } else { const requestResponse = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":1,"CodeScope":"0x100000000ff","CodeSpecId":"0xff","State":1,"Values":["TestCode"]}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(requestResponse), "CodeEvent"); } - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(CodeEvent); chai.expect(event!.iModelId!).to.be.equal(imodelId); @@ -224,16 +224,16 @@ describe("iModelHub EventHandler", () => { } let receivedEventsCount = 0; - const deleteListener = imodelHubClient.Events().createListener(alctx, async () => { - return await utils.login(); + const deleteListener = imodelHubClient.events.createListener(alctx, async () => { + return utils.login(); }, subscription.wsgId, imodelId, (receivedEvent: IModelHubEvent) => { if (receivedEvent instanceof CodeEvent) receivedEventsCount++; }); if (!TestConfig.enableMocks) { - await imodelHubClient.Codes().update(alctx, accessToken, imodelId, [codes[0]]); - await imodelHubClient.Codes().update(alctx, accessToken, imodelId, [codes[1]]); + await imodelHubClient.codes.update(alctx, accessToken, imodelId, [codes[0]]); + await imodelHubClient.codes.update(alctx, accessToken, imodelId, [codes[1]]); } let timeoutCounter = 0; @@ -251,7 +251,7 @@ describe("iModelHub EventHandler", () => { mockUpdateEventSubscription(imodelId, subscription.wsgId, eventTypes); subscription.eventTypes = eventTypes; - subscription = await imodelHubClient.Events().Subscriptions().update(alctx, accessToken, imodelId, subscription); + subscription = await imodelHubClient.events.subscriptions.update(alctx, accessToken, imodelId, subscription); chai.assert(subscription); chai.expect(subscription.eventTypes).to.be.deep.equal(eventTypes); }); @@ -263,7 +263,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":2,"LockType":"1","LockLevel":"1","ObjectIds":["0x1"],"ReleasedWithChangeSet":"1"}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "LockEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(LockEvent); chai.assert(!!event!.iModelId); const typedEvent = event as LockEvent; @@ -282,7 +282,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":2}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "AllLocksDeletedEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(AllLocksDeletedEvent); chai.assert(!!event!.iModelId); }); @@ -294,7 +294,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":2,"ChangeSetId":"789","ChangeSetIndex":2}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "ChangeSetPostPushEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(ChangeSetPostPushEvent); chai.assert(!!event!.iModelId); }); @@ -306,7 +306,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":""}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "ChangeSetPrePushEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(ChangeSetPrePushEvent); chai.assert(!!event!.iModelId); }); @@ -318,7 +318,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":2}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "AllCodesDeletedEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(AllCodesDeletedEvent); chai.assert(!!event!.iModelId); }); @@ -330,7 +330,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","BriefcaseId":2}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "BriefcaseDeletedEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(BriefcaseDeletedEvent); chai.assert(!!event!.iModelId); }); @@ -342,7 +342,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":""}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "iModelDeletedEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(IModelDeletedEvent); chai.assert(!!event!.iModelId); }); @@ -355,7 +355,7 @@ describe("iModelHub EventHandler", () => { const eventBody = `{"EventTopic":"${imodelId}","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","VersionId":"${versionId}","VersionName":"ABC","ChangeSetId":"2"}`; mockGetEvent(imodelId, subscription.wsgId, JSON.parse(eventBody), "VersionEvent"); - const event = await imodelHubClient.Events().getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); + const event = await imodelHubClient.events.getEvent(alctx, sasToken.sasToken!, sasToken.baseAddress!, subscription.wsgId); chai.expect(event).to.be.instanceof(VersionEvent); chai.assert(!!event!.iModelId); const typedEvent = event as VersionEvent; @@ -365,6 +365,6 @@ describe("iModelHub EventHandler", () => { it("should delete subscription", async () => { mockDeleteEventSubscription(imodelId, subscription.wsgId); - await imodelHubClient.Events().Subscriptions().delete(alctx, accessToken, imodelId, subscription.wsgId); + await imodelHubClient.events.subscriptions.delete(alctx, accessToken, imodelId, subscription.wsgId); }); }); diff --git a/core/clients/src/test/imodelhub/GlobalEvents.test.ts b/core/clients/src/test/imodelhub/GlobalEvents.test.ts index 30496b3..3e61bd4 100644 --- a/core/clients/src/test/imodelhub/GlobalEvents.test.ts +++ b/core/clients/src/test/imodelhub/GlobalEvents.test.ts @@ -131,7 +131,7 @@ describe("iModelHub GlobalEventHandler", () => { if (!TestConfig.enableMocks) { utils.getRequestBehaviorOptionsHandler().disableBehaviorOption("DisableGlobalEvents"); - imodelHubClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + imodelHubClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); } projectId = await utils.getProjectId(accessToken); @@ -148,7 +148,7 @@ describe("iModelHub GlobalEventHandler", () => { if (!TestConfig.enableMocks) { utils.getRequestBehaviorOptionsHandler().resetDefaultBehaviorOptions(); - imodelHubClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + imodelHubClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); } }); @@ -162,7 +162,7 @@ describe("iModelHub GlobalEventHandler", () => { const id = Guid.createValue(); mockCreateGlobalEventsSubscription(id, eventTypesList); - globalEventSubscription = await imodelHubClient.GlobalEvents().Subscriptions().create(alctx, serviceAccountAccessToken, id, eventTypesList); + globalEventSubscription = await imodelHubClient.globalEvents.subscriptions.create(alctx, serviceAccountAccessToken, id, eventTypesList); chai.assert(globalEventSubscription); chai.assert(globalEventSubscription.eventTypes); chai.expect(globalEventSubscription.eventTypes!).to.be.deep.equal(eventTypesList); @@ -170,7 +170,7 @@ describe("iModelHub GlobalEventHandler", () => { it("should retrieve Global Event SAS token", async () => { mockGetGlobalEventSASToken(); - globalEventSas = await imodelHubClient.GlobalEvents().getSASToken(alctx, serviceAccountAccessToken); + globalEventSas = await imodelHubClient.globalEvents.getSASToken(alctx, serviceAccountAccessToken); }); it("should receive Global Event iModelCreatedEvent", async () => { @@ -178,7 +178,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${projectId}","iModelId":"${Guid.createValue()}"}`; mockGetGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "iModelCreatedEvent"); - const event = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); + const event = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); chai.expect(event).to.be.instanceof(IModelCreatedEvent); chai.assert(!!event!.iModelId); @@ -189,7 +189,7 @@ describe("iModelHub GlobalEventHandler", () => { mockUpdateGlobalEventSubscription(globalEventSubscription.wsgId, globalEventSubscription.subscriptionId!, newEventTypesList); globalEventSubscription.eventTypes = newEventTypesList; - globalEventSubscription = await imodelHubClient.GlobalEvents().Subscriptions().update(alctx, serviceAccountAccessToken, globalEventSubscription); + globalEventSubscription = await imodelHubClient.globalEvents.subscriptions.update(alctx, serviceAccountAccessToken, globalEventSubscription); chai.assert(globalEventSubscription); chai.assert(globalEventSubscription.eventTypes); chai.expect(globalEventSubscription.eventTypes!).to.be.deep.equal(newEventTypesList); @@ -204,8 +204,8 @@ describe("iModelHub GlobalEventHandler", () => { } let receivedEventsCount = 0; - const deleteListener = imodelHubClient.GlobalEvents().createListener(alctx, async () => { - return await utils.login(TestUsers.serviceAccount1); + const deleteListener = imodelHubClient.globalEvents.createListener(alctx, async () => { + return utils.login(TestUsers.serviceAccount1); }, globalEventSubscription.wsgId, (receivedEvent: IModelHubGlobalEvent) => { if (receivedEvent instanceof SoftiModelDeleteEvent) receivedEventsCount++; @@ -229,7 +229,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${projectId}","iModelId":"${Guid.createValue()}"}`; mockPeekLockGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "iModelCreatedEvent"); - const lockedEvent = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId, undefined, GetEventOperationType.Peek); + const lockedEvent = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId, undefined, GetEventOperationType.Peek); mockDeleteLockedEvent(globalEventSubscription.wsgId); const deleted = await lockedEvent!.delete(alctx); @@ -243,7 +243,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${Guid.createValue()}","iModelId":"${Guid.createValue()}"}`; mockGetGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "SoftiModelDeleteEvent"); - const event = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); + const event = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); chai.expect(event).to.be.instanceof(SoftiModelDeleteEvent); chai.assert(!!event!.iModelId); @@ -256,7 +256,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${Guid.createValue()}","iModelId":"${Guid.createValue()}"}`; mockGetGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "HardiModelDeleteEvent"); - const event = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); + const event = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); chai.expect(event).to.be.instanceof(HardiModelDeleteEvent); chai.assert(!!event!.iModelId); @@ -269,7 +269,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${Guid.createValue()}","iModelId":"${Guid.createValue()}","BriefcaseId":2,"ChangeSetId":"369","ChangeSetIndex":"1"}`; mockGetGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "ChangeSetCreatedEvent"); - const event = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); + const event = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); chai.expect(event).to.be.instanceof(ChangeSetCreatedEvent); chai.assert(!!event!.iModelId); @@ -282,7 +282,7 @@ describe("iModelHub GlobalEventHandler", () => { const eventBody = `{"EventTopic":"iModelHubGlobalEvents","FromEventSubscriptionId":"${Guid.createValue()}","ToEventSubscriptionId":"","ProjectId":"${Guid.createValue()}","iModelId":"${Guid.createValue()}","ChangeSetId":"369","VersionId":"${Guid.createValue()}","VersionName":"357"}`; mockGetGlobalEvent(globalEventSubscription.wsgId, JSON.parse(eventBody), "NamedVersionCreatedEvent"); - const event = await imodelHubClient.GlobalEvents().getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); + const event = await imodelHubClient.globalEvents.getEvent(alctx, globalEventSas.sasToken!, globalEventSas.baseAddress!, globalEventSubscription.wsgId); chai.expect(event).to.be.instanceof(NamedVersionCreatedEvent); chai.assert(!!event!.iModelId); @@ -293,6 +293,6 @@ describe("iModelHub GlobalEventHandler", () => { it("should delete Global Event subscription by InstanceId", async () => { mockDeleteGlobalEventsSubscription(globalEventSubscription.wsgId); - await imodelHubClient.GlobalEvents().Subscriptions().delete(alctx, serviceAccountAccessToken, globalEventSubscription.wsgId); + await imodelHubClient.globalEvents.subscriptions.delete(alctx, serviceAccountAccessToken, globalEventSubscription.wsgId); }); }); diff --git a/core/clients/src/test/imodelhub/IModelHubCloudEnv.ts b/core/clients/src/test/imodelhub/IModelHubCloudEnv.ts index 26e4fe0..7fc729c 100644 --- a/core/clients/src/test/imodelhub/IModelHubCloudEnv.ts +++ b/core/clients/src/test/imodelhub/IModelHubCloudEnv.ts @@ -2,7 +2,7 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { AccessToken, UserProfile, ConnectClient, Project } from "../.."; +import { AccessToken, UserInfo, ConnectClient, Project } from "../.."; import { IModelHubClient } from "../.."; import { TestConfig } from "../TestConfig"; import { ContextManagerClient, IModelAuthorizationClient, IModelCloudEnvironment } from "../../IModelCloudEnvironment"; @@ -12,7 +12,7 @@ import { ActivityLoggingContext } from "@bentley/bentleyjs-core"; /** An implementation of IModelProjectAbstraction backed by a iModelHub/Connect project */ class TestConnectClient implements ContextManagerClient { public async queryContextByName(alctx: ActivityLoggingContext, accessToken: AccessToken, name: string): Promise { - const client = await new ConnectClient(); + const client = new ConnectClient(); return client.getProject(alctx, accessToken, { $select: "*", $filter: `Name+eq+'${name}'`, @@ -21,7 +21,7 @@ class TestConnectClient implements ContextManagerClient { } class TestIModelHubUserMgr implements IModelAuthorizationClient { - public async authorizeUser(alctx: ActivityLoggingContext, _userProfile: UserProfile | undefined, userCredentials: any): Promise { + public async authorizeUser(alctx: ActivityLoggingContext, _userInfo: UserInfo | undefined, userCredentials: any): Promise { const authToken = await TestConfig.login(userCredentials); const client = getDefaultClient() as IModelHubClient; return client.getAccessToken(alctx, authToken); diff --git a/core/clients/src/test/imodelhub/Locks.test.ts b/core/clients/src/test/imodelhub/Locks.test.ts index 71084e9..9137b3d 100644 --- a/core/clients/src/test/imodelhub/Locks.test.ts +++ b/core/clients/src/test/imodelhub/Locks.test.ts @@ -11,7 +11,7 @@ import { IModelHubClientError, } from "../../"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; import { GuidString, Guid, IModelHubStatus, ActivityLoggingContext, Id64, Id64String } from "@bentley/bentleyjs-core"; @@ -38,7 +38,7 @@ describe("iModelHubClient LockHandler", () => { const alctx = new ActivityLoggingContext(""); before(async function (this: Mocha.IHookCallbackContext) { - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); // Does not create an imodel right now, but should in the future await utils.createIModel(accessToken, imodelName, undefined, true); imodelId = await utils.getIModelId(accessToken, imodelName); @@ -47,18 +47,18 @@ describe("iModelHubClient LockHandler", () => { lastObjectId = await utils.getLastLockObjectId(accessToken, imodelId); changeSet = (await utils.createChangeSets(accessToken, imodelId, briefcases[0]))[0]; if (changeSet === undefined) { - changeSet = (await iModelClient.ChangeSets().get(alctx, accessToken, imodelId))[0]; + changeSet = (await iModelClient.changeSets.get(alctx, accessToken, imodelId))[0]; } // make sure there exists at least two locks if ((!TestConfig.enableMocks) && lastObjectId.toString() === "0") { lastObjectId = utils.incrementLockObjectId(lastObjectId); - await iModelClient.Locks().update(alctx, accessToken, imodelId, + await iModelClient.locks.update(alctx, accessToken, imodelId, [utils.generateLock(briefcases[0].briefcaseId!, lastObjectId, LockType.Model, LockLevel.Shared, briefcases[0].fileId, changeSet.id, changeSet.index)]); lastObjectId = utils.incrementLockObjectId(lastObjectId); - await iModelClient.Locks().update(alctx, accessToken, imodelId, + await iModelClient.locks.update(alctx, accessToken, imodelId, [utils.generateLock(briefcases[1].briefcaseId!, lastObjectId, LockType.Model, LockLevel.Shared, briefcases[1].fileId)]); } }); @@ -71,7 +71,7 @@ describe("iModelHubClient LockHandler", () => { lastObjectId = utils.incrementLockObjectId(lastObjectId); const generatedLock = utils.generateLock(briefcases[0].briefcaseId!, lastObjectId, 1, 1, briefcases[0].fileId); utils.mockUpdateLocks(imodelId, [generatedLock]); - const lock = (await iModelClient.Locks().update(alctx, accessToken, imodelId, [generatedLock]))[0]; + const lock = (await iModelClient.locks.update(alctx, accessToken, imodelId, [generatedLock]))[0]; chai.assert(lock); chai.expect(lock.briefcaseId).equal(briefcases[0].briefcaseId); @@ -86,7 +86,7 @@ describe("iModelHubClient LockHandler", () => { const generatedLock2 = utils.generateLock(briefcases[0].briefcaseId!, lastObjectId, 1, 1, briefcases[0].fileId); utils.mockUpdateLocks(imodelId, [generatedLock1, generatedLock2]); - const locks = (await iModelClient.Locks().update(alctx, accessToken, imodelId, [generatedLock1, generatedLock2])); + const locks = (await iModelClient.locks.update(alctx, accessToken, imodelId, [generatedLock1, generatedLock2])); chai.assert(locks); chai.expect(locks.length).to.be.equal(2); @@ -97,12 +97,12 @@ describe("iModelHubClient LockHandler", () => { const generatedLock = utils.generateLock(briefcases[0].briefcaseId!, lastObjectId, 1, 1, briefcases[0].fileId); utils.mockUpdateLocks(imodelId, [generatedLock]); - let lock = (await iModelClient.Locks().update(alctx, accessToken, imodelId, [generatedLock]))[0]; + let lock = (await iModelClient.locks.update(alctx, accessToken, imodelId, [generatedLock]))[0]; lock.seedFileId = briefcases[0].fileId!; lock.lockLevel = LockLevel.None; utils.mockUpdateLocks(imodelId, [lock]); - lock = (await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock]))[0]; + lock = (await iModelClient.locks.update(alctx, accessToken, imodelId, [lock]))[0]; chai.assert(lock); chai.expect(lock.lockLevel).equals(LockLevel.None); @@ -110,7 +110,7 @@ describe("iModelHubClient LockHandler", () => { lock.lockLevel = LockLevel.Shared; lock.releasedWithChangeSet = changeSet.id; utils.mockUpdateLocks(imodelId, [lock]); - lock = (await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock]))[0]; + lock = (await iModelClient.locks.update(alctx, accessToken, imodelId, [lock]))[0]; chai.assert(lock); chai.expect(lock.lockLevel).equals(LockLevel.Shared); }); @@ -119,7 +119,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, "", ResponseBuilder.generateObject(Lock)); // Needs to acquire before expecting more than 0. - const locks: Lock[] = await iModelClient.Locks().get(alctx, accessToken, imodelId); + const locks: Lock[] = await iModelClient.locks.get(alctx, accessToken, imodelId); chai.expect(locks.length).to.be.greaterThan(0); }); @@ -128,7 +128,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, filter, utils.generateLock(briefcases[0].briefcaseId)); const query = new LockQuery().byBriefcaseId(briefcases[0].briefcaseId!); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks).length.to.be.greaterThan(0); locks.forEach((lock) => chai.expect(lock.briefcaseId).to.be.equal(briefcases[0].briefcaseId)); @@ -139,7 +139,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, undefined, utils.generateLock(undefined, objectId)); const query = new LockQuery().byObjectId(objectId); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks).length.to.be.greaterThan(0); locks.forEach((lock) => chai.expect(lock.objectId!.toString()).to.be.equal(objectId!.toString())); @@ -152,9 +152,9 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, "", ...mockedLocks); utils.mockGetLocks(imodelId, filter, mockedLocks[0]); - const allLocks = await iModelClient.Locks().get(alctx, accessToken, imodelId); + const allLocks = await iModelClient.locks.get(alctx, accessToken, imodelId); const query = new LockQuery().byReleasedWithChangeSet(changeSet.id!); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks.length).to.be.greaterThan(0); chai.expect(locks.length).to.be.lessThan(allLocks.length); @@ -167,9 +167,9 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, "", ...mockedLocks); utils.mockGetLocks(imodelId, filter, mockedLocks[0]); - const allLocks = await iModelClient.Locks().get(alctx, accessToken, imodelId); + const allLocks = await iModelClient.locks.get(alctx, accessToken, imodelId); const query = new LockQuery().byReleasedWithChangeSetIndex(Number.parseInt(changeSet.index!, 10)); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks).length.to.be.greaterThan(0); chai.expect(locks.length).to.be.lessThan(allLocks.length); @@ -180,7 +180,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, filter, utils.generateLock(briefcases[0].briefcaseId)); const query = new LockQuery().byLockLevel(LockLevel.Shared).byLockType(LockType.Model); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks).length.to.be.greaterThan(0); locks.forEach((lock) => { @@ -195,13 +195,13 @@ describe("iModelHubClient LockHandler", () => { utils.generateLock(briefcases[1].briefcaseId, undefined, LockType.Model, LockLevel.Shared, fileId, "", "0")]; utils.mockGetLocks(imodelId, "?$filter=BriefcaseId+eq+2", ...mockedLocks); - let existingLocks = await iModelClient.Locks().get(alctx, accessToken, imodelId, new LockQuery().byBriefcaseId(briefcases[0].briefcaseId!)); + let existingLocks = await iModelClient.locks.get(alctx, accessToken, imodelId, new LockQuery().byBriefcaseId(briefcases[0].briefcaseId!)); existingLocks = existingLocks.slice(0, 2); utils.mockGetLocks(imodelId, undefined, ...mockedLocks); const query = new LockQuery().byLocks(existingLocks); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks.length).to.be.equal(existingLocks.length); for (let i = 0; i < locks.length; ++i) { @@ -223,7 +223,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockGetLocks(imodelId, filter, ...mockedLocks); } const query = new LockQuery().unavailableLocks(briefcases[0].briefcaseId!, changeSet.index!); - const locks = await iModelClient.Locks().get(alctx, accessToken, imodelId, query); + const locks = await iModelClient.locks.get(alctx, accessToken, imodelId, query); chai.assert(locks); chai.expect(locks.length).to.be.greaterThan(0); locks.forEach((lock: Lock) => { @@ -239,7 +239,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockUpdateLocks(imodelId, [lock1, lock2, lock3]); - const result = await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); + const result = await iModelClient.locks.update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); chai.assert(result); chai.expect(result.length).to.be.equal(3); @@ -253,7 +253,7 @@ describe("iModelHubClient LockHandler", () => { let receivedError: Error | undefined; try { - await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock2, lock3, lock4], + await iModelClient.locks.update(alctx, accessToken, imodelId, [lock2, lock3, lock4], { deniedLocks: false, locksPerRequest: 1 }); } catch (error) { receivedError = error; @@ -270,7 +270,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockUpdateLocks(imodelId, [lock1, lock2, lock3]); - const result = await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); + const result = await iModelClient.locks.update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); chai.assert(result); chai.expect(result.length).to.be.equal(3); @@ -282,7 +282,7 @@ describe("iModelHubClient LockHandler", () => { let receivedError: ConflictingLocksError | undefined; try { - await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock2, lock3], + await iModelClient.locks.update(alctx, accessToken, imodelId, [lock2, lock3], { deniedLocks: true, locksPerRequest: 1 }); } catch (error) { chai.expect(error).to.be.instanceof(ConflictingLocksError); @@ -303,7 +303,7 @@ describe("iModelHubClient LockHandler", () => { utils.mockUpdateLocks(imodelId, [lock1, lock2, lock3]); - const result = await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); + const result = await iModelClient.locks.update(alctx, accessToken, imodelId, [lock1, lock2, lock3]); chai.assert(result); chai.expect(result.length).to.be.equal(3); @@ -317,7 +317,7 @@ describe("iModelHubClient LockHandler", () => { let receivedError: ConflictingLocksError | undefined; try { - await iModelClient.Locks().update(alctx, accessToken, imodelId, [lock2, lock3, lock4], + await iModelClient.locks.update(alctx, accessToken, imodelId, [lock2, lock3, lock4], { deniedLocks: true, locksPerRequest: 1, continueOnConflict: true }); } catch (error) { chai.expect(error).to.be.instanceof(ConflictingLocksError); @@ -335,7 +335,7 @@ describe("iModelHubClient LockHandler", () => { it("should delete all locks", async () => { for (const briefcase of briefcases) { mockDeleteAllLocks(imodelId, briefcase.briefcaseId!); - await iModelClient.Locks().deleteAll(alctx, accessToken, imodelId, briefcase.briefcaseId!); + await iModelClient.locks.deleteAll(alctx, accessToken, imodelId, briefcase.briefcaseId!); } }); @@ -368,7 +368,7 @@ describe("iModelHubClient LockHandler", () => { it("should fail deleting all locks with invalid briefcase id", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.Locks().deleteAll(alctx, accessToken, imodelId, 0); + await iModelClient.locks.deleteAll(alctx, accessToken, imodelId, 0); } catch (err) { if (err instanceof IModelHubClientError) error = err; diff --git a/core/clients/src/test/imodelhub/Performance.test.ts b/core/clients/src/test/imodelhub/Performance.test.ts index b8adbc1..fe39cc0 100644 --- a/core/clients/src/test/imodelhub/Performance.test.ts +++ b/core/clients/src/test/imodelhub/Performance.test.ts @@ -49,7 +49,7 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac code.value = `${j++}`; return code; }); - await imodelHubClient.Codes().update(alctx, accessToken, imodelId, codes, { codesPerRequest: perRequest }); + await imodelHubClient.codes.update(alctx, accessToken, imodelId, codes, { codesPerRequest: perRequest }); } it.skip("Reserve codes", async () => { @@ -80,7 +80,7 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac async function ensureCodesCount(count: number, briefcase: Briefcase, codeScope: string, query = new CodeQuery()) { try { - const currentCount = (await imodelHubClient.Codes().get(alctx, accessToken, imodelId, query)).length; + const currentCount = (await imodelHubClient.codes.get(alctx, accessToken, imodelId, query)).length; await reserveCodes(currentCount, count - currentCount, 50000, briefcase, codeScope); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { @@ -101,12 +101,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac let startTime = Date.now(); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Codes().get(alctx, accessToken, imodelId); + await imodelHubClient.codes.get(alctx, accessToken, imodelId); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Codes().get(alctx, accessToken, imodelId, new CodeQuery()); + await imodelHubClient.codes.get(alctx, accessToken, imodelId, new CodeQuery()); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); @@ -125,12 +125,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac let startTime = Date.now(); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Codes().get(alctx, accessToken, imodelId, query); + await imodelHubClient.codes.get(alctx, accessToken, imodelId, query); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Codes().get(alctx, accessToken, imodelId, query); + await imodelHubClient.codes.get(alctx, accessToken, imodelId, query); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); @@ -143,7 +143,7 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac const sizes: number[] = [3000, 4000, 5000, 6000, 7000, 8000]; const runCount = 25; await ensureCodesCount(10000, briefcase1, "RetrieveCodesByIds"); - const codes = await imodelHubClient.Codes().get(alctx, accessToken, imodelId); + const codes = await imodelHubClient.codes.get(alctx, accessToken, imodelId); for (const size of sizes) { Logger.logTrace("performance", `Test Case ${size} Started`); for (let run = 0; run < runCount; ++run) { @@ -151,12 +151,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac const query = new CodeQuery().byCodes(codes.slice(0, size)); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Codes().get(alctx, accessToken, imodelId, query); + await imodelHubClient.codes.get(alctx, accessToken, imodelId, query); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Codes().get(alctx, accessToken, imodelId, query); + await imodelHubClient.codes.get(alctx, accessToken, imodelId, query); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); @@ -180,14 +180,14 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac j++; return lock; }); - await imodelHubClient.Locks().update(alctx, accessToken, imodelId, locks, { locksPerRequest: perRequest }); + await imodelHubClient.locks.update(alctx, accessToken, imodelId, locks, { locksPerRequest: perRequest }); } it.skip("Acquire locks", async () => { await setup(true); const sizes: number[] = [10000, 20000, 30000, 40000, 50000, 70000, 80000, 90000, 100000]; const runCount = 25; - let startingCount = (await imodelHubClient.Locks().get(alctx, accessToken, imodelId)).length; + let startingCount = (await imodelHubClient.locks.get(alctx, accessToken, imodelId)).length; for (const size of sizes) { Logger.logTrace("performance", `Test Case ${size} Started`); for (let run = 0; run < runCount; ++run) { @@ -210,7 +210,7 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac async function ensureLocksCount(startingCount: number, count: number, briefcase: Briefcase, query = new LockQuery()) { try { - const currentCount = (await imodelHubClient.Locks().get(alctx, accessToken, imodelId, query)).length; + const currentCount = (await imodelHubClient.locks.get(alctx, accessToken, imodelId, query)).length; await acquireLocks(startingCount + currentCount, count - currentCount, 1000, briefcase); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { @@ -231,12 +231,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac let startTime = Date.now(); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Locks().get(alctx, accessToken, imodelId); + await imodelHubClient.locks.get(alctx, accessToken, imodelId); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Locks().get(alctx, accessToken, imodelId); + await imodelHubClient.locks.get(alctx, accessToken, imodelId); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); @@ -255,12 +255,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac let startTime = Date.now(); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Locks().get(alctx, accessToken, imodelId, query); + await imodelHubClient.locks.get(alctx, accessToken, imodelId, query); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Locks().get(alctx, accessToken, imodelId, query); + await imodelHubClient.locks.get(alctx, accessToken, imodelId, query); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); @@ -274,7 +274,7 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac const runCount = 25; const briefcaseQuery = new LockQuery().byBriefcaseId(briefcase1.briefcaseId!); await ensureLocksCount(0, 1000000, briefcase1, briefcaseQuery); - const locks = await imodelHubClient.Locks().get(alctx, accessToken, imodelId, briefcaseQuery); + const locks = await imodelHubClient.locks.get(alctx, accessToken, imodelId, briefcaseQuery); for (const size of sizes) { Logger.logTrace("performance", `Test Case ${size} Started`); for (let run = 0; run < runCount; ++run) { @@ -282,12 +282,12 @@ describe.skip("iModelHub Performance tests", function (this: Mocha.ISuiteCallbac const query = new LockQuery().byLocks(locks.slice(0, size)); Logger.logTrace("performance", `Test ${run} Started`); try { - await imodelHubClient.Locks().get(alctx, accessToken, imodelId, query); + await imodelHubClient.locks.get(alctx, accessToken, imodelId, query); } catch (err) { if ((err instanceof ResponseError && err.status === 401) || err instanceof AuthenticationError) { accessToken = await utils.login(); startTime = Date.now(); - await imodelHubClient.Locks().get(alctx, accessToken, imodelId, query); + await imodelHubClient.locks.get(alctx, accessToken, imodelId, query); } } Logger.logTrace("performance", `Test ${run} End ${Date.now() - startTime}`); diff --git a/core/clients/src/test/imodelhub/TestUtils.ts b/core/clients/src/test/imodelhub/TestUtils.ts index dae26a7..0336cb5 100644 --- a/core/clients/src/test/imodelhub/TestUtils.ts +++ b/core/clients/src/test/imodelhub/TestUtils.ts @@ -8,7 +8,7 @@ import * as chai from "chai"; import { GuidString, Guid, ActivityLoggingContext, Id64, Id64String } from "@bentley/bentleyjs-core"; import { - ECJsonTypeMap, AccessToken, UserProfile, Project, + ECJsonTypeMap, AccessToken, UserInfo, Project, ProgressInfo, } from "../../"; import { @@ -46,9 +46,15 @@ function configMockSettings() { /** Other services */ export class MockAccessToken extends AccessToken { public constructor() { super(""); } - public getUserProfile(): UserProfile | undefined { - return new UserProfile("test", "user", "testuser001@mailinator.com", "596c0d8b-eac2-46a0-aa4a-b590c3314e7c", "Bentley", "fefac5b-bcad-488b-aed2-df27bffe5786", "1004144426", "US"); + public getUserInfo(): UserInfo | undefined { + const id = "596c0d8b-eac2-46a0-aa4a-b590c3314e7c"; + const email = { id: "testuser001@mailinator.com" }; + const profile = { firstName: "test", lastName: "user" }; + const organization = { id: "fefac5b-bcad-488b-aed2-df27bffe5786", name: "Bentley" }; + const featureTracking = { ultimateSite: "1004144426", usageCountryIso: "US" }; + return new UserInfo(id, email, profile, organization, featureTracking); } + public toTokenString() { return ""; } } @@ -95,7 +101,7 @@ function getImodelHubClient() { return _imodelHubClient; _imodelHubClient = new IModelHubClient(new AzureFileHandler()); if (!TestConfig.enableMocks) { - _imodelHubClient.RequestOptions().setCustomOptions(requestBehaviorOptions.toCustomRequestOptions()); + _imodelHubClient.requestOptions.setCustomOptions(requestBehaviorOptions.toCustomRequestOptions()); } return _imodelHubClient; } @@ -159,7 +165,7 @@ export function createRequestUrl(scope: ScopeType, id: string | GuidString, clas return requestUrl; } -export function delay(ms: number) { +export async function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -204,10 +210,10 @@ export async function deleteIModelByName(accessToken: AccessToken, projectId: st return; const client = getDefaultClient(); - const imodels = await client.IModels().get(actx, accessToken, projectId, new IModelQuery().byName(imodelName)); + const imodels = await client.iModels.get(actx, accessToken, projectId, new IModelQuery().byName(imodelName)); for (const imodel of imodels) { - await client.IModels().delete(actx, accessToken, projectId, imodel.id!); + await client.iModels.delete(actx, accessToken, projectId, imodel.id!); } } @@ -218,7 +224,7 @@ export async function getIModelId(accessToken: AccessToken, imodelName: string): const projectId = await getProjectId(accessToken); const client = getDefaultClient(); - const imodels = await client.IModels().get(actx, accessToken, projectId, new IModelQuery().byName(imodelName)); + const imodels = await client.iModels.get(actx, accessToken, projectId, new IModelQuery().byName(imodelName)); if (!imodels[0] || !imodels[0].id) return Promise.reject(`iModel with name ${imodelName} doesn't exist.`); @@ -257,12 +263,12 @@ export async function getBriefcases(accessToken: AccessToken, imodelId: GuidStri } const client = getDefaultClient(); - let briefcases = await client.Briefcases().get(actx, accessToken, imodelId); + let briefcases = await client.briefcases.get(actx, accessToken, imodelId); if (briefcases.length < count) { for (let i = 0; i < count - briefcases.length; ++i) { - await client.Briefcases().create(actx, accessToken, imodelId); + await client.briefcases.create(actx, accessToken, imodelId); } - briefcases = await client.Briefcases().get(actx, accessToken, imodelId); + briefcases = await client.briefcases.get(actx, accessToken, imodelId); } return briefcases; } @@ -437,7 +443,7 @@ export async function getLastLockObjectId(accessToken: AccessToken, imodelId: Gu return Id64.fromString("0x0"); const client = getDefaultClient(); - const locks = await client.Locks().get(actx, accessToken, imodelId); + const locks = await client.locks.get(actx, accessToken, imodelId); locks.sort((lock1, lock2) => (parseInt(lock1.objectId!.toString(), 16) > parseInt(lock2.objectId!.toString(), 16) ? -1 : 1)); @@ -632,17 +638,17 @@ export async function createIModel(accessToken: AccessToken, name: string, proje const client = getDefaultClient(); - const imodels = await client.IModels().get(actx, accessToken, projectId, new IModelQuery().byName(name)); + const imodels = await client.iModels.get(actx, accessToken, projectId, new IModelQuery().byName(name)); if (imodels.length > 0) { if (deleteIfExists) { - await client.IModels().delete(actx, accessToken, projectId, imodels[0].id!); + await client.iModels.delete(actx, accessToken, projectId, imodels[0].id!); } else { return; } } - return client.IModels().create(actx, accessToken, projectId, name, getMockSeedFilePath(), ""); + return client.iModels.create(actx, accessToken, projectId, name, getMockSeedFilePath(), ""); } export function getMockChangeSets(briefcase: Briefcase): ChangeSet[] { @@ -680,14 +686,14 @@ export async function createChangeSets(accessToken: AccessToken, imodelId: GuidS const client = getDefaultClient(); - const currentCount = (await client.ChangeSets().get(actx, accessToken, imodelId)).length; + const currentCount = (await client.changeSets.get(actx, accessToken, imodelId)).length; const changeSets = getMockChangeSets(briefcase); const result: ChangeSet[] = []; for (let i = currentCount; i < startingId + count; ++i) { const changeSetPath = getMockChangeSetPath(i, changeSets[i].id!); - const changeSet = await client.ChangeSets().create(actx, accessToken, imodelId, changeSets[i], changeSetPath); + const changeSet = await client.changeSets.create(actx, accessToken, imodelId, changeSets[i], changeSetPath); result.push(changeSet); } return result; @@ -709,7 +715,7 @@ export async function createLocks(accessToken: AccessToken, imodelId: GuidString releasedWithChangeSet, releasedWithChangeSetIndex)); } - await client.Locks().update(actx, accessToken, imodelId, generatedLocks); + await client.locks.update(actx, accessToken, imodelId, generatedLocks); } export async function createVersions(accessToken: AccessToken, imodelId: GuidString, changesetIds: string[], versionNames: string[]) { @@ -719,9 +725,9 @@ export async function createVersions(accessToken: AccessToken, imodelId: GuidStr const client = getDefaultClient(); for (let i = 0; i < changesetIds.length; i++) { // check if changeset does not have version - const version = await client.Versions().get(actx, accessToken, imodelId, new VersionQuery().byChangeSet(changesetIds[i])); + const version = await client.versions.get(actx, accessToken, imodelId, new VersionQuery().byChangeSet(changesetIds[i])); if (!version || version.length === 0) { - await client.Versions().create(actx, accessToken, imodelId, changesetIds[i], versionNames[i]); + await client.versions.create(actx, accessToken, imodelId, changesetIds[i], versionNames[i]); } } } diff --git a/core/clients/src/test/imodelhub/Thumbnails.test.ts b/core/clients/src/test/imodelhub/Thumbnails.test.ts index fd22bc5..558ca2a 100644 --- a/core/clients/src/test/imodelhub/Thumbnails.test.ts +++ b/core/clients/src/test/imodelhub/Thumbnails.test.ts @@ -7,7 +7,7 @@ import * as chai from "chai"; import { ChangeSet, Version, Thumbnail, ThumbnailSize, ThumbnailQuery } from "../../"; import { AccessToken, IModelClient } from "../../"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; import { ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -41,8 +41,8 @@ interface TestParameters { size: ThumbnailSize; } -async function getIModelId(accessToken: AccessToken, name: string) { - return await utils.getIModelId(accessToken, name); +async function getIModelId(accessToken: AccessToken, name: string): Promise { + return utils.getIModelId(accessToken, name); } describe("iModelHub ThumbnailHandler", () => { @@ -59,10 +59,10 @@ describe("iModelHub ThumbnailHandler", () => { this.enableTimeouts(false); if (!TestConfig.enableMocks) { utils.getRequestBehaviorOptionsHandler().disableBehaviorOption("DoNotScheduleRenderThumbnailJob"); - imodelHubClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + imodelHubClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); } - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); _projectId = await utils.getProjectId(accessToken); await utils.createIModel(accessToken, imodelName, _projectId); imodelId = await getIModelId(accessToken, imodelName); @@ -73,7 +73,7 @@ describe("iModelHub ThumbnailHandler", () => { } // Delete and create a new iModel if we have not expected number of versions. - versions = (await imodelHubClient.Versions().get(actx, accessToken, imodelId)); + versions = (await imodelHubClient.versions.get(actx, accessToken, imodelId)); if (versions.length !== 0 && versions.length !== 3) { await utils.createIModel(accessToken, imodelName, _projectId, true); imodelId = await getIModelId(accessToken, imodelName); @@ -85,12 +85,12 @@ describe("iModelHub ThumbnailHandler", () => { const briefcase = (await utils.getBriefcases(accessToken, imodelId, 1))[0]; const changeSets: ChangeSet[] = await utils.createChangeSets(accessToken, imodelId, briefcase, 0, 3); for (let i = 0; i < 3; i++) - await imodelHubClient.Versions().create(actx, accessToken, imodelId, changeSets[i].id!, `Version ${i + 1}`); - versions = (await imodelHubClient.Versions().get(actx, accessToken, imodelId)); + await imodelHubClient.versions.create(actx, accessToken, imodelId, changeSets[i].id!, `Version ${i + 1}`); + versions = (await imodelHubClient.versions.get(actx, accessToken, imodelId)); // Wait for all 4 thumbnails (tip and 3 named versions). for (let i = 0; i < 5; i++) { - const largeThumbnails: Thumbnail[] = await imodelHubClient.Thumbnails().get(actx, accessToken, imodelId, "Large"); + const largeThumbnails: Thumbnail[] = await imodelHubClient.thumbnails.get(actx, accessToken, imodelId, "Large"); if (largeThumbnails.length === 4) break; await utils.delay(6000); @@ -101,7 +101,7 @@ describe("iModelHub ThumbnailHandler", () => { after(() => { if (!TestConfig.enableMocks) { utils.getRequestBehaviorOptionsHandler().resetDefaultBehaviorOptions(); - imodelHubClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + imodelHubClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); } }); @@ -114,10 +114,10 @@ describe("iModelHub ThumbnailHandler", () => { it(`should get ${params.size}Thumbnail's ids`, async () => { const mockedThumbnails = Array(3).fill(0).map(() => utils.generateThumbnail(params.size)); utils.mockGetThumbnails(imodelId, params.size, ...mockedThumbnails); - params.thumbnails = await imodelHubClient.Thumbnails().get(actx, accessToken, imodelId, params.size); + params.thumbnails = await imodelHubClient.thumbnails.get(actx, accessToken, imodelId, params.size); if (params.thumbnails.length < 3) { - utils.deleteIModelByName(accessToken, _projectId, imodelName); + await utils.deleteIModelByName(accessToken, _projectId, imodelName); chai.expect(params.thumbnails.length).to.be.gte(3); } }); @@ -127,7 +127,7 @@ describe("iModelHub ThumbnailHandler", () => { it(`should get ${params.size}Thumbnail by id`, async () => { for (let i = 0; i < 3; i++) { utils.mockGetThumbnailById(imodelId, params.size, params.thumbnails[i]); - const actualThumbnail: Thumbnail = (await imodelHubClient.Thumbnails().get(actx, accessToken, imodelId, params.size, new ThumbnailQuery().byId(params.thumbnails[i].id!)))[0]; + const actualThumbnail: Thumbnail = (await imodelHubClient.thumbnails.get(actx, accessToken, imodelId, params.size, new ThumbnailQuery().byId(params.thumbnails[i].id!)))[0]; chai.assert(!!actualThumbnail); chai.expect(actualThumbnail.id!.toString()).to.be.equal(params.thumbnails[i].id!.toString()); } @@ -138,7 +138,7 @@ describe("iModelHub ThumbnailHandler", () => { it(`should get ${params.size}Thumbnail by version id`, async () => { for (let i = 0; i < 3; i++) { utils.mockGetThumbnailsByVersionId(imodelId, params.size, versions[i].id!, params.thumbnails[i]); - const actualThumbnail: Thumbnail = (await imodelHubClient.Thumbnails().get(actx, accessToken, imodelId, params.size, new ThumbnailQuery().byVersionId(versions[i].id!)))[0]; + const actualThumbnail: Thumbnail = (await imodelHubClient.thumbnails.get(actx, accessToken, imodelId, params.size, new ThumbnailQuery().byVersionId(versions[i].id!)))[0]; chai.assert(!!actualThumbnail); chai.expect(actualThumbnail.id!.toString()).to.be.equal(params.thumbnails[i].id!.toString()); } @@ -148,7 +148,7 @@ describe("iModelHub ThumbnailHandler", () => { test.forEach((params: TestParameters) => { it(`should download latest iModel's ${params.size}Thumbnail as a PNG file`, async () => { mockDownloadLatestThumbnail(_projectId, imodelId, params.size); - const image: string = await imodelHubClient.Thumbnails().download(actx, accessToken, imodelId, { projectId: _projectId, size: params.size }); + const image: string = await imodelHubClient.thumbnails.download(actx, accessToken, imodelId, { projectId: _projectId, size: params.size }); chai.assert(image); chai.expect(image.length).greaterThan(getThumbnailLength(params.size)); chai.assert(image.startsWith(pngPrefixStr)); @@ -160,7 +160,7 @@ describe("iModelHub ThumbnailHandler", () => { const expectedLength = getThumbnailLength(params.size); for (let i = 0; i < 3; i++) { mockDownloadThumbnailById(imodelId, params.thumbnails[i].wsgId, params.size); - const image: string = await imodelHubClient.Thumbnails().download(actx, accessToken, imodelId, params.thumbnails[i]); + const image: string = await imodelHubClient.thumbnails.download(actx, accessToken, imodelId, params.thumbnails[i]); chai.assert(image); chai.expect(image.length).greaterThan(expectedLength); chai.assert(image.startsWith(pngPrefixStr)); diff --git a/core/clients/src/test/imodelhub/UrlValidator.test.ts b/core/clients/src/test/imodelhub/UrlValidator.test.ts new file mode 100644 index 0000000..71f9e46 --- /dev/null +++ b/core/clients/src/test/imodelhub/UrlValidator.test.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import { assert, should } from "chai"; + +import * as fs from "fs"; +import * as path from "path"; +import { urlLogPath } from "../TestConfig"; +import { IModelBaseHandler } from "../../imodelhub/BaseHandler"; +import { UrlDiscoveryClient } from "../../Client"; +import { ActivityLoggingContext, Guid } from "@bentley/bentleyjs-core"; + +export const whitelistRelPath: string = "../assets/whitelist.txt"; + +should(); + +describe("iModelHub URL Whitelist Validator", () => { + + function normalizeUrl(loggedUrl: string, hubBaseUrl: string): string | undefined { + const extractRegex = new RegExp(hubBaseUrl + "\\/s?v(\\d+).(\\d+)\\/Repositories\\/(iModel|Project|Global)--(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}|Global)\\/(.*)", "i"); + + const matches = loggedUrl.match(extractRegex); + let normalizedUrl: string = ""; + if (!!matches) + normalizedUrl = matches.pop() || ""; + + if (normalizedUrl === "") + return undefined; + + const replaceRegex = [ + // All GUIDs + [/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/g, "********-****-****-****-************"], + // 40-character alphanumeric Ids in quotes + [/'\w{40}'/g, "'****************************************'"], + // Briefcase Id path (any numeric value) + [/Briefcase\/(\d+)/g, "Briefcase/*"], + // Changeset Id path (any 40-character alphanumeric Id) + [/ChangeSet\/(\w){40}/g, "ChangeSet/****************************************"], + // Message Id path (any numeric value) + [/messages\/(\d+)/g, "messages/*"], + // Numeric comparison query string params + [/(eq|ne|gt|lt)\+(\d+?)\b/g, "$1+*"], + // String comparison query string params (in quotes or URL-encoded quotes) + [/(eq|ne|gt|lt)\+('|%27)([a-zA-Z0-9._ \-]+?)('|%27)/g, "$1+'*'"], + // Other numeric query string params + [/(=|-)\d+(\b|$)/g, "$1*"]]; + + // Genericizes dynamic URL parameters and replaces them with asterisks. + // Predictable patterns (such as GUIDs and ChangeSetIds) retain their form, others are replaced with a single asterisk. + for (const regexPair of replaceRegex) { + const expression = regexPair[0]; + const replacement = regexPair[1]; + normalizedUrl = normalizedUrl.replace(expression, replacement.toString()); + } + + return normalizedUrl === "" ? undefined : normalizedUrl; + } + + it("Detect whether new iModelHub APIs have been added to which iModelBank has to react", async () => { + const whitelistPath: string = path.join(__dirname, whitelistRelPath); + assert.isTrue(fs.existsSync(whitelistPath), `Whitelist file is expected to exist in the assets to run this test: ${whitelistPath}`); + + const whiteListFileContent: string = fs.readFileSync(whitelistPath, "utf8"); + assert.isTrue(whiteListFileContent.length !== 0, `No whitelist URLs found in ${whitelistPath}`); + // Split into line array + const whitelistUrls: string[] = whiteListFileContent.split(/\r?\n/); + + assert.isTrue(fs.existsSync(urlLogPath), `URL log file ${urlLogPath} is expected to exist run this test.`); + const logFileContent: string = fs.readFileSync(urlLogPath, "utf8"); + assert.isTrue(logFileContent.length !== 0, `No logged URLs found in ${urlLogPath}. Make sure to have run the full suite of integration tests before.`); + // filter out duplicate URLs by putting the lines in a set and create an array from it again + const loggedUrls: string[] = Array.from(new Set(logFileContent.split(/\r?\n/))); + + const actCtx = new ActivityLoggingContext(Guid.createValue()); + let baseUrl: string = await new UrlDiscoveryClient().discoverUrl(actCtx, IModelBaseHandler.searchKey, undefined); + if (baseUrl.endsWith("/") || baseUrl.endsWith("\\")) + baseUrl = baseUrl.substring(1, baseUrl.length - 1); + + const violatingUrls: string[] = []; + for (const url of loggedUrls) { + if (url.length === 0) + continue; + + const normalizedUrl: string | undefined = normalizeUrl(url, baseUrl); + if (!normalizedUrl || normalizedUrl.length === 0) + continue; + + if (whitelistUrls.indexOf(normalizedUrl) < 0) + violatingUrls.push(url); + } + + const violatingUrlsString: string = violatingUrls.length === 0 ? "" : violatingUrls.reduce((str: string, current: string) => str + "|" + current); + assert.isTrue(violatingUrls.length === 0, `The URLs '${violatingUrlsString}' are not whitelisted.\n` + + "If this is caused by a necessary API change, update the whitelist and notify iModelBank of the updates. " + + "If the whitelist violation is unintentional, modify your changes to use existing API functionality."); + }); +}); diff --git a/core/clients/src/test/imodelhub/UserInfo.test.ts b/core/clients/src/test/imodelhub/UserInfo.test.ts index 403214b..46a9cdb 100644 --- a/core/clients/src/test/imodelhub/UserInfo.test.ts +++ b/core/clients/src/test/imodelhub/UserInfo.test.ts @@ -10,14 +10,14 @@ import { AccessToken } from "../../Token"; import { ResponseBuilder, ScopeType, RequestType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; import { TestUsers } from "../TestConfig"; -import { UserInfoQuery, UserInfo, UserProfile, IModelHubClientError, IModelClient } from "../.."; +import { UserInfoQuery, HubUserInfo, UserInfo, IModelHubClientError, IModelClient } from "../.."; import { IModelHubStatus, ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; -function mockGetUserInfo(imodelId: GuidString, userInfo: UserInfo[], query?: string) { +function mockGetUserInfo(imodelId: GuidString, userInfo: HubUserInfo[], query?: string) { if (!TestConfig.enableMocks) return; - const requestResponse = ResponseBuilder.generateGetArrayResponse(userInfo); + const requestResponse = ResponseBuilder.generateGetArrayResponse(userInfo); let requestPath; if (query === undefined) { requestPath = utils.createRequestUrl(ScopeType.iModel, imodelId, "UserInfo", "$query"); @@ -28,14 +28,14 @@ function mockGetUserInfo(imodelId: GuidString, userInfo: UserInfo[], query?: str } } -function generateUserInfo(userProfiles: UserProfile[]): UserInfo[] { - const users: UserInfo[] = []; - userProfiles.forEach((user: UserProfile) => { - const userInfo = new UserInfo(); - userInfo.id = user.userId; - userInfo.firstName = user.firstName; - userInfo.lastName = user.lastName; - userInfo.email = user.email; +function generateHubUserInfo(userInfos: UserInfo[]): HubUserInfo[] { + const users: HubUserInfo[] = []; + userInfos.forEach((user: UserInfo) => { + const userInfo = new HubUserInfo(); + userInfo.id = user.id; + userInfo.firstName = user.profile!.firstName; + userInfo.lastName = user.profile!.lastName; + userInfo.email = user.email!.id; users.push(userInfo); }); return users; @@ -50,10 +50,10 @@ describe("iModelHubClient UserInfoHandler", () => { const imodelHubClient: IModelClient = utils.getDefaultClient(); before(async function (this: Mocha.IHookCallbackContext) { - accessTokens.push(await utils.login()); - accessTokens.push(await utils.login(TestUsers.manager)); + accessTokens.push(TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super)); + accessTokens.push(TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.manager)); - accessTokens.sort((a: AccessToken, b: AccessToken) => a.getUserProfile()!.userId.localeCompare(b.getUserProfile()!.userId)); + accessTokens.sort((a: AccessToken, b: AccessToken) => a.getUserInfo()!.id.localeCompare(b.getUserInfo()!.id)); await utils.createIModel(accessTokens[0], imodelName); imodelId = await utils.getIModelId(accessTokens[0], imodelName); @@ -69,43 +69,43 @@ describe("iModelHubClient UserInfoHandler", () => { it("should get one user info", async function (this: Mocha.ITestCallbackContext) { if (TestConfig.enableMocks) { - const mockedUserInfo = generateUserInfo([accessTokens[0].getUserProfile()!]); + const mockedUserInfo = generateHubUserInfo([accessTokens[0].getUserInfo()!]); mockGetUserInfo(imodelId, mockedUserInfo, `${mockedUserInfo[0].id}`); } - const query = new UserInfoQuery().byId(accessTokens[0].getUserProfile()!.userId); - const userInfo = (await imodelHubClient.Users().get(actx, accessTokens[0], imodelId, query)); + const query = new UserInfoQuery().byId(accessTokens[0].getUserInfo()!.id); + const userInfo = (await imodelHubClient.users.get(actx, accessTokens[0], imodelId, query)); chai.assert(userInfo); chai.expect(userInfo.length).to.be.equal(1); - chai.expect(userInfo[0].id).to.be.equal(accessTokens[0].getUserProfile()!.userId); - chai.expect(userInfo[0].firstName).to.be.equal(accessTokens[0].getUserProfile()!.firstName); - chai.expect(userInfo[0].lastName).to.be.equal(accessTokens[0].getUserProfile()!.lastName); + chai.expect(userInfo[0].id).to.be.equal(accessTokens[0].getUserInfo()!.id); + chai.expect(userInfo[0].firstName).to.be.equal(accessTokens[0].getUserInfo()!.profile!.firstName); + chai.expect(userInfo[0].lastName).to.be.equal(accessTokens[0].getUserInfo()!.profile!.lastName); }); it("should get several users info", async function (this: Mocha.ITestCallbackContext) { if (TestConfig.enableMocks) { - const mockedUsersInfo = generateUserInfo([accessTokens[0].getUserProfile()!, accessTokens[1].getUserProfile()!]); + const mockedUsersInfo = generateHubUserInfo([accessTokens[0].getUserInfo()!, accessTokens[1].getUserInfo()!]); mockGetUserInfo(imodelId, mockedUsersInfo); } const query = new UserInfoQuery().byIds( - [accessTokens[0].getUserProfile()!.userId, - accessTokens[1].getUserProfile()!.userId]); - const userInfo = (await imodelHubClient.Users().get(actx, accessTokens[0], imodelId, query)); - userInfo.sort((a: UserInfo, b: UserInfo) => a.id!.localeCompare(b.id!)); + [accessTokens[0].getUserInfo()!.id, + accessTokens[1].getUserInfo()!.id]); + const userInfo = (await imodelHubClient.users.get(actx, accessTokens[0], imodelId, query)); + userInfo.sort((a: HubUserInfo, b: HubUserInfo) => a.id!.localeCompare(b.id!)); chai.assert(userInfo); chai.expect(userInfo.length).to.be.equal(2); for (let i = 0; i < 2; ++i) { - chai.expect(userInfo[i].id).to.be.equal(accessTokens[i].getUserProfile()!.userId); - chai.expect(userInfo[i].firstName).to.be.equal(accessTokens[i].getUserProfile()!.firstName); - chai.expect(userInfo[i].lastName).to.be.equal(accessTokens[i].getUserProfile()!.lastName); + chai.expect(userInfo[i].id).to.be.equal(accessTokens[i].getUserInfo()!.id); + chai.expect(userInfo[i].firstName).to.be.equal(accessTokens[i].getUserInfo()!.profile!.firstName); + chai.expect(userInfo[i].lastName).to.be.equal(accessTokens[i].getUserInfo()!.profile!.lastName); } }); it("should fail to get users without ids", async () => { let error: IModelHubClientError | undefined; try { - await imodelHubClient.Users().get(actx, accessTokens[0], imodelId, new UserInfoQuery().byIds([])); + await imodelHubClient.users.get(actx, accessTokens[0], imodelId, new UserInfoQuery().byIds([])); } catch (err) { if (err instanceof IModelHubClientError) error = err; diff --git a/core/clients/src/test/imodelhub/UserStatistics.test.ts b/core/clients/src/test/imodelhub/UserStatistics.test.ts index f86867b..bf468bc 100644 --- a/core/clients/src/test/imodelhub/UserStatistics.test.ts +++ b/core/clients/src/test/imodelhub/UserStatistics.test.ts @@ -73,8 +73,8 @@ describe("iModelHubClient UserStatisticsHandler", () => { const user2PushedChangesetsCount = 0; before(async function (this: Mocha.IHookCallbackContext) { - accessTokens.push(await utils.login()); - accessTokens.push(await utils.login(TestUsers.manager)); + accessTokens.push(TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super)); + accessTokens.push(TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.manager)); await utils.createIModel(accessTokens[0], imodelName, undefined, true); imodelId = await utils.getIModelId(accessTokens[0], imodelName); @@ -100,38 +100,38 @@ describe("iModelHubClient UserStatisticsHandler", () => { }); it("should get user briefcases count", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId).selectBriefcasesCount(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.BriefcasesCount`; + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id).selectBriefcasesCount(); + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.BriefcasesCount`; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], [user1BriefcasesCount]), textQuery); + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], [user1BriefcasesCount]), textQuery); - const briefcasesCount = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, query))[0]; + const briefcasesCount = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(briefcasesCount); chai.expect(briefcasesCount.briefcasesCount).to.be.equal(user1BriefcasesCount); }); it("should get user owned locks count", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId).selectOwnedLocksCount(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.OwnedLocksCount`; + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id).selectOwnedLocksCount(); + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.OwnedLocksCount`; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], undefined, + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], undefined, [user1OwnedLocksCount]), textQuery); - const ownedLocksCount = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, query))[0]; + const ownedLocksCount = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(ownedLocksCount); chai.expect(ownedLocksCount.ownedLocksCount).to.be.equal(user1OwnedLocksCount); }); it("should get user pushed changesets count", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId).selectPushedChangeSetsCount(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.PushedChangeSetsCount`; + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id).selectPushedChangeSetsCount(); + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.PushedChangeSetsCount`; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], undefined, + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], undefined, undefined, [user1PushedChangesetsCount]), textQuery); - const pushedChangesetsCount = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, + const pushedChangesetsCount = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(pushedChangesetsCount); @@ -139,13 +139,13 @@ describe("iModelHubClient UserStatisticsHandler", () => { }); it("should get user last changeset push date", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId).selectLastChangeSetPushDate(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.LastChangeSetPushDate`; + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id).selectLastChangeSetPushDate(); + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.LastChangeSetPushDate`; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], undefined, + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], undefined, undefined, undefined, ["date"]), textQuery); - const lastChangeSetPushDate = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, + const lastChangeSetPushDate = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(lastChangeSetPushDate); @@ -154,15 +154,15 @@ describe("iModelHubClient UserStatisticsHandler", () => { }); it("should get user pushed changesets count and last changeset push date", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId) + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id) .selectPushedChangeSetsCount().selectLastChangeSetPushDate(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.PushedChangeSetsCount,` + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.PushedChangeSetsCount,` + "HasStatistics-forward-Statistics.LastChangeSetPushDate"; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], undefined, + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], undefined, undefined, [user1PushedChangesetsCount], ["date"]), textQuery); - const changesetStatistics = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, + const changesetStatistics = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(changesetStatistics); @@ -171,14 +171,14 @@ describe("iModelHubClient UserStatisticsHandler", () => { }); it("should get briefcases and owned locks count", async function (this: Mocha.ITestCallbackContext) { - const query = new UserStatisticsQuery().byId(accessTokens[0].getUserProfile()!.userId).selectBriefcasesCount().selectOwnedLocksCount(); - const textQuery = `${accessTokens[0].getUserProfile()!.userId}?$select=*,HasStatistics-forward-Statistics.BriefcasesCount,` + const query = new UserStatisticsQuery().byId(accessTokens[0].getUserInfo()!.id).selectBriefcasesCount().selectOwnedLocksCount(); + const textQuery = `${accessTokens[0].getUserInfo()!.id}?$select=*,HasStatistics-forward-Statistics.BriefcasesCount,` + "HasStatistics-forward-Statistics.OwnedLocksCount"; - mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserProfile()!.userId], + mockGetUserStatistics(imodelId, generateUsersStatistics(1, [accessTokens[0].getUserInfo()!.id], [user1BriefcasesCount], [user1OwnedLocksCount]), textQuery); - const briefcasesLocksStatistics = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, + const briefcasesLocksStatistics = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query))[0]; chai.assert(briefcasesLocksStatistics); @@ -191,10 +191,10 @@ describe("iModelHubClient UserStatisticsHandler", () => { const textQuery = "?$select=*,HasStatistics-forward-Statistics.BriefcasesCount"; mockGetUserStatistics(imodelId, generateUsersStatistics(2, - [accessTokens[0].getUserProfile()!.userId, accessTokens[1].getUserProfile()!.userId], + [accessTokens[0].getUserInfo()!.id, accessTokens[1].getUserInfo()!.id], [user1BriefcasesCount, user2BriefcasesCount]), textQuery); - const iModelStatistics = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, query)); + const iModelStatistics = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query)); chai.assert(iModelStatistics); chai.expect(iModelStatistics.length === 2); @@ -205,14 +205,14 @@ describe("iModelHubClient UserStatisticsHandler", () => { it("should get two users Pushed Changesets count", async function (this: Mocha.ITestCallbackContext) { const query = new UserStatisticsQuery() - .byIds([accessTokens[0].getUserProfile()!.userId, accessTokens[1].getUserProfile()!.userId]) + .byIds([accessTokens[0].getUserInfo()!.id, accessTokens[1].getUserInfo()!.id]) .selectPushedChangeSetsCount(); mockGetUserStatistics(imodelId, generateUsersStatistics(2, - [accessTokens[0].getUserProfile()!.userId, accessTokens[1].getUserProfile()!.userId], + [accessTokens[0].getUserInfo()!.id, accessTokens[1].getUserInfo()!.id], undefined, undefined, [user1PushedChangesetsCount, user2PushedChangesetsCount])); - const iModelStatistics = (await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, query)); + const iModelStatistics = (await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, query)); chai.assert(iModelStatistics); chai.expect(iModelStatistics.length === 2); @@ -225,12 +225,12 @@ describe("iModelHubClient UserStatisticsHandler", () => { const textQuery = "?$select=*,HasStatistics-forward-Statistics.*"; mockGetUserStatistics(imodelId, generateUsersStatistics(2, - [accessTokens[0].getUserProfile()!.userId, accessTokens[1].getUserProfile()!.userId], + [accessTokens[0].getUserInfo()!.id, accessTokens[1].getUserInfo()!.id], [user1BriefcasesCount, user2BriefcasesCount], [user1OwnedLocksCount, user2OwnedLocksCount], [user1PushedChangesetsCount, user2PushedChangesetsCount]), textQuery); - const iModelStatistics = await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId); + const iModelStatistics = await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId); chai.assert(iModelStatistics); chai.expect(iModelStatistics.length === 2); @@ -246,7 +246,7 @@ describe("iModelHubClient UserStatisticsHandler", () => { it("should fail to get user statistics without ids", async () => { let error: IModelHubClientError | undefined; try { - await imodelHubClient.Users().Statistics().get(actx, accessTokens[0], imodelId, new UserStatisticsQuery().byIds([])); + await imodelHubClient.users.statistics.get(actx, accessTokens[0], imodelId, new UserStatisticsQuery().byIds([])); } catch (err) { if (err instanceof IModelHubClientError) error = err; diff --git a/core/clients/src/test/imodelhub/Versions.test.ts b/core/clients/src/test/imodelhub/Versions.test.ts index 96a4669..77a75f0 100644 --- a/core/clients/src/test/imodelhub/Versions.test.ts +++ b/core/clients/src/test/imodelhub/Versions.test.ts @@ -10,7 +10,7 @@ import { ThumbnailQuery, ThumbnailSize, } from "../../"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; import { ActivityLoggingContext, GuidString } from "@bentley/bentleyjs-core"; @@ -56,35 +56,35 @@ describe("iModelHub VersionHandler", () => { utils.getRequestBehaviorOptionsHandler().disableBehaviorOption("DoNotScheduleRenderThumbnailJob"); } - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); iModelClient = utils.getDefaultClient(); briefcase = (await utils.getBriefcases(accessToken, imodelId, 1))[0]; if (!TestConfig.enableMocks) { - iModelClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); - const changeSetCount = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId)).length; + iModelClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + const changeSetCount = (await iModelClient.changeSets.get(actx, accessToken, imodelId)).length; if (changeSetCount > 9) { // Recreate iModel if can't create any new changesets await utils.createIModel(accessToken, imodelName, undefined, true); imodelId = await utils.getIModelId(accessToken, imodelName); briefcase = (await utils.getBriefcases(accessToken, imodelId, 1))[0]; } - const versionsCount = (await iModelClient.Versions().get(actx, accessToken, imodelId)).length; + const versionsCount = (await iModelClient.versions.get(actx, accessToken, imodelId)).length; if (versionsCount === 0) { // Create at least 1 named version let changeSet: ChangeSet; if (changeSetCount === 0 || changeSetCount > 9) { changeSet = (await utils.createChangeSets(accessToken, imodelId, briefcase, 0, 1))[0]; } else { - changeSet = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId))[0]; + changeSet = (await iModelClient.changeSets.get(actx, accessToken, imodelId))[0]; } - const version: Version = await iModelClient.Versions().create(actx, accessToken, imodelId, changeSet.id!, "Version 1"); + const version: Version = await iModelClient.versions.create(actx, accessToken, imodelId, changeSet.id!, "Version 1"); if (utils.getCloudEnv().isIModelHub) { // Wait for large thumbnail. for (let i = 0; i < 5; i++) { - const largeThumbnails = (await iModelClient.Thumbnails().get(actx, accessToken, imodelId, "Large", new ThumbnailQuery().byVersionId(version.id!))); + const largeThumbnails = (await iModelClient.thumbnails.get(actx, accessToken, imodelId, "Large", new ThumbnailQuery().byVersionId(version.id!))); if (largeThumbnails.length > 0) break; await utils.delay(6000); @@ -97,7 +97,7 @@ describe("iModelHub VersionHandler", () => { after(() => { if (!TestConfig.enableMocks) { utils.getRequestBehaviorOptionsHandler().resetDefaultBehaviorOptions(); - iModelClient.RequestOptions().setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); + iModelClient.requestOptions.setCustomOptions(utils.getRequestBehaviorOptionsHandler().toCustomRequestOptions()); } }); @@ -108,14 +108,14 @@ describe("iModelHub VersionHandler", () => { it("should create named version", async function (this: Mocha.ITestCallbackContext) { const mockedChangeSets = Array(1).fill(0).map(() => utils.generateChangeSet()); utils.mockGetChangeSet(imodelId, false, undefined, ...mockedChangeSets); - const changeSetsCount = (await iModelClient.ChangeSets().get(actx, accessToken, imodelId)).length; + const changeSetsCount = (await iModelClient.changeSets.get(actx, accessToken, imodelId)).length; // creating changeset for new named version const changeSet = (await utils.createChangeSets(accessToken, imodelId, briefcase, changeSetsCount, 1))[0]; const versionName = `Version ${changeSetsCount + 1}`; utils.mockCreateVersion(imodelId, versionName, changeSet.id); - const version: Version = await iModelClient.Versions().create(actx, accessToken, imodelId, changeSet.id!, versionName); + const version: Version = await iModelClient.versions.create(actx, accessToken, imodelId, changeSet.id!, versionName); chai.assert(!!version); chai.expect(!!version.id); @@ -127,12 +127,12 @@ describe("iModelHub VersionHandler", () => { const mockedVersions = Array(3).fill(0).map(() => utils.generateVersion()); utils.mockGetVersions(imodelId, undefined, ...mockedVersions); // Needs to create before expecting more than 0 - const versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId); + const versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId); let i = 0; for (const expectedVersion of versions) { utils.mockGetVersionById(imodelId, mockedVersions[i++]); - const actualVersion: Version = (await iModelClient.Versions().get(actx, accessToken, imodelId, new VersionQuery().byId(expectedVersion.id!)))[0]; + const actualVersion: Version = (await iModelClient.versions.get(actx, accessToken, imodelId, new VersionQuery().byId(expectedVersion.id!)))[0]; chai.assert(!!actualVersion); chai.expect(actualVersion.changeSetId).to.be.equal(expectedVersion.changeSetId); } @@ -143,10 +143,10 @@ describe("iModelHub VersionHandler", () => { utils.mockGetVersions(imodelId, undefined, mockedVersion); utils.mockGetVersions(imodelId, `?$filter=ChangeSetId+eq+%27${mockedVersion.changeSetId!}%27`, mockedVersion); - const expectedVersion: Version = (await iModelClient.Versions().get(actx, accessToken, imodelId))[0]; + const expectedVersion: Version = (await iModelClient.versions.get(actx, accessToken, imodelId))[0]; chai.assert(expectedVersion); - const version: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId, new VersionQuery().byChangeSet(expectedVersion.changeSetId!)); + const version: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId, new VersionQuery().byChangeSet(expectedVersion.changeSetId!)); chai.assert(version); chai.expect(version.length).to.be.equal(1); chai.expect(version[0].changeSetId).to.be.equal(expectedVersion.changeSetId); @@ -155,7 +155,7 @@ describe("iModelHub VersionHandler", () => { it("should get named versions with thumbnail id", async () => { let mockedVersions = Array(1).fill(0).map(() => utils.generateVersion()); utils.mockGetVersions(imodelId, undefined, ...mockedVersions); - let versions: Version[] = await iModelClient.Versions().get(actx, accessToken, imodelId, new VersionQuery()); + let versions: Version[] = await iModelClient.versions.get(actx, accessToken, imodelId, new VersionQuery()); chai.expect(versions.length >= 1); const firstVersion = versions[versions.length - 1]; chai.expect(firstVersion.smallThumbnailId).to.be.undefined; @@ -163,15 +163,15 @@ describe("iModelHub VersionHandler", () => { const mockedSmallThumbnail = utils.generateThumbnail("Small"); utils.mockGetThumbnailsByVersionId(imodelId, "Small", firstVersion.id!, mockedSmallThumbnail); - const smallThumbnail: Thumbnail = (await iModelClient.Thumbnails().get(actx, accessToken, imodelId, "Small", new ThumbnailQuery().byVersionId(firstVersion.id!)))[0]; + const smallThumbnail: Thumbnail = (await iModelClient.thumbnails.get(actx, accessToken, imodelId, "Small", new ThumbnailQuery().byVersionId(firstVersion.id!)))[0]; const mockedLargeThumbnail = utils.generateThumbnail("Large"); utils.mockGetThumbnailsByVersionId(imodelId, "Large", firstVersion.id!, mockedLargeThumbnail); - const largeThumbnail: Thumbnail = (await iModelClient.Thumbnails().get(actx, accessToken, imodelId, "Large", new ThumbnailQuery().byVersionId(firstVersion.id!)))[0]; + const largeThumbnail: Thumbnail = (await iModelClient.thumbnails.get(actx, accessToken, imodelId, "Large", new ThumbnailQuery().byVersionId(firstVersion.id!)))[0]; mockedVersions = Array(1).fill(0).map(() => utils.generateVersion(undefined, undefined, true, mockedSmallThumbnail.id!, mockedLargeThumbnail.id!)); mockGetVersionsByIdWithThumbnails(imodelId, firstVersion.id!, ["Small", "Large"], ...mockedVersions); - versions = await iModelClient.Versions().get(actx, accessToken, imodelId, new VersionQuery().byId(firstVersion.id!).selectThumbnailId("Small", "Large")); + versions = await iModelClient.versions.get(actx, accessToken, imodelId, new VersionQuery().byId(firstVersion.id!).selectThumbnailId("Small", "Large")); chai.expect(versions.length === 1); chai.assert(!!versions[0].smallThumbnailId); chai.expect(versions[0].smallThumbnailId!.toString()).to.be.equal(smallThumbnail.id!.toString()); @@ -180,7 +180,7 @@ describe("iModelHub VersionHandler", () => { mockedVersions = Array(1).fill(0).map(() => utils.generateVersion(undefined, undefined, true, undefined, mockedLargeThumbnail.id!)); mockGetVersionsByNameWithThumbnails(imodelId, firstVersion.name!, ["Large"], ...mockedVersions); - versions = await iModelClient.Versions().get(actx, accessToken, imodelId, new VersionQuery().byName(firstVersion.name!).selectThumbnailId("Large")); + versions = await iModelClient.versions.get(actx, accessToken, imodelId, new VersionQuery().byName(firstVersion.name!).selectThumbnailId("Large")); chai.expect(versions.length === 1); chai.expect(versions[0].smallThumbnailId).to.be.undefined; chai.assert(!!versions[0].largeThumbnailId); @@ -193,7 +193,7 @@ describe("iModelHub VersionHandler", () => { const mockedVersions = Array(1).fill(0).map(() => utils.generateVersion()); utils.mockGetVersions(imodelId, undefined, ...mockedVersions); - let version: Version = (await iModelClient.Versions().get(actx, accessToken, imodelId))[0]; + let version: Version = (await iModelClient.versions.get(actx, accessToken, imodelId))[0]; chai.assert(!!version); chai.assert(!!version.id); chai.expect(version.changeSetId).to.be.equal(version.changeSetId!); @@ -201,7 +201,7 @@ describe("iModelHub VersionHandler", () => { version.name += " updated"; utils.mockUpdateVersion(imodelId, version); - version = await iModelClient.Versions().update(actx, accessToken, imodelId, version); + version = await iModelClient.versions.update(actx, accessToken, imodelId, version); chai.assert(!!version); chai.expect(!!version.id); diff --git a/core/clients/src/test/imodelhub/iModels.test.ts b/core/clients/src/test/imodelhub/iModels.test.ts index dc0bb35..5cea825 100644 --- a/core/clients/src/test/imodelhub/iModels.test.ts +++ b/core/clients/src/test/imodelhub/iModels.test.ts @@ -8,13 +8,13 @@ import * as path from "path"; import { Guid, GuidString, IModelHubStatus, ActivityLoggingContext } from "@bentley/bentleyjs-core"; -import { AccessToken, WsgError, IModelQuery, IModelClient } from "../../"; +import { AccessToken, WsgError, IModelQuery, IModelClient, InitializationState } from "../../"; import { IModelHubClient, HubIModel, SeedFile, IModelHubError, IModelHubClientError, } from "../../"; -import { TestConfig } from "../TestConfig"; +import { TestConfig, TestUsers } from "../TestConfig"; import { ResponseBuilder, RequestType, ScopeType } from "../ResponseBuilder"; import * as utils from "./TestUtils"; @@ -36,6 +36,23 @@ function mockGetIModelByName(projectId: string, name: string, description = "", ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, requestResponse); } +function mockGetIModel(projectId: string, imodelName: string, imodelId: GuidString, imodelsCount?: number, description = "") { + if (!TestConfig.enableMocks) + return; + + imodelId = imodelId || Guid.createValue(); + + const requestPath = utils.createRequestUrl(ScopeType.Project, projectId, "iModel", "?$orderby=CreatedDate+asc&$top=1"); + const requestResponse = ResponseBuilder.generateGetResponse( + ResponseBuilder.generateObject(HubIModel, new Map([ + ["name", imodelName], + ["description", description], + ["wsgId", imodelId.toString()], + ["id", imodelId], + ])), imodelsCount); + ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, requestResponse); +} + function mockPostiModel(projectId: string, imodelId: GuidString, imodelName: string, description: string) { const requestPath = utils.createRequestUrl(ScopeType.Project, projectId, "iModel"); const postBody = ResponseBuilder.generatePostBody( @@ -141,7 +158,7 @@ function mockUpdateiModel(projectId: string, imodel: HubIModel) { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Post, requestPath, requestResponse, 1, postBody); } -describe("iModelHub iModelHandler", () => { +describe("iModelHub iModelsHandler", () => { let accessToken: AccessToken; let projectId: string; let imodelId: GuidString; @@ -153,7 +170,7 @@ describe("iModelHub iModelHandler", () => { before(async function (this: Mocha.IHookCallbackContext) { this.enableTimeouts(false); - accessToken = await utils.login(); + accessToken = TestConfig.enableMocks ? new utils.MockAccessToken() : await utils.login(TestUsers.super); projectId = await utils.getProjectId(accessToken, undefined); await utils.createIModel(accessToken, imodelName); imodelId = await utils.getIModelId(accessToken, imodelName); @@ -182,13 +199,13 @@ describe("iModelHub iModelHandler", () => { } let imodels: HubIModel[]; - imodels = await imodelClient.IModels().get(alctx, accessToken, projectId, undefined); + imodels = await imodelClient.iModels.get(alctx, accessToken, projectId, undefined); chai.expect(imodels.length).to.be.greaterThan(0); }); it("should get a specific IModel", async () => { mockGetIModelByName(projectId, imodelName); - const iModel: HubIModel = (await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byName(imodelName)))[0]; + const iModel: HubIModel = (await imodelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byName(imodelName)))[0]; chai.expect(iModel.name).to.be.equal(imodelName); }); @@ -200,10 +217,10 @@ describe("iModelHub iModelHandler", () => { const names = ["22_LargePlant.166.i"]; for (const name of names) { mockGetIModelByName(projectId, name); - const iModel: HubIModel = (await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byName(name)))[0]; + const iModel: HubIModel = (await imodelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byName(name)))[0]; chai.expect(iModel.name).to.be.equal(name); mockDeleteiModel(projectId, iModel.id!); - await imodelClient.IModels().delete(alctx, accessToken, projectId, iModel.id!); + await imodelClient.iModels.delete(alctx, accessToken, projectId, iModel.id!); } }); @@ -215,7 +232,7 @@ describe("iModelHub iModelHandler", () => { ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, requestResponse); } - const iModel: HubIModel = (await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byId(imodelId)))[0]; + const iModel: HubIModel = (await imodelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byId(imodelId)))[0]; chai.expect(iModel.id!).to.be.equal(imodelId); }); @@ -230,7 +247,7 @@ describe("iModelHub iModelHandler", () => { let error: WsgError | undefined; try { - await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byId(mockGuid)); + await imodelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byId(mockGuid)); } catch (err) { if (err instanceof WsgError) error = err; @@ -242,7 +259,7 @@ describe("iModelHub iModelHandler", () => { it("should fail getting an iModel without projectId", async () => { let error: IModelHubClientError | undefined; try { - await imodelClient.IModels().get(alctx, accessToken, "", new IModelQuery().byId(imodelId)); + await imodelClient.iModels.get(alctx, accessToken, "", new IModelQuery().byId(imodelId)); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -251,31 +268,6 @@ describe("iModelHub iModelHandler", () => { chai.assert(error); chai.expect(error!.errorNumber).to.be.equal(IModelHubStatus.UndefinedArgumentError); }); - - it("should get primary IModel", async () => { - if (TestConfig.enableMocks) { - const requestPath = utils.createRequestUrl(ScopeType.Project, projectId, "iModel", "?$orderby=CreatedDate+asc&$top=1"); - const requestResponse = ResponseBuilder.generateGetResponse(ResponseBuilder.generateObject(HubIModel, - new Map([["name", imodelName]]))); - ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, requestResponse); - } - - const imodels = await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().primary()); - chai.expect(imodels.length).to.be.equal(1); - }); - - it("should return empty list if no IModels returned", async () => { - if (!TestConfig.enableMocks) - return; - - const requestPath = utils.createRequestUrl(ScopeType.Project, projectId, "iModel", "?$orderby=CreatedDate+asc&$top=1"); - const requestResponse = ResponseBuilder.generateGetResponse(ResponseBuilder.generateObject(HubIModel), 0); - ResponseBuilder.mockResponse(utils.IModelHubUrlMock.getUrl(), RequestType.Get, requestPath, requestResponse); - - const imodels = await imodelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().primary()); - chai.expect(imodels.length).to.be.equal(0); - }); - it("should fail creating existing and initialized iModel", async function (this: Mocha.ITestCallbackContext) { if (TestConfig.enableMocks) { const requestPath = utils.createRequestUrl(ScopeType.Project, projectId, "iModel"); @@ -286,7 +278,7 @@ describe("iModelHub iModelHandler", () => { let error: IModelHubError | undefined; try { - await imodelClient.IModels().create(alctx, accessToken, projectId, imodelName, utils.getMockSeedFilePath(), ""); + await imodelClient.iModels.create(alctx, accessToken, projectId, imodelName, utils.getMockSeedFilePath(), ""); } catch (err) { if (err instanceof IModelHubError) error = err; @@ -300,7 +292,7 @@ describe("iModelHub iModelHandler", () => { const description = "Test iModel created by imodeljs-clients tests"; mockCreateiModel(projectId, Guid.createValue(), createIModelName, description, filePath, 2); const progressTracker = new utils.ProgressTracker(); - const iModel = await imodelClient.IModels().create(alctx, accessToken, projectId, createIModelName, filePath, description, progressTracker.track()); + const iModel = await imodelClient.iModels.create(alctx, accessToken, projectId, createIModelName, filePath, description, progressTracker.track()); chai.expect(iModel.name).to.be.equal(createIModelName); chai.expect(iModel.initialized).to.be.equal(true); @@ -329,7 +321,7 @@ describe("iModelHub iModelHandler", () => { mockGetSeedFile(imodelId); } - const iModel = await imodelClient.IModels().create(alctx, accessToken, projectId, imodelName, filePath, ""); + const iModel = await imodelClient.iModels.create(alctx, accessToken, projectId, imodelName, filePath, ""); chai.expect(iModel.id!.toString()).to.be.equal(imodelId!.toString()); chai.expect(iModel.name).to.be.equal(imodelName); @@ -342,7 +334,7 @@ describe("iModelHub iModelHandler", () => { utils.mockFileResponse(); const progressTracker = new utils.ProgressTracker(); - await iModelClient.IModels().download(alctx, accessToken, imodelId, downloadToPathname, progressTracker.track()); + await iModelClient.iModels.download(alctx, accessToken, imodelId, downloadToPathname, progressTracker.track()); progressTracker.check(); fs.existsSync(downloadToPathname).should.be.equal(true); }); @@ -351,7 +343,7 @@ describe("iModelHub iModelHandler", () => { let error: IModelHubClientError | undefined; const invalidClient = new IModelHubClient(); try { - await invalidClient.IModels().download(alctx, accessToken, imodelId, utils.workDir); + await invalidClient.iModels.download(alctx, accessToken, imodelId, utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -367,7 +359,7 @@ describe("iModelHub iModelHandler", () => { let error: IModelHubClientError | undefined; const invalidClient = new IModelHubClient(); try { - await invalidClient.IModels().create(alctx, accessToken, projectId, createIModelName, utils.workDir); + await invalidClient.iModels.create(alctx, accessToken, projectId, createIModelName, utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -379,7 +371,7 @@ describe("iModelHub iModelHandler", () => { it("should fail creating an iModel with no file", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.IModels().create(alctx, accessToken, projectId, createIModelName, utils.workDir + "InvalidiModel.bim"); + await iModelClient.iModels.create(alctx, accessToken, projectId, createIModelName, utils.workDir + "InvalidiModel.bim"); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -391,7 +383,7 @@ describe("iModelHub iModelHandler", () => { it("should fail creating an iModel with directory path", async () => { let error: IModelHubClientError | undefined; try { - await iModelClient.IModels().create(alctx, accessToken, projectId, createIModelName, utils.workDir); + await iModelClient.iModels.create(alctx, accessToken, projectId, createIModelName, utils.workDir); } catch (err) { if (err instanceof IModelHubClientError) error = err; @@ -402,7 +394,7 @@ describe("iModelHub iModelHandler", () => { it("should update iModel name and description", async () => { mockGetIModelByName(projectId, imodelName); - const imodel: HubIModel = (await iModelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byName(imodelName)))[0]; + const imodel: HubIModel = (await iModelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byName(imodelName)))[0]; chai.expect(imodel.name).to.be.equal(imodelName); const newName = imodel.name + "_updated"; @@ -411,14 +403,14 @@ describe("iModelHub iModelHandler", () => { imodel.name = newName; imodel.description = newDescription; mockUpdateiModel(projectId, imodel); - let updatediModel = await iModelClient.IModels().update(alctx, accessToken, projectId, imodel); + let updatediModel = await iModelClient.iModels.update(alctx, accessToken, projectId, imodel); chai.expect(updatediModel.wsgId).to.be.equal(imodel.wsgId); chai.expect(updatediModel.name).to.be.equal(newName); chai.expect(updatediModel.description).to.be.equal(newDescription); mockGetIModelByName(projectId, newName, newDescription, imodel.id); - updatediModel = (await iModelClient.IModels().get(alctx, accessToken, projectId, new IModelQuery().byName(newName)))[0]; + updatediModel = (await iModelClient.iModels.get(alctx, accessToken, projectId, new IModelQuery().byName(newName)))[0]; await utils.deleteIModelByName(accessToken, projectId, newName); @@ -427,4 +419,149 @@ describe("iModelHub iModelHandler", () => { chai.expect(updatediModel.name).to.be.equal(newName); chai.expect(updatediModel.description).to.be.equal(newDescription); }); + + it("should get oldest IModel", async () => { + if (TestConfig.enableMocks) { + mockGetIModel(projectId, imodelName, Guid.createValue(), 1); + } + + const imodel = await imodelClient.iModel.get(alctx, accessToken, projectId); + chai.assert(imodel); + }); + + it("should throw if no IModels returned", async () => { + if (!TestConfig.enableMocks) + return; + + mockGetIModel(projectId, imodelName, Guid.createValue(), 0); + + let error: IModelHubError | undefined; + try { + await imodelClient.iModel.get(alctx, accessToken, projectId); + } catch (err) { + if (err instanceof IModelHubError) + error = err; + } + chai.assert(error); + chai.expect(error!.errorNumber).to.be.equal(IModelHubStatus.iModelDoesNotExist); + }); + + it("should be able to delete iModel", async function (this: Mocha.ITestCallbackContext) { + if (!TestConfig.enableMocks) + this.skip(); + + // Used only for maintenance + imodelId = imodelId || Guid.createValue(); + mockGetIModel(projectId, "22_LargePlant.166.i", imodelId, 1); + mockDeleteiModel(projectId, imodelId); + await imodelClient.iModel.delete(alctx, accessToken, projectId); + }); + + it("delete iModel should throw if iModel does not exist", async function (this: Mocha.ITestCallbackContext) { + if (!TestConfig.enableMocks) + this.skip(); + + // Used only for maintenance + mockGetIModel(projectId, "22_LargePlant.166.i", Guid.createValue(), 0); + mockDeleteiModel(projectId, imodelId); + + let error: IModelHubError | undefined; + try { + await imodelClient.iModel.delete(alctx, accessToken, projectId); + } catch (err) { + if (err instanceof IModelHubError) + error = err; + } + chai.assert(error); + chai.expect(error!.errorNumber).to.be.equal(IModelHubStatus.iModelDoesNotExist); + }); + + it("should return initialization status", async function (this: Mocha.ITestCallbackContext) { + if (!TestConfig.enableMocks) + this.skip(); + + imodelId = imodelId || Guid.createValue(); + mockGetIModel(projectId, "22_LargePlant.166.i", imodelId, 1); + mockGetSeedFile(imodelId); + + const initializationState = await imodelClient.iModel.getInitializationState(alctx, accessToken, projectId); + + chai.expect(initializationState).to.be.equal(InitializationState.Successful); + }); + + it("should create iModel if iModel does not exist", async function (this: Mocha.ITestCallbackContext) { + if (!TestConfig.enableMocks) + this.skip(); + + const filePath = utils.assetsPath + "LargerSeedFile.bim"; + const description = "Test iModel created by imodeljs-clients tests"; + imodelId = imodelId || Guid.createValue(); + mockGetIModel(projectId, createIModelName, imodelId, 0); + mockCreateiModel(projectId, imodelId, createIModelName, description, filePath, 2); + const progressTracker = new utils.ProgressTracker(); + const iModel = await imodelClient.iModel.create(alctx, accessToken, projectId, createIModelName, filePath, description, progressTracker.track()); + + chai.expect(iModel.name).to.be.equal(createIModelName); + chai.expect(iModel.initialized).to.be.equal(true); + progressTracker.check(); + }); + + it("should throw iModelAlreadyExists if iModel already exist", async function (this: Mocha.ITestCallbackContext) { + const filePath = utils.assetsPath + "LargerSeedFile.bim"; + const description = "Test iModel created by imodeljs-clients tests"; + mockGetIModel(projectId, createIModelName, Guid.createValue(), 1); + mockCreateiModel(projectId, Guid.createValue(), createIModelName, description, filePath, 2); + const progressTracker = new utils.ProgressTracker(); + + let error: IModelHubError | undefined; + try { + await imodelClient.iModel.create(alctx, accessToken, projectId, createIModelName, filePath, description, progressTracker.track()); + } catch (err) { + if (err instanceof IModelHubError) + error = err; + } + chai.assert(error); + chai.expect(error!.errorNumber).to.be.equal(IModelHubStatus.iModelAlreadyExists); + }); + + it("should update iModel", async () => { + imodelId = imodelId || Guid.createValue(); + mockGetIModel(projectId, imodelName, imodelId, 1); + const newName = imodelName + "_updated"; + const newDescription = "Description_updated"; + + const imodel = await iModelClient.iModel.get(alctx, accessToken, projectId); + imodel.name = newName; + imodel.description = newDescription; + mockUpdateiModel(projectId, imodel); + let updatediModel = await iModelClient.iModel.update(alctx, accessToken, projectId, imodel); + + chai.expect(updatediModel.wsgId).to.be.equal(imodel.wsgId); + chai.expect(updatediModel.name).to.be.equal(newName); + chai.expect(updatediModel.description).to.be.equal(newDescription); + + mockGetIModel(projectId, newName, imodel.id!, 1, newDescription); + updatediModel = await iModelClient.iModel.get(alctx, accessToken, projectId); + + mockGetIModel(projectId, newName, imodel.id!, 1, newDescription); + mockDeleteiModel(projectId, imodel.id!); + await iModelClient.iModel.delete(alctx, accessToken, projectId); + + chai.assert(!!updatediModel); + chai.expect(updatediModel.wsgId).to.be.equal(imodel.wsgId); + chai.expect(updatediModel.name).to.be.equal(newName); + chai.expect(updatediModel.description).to.be.equal(newDescription); + }); + + it("should download a Seed File if iModel exist", async () => { + mockGetSeedFile(imodelId, true); + mockGetIModel(projectId, imodelName, imodelId, 1); + const downloadToPathname: string = path.join(utils.workDir, imodelId.toString()); + utils.mockFileResponse(); + + const progressTracker = new utils.ProgressTracker(); + await iModelClient.iModel.download(alctx, accessToken, projectId, downloadToPathname, progressTracker.track()); + progressTracker.check(); + fs.existsSync(downloadToPathname).should.be.equal(true); + }); }); diff --git a/core/clients/tslint.json b/core/clients/tslint.json index 04f8602..7cf8283 100644 --- a/core/clients/tslint.json +++ b/core/clients/tslint.json @@ -4,14 +4,6 @@ "no-console": [ false ], - "unified-signatures": false, - "space-before-function-paren": [ - true, - { - "anonymous": "always", - "named": "never", - "asyncArrow": "always" - } - ] + "unified-signatures": false } } \ No newline at end of file diff --git a/core/common/CHANGELOG.json b/core/common/CHANGELOG.json index 4aaf474..11708c2 100644 --- a/core/common/CHANGELOG.json +++ b/core/common/CHANGELOG.json @@ -1,6 +1,114 @@ { "name": "@bentley/imodeljs-common", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-common_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "Polyfill URLSearchParams for edge." + }, + { + "comment": "Front end \"read pixels\" can now provide subCategoryId and GeometryClass to backend." + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-common_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Fix GeometryParams constructor. Added test to ensure subcategory id set correctly." + }, + { + "comment": "Remove dependency on 'window'-named global object." + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-common_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": { + "none": [ + { + "comment": "GeometryStream markdown" + } + ] + } + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-common_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-common_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Add CartographicRange" + }, + { + "comment": "Use URL query instead of path segment for cacheable RPC request parameters." + }, + { + "comment": "Updated Mobile RPC to deal with binary data" + }, + { + "comment": "Temporarily disable tile caching." + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-common_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-common_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-common_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Fix JSON representation of display styles." + }, + { + "comment": "GeoJson and Analysis Importer simplification" + }, + { + "comment": "ModelSelectorProps, CategorySelectorProps, and DisplayStyleProps now properly extend DefinitionElementProps" + }, + { + "comment": "Support displacement scale for PolyfaceAuxData" + }, + { + "comment": "Do not diffentiate between backend provisioning and imodel downloading state in RPC wire format (202 for all)." + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-common_v0.163.0", diff --git a/core/common/CHANGELOG.md b/core/common/CHANGELOG.md index 66cfe3c..0d97cdf 100644 --- a/core/common/CHANGELOG.md +++ b/core/common/CHANGELOG.md @@ -1,6 +1,66 @@ # Change Log - @bentley/imodeljs-common -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- Polyfill URLSearchParams for edge. +- Front end "read pixels" can now provide subCategoryId and GeometryClass to backend. + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Fix GeometryParams constructor. Added test to ensure subcategory id set correctly. +- Remove dependency on 'window'-named global object. + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +### Updates + +- GeometryStream markdown + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Add CartographicRange +- Use URL query instead of path segment for cacheable RPC request parameters. +- Updated Mobile RPC to deal with binary data +- Temporarily disable tile caching. + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Fix JSON representation of display styles. +- GeoJson and Analysis Importer simplification +- ModelSelectorProps, CategorySelectorProps, and DisplayStyleProps now properly extend DefinitionElementProps +- Support displacement scale for PolyfaceAuxData +- Do not diffentiate between backend provisioning and imodel downloading state in RPC wire format (202 for all). +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/common/package.json b/core/common/package.json index 0aeadc0..ed63d0c 100644 --- a/core/common/package.json +++ b/core/common/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-common", - "version": "0.163.0", + "version": "0.171.0", "description": "iModel.js components common to frontend and backend", "license": "MIT", "main": "lib/common.js", @@ -13,8 +13,8 @@ "clean": "rimraf lib package-deps.json", "docs": "node ./node_modules/@bentley/build-tools/scripts/docs.js --source=./src --includes=../../generated-docs/extract --json=../../generated-docs/core/imodeljs-common/file.json --tsIndexFile=./common.ts --onlyJson %TYPEDOC_THEME%", "lint": "tslint --project . 1>&2", - "test": "", - "cover": "" + "test": "node ./node_modules/@bentley/build-tools/scripts/test.js", + "cover": "nyc npm test" }, "repository": { "type": "git", @@ -34,34 +34,48 @@ "NOTE: these dependencies should be only for things that DO NOT APPEAR IN THE API" ], "dependencies": { - "semver": "^5.5.0" + "semver": "^5.5.0", + "url-search-params": "^1.1.0" }, "//peerDependencies": [ "NOTE: peerDependencies are a standard way for npm to perform a module compatibility check" ], "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0" }, "//devDependencies": [ "NOTE: Must include modules mentioned in peerDependencies since those are not auto-installed", "NOTE: Must include modules used by the scripts section of package.json" ], "devDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0", "@types/semver": "^5.5.0", + "@types/url-search-params": "^0.10.2", "cpx": "^1.5.0", "rimraf": "^2.6.2", "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0", + "@types/chai": "^4.1.4", + "@types/mocha": "^5.2.5", + "@types/node": "10.10.3", + "chai": "^4.1.2", + "mocha": "^5.2.0", + "ts-node": "^7.0.1", + "nyc": "^13.0.1", + "source-map-support": "^0.5.6" }, "//optionalDependencies": [ "NOTE: Rush (as of 4.2.5) does not support optionalDependencies!" - ] + ], + "nyc": { + "nycrc-path": "./node_modules/@bentley/build-tools/.nycrc", + "all": true + } } diff --git a/core/common/src/EntityProps.ts b/core/common/src/EntityProps.ts index b3d6af9..01b26a0 100644 --- a/core/common/src/EntityProps.ts +++ b/core/common/src/EntityProps.ts @@ -14,13 +14,6 @@ export interface EntityProps { classFullName: string; /** The Id of the entity. Must be present for SELECT, UPDATE, or DELETE, ignored for INSERT. */ id?: Id64String; - /** - * If this Entity is *not* from the `BisCore` schema, the name of the highest level (leaf-most) - * class *in the BisCore schema* for this entity. If the entity is an instance of a class in the BisCore schema - * this will be the same class name as in `classFullName`. This can be helpful to classify entities on the - * front end where the class hierarchy is not available. - */ - bisBaseClass?: string; [propName: string]: any; } diff --git a/core/common/src/IModel.ts b/core/common/src/IModel.ts index 33c7428..5d347fc 100644 --- a/core/common/src/IModel.ts +++ b/core/common/src/IModel.ts @@ -5,7 +5,7 @@ /** @module iModels */ import { Id64String, Id64, GuidString, IModelStatus, OpenMode } from "@bentley/bentleyjs-core"; -import { Point3d, XYZProps, Range3dProps, YawPitchRollProps, YawPitchRollAngles, Transform, XYAndZ } from "@bentley/geometry-core"; +import { Point3d, XYZProps, Range3dProps, YawPitchRollProps, YawPitchRollAngles, Transform, XYAndZ, Vector3d, Matrix3d, AxisOrder } from "@bentley/geometry-core"; import { AxisAlignedBox3d } from "./geometry/Primitives"; import { ThumbnailProps } from "./Thumbnail"; import { IModelError } from "./IModelError"; @@ -53,6 +53,14 @@ export class EcefLocation implements EcefLocationProps { this.origin.freeze(); // may not be modified this.orientation.freeze(); // may not be modified } + /** Construct ECEF Location from cartographic origin. */ + public static createFromCartographicOrigin(origin: Cartographic) { + const ecefOrigin = origin.toEcef(); + const zVector = Vector3d.createFrom(ecefOrigin).normalize(); + const xVector = Vector3d.create(-Math.sin(origin.longitude), Math.cos(origin.latitude), 0.0); + const matrix = Matrix3d.createRigidFromColumns(zVector!, xVector, AxisOrder.ZXY); + return new EcefLocation({ origin: ecefOrigin, orientation: YawPitchRollAngles.createFromMatrix3d(matrix!)! }); + } } /** Properties of the [Root Subject]($docs/bis/intro/glossary#subject-root). */ diff --git a/core/common/src/IModelVersion.ts b/core/common/src/IModelVersion.ts index b523254..80c5575 100644 --- a/core/common/src/IModelVersion.ts +++ b/core/common/src/IModelVersion.ts @@ -87,7 +87,7 @@ export class IModelVersion { * version was already specified as of a ChangeSet, the method simply returns * that Id without any validation. */ - public evaluateChangeSet(alctx: ActivityLoggingContext, accessToken: AccessToken, iModelId: string, imodelClient: IModelClient): Promise { + public async evaluateChangeSet(alctx: ActivityLoggingContext, accessToken: AccessToken, iModelId: string, imodelClient: IModelClient): Promise { if (this._first) return Promise.resolve(""); @@ -108,7 +108,7 @@ export class IModelVersion { /** Gets the last change set that was applied to the imodel */ private static async getLatestChangeSetId(alctx: ActivityLoggingContext, imodelClient: IModelClient, accessToken: AccessToken, iModelId: GuidString): Promise { - const changeSets: ChangeSet[] = await imodelClient.ChangeSets().get(alctx, accessToken, iModelId, new ChangeSetQuery().top(1).latest()); + const changeSets: ChangeSet[] = await imodelClient.changeSets.get(alctx, accessToken, iModelId, new ChangeSetQuery().top(1).latest()); // todo: Need a more efficient iModel Hub API to get this information from the Hub. return (changeSets.length === 0) ? "" : changeSets[changeSets.length - 1].wsgId; @@ -116,7 +116,7 @@ export class IModelVersion { /** Get the change set from the specified named version */ private static async getChangeSetFromNamedVersion(alctx: ActivityLoggingContext, imodelClient: IModelClient, accessToken: AccessToken, iModelId: string, versionName: string): Promise { - const versions = await imodelClient.Versions().get(alctx, accessToken, iModelId, new VersionQuery().select("ChangeSetId").byName(versionName)); + const versions = await imodelClient.versions.get(alctx, accessToken, iModelId, new VersionQuery().select("ChangeSetId").byName(versionName)); if (!versions[0] || !versions[0].changeSetId) { return Promise.reject(new IModelError(BentleyStatus.ERROR, "Problem getting versions")); diff --git a/core/common/src/Render.ts b/core/common/src/Render.ts index 66f5596..163fcfc 100644 --- a/core/common/src/Render.ts +++ b/core/common/src/Render.ts @@ -5,17 +5,16 @@ /** @module Rendering */ import { Id64, Id64String, JsonUtils, assert, IndexMap, IndexedValue, Comparable, compare, compareNumbers, compareStrings, IDisposable } from "@bentley/bentleyjs-core"; -import { ColorDef, ColorDefProps } from "./ColorDef"; +import { ColorDef, ColorDefProps, ColorByName } from "./ColorDef"; import { Light } from "./Lighting"; import { IModel } from "./IModel"; import { Point3d, XYAndZ, Transform, Angle, AngleProps, Vector3d, ClipPlane, Point2d, IndexedPolyfaceVisitor, PolyfaceVisitor, Range1d } from "@bentley/geometry-core"; import { LineStyle } from "./geometry/LineStyle"; -import { CameraProps } from "./ViewProps"; +import { CameraProps, ViewFlagProps, GroundPlaneProps } from "./ViewProps"; import { OctEncodedNormalPair } from "./OctEncodedNormal"; import { AreaPattern } from "./geometry/AreaPattern"; import { Frustum } from "./Frustum"; import { ImageBuffer, ImageBufferFormat } from "./Image"; -import { ViewFlagProps } from "./ViewProps"; /** Flags indicating whether and how the interiors of closed planar regions is displayed within a view. */ export enum FillFlags { @@ -240,7 +239,7 @@ export abstract class RenderTexture implements IDisposable { } /** Releases any WebGL resources owned by this texture. - * If [[RenderTexture.isOwned]] is true, then whatever object claims ownership of the texture is reponsible for disposing of it when it is no longer needed. + * If [[RenderTexture.isOwned]] is true, then whatever object claims ownership of the texture is responsible for disposing of it when it is no longer needed. * Otherwise, imodeljs will handle its disposal. */ public abstract dispose(): void; @@ -457,10 +456,20 @@ export class Camera implements CameraProps { this.focusDist = rhs.focusDist; this.eye.setFrom(rhs.eye); } - public constructor(json: CameraProps) { - this.lens = Angle.fromJSON(json.lens); - this.focusDist = JsonUtils.asDouble(json.focusDist); - this.eye = Point3d.fromJSON(json.eye); + + /** Construct a Camera + * @param props The properties of the new camera. If undefined, create a camera with eye at {0,0,0}, 90 degree lens, 1 meter focus distance. + */ + public constructor(props?: CameraProps) { + if (props !== undefined) { + this.lens = Angle.fromJSON(props.lens); + this.focusDist = JsonUtils.asDouble(props.focusDist); + this.eye = Point3d.fromJSON(props.eye); + return; + } + this.lens = Angle.createRadians(Math.PI / 2.0); + this.focusDist = 1; + this.eye = new Point3d(); } } @@ -1618,7 +1627,7 @@ export class GeometryParams { */ public backgroundFill?: BackgroundFill; /** Optional fill specification that determines when and if a region interior will display using [[gradient]], [[backgroundFill]], or [[fillColor]] in that order of preference. - * Fill only applies to [[RenderMode.Wireframe]] views. In a [[RenderMode.SmoothShade]] or [[RenderMode.SolidFill]] view, regions will always display as surfaces prefering [[fillColor]] when present over [[lineColor]]. + * Fill only applies to [[RenderMode.Wireframe]] views. In a [[RenderMode.SmoothShade]] or [[RenderMode.SolidFill]] view, regions will always display as surfaces preferring [[fillColor]] when present over [[lineColor]]. * Default is [[FillDisplay.Never]]. */ public fillDisplay?: FillDisplay; @@ -1655,7 +1664,7 @@ export class GeometryParams { * @note If a valid SubCategory Id is not supplied, the default SubCategory for the parent Category is used. To be considered valid, [[SubCategory.getCategoryId]] must refer to the specified Category Id. */ constructor(public categoryId: Id64String, public subCategoryId = Id64.invalid) { - if (Id64.isValid(subCategoryId)) + if (!Id64.isValid(subCategoryId)) this.subCategoryId = IModel.getDefaultSubCategoryId(categoryId); } @@ -2149,13 +2158,14 @@ export namespace TextureMapping { } } } + /** Properties for display of analysis data */ export interface AnalysisStyleProps { inputName?: string; displacementChannelName?: string; scalarChannelName?: string; normalChannelName?: string; - displacementScale: number; + displacementScale?: number; scalarRange?: Range1d; scalarThematicSettings?: Gradient.ThematicSettingsProps; inputRange?: Range1d; @@ -2166,7 +2176,7 @@ export class AnalysisStyle implements AnalysisStyleProps { public displacementChannelName?: string; public scalarChannelName?: string; public normalChannelName?: string; - public displacementScale: number = 1.0; + public displacementScale?: number; public scalarRange?: Range1d; public scalarThematicSettings?: Gradient.ThematicSettings; public inputRange?: Range1d; @@ -2206,3 +2216,66 @@ export class AnalysisStyle implements AnalysisStyleProps { return result; } } + +/** A circle drawn at a Z elevation, whose diameter is the the XY diagonal of the project extents, used to represent the ground as a reference point within a spatial view. */ +export class GroundPlane implements GroundPlaneProps { + /** Whether the ground plane should be displayed. */ + public display: boolean = false; + /** The Z height at which to draw the plane. */ + public elevation: number = 0.0; + /** The color in which to draw the ground plane when viewed from above. */ + public aboveColor: ColorDef; + /** The color in which to draw the ground plane when viewed from below. */ + public belowColor: ColorDef; + private _aboveSymb?: Gradient.Symb; + private _belowSymb?: Gradient.Symb; + + public constructor(ground?: GroundPlaneProps) { + ground = ground ? ground : {}; + this.display = JsonUtils.asBool(ground.display, false); + this.elevation = JsonUtils.asDouble(ground.elevation, -.01); + this.aboveColor = (undefined !== ground.aboveColor) ? ColorDef.fromJSON(ground.aboveColor) : new ColorDef(ColorByName.darkGreen); + this.belowColor = (undefined !== ground.belowColor) ? ColorDef.fromJSON(ground.belowColor) : new ColorDef(ColorByName.darkBrown); + } + + public toJSON(): GroundPlaneProps { + return { + display: this.display, + elevation: this.elevation, + aboveColor: this.aboveColor.toJSON(), + belowColor: this.belowColor.toJSON(), + }; + } + + /** + * Returns and locally stores gradient symbology for the ground plane texture depending on whether we are looking from above or below. + * Will store the ground colors used in the optional ColorDef array provided. + * @hidden + */ + public getGroundPlaneGradient(aboveGround: boolean): Gradient.Symb { + let gradient = aboveGround ? this._aboveSymb : this._belowSymb; + if (undefined !== gradient) + return gradient; + + const values = [0, .25, .5]; // gradient goes from edge of rectangle (0.0) to center (1.0)... + const color = aboveGround ? this.aboveColor : this.belowColor; + const alpha = aboveGround ? 0x80 : 0x85; + const groundColors = [color.clone(), color.clone(), color.clone()]; + groundColors[0].setTransparency(0xff); + groundColors[1].setTransparency(alpha); + groundColors[2].setTransparency(alpha); + + // Get the possibly cached gradient from the system, specific to whether or not we want ground from above or below. + gradient = new Gradient.Symb(); + gradient.mode = Gradient.Mode.Spherical; + gradient.keys = [{ color: groundColors[0], value: values[0] }, { color: groundColors[1], value: values[1] }, { color: groundColors[2], value: values[2] }]; + + // Store the gradient for possible future use + if (aboveGround) + this._aboveSymb = gradient; + else + this._belowSymb = gradient; + + return gradient; + } +} diff --git a/core/common/src/RenderSchedule.ts b/core/common/src/RenderSchedule.ts new file mode 100644 index 0000000..b671849 --- /dev/null +++ b/core/common/src/RenderSchedule.ts @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Rendering */ + +import { Id64, Id64String } from "@bentley/bentleyjs-core"; +import { Range1d } from "@bentley/geometry-core"; +import { RgbColor } from "./ColorDef"; + +export namespace RenderSchedule { + + export class SymbologyOverride { + public rgb?: RgbColor; + public transparency?: number; + constructor(rgb?: RgbColor, transparency?: number) { this.rgb = rgb; this.transparency = transparency; } + } + class Interval { + constructor(public index0: number = 0, public index1: number = 0, public fraction: number = 0.0) { } + public init(index0: number, index1: number, fraction: number) { this.index0 = index0; this.index1 = index1; this.fraction = fraction; } + } + export interface TimelineEntryProps { + time: number; + interpolation: number; + } + export class TimelineEntry implements TimelineEntryProps { + public time: number; + public interpolation: number; + constructor(props: TimelineEntryProps) { + this.time = props.time; + this.interpolation = props.interpolation; + } + } + + export interface VisibilityEntryProps extends TimelineEntryProps { + value: number; + } + export class VisibilityEntry extends TimelineEntry implements VisibilityEntryProps { + public value: number = 100.0; + constructor(props: VisibilityEntryProps) { + super(props); + this.value = props.value; + } + } + export interface ColorEntryProps extends TimelineEntryProps { + value: { red: number, green: number, blue: number }; + } + export class ColorEntry extends TimelineEntry implements ColorEntryProps { + public value: { red: number, green: number, blue: number }; + constructor(props: ColorEntryProps) { + super(props); + this.value = props.value; + } + } + export interface ElementTimelineProps { + elementID: Id64String; + visibilityTimeline?: VisibilityEntryProps[]; + colorTimeline?: ColorEntryProps[]; + } + function interpolate(value0: number, value1: number, fraction: number) { + return value0 + fraction * (value1 - value0); + } + export class ElementTimeline implements ElementTimelineProps { + public elementID: Id64String; + public visibilityTimeline?: VisibilityEntry[]; + public colorTimeline?: ColorEntry[]; + public get isValid() { return !Id64.isInvalid(this.elementID) && (Array.isArray(this.visibilityTimeline) && this.visibilityTimeline.length > 0) || (Array.isArray(this.colorTimeline) && this.colorTimeline.length > 0); } + private constructor(elementId: Id64String) { this.elementID = elementId; } + public static fromJSON(json?: ElementTimelineProps): ElementTimeline { + if (!json) + return new ElementTimeline(""); + + const val = new ElementTimeline(json.elementID); + if (json.visibilityTimeline) { + val.visibilityTimeline = []; + json.visibilityTimeline.forEach((entry) => val.visibilityTimeline!.push(new VisibilityEntry(entry))); + } + if (json.colorTimeline) { + val.colorTimeline = []; + json.colorTimeline.forEach((entry) => val.colorTimeline!.push(new ColorEntry(entry))); + } + return val; + } + public get duration() { + const duration = Range1d.createNull(); + if (this.visibilityTimeline) this.visibilityTimeline.forEach((entry) => duration.extendX(entry.time)); + if (this.colorTimeline) this.colorTimeline.forEach((entry) => duration.extendX(entry.time)); + + return duration; + } + public get containsFeatureOverrides() { return undefined !== this.visibilityTimeline && undefined !== this.colorTimeline; } + + private static findTimelineInterval(interval: Interval, time: number, timeline?: TimelineEntry[]) { + if (!timeline || timeline.length === 0) + return false; + + if (time <= timeline[0].time) { + interval.init(0, 0, 0.0); + return true; + } + const last = timeline.length - 1; + if (time >= timeline[last].time) { + interval.init(last, last, 0.0); + return true; + } + let i: number; + for (i = 0; i < last; i++) + if (timeline[i].time <= time && timeline[i + 1].time >= time) { + interval.init(i, i + 1, timeline[i].interpolation ? ((time - timeline[i].time) / (timeline[i + 1].time - timeline[i].time)) : 0.0); + break; + } + return true; + } + + public getSymbologyOverrides(overrides: Map, time: number) { + const interval = new Interval(); + let override; + if (ElementTimeline.findTimelineInterval(interval, time, this.colorTimeline)) { + const entry0 = this.colorTimeline![interval.index0], entry1 = this.colorTimeline![interval.index1]; + const color = new RgbColor(interpolate(entry0.value.red, entry1.value.red, interval.fraction), interpolate(entry0.value.green, entry1.value.green, interval.fraction), interpolate(entry0.value.blue, entry1.value.blue, interval.fraction)); + override = new SymbologyOverride(color); + } + if (ElementTimeline.findTimelineInterval(interval, time, this.visibilityTimeline)) { + const timeline = this.visibilityTimeline!; + if (!override) override = new SymbologyOverride(); + override.transparency = 1.0 - interpolate(timeline[interval.index0].value, timeline[interval.index1].value, interval.fraction) / 100.0; + } + if (override) + overrides.set(this.elementID, override); + } + } + + export class Script { + public duration: Range1d = Range1d.createNull(); + public elementTimelines?: ElementTimeline[]; + public containsFeatureOverrides: boolean = false; + + public static fromJSON(elementTimelines: ElementTimelineProps[]): Script | undefined { + if (elementTimelines.length === 0) + return undefined; + + const value = new Script(); + value.elementTimelines = []; + elementTimelines.forEach((entry) => { + const elementTimeline = ElementTimeline.fromJSON(entry); + value.elementTimelines!.push(elementTimeline); + value.duration.extendRange(elementTimeline.duration); + if (elementTimeline.containsFeatureOverrides) + value.containsFeatureOverrides = true; + }); + + return value; + } + + public getSymbologyOverrides(time: number) { + const overrides: Map = new Map(); + if (this.elementTimelines) + this.elementTimelines.forEach((entry) => entry.getSymbologyOverrides(overrides, time)); + + return overrides; + } + } +} diff --git a/core/common/src/RpcInterface.ts b/core/common/src/RpcInterface.ts index 1800621..6b14195 100644 --- a/core/common/src/RpcInterface.ts +++ b/core/common/src/RpcInterface.ts @@ -32,7 +32,7 @@ export abstract class RpcInterface { public readonly configuration = RpcConfiguration.supply(this); /** Obtains the implementation result for an RPC operation. */ - public forward(operation: string, ...parameters: any[]): Promise { + public async forward(operation: string, ...parameters: any[]): Promise { const request = new (this.configuration.protocol.requestType as any)(this, operation, parameters); request.submit(); (this as any)[CURRENT_REQUEST] = request; diff --git a/core/common/src/RpcManager.ts b/core/common/src/RpcManager.ts index e50ff91..8be6620 100644 --- a/core/common/src/RpcManager.ts +++ b/core/common/src/RpcManager.ts @@ -54,7 +54,7 @@ export class RpcManager { * Describes the RPC interfaces and endpoints that are currently available from the backend. * @note Some endpoints may be marked incompatible if the frontend expected a different interface declaration than the backend supplied. RPC operations against an incompatible interface will fail. */ - public static describeAvailableEndpoints(): Promise { + public static async describeAvailableEndpoints(): Promise { return RpcRegistry.instance.describeAvailableEndpoints(); } } diff --git a/core/common/src/Snapping.ts b/core/common/src/Snapping.ts index 151f365..a3a4df5 100644 --- a/core/common/src/Snapping.ts +++ b/core/common/src/Snapping.ts @@ -7,6 +7,7 @@ import { Id64String, Id64Array } from "@bentley/bentleyjs-core"; import { XYZProps, Matrix4dProps } from "@bentley/geometry-core"; import { GeometryStreamProps } from "./geometry/GeometryStream"; +import { GeometryClass } from "./Render"; /** * Information required to request a *snap* to a pickable decoration from the front end to the back end. @@ -28,7 +29,8 @@ export interface SnapRequestProps { snapModes?: number[]; snapAperture?: number; snapDivisor?: number; - offSubCategories?: Id64Array; + subCategoryId?: Id64String; + geometryClass?: GeometryClass; intersectCandidates?: Id64Array; decorationGeometry?: DecorationGeometryProps[]; } @@ -42,8 +44,6 @@ export interface SnapResponseProps { heat?: number; geomType?: number; parentGeomType?: number; - category?: string; - subCategory?: string; hitPoint?: XYZProps; snapPoint?: XYZProps; normal?: XYZProps; diff --git a/core/common/src/ViewProps.ts b/core/common/src/ViewProps.ts index e38005a..4171349 100644 --- a/core/common/src/ViewProps.ts +++ b/core/common/src/ViewProps.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ /** @module Views */ -import { Id64String, Id64Array } from "@bentley/bentleyjs-core"; +import { Id64, Id64String, Id64Array, JsonUtils } from "@bentley/bentleyjs-core"; import { EntityQueryParams } from "./EntityProps"; import { AngleProps, XYZProps, XYProps, YawPitchRollProps } from "@bentley/geometry-core"; import { ElementProps, DefinitionElementProps, SheetProps } from "./ElementProps"; -import { ColorDef } from "./ColorDef"; -import { ViewFlags, AnalysisStyle } from "./Render"; +import { ColorDef, ColorDefProps } from "./ColorDef"; +import { ViewFlags, AnalysisStyleProps, AnalysisStyle, HiddenLine } from "./Render"; +import { SubCategoryAppearance, SubCategoryOverride } from "./SubCategoryAppearance"; +import { RenderSchedule } from "./RenderSchedule"; /** Returned from [IModelDb.Views.getViewStateData]($backend) */ export interface ViewStateData { @@ -20,22 +22,15 @@ export interface ViewStateData { sheetProps?: SheetProps; sheetAttachments?: Id64Array; } + /** Properties that define a ModelSelector */ -export interface ModelSelectorProps extends ElementProps { - models: string[]; +export interface ModelSelectorProps extends DefinitionElementProps { + models: Id64Array; } /** Properties that define a CategorySelector */ -export interface CategorySelectorProps extends ElementProps { - categories: string[]; -} - -/** Properties that define a DisplayStyle */ -export interface DisplayStyleProps extends ElementProps { - viewFlags: ViewFlags; - backgroundColor: ColorDef; - monochromeColor: ColorDef; - AnalysisStyle?: AnalysisStyle; +export interface CategorySelectorProps extends DefinitionElementProps { + categories: Id64Array; } export interface ViewQueryParams extends EntityQueryParams { @@ -101,6 +96,161 @@ export interface ViewFlagProps { backgroundMap?: boolean; } +/** Describes the [[SubCategoryOverride]]s applied to a [[SubCategory]] by a [[DisplayStyle]]. + * @see [[DisplayStyleSettingsProps]] + */ +export interface DisplayStyleSubCategoryProps extends SubCategoryAppearance.Props { + /** The Id of the [[SubCategory]] whose appearance is to be overridden. */ + subCategory?: Id64String; +} + +/** Describes the type of background map displayed by a [[DisplayStyle]] + * @see [[BackgroundMapProps]] + * @see [[DisplayStyleSettingsProps]] + */ +export const enum BackgroundMapType { + Street = 1, + Aerial = 2, + Hybrid = 3, +} + +/** JSON representation of the settings associated with a background map displayed by a [[DisplayStyle]]. + * @see [[DisplayStyleSettingsProps]] + */ +export interface BackgroundMapProps { + groundBias?: number; + /** "BingProvider" | "MapProvider" currently supported; others may be added in future. */ + providerName?: string; + providerData?: { + mapType?: BackgroundMapType; + }; +} + +/** JSON representation of a [[GroundPlane]]. */ +export interface GroundPlaneProps { + /** Whether the ground plane should be displayed. Defaults to false. */ + display?: boolean; + /** The Z height at which to draw the ground plane. */ + elevation?: number; + /** The color in which to draw the ground plane when viewed from above. */ + aboveColor?: ColorDefProps; + /** The color in which to draw the ground plane when viewed from below. */ + belowColor?: ColorDefProps; +} + +/** Enumerates the supported types of [[SkyBox]] images. */ +export const enum SkyBoxImageType { + None, + /** A single image mapped to the surface of a sphere. @see [[SkySphere]] */ + Spherical, + /** 6 images mapped to the faces of a cube. @see [[SkyCube]] */ + Cube, + /** @hidden not yet supported */ + Cylindrical, +} + +/** JSON representation of a set of images used by a [[SkyCube]]. Each property specifies the element ID of a texture associated with one face of the cube. */ +export interface SkyCubeProps { + front?: Id64String; + back?: Id64String; + top?: Id64String; + bottom?: Id64String; + right?: Id64String; + left?: Id64String; +} + +/** JSON representation of an image or images used by a [[SkySphere]] or [[SkyCube]]. */ +export interface SkyBoxImageProps { + /** The type of skybox image. */ + type?: SkyBoxImageType; + /** For [[SkyBoxImageType.Spherical]], the element ID of the texture to be drawn as the "sky". */ + texture?: Id64String; + /** For [[SkyBoxImageType.Cube]], the IDs of the texture elements drawn on each face of the cube. */ + textures?: SkyCubeProps; +} + +/** JSON representation of a [[SkyBox]]. */ +export interface SkyBoxProps { + /** Whether or not the skybox should be displayed. Defaults to false. */ + display?: boolean; + /** @hidden ###TODO Figure out how this is used... */ + twoColor?: boolean; + /** @hidden ###TODO Figure out how this is used... */ + groundExponent?: number; + /** @hidden ###TODO Figure out how this is used... */ + skyExponent?: number; + /** For a [[SkyGradient]], the color of the ground. */ + groundColor?: ColorDefProps; + /** @hidden ###TODO Figure out how this is used... */ + zenithColor?: ColorDefProps; + /** @hidden ###TODO Figure out how this is used... */ + nadirColor?: ColorDefProps; + /** For a [[SkyGradient]], the color of the sky. */ + skyColor?: ColorDefProps; + /** For a [[SkySphere]] or [[SkyCube]], the skybox image(s). */ + image?: SkyBoxImageProps; +} + +/** JSON representation of the environment setup of a [[DisplayStyle3d]]. */ +export interface EnvironmentProps { + ground?: GroundPlaneProps; + sky?: SkyBoxProps; +} +/** JSON representation of a context reality model */ +export interface ContextRealityModelProps { + tilesetUrl: string; + name?: string; +} + +/** JSON representation of the settings associated with a [[DisplayStyleProps]]. + * These settings are not stored directly as members of the [[DisplayStyleProps]]. Instead, they are stored + * as members of `jsonProperties.styles`. + * @see [[DisplayStyleSettings]]. + */ +export interface DisplayStyleSettingsProps { + viewflags?: ViewFlagProps; + /** The color displayed in the view background. Defaults to black. */ + backgroundColor?: ColorDefProps; + /** The color used in monochrome mode. Defaults to white. */ + monochromeColor?: ColorDefProps; + /** Settings controlling display of analytical models. */ + analysisStyle?: AnalysisStyleProps; + /** Schedule script */ + scheduleScript?: RenderSchedule.ElementTimelineProps[]; + /** Overrides applied to the appearances of subcategories in the view. */ + subCategoryOvr?: DisplayStyleSubCategoryProps[]; + /** Settings controlling display of map imagery within views of geolocated models. */ + backgroundMap?: BackgroundMapProps; + /** Contexual Reality Models */ + ContextRealityModels?: ContextRealityModelProps[]; +} + +/** JSON representation of settings assocaited with a [[DisplayStyle3dProps]]. + * @see [[DisplayStyle3dSettings]]. + */ +export interface DisplayStyle3dSettingsProps extends DisplayStyleSettingsProps { + /** Settings controlling display of skybox and ground plane. */ + environment?: EnvironmentProps; + /** Settings controlling display of visible and hidden edges. */ + hline?: HiddenLine.SettingsProps; +} + +/** JSON representation of a [[DisplayStyle]] or [[DisplayStyleState]]. */ +export interface DisplayStyleProps extends DefinitionElementProps { + /** Display styles store their settings in a `styles` property within [[ElementProps.jsonProperties]]. */ + jsonProperties?: { + styles?: DisplayStyleSettingsProps; + }; +} + +/** JSON representation of a [[DisplayStyle3d]] or [[DisplayStyle3dState]]. */ +export interface DisplayStyle3dProps extends DisplayStyleProps { + /** Display styles store their settings in a `styles` property within [[ElementProps.jsonProperties]]. */ + jsonProperties?: { + styles?: DisplayStyle3dSettingsProps; + }; +} + /** properties of a camera */ export interface CameraProps { lens: AngleProps; @@ -159,3 +309,207 @@ export interface AuxCoordSystem3dProps extends AuxCoordSystemProps { /** Roll angle */ roll?: AngleProps; } + +/** Provides access to the settings defined by a [[DisplayStyle]] or [[DisplayStyleState]], and ensures that + * the style's JSON properties are kept in sync. + */ +export class DisplayStyleSettings { + protected readonly _json: DisplayStyleSettingsProps; + private readonly _viewFlags: ViewFlags; + private readonly _background: ColorDef; + private readonly _monochrome: ColorDef; + private _analysisStyle?: AnalysisStyle; + private _scheduleScript?: RenderSchedule.Script; + private readonly _subCategoryOverrides: Map = new Map(); + + /** Construct a new DisplayStyleSettings from an [[ElementProps.jsonProperties]]. + * @param jsonProperties An object with an optional `styles` property containing a display style's settings. + * @note When the `DisplayStyleSetting`'s properties are modified by public setters, the `jsonProperties`'s `styles` object will be updated to reflect the change. + * @note If `jsonProperties` contains no `styles` member, one will be added as an empty object. + * @note Generally there is no reason to create an object of this type directly; a [[DisplayStyle]] or [[DisplayStyleState]] constructs one as part of its own construction. + */ + public constructor(jsonProperties: { styles?: DisplayStyleSettingsProps }) { + if (undefined === jsonProperties.styles) + jsonProperties.styles = {}; + + this._json = jsonProperties.styles; + + this._viewFlags = ViewFlags.fromJSON(this._json.viewflags); + this._background = ColorDef.fromJSON(this._json.backgroundColor); + this._monochrome = undefined !== this._json.monochromeColor ? ColorDef.fromJSON(this._json.monochromeColor) : ColorDef.white.clone(); + + if (undefined !== this._json.analysisStyle) + this._analysisStyle = AnalysisStyle.fromJSON(this._json.analysisStyle); + + if (undefined !== this._json.scheduleScript && Array.isArray(this._json.scheduleScript)) + this._scheduleScript = RenderSchedule.Script.fromJSON(this._json.scheduleScript); + + const ovrsArray = JsonUtils.asArray(this._json.subCategoryOvr); + if (undefined !== ovrsArray) { + for (const ovrJson of ovrsArray) { + const subCatId = Id64.fromJSON(ovrJson.subCategory); + if (Id64.isValid(subCatId)) { + const subCatOvr = SubCategoryOverride.fromJSON(ovrJson); + if (subCatOvr.anyOverridden) + this.changeSubCategoryOverride(subCatId, false, subCatOvr); + } + } + } + } + + /** The ViewFlags associated with the display style. + * @note If the style is associated with a [[ViewState]] attached to a [[Viewport]], use [[ViewState.viewFlags]] to modify the ViewFlags to ensure + * the changes are promptly visible on the screen. + * @note Do not modify the ViewFlags in place. Clone them and pass the clone to the setter. + */ + public get viewFlags(): ViewFlags { return this._viewFlags; } + public set viewFlags(flags: ViewFlags) { + flags.clone(this._viewFlags); + this._json.viewflags = flags.toJSON(); + } + + /** The background color. + * @note Do not modify the color in place. Clone it and pass the clone to the setter. + */ + public get backgroundColor(): ColorDef { return this._background; } + public set backgroundColor(color: ColorDef) { + this._background.setFrom(color); + this._json.backgroundColor = color.toJSON(); + } + + /** The color used to draw geometry in monochrome mode. + * @note Do not modify the color in place. Clone it and pass the clone to the setter. + * @see [[ViewFlags.monochrome]] for enabling monochrome mode. + */ + public get monochromeColor(): ColorDef { return this._monochrome; } + public set monochromeColor(color: ColorDef) { + this._monochrome.setFrom(color); + this._json.monochromeColor = color.toJSON(); + } + + /** @hidden */ + public get backgroundMap(): BackgroundMapProps | undefined { + const props = this._json.backgroundMap; + return undefined !== props ? props : {}; + } + /** @hidden */ + public set backgroundMap(map: BackgroundMapProps | undefined) { this._json.backgroundMap = map; } + + /** Settings controlling display of analytical models. + * @note Do not modify the style in place. Clone it and pass the clone to the setter. + */ + public get analysisStyle(): AnalysisStyle | undefined { return this._analysisStyle; } + public set analysisStyle(style: AnalysisStyle | undefined) { + if (undefined === style) { + this._analysisStyle = undefined; + } else { + if (undefined === this._analysisStyle) + this._analysisStyle = AnalysisStyle.fromJSON(style); + else + this._analysisStyle.copyFrom(style); + } + + this._json.analysisStyle = this._analysisStyle; + } + public get scheduleScript(): RenderSchedule.Script | undefined { return this._scheduleScript; } + + /** Customize the way geometry belonging to a [[SubCategory]] is drawn by this display style. + * @param id The ID of the SubCategory whose appearance is to be overridden. + * @param ovr The overrides to apply to the [[SubCategoryAppearance]]. + * @note If this style is associated with a [[ViewState]] attached to a [[Viewport]], use [[ViewState.overrideSubCategory]] to ensure + * the changes are promptly visible on the screen. + * @see [[dropSubCategoryOverride]] + */ + public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride): void { this.changeSubCategoryOverride(id, true, ovr); } + + /** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style. + * @param id The ID of the [[SubCategory]]. + * @note If this style is associated with a [[ViewState]] attached to a [[Viewport]], use [[ViewState.dropSubCategoryOverride]] to ensure + * the changes are promptly visible on the screen. + * @see [[overrideSubCategory]] + */ + public dropSubCategoryOverride(id: Id64String): void { this.changeSubCategoryOverride(id, true); } + + /** Obtain the overrides applied to a [[SubCategoryAppearance]] by this style. + * @param id The ID of the [[SubCategory]]. + * @returns The corresponding SubCategoryOverride, or undefined if the SubCategory's appearance is not overridden. + * @see [[overrideSubCategory]] + */ + public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined { return this._subCategoryOverrides.get(id); } + + /** Returns true if an [[SubCategoryOverride]s are defined by this style. */ + public get hasSubCategoryOverride(): boolean { return this._subCategoryOverrides.size > 0; } + + public toJSON(): DisplayStyleSettingsProps { return this._json; } + + private findIndexOfSubCategoryOverrideInJSON(id: Id64String, allowAppend: boolean): number { + const ovrsArray = JsonUtils.asArray(this._json.subCategoryOvr); + if (undefined === ovrsArray) { + if (allowAppend) { + this._json.subCategoryOvr = []; + return 0; + } else { + return -1; + } + } else { + for (let i = 0; i < ovrsArray.length; i++) { + if (ovrsArray[i].subCategory === id) + return i; + } + + return allowAppend ? ovrsArray.length : -1; + } + } + + private changeSubCategoryOverride(id: Id64String, updateJson: boolean, ovr?: SubCategoryOverride): void { + if (undefined === ovr) { + // undefined => drop the override if present. + this._subCategoryOverrides.delete(id); + if (updateJson) { + const index = this.findIndexOfSubCategoryOverrideInJSON(id, false); + if (-1 !== index) + this._json.subCategoryOvr!.splice(index, 1); + } + } else { + // add override, or update if present. + this._subCategoryOverrides.set(id, ovr); + if (updateJson) { + const index = this.findIndexOfSubCategoryOverrideInJSON(id, true); + this._json.subCategoryOvr![index] = ovr.toJSON(); + } + } + } +} + +/** Provides access to the settings defined by a [[DisplayStyle3d]] or [[DisplayStyle3dState]], and ensures that + * the style's JSON properties are kept in sync. + */ +export class DisplayStyle3dSettings extends DisplayStyleSettings { + private _hline: HiddenLine.Settings; + private get _json3d(): DisplayStyle3dSettingsProps { return this._json as DisplayStyle3dSettingsProps; } + + public constructor(jsonProperties: { styles?: DisplayStyle3dSettingsProps }) { + super(jsonProperties); + this._hline = HiddenLine.Settings.fromJSON(this._json3d.hline); + } + + public toJSON(): DisplayStyle3dSettingsProps { return this._json3d; } + + /** The settings that control how visible and hidden edges are displayed. + * @note Do not modify the settings in place. Clone them and pass the clone to the setter. + */ + public get hiddenLineSettings(): HiddenLine.Settings { return this._hline; } + public set hiddenLineSettings(hline: HiddenLine.Settings) { + this._hline = hline; + this._json3d.hline = hline.toJSON(); + } + + /** @hidden */ + public get environment(): EnvironmentProps { + const env = this._json3d.environment; + return undefined !== env ? env : {}; + } + + /** @hidden */ + public set environment(environment: EnvironmentProps) { this._json3d.environment = environment; } +} diff --git a/core/common/src/common.ts b/core/common/src/common.ts index 04cf88f..4c482e4 100644 --- a/core/common/src/common.ts +++ b/core/common/src/common.ts @@ -27,6 +27,7 @@ export * from "./TileProps"; export * from "./Thumbnail"; export * from "./ViewProps"; export * from "./Render"; +export * from "./RenderSchedule"; export * from "./domains/FunctionalElementProps"; export * from "./domains/GenericElementProps"; export * from "./geometry/AreaPattern"; diff --git a/core/common/src/geometry/Cartographic.ts b/core/common/src/geometry/Cartographic.ts index c0f6419..777597a 100644 --- a/core/common/src/geometry/Cartographic.ts +++ b/core/common/src/geometry/Cartographic.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** @module Geometry */ -import { Angle, Point3d, Vector3d, XYZ, XYAndZ } from "@bentley/geometry-core"; +import { Angle, Point3d, Vector3d, XYZ, XYAndZ, Range1d, Range2d, Range3d, Transform } from "@bentley/geometry-core"; // portions adapted from Cesium.js Copyright 2011 - 2017 Cesium Contributors export interface LatAndLong { longitude: number; latitude: number; } @@ -267,3 +267,42 @@ export class Cartographic implements LatLongAndHeight { return result; } } +/** A cartographic range representing a rectangular region if low longitude/latitude > high then area crossing seam is indicated. + */ +export class CartographicRange { + private _ranges: Range2d[]; + constructor(spatialRange: Range3d, spatialToEcef: Transform) { + + const ecefRange = spatialToEcef.multiplyRange(spatialRange); + + const low = Cartographic.fromEcef(ecefRange.low)!; + const high = Cartographic.fromEcef(ecefRange.high)!; + + const longitudeRanges = []; + const minLongitude = Math.min(low.longitude, high.longitude), maxLongitude = Math.max(low.longitude, high.longitude); + if (maxLongitude - minLongitude > Angle.piRadians) { + longitudeRanges.push(Range1d.createXX(0.0, minLongitude)); + longitudeRanges.push(Range1d.createXX(maxLongitude, Angle.pi2Radians)); + } else { + longitudeRanges.push(Range1d.createXX(minLongitude, maxLongitude)); + } + + this._ranges = []; + for (const longitudeRange of longitudeRanges) { + const minLatitude = Math.min(low.latitude, high.latitude), maxLatitude = Math.max(low.latitude, high.latitude); + if (maxLatitude - minLatitude > Angle.piOver2Radians) { + this._ranges.push(Range2d.createXYXY(longitudeRange.low, 0.0, longitudeRange.high, minLatitude)); + this._ranges.push(Range2d.createXYXY(longitudeRange.low, maxLatitude, longitudeRange.high, Angle.piRadians)); + } else { + this._ranges.push(Range2d.createXYXY(longitudeRange.low, minLatitude, longitudeRange.high, maxLatitude)); + } + } + } + public intersectsRange(other: CartographicRange): boolean { + for (const range of this._ranges) + for (const otherRange of other._ranges) + if (range.intersectsRange(otherRange)) + return true; + return false; + } +} diff --git a/core/common/src/geometry/GeometryStream.ts b/core/common/src/geometry/GeometryStream.ts index 86a667d..cfc4aa8 100644 --- a/core/common/src/geometry/GeometryStream.ts +++ b/core/common/src/geometry/GeometryStream.ts @@ -119,7 +119,9 @@ export interface GeometryPartInstanceProps { scale?: number; } -/** Allowed GeometryStream entries - should only set one value */ +/** Allowed GeometryStream entries - should only set one value. + * @see [GeometryStream]($docs/learning/common/geometrystream.md) + */ export interface GeometryStreamEntryProps extends GeomJson.GeometryProps { appearance?: GeometryAppearanceProps; styleMod?: LineStyle.ModifierProps; diff --git a/core/common/src/rpc/IModelReadRpcInterface.ts b/core/common/src/rpc/IModelReadRpcInterface.ts index bf49f06..12c4bf5 100644 --- a/core/common/src/rpc/IModelReadRpcInterface.ts +++ b/core/common/src/rpc/IModelReadRpcInterface.ts @@ -55,23 +55,23 @@ export abstract class IModelReadRpcInterface extends RpcInterface { NOTE: Any add/remove/change to the methods below requires an update of the interface version. NOTE: Please consult the README in this folder for the semantic versioning rules. ===========================================================================================*/ - public openForRead(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public close(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public executeQuery(_iModelToken: IModelToken, _ecsql: string, _bindings?: any[] | object): Promise { return this.forward.apply(this, arguments); } - public getModelProps(_iModelToken: IModelToken, _modelIds: Id64Set): Promise { return this.forward.apply(this, arguments); } - public queryModelProps(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } - public getElementProps(_iModelToken: IModelToken, _elementIds: Id64Set): Promise { return this.forward.apply(this, arguments); } - public queryElementProps(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } - public queryEntityIds(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } - public formatElements(_iModelToken: IModelToken, _elementIds: Id64Set): Promise { return this.forward.apply(this, arguments); } - public getClassHierarchy(_iModelToken: IModelToken, _startClassName: string): Promise { return this.forward.apply(this, arguments); } - public getAllCodeSpecs(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public getViewStateData(_iModelToken: IModelToken, _viewDefinitionId: string): Promise { return this.forward.apply(this, arguments); } - public readFontJson(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public requestSnap(_iModelToken: IModelToken, _connectionId: string, _props: SnapRequestProps): Promise { return this.forward.apply(this, arguments); } - public cancelSnap(_iModelToken: IModelToken, _connectionId: string): Promise { return this.forward.apply(this, arguments); } - public loadNativeAsset(_iModelToken: IModelToken, _assetName: string): Promise { return this.forward.apply(this, arguments); } - public getToolTipMessage(_iModelToken: IModelToken, _elementId: string): Promise { return this.forward.apply(this, arguments); } - public getViewThumbnail(_iModelToken: IModelToken, _viewId: string): Promise { return this.forward.apply(this, arguments); } - public getDefaultViewId(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async openForRead(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async close(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async executeQuery(_iModelToken: IModelToken, _ecsql: string, _bindings?: any[] | object): Promise { return this.forward.apply(this, arguments); } + public async getModelProps(_iModelToken: IModelToken, _modelIds: Id64Set): Promise { return this.forward.apply(this, arguments); } + public async queryModelProps(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } + public async getElementProps(_iModelToken: IModelToken, _elementIds: Id64Set): Promise { return this.forward.apply(this, arguments); } + public async queryElementProps(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } + public async queryEntityIds(_iModelToken: IModelToken, _params: EntityQueryParams): Promise { return this.forward.apply(this, arguments); } + public async formatElements(_iModelToken: IModelToken, _elementIds: Id64Set): Promise { return this.forward.apply(this, arguments); } + public async getClassHierarchy(_iModelToken: IModelToken, _startClassName: string): Promise { return this.forward.apply(this, arguments); } + public async getAllCodeSpecs(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async getViewStateData(_iModelToken: IModelToken, _viewDefinitionId: string): Promise { return this.forward.apply(this, arguments); } + public async readFontJson(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async requestSnap(_iModelToken: IModelToken, _connectionId: string, _props: SnapRequestProps): Promise { return this.forward.apply(this, arguments); } + public async cancelSnap(_iModelToken: IModelToken, _connectionId: string): Promise { return this.forward.apply(this, arguments); } + public async loadNativeAsset(_iModelToken: IModelToken, _assetName: string): Promise { return this.forward.apply(this, arguments); } + public async getToolTipMessage(_iModelToken: IModelToken, _elementId: string): Promise { return this.forward.apply(this, arguments); } + public async getViewThumbnail(_iModelToken: IModelToken, _viewId: string): Promise { return this.forward.apply(this, arguments); } + public async getDefaultViewId(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/IModelTileRpcInterface.ts b/core/common/src/rpc/IModelTileRpcInterface.ts index 582c7fc..5342277 100644 --- a/core/common/src/rpc/IModelTileRpcInterface.ts +++ b/core/common/src/rpc/IModelTileRpcInterface.ts @@ -8,7 +8,6 @@ import { RpcInterface } from "../RpcInterface"; import { RpcManager } from "../RpcManager"; import { IModelToken } from "../IModel"; import { TileTreeProps } from "../TileProps"; -import { RpcOperation } from "./core/RpcOperation"; export abstract class IModelTileRpcInterface extends RpcInterface { public static types = () => [ @@ -24,8 +23,7 @@ export abstract class IModelTileRpcInterface extends RpcInterface { NOTE: Any add/remove/change to the methods below requires an update of the interface version. NOTE: Please consult the README in this folder for the semantic versioning rules. ===========================================================================================*/ - public getTileTreeProps(_iModelToken: IModelToken, _id: string): Promise { return this.forward.apply(this, arguments); } + public async getTileTreeProps(_iModelToken: IModelToken, _id: string): Promise { return this.forward.apply(this, arguments); } - @RpcOperation.allowResponseCaching() - public getTileContent(_iModelToken: IModelToken, _treeId: string, _contentId: string): Promise { return this.forward.apply(this, arguments); } + public async getTileContent(_iModelToken: IModelToken, _treeId: string, _contentId: string): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/IModelUnitTestRpcInterface.ts b/core/common/src/rpc/IModelUnitTestRpcInterface.ts index da13451..58d410f 100644 --- a/core/common/src/rpc/IModelUnitTestRpcInterface.ts +++ b/core/common/src/rpc/IModelUnitTestRpcInterface.ts @@ -22,5 +22,5 @@ export abstract class IModelUnitTestRpcInterface extends RpcInterface { /** Returns the IModelUnitTestRpcInterface client instance for the frontend. */ public static getClient(): IModelUnitTestRpcInterface { return RpcManager.getClientForInterface(IModelUnitTestRpcInterface); } - public executeTest(_iModelToken: IModelToken, _testName: string, _params: any): Promise { return this.forward.apply(this, arguments); } + public async executeTest(_iModelToken: IModelToken, _testName: string, _params: any): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/IModelWriteRpcInterface.ts b/core/common/src/rpc/IModelWriteRpcInterface.ts index 19c6b3a..e12f2a6 100644 --- a/core/common/src/rpc/IModelWriteRpcInterface.ts +++ b/core/common/src/rpc/IModelWriteRpcInterface.ts @@ -37,8 +37,8 @@ export abstract class IModelWriteRpcInterface extends RpcInterface { NOTE: Any add/remove/change to the methods below requires an update of the interface version. NOTE: Please consult the README in this folder for the semantic versioning rules. ===========================================================================================*/ - public openForWrite(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public saveChanges(_iModelToken: IModelToken, _description?: string): Promise { return this.forward.apply(this, arguments); } - public updateProjectExtents(_iModelToken: IModelToken, _newExtents: AxisAlignedBox3d): Promise { return this.forward.apply(this, arguments); } - public saveThumbnail(_iModelToken: IModelToken, _val: Uint8Array): Promise { return this.forward.apply(this, arguments); } + public async openForWrite(_accessToken: AccessToken, _iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async saveChanges(_iModelToken: IModelToken, _description?: string): Promise { return this.forward.apply(this, arguments); } + public async updateProjectExtents(_iModelToken: IModelToken, _newExtents: AxisAlignedBox3d): Promise { return this.forward.apply(this, arguments); } + public async saveThumbnail(_iModelToken: IModelToken, _val: Uint8Array): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/StandaloneIModelRpcInterface.ts b/core/common/src/rpc/StandaloneIModelRpcInterface.ts index 85bc2e5..14c3470 100644 --- a/core/common/src/rpc/StandaloneIModelRpcInterface.ts +++ b/core/common/src/rpc/StandaloneIModelRpcInterface.ts @@ -23,6 +23,6 @@ export abstract class StandaloneIModelRpcInterface extends RpcInterface { /** Returns the StandaloneIModelRpcInterface client instance for the frontend. */ public static getClient(): StandaloneIModelRpcInterface { return RpcManager.getClientForInterface(StandaloneIModelRpcInterface); } - public openStandalone(_fileName: string, _openMode: OpenMode): Promise { return this.forward.apply(this, arguments); } - public closeStandalone(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async openStandalone(_fileName: string, _openMode: OpenMode): Promise { return this.forward.apply(this, arguments); } + public async closeStandalone(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/WipRpcInterface.ts b/core/common/src/rpc/WipRpcInterface.ts index 5f61f6a..c837042 100644 --- a/core/common/src/rpc/WipRpcInterface.ts +++ b/core/common/src/rpc/WipRpcInterface.ts @@ -33,8 +33,8 @@ export abstract class WipRpcInterface extends RpcInterface { NOTE: Any add/remove/change to the methods below requires an update of the interface version. NOTE: Please consult the README in this folder for the semantic versioning rules. ===========================================================================================*/ - public placeholder(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } // here to test that WipRpcInterface is configured properly - public isChangeCacheAttached(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public attachChangeCache(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } - public detachChangeCache(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async placeholder(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } // here to test that WipRpcInterface is configured properly + public async isChangeCacheAttached(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async attachChangeCache(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } + public async detachChangeCache(_iModelToken: IModelToken): Promise { return this.forward.apply(this, arguments); } } diff --git a/core/common/src/rpc/core/RpcConfiguration.ts b/core/common/src/rpc/core/RpcConfiguration.ts index 1152949..5a430d2 100644 --- a/core/common/src/rpc/core/RpcConfiguration.ts +++ b/core/common/src/rpc/core/RpcConfiguration.ts @@ -111,7 +111,7 @@ export class RpcDirectRequest extends RpcRequest { public headers: Map = new Map(); public fulfillment: RpcRequestFulfillment | undefined = undefined; - protected send() { + protected async send() { const request = this.protocol.serialize(this); return new Promise(async (resolve, reject) => { try { @@ -127,7 +127,7 @@ export class RpcDirectRequest extends RpcRequest { this.headers.set(name, value); } - protected load() { + protected async load() { return Promise.resolve(this.fulfillment!.result); } } diff --git a/core/common/src/rpc/core/RpcConstants.ts b/core/common/src/rpc/core/RpcConstants.ts index d0f1a1b..d47e586 100644 --- a/core/common/src/rpc/core/RpcConstants.ts +++ b/core/common/src/rpc/core/RpcConstants.ts @@ -46,7 +46,6 @@ export enum RpcRequestStatus { Unknown, Created, Submitted, - Provisioning, Pending, Resolved, Rejected, diff --git a/core/common/src/rpc/core/RpcControl.ts b/core/common/src/rpc/core/RpcControl.ts index cadc4e0..8e92572 100644 --- a/core/common/src/rpc/core/RpcControl.ts +++ b/core/common/src/rpc/core/RpcControl.ts @@ -52,7 +52,7 @@ export class RpcControlChannel { } /** @hidden */ - public describeEndpoints() { + public async describeEndpoints() { this.activateClient(); return this._describeEndpoints(); } @@ -72,11 +72,11 @@ export class RpcControlChannel { private _channelInterface = class extends RpcInterface { public static readonly version = "CONTROL"; public static readonly types = () => []; - public describeEndpoints(): Promise { return this.forward.apply(this, arguments); } + public async describeEndpoints(): Promise { return this.forward.apply(this, arguments); } }; private _channelImpl = class extends RpcInterface { - public describeEndpoints(): Promise { + public async describeEndpoints(): Promise { const endpoints: RpcInterfaceEndpoints[] = []; this.configuration.interfaces().forEach((definition) => { @@ -119,7 +119,7 @@ export class RpcControlChannel { this._clientActive = true; RpcOperation.forEach(this._channelInterface, (operation) => operation.policy.token = (_request) => RpcOperation.fallbackToken || new IModelToken("none", "none", "none", "none", undefined)); const client = RpcManager.getClientForInterface(this._channelInterface); - this._describeEndpoints = () => client.describeEndpoints(); + this._describeEndpoints = async () => client.describeEndpoints(); } /** @hidden */ diff --git a/core/common/src/rpc/core/RpcInvocation.ts b/core/common/src/rpc/core/RpcInvocation.ts index da3871d..4391970 100644 --- a/core/common/src/rpc/core/RpcInvocation.ts +++ b/core/common/src/rpc/core/RpcInvocation.ts @@ -106,7 +106,7 @@ export class RpcInvocation { return this.protocol.configuration.controlChannel.handleUnknownOperation(this, error); } - private resolve(): Promise { + private async resolve(): Promise { const parameters = RpcMarshaling.deserialize(this.operation, this.protocol, this.request.parameters); const impl = RpcRegistry.instance.getImplForInterface(this.operation.interfaceDefinition); (impl as any)[CURRENT_INVOCATION] = this; @@ -116,7 +116,7 @@ export class RpcInvocation { return Promise.resolve(op.call(impl, ...parameters)); } - private reject(error: any): Promise { + private async reject(error: any): Promise { this._threw = true; this.protocol.events.raiseEvent(RpcProtocolEvent.BackendErrorOccurred, this); return Promise.reject(error); diff --git a/core/common/src/rpc/core/RpcPendingQueue.ts b/core/common/src/rpc/core/RpcPendingQueue.ts index d13bea3..919e84c 100644 --- a/core/common/src/rpc/core/RpcPendingQueue.ts +++ b/core/common/src/rpc/core/RpcPendingQueue.ts @@ -70,7 +70,7 @@ export class RpcPendingQueue { continue; } - request.submit(); + request.submit(); // tslint:disable-line:no-floating-promises } --this._pendingLock; diff --git a/core/common/src/rpc/core/RpcProtocol.ts b/core/common/src/rpc/core/RpcProtocol.ts index dbedd7a..1f331fe 100644 --- a/core/common/src/rpc/core/RpcProtocol.ts +++ b/core/common/src/rpc/core/RpcProtocol.ts @@ -112,7 +112,7 @@ export abstract class RpcProtocol { } /** Obtains the implementation result on the backend for an RPC operation request. */ - public fulfill(request: SerializedRpcRequest): Promise { + public async fulfill(request: SerializedRpcRequest): Promise { return new (this.invocationType)(this, request).fulfillment; } diff --git a/core/common/src/rpc/core/RpcRegistry.ts b/core/common/src/rpc/core/RpcRegistry.ts index 1df027f..855f7cf 100644 --- a/core/common/src/rpc/core/RpcRegistry.ts +++ b/core/common/src/rpc/core/RpcRegistry.ts @@ -61,7 +61,7 @@ export class RpcRegistry { return this.definitionClasses.get(name) as RpcInterfaceDefinition; } - public describeAvailableEndpoints(): Promise { + public async describeAvailableEndpoints(): Promise { const requests: Array> = []; for (const channel of RpcControlChannel.channels) { requests.push(channel.describeEndpoints()); diff --git a/core/common/src/rpc/core/RpcRequest.ts b/core/common/src/rpc/core/RpcRequest.ts index 2819a97..b37683b 100644 --- a/core/common/src/rpc/core/RpcRequest.ts +++ b/core/common/src/rpc/core/RpcRequest.ts @@ -114,7 +114,6 @@ export abstract class RpcRequest { public get pending(): boolean { switch (this.status) { case RpcRequestStatus.Submitted: - case RpcRequestStatus.Provisioning: case RpcRequestStatus.Pending: { return true; } @@ -218,7 +217,6 @@ export abstract class RpcRequest { return this.handleRejected(value); } - case RpcRequestStatus.Provisioning: case RpcRequestStatus.Pending: { return this.setPending(status, value.objects); } @@ -267,7 +265,7 @@ export abstract class RpcRequest { throw new IModelError(BentleyStatus.ERROR, `Already resubmitted using this handler.`); resubmitted = true; - this.submit(); + this.submit(); // tslint:disable-line:no-floating-promises }, (reason: any) => this.reject(reason)); return; } @@ -304,7 +302,7 @@ export abstract class RpcRequest { } } - private setPending(status: RpcRequestStatus.Provisioning | RpcRequestStatus.Pending, extendedStatus: string): void { + private setPending(status: RpcRequestStatus.Pending, extendedStatus: string): void { if (!this._active) return; @@ -352,7 +350,6 @@ export const initializeRpcRequest = (() => { break; } - case RpcRequestStatus.Provisioning: case RpcRequestStatus.Pending: case RpcRequestStatus.Resolved: case RpcRequestStatus.Rejected: { diff --git a/core/common/src/rpc/electron/ElectronRpcRequest.ts b/core/common/src/rpc/electron/ElectronRpcRequest.ts index d8f8dd2..8a89f79 100644 --- a/core/common/src/rpc/electron/ElectronRpcRequest.ts +++ b/core/common/src/rpc/electron/ElectronRpcRequest.ts @@ -17,7 +17,7 @@ export class ElectronRpcRequest extends RpcRequest { public readonly protocol: ElectronRpcProtocol = this.client.configuration.protocol as any; /** Sends the request. */ - protected send() { + protected async send() { try { this.protocol.requests.set(this.id, this); const request = this.protocol.serialize(this); @@ -30,7 +30,7 @@ export class ElectronRpcRequest extends RpcRequest { } /** Loads the request. */ - protected load() { + protected async load() { const fulfillment = this._fulfillment; if (!fulfillment) { return Promise.reject("No request fulfillment available."); diff --git a/core/common/src/rpc/mobile/MobileRpcProtocol.ts b/core/common/src/rpc/mobile/MobileRpcProtocol.ts index 879214a..dff60ed 100644 --- a/core/common/src/rpc/mobile/MobileRpcProtocol.ts +++ b/core/common/src/rpc/mobile/MobileRpcProtocol.ts @@ -9,7 +9,6 @@ import { RpcRequestFulfillment } from "../core/RpcProtocol"; import { BentleyStatus } from "@bentley/bentleyjs-core"; import { IModelError } from "../../IModelError"; import { RpcSerializedValue } from "../core/RpcMarshaling"; -import { RpcMultipart } from "../web/RpcMultipart"; import { RpcEndpoint } from "../core/RpcConstants"; /** @hidden */ declare var bentley: any; @@ -29,141 +28,224 @@ export const interop = (() => { return mobilegateway; })(); +export type MobileRpcChunks = Array; + +interface MobileRpcGateway { + handler: (payload: ArrayBuffer | string) => void; + sendString: (message: string) => void; + sendBinary: (message: Uint8Array) => void; + port: number; +} + /** RPC interface protocol for an Mobile-based application. */ export class MobileRpcProtocol extends RpcProtocol { public socket: WebSocket = (undefined as any); public requests: Map = new Map(); - public pending: Blob[] = []; + private _pending: MobileRpcChunks[] = []; + private _capacity: number = 1; + private _sendInterval: number | undefined = undefined; + private _sendIntervalHandler = () => this.trySend(); public readonly requestType = MobileRpcRequest; + private _partialRequest: SerializedRpcRequest | undefined = undefined; + private _partialFulfillment: RpcRequestFulfillment | undefined = undefined; + private _partialData: Uint8Array[] = []; - /** Encodes a request for transport. */ - public static encodeRequest(request: MobileRpcRequest): Uint8Array[] { + public static encodeRequest(request: MobileRpcRequest): MobileRpcChunks { const serialized = request.protocol.serialize(request); const data = serialized.parameters.data; - MobileRpcProtocol._deflateData(serialized.parameters); - return MobileRpcProtocol._encode(JSON.stringify(serialized), data); + serialized.parameters.data = data.map((v) => v.byteLength) as any[]; + return [JSON.stringify(serialized), ...data]; } - /** Encodes a response for transport. */ - public static encodeResponse(fulfillment: RpcRequestFulfillment): Uint8Array[] { + private static encodeResponse(fulfillment: RpcRequestFulfillment): MobileRpcChunks { const data = fulfillment.result.data; - MobileRpcProtocol._deflateData(fulfillment.result); - return MobileRpcProtocol._encode(JSON.stringify(fulfillment), data); + fulfillment.result.data = data.map((v) => v.byteLength) as any[]; + const raw = fulfillment.rawResult; + fulfillment.rawResult = undefined; + const encoded = [JSON.stringify(fulfillment), ...data]; + fulfillment.rawResult = raw; + return encoded; } - /** Decodes a request from transport format. */ - public static decodeRequest(data: ArrayBuffer): SerializedRpcRequest { - const header = MobileRpcProtocol._decodeHeader(data); - const objectString = MobileRpcProtocol._decodeObject(header, data); - const request = JSON.parse(objectString) as SerializedRpcRequest; - MobileRpcProtocol._inflateData(request.parameters, data, header); - return request; + constructor(configuration: MobileRpcConfiguration, endPoint: RpcEndpoint) { + super(configuration); + + if (endPoint === RpcEndpoint.Frontend) { + this.initializeFrontend(); + } else if (endPoint === RpcEndpoint.Backend) { + this.initializeBackend(); + } + } + + private initializeFrontend() { + if (typeof (WebSocket) === "undefined") { + throw new IModelError(BentleyStatus.ERROR, "MobileRpcProtocol on frontend require websocket to work"); + } + + this.socket = new WebSocket(`ws://localhost:${window.location.hash.substr(1)}`); + this.socket.binaryType = "arraybuffer"; + this.socket.addEventListener("message", async (event) => this.handleMessageFromBackend(event.data)); + this.socket.addEventListener("open", (_event) => this.scheduleSend()); } - /** Decodes a request from transport format. */ - public static decodeResponse(data: ArrayBuffer): RpcRequestFulfillment { - const header = MobileRpcProtocol._decodeHeader(data); - const objectString = MobileRpcProtocol._decodeObject(header, data); - const fulfillment = JSON.parse(objectString) as RpcRequestFulfillment; - MobileRpcProtocol._inflateData(fulfillment.result, data, header); - return fulfillment; + private scheduleSend() { + if (!this._pending.length) { + return; + } + + this.trySend(); + + if (this._pending.length && typeof (this._sendInterval) === "undefined") { + this._sendInterval = window.setInterval(this._sendIntervalHandler, 0); + } } - private static _deflateData(value: RpcSerializedValue): void { - value.data = value.data.map((v) => v.byteLength) as any[]; + private trySend() { + if (this.socket.readyState !== WebSocket.OPEN) { + return; + } + + while (this._capacity !== 0 && this._pending.length) { + --this._capacity; + const next = this._pending.shift()!; + for (const chunk of next) { + this.socket.send(chunk); + } + } + + if (!this._pending.length && typeof (this._sendInterval) !== "undefined") { + window.clearInterval(this._sendInterval); + this._sendInterval = undefined; + } } - private static _inflateData(value: RpcSerializedValue, data: ArrayBuffer, offset: number): void { - let i = offset + 4; - for (let j = 0; j !== value.data.length; ++j) { - const l = value.data[j] as any as number; - value.data[j] = new Uint8Array(data, i, l); - i += l; + private handleMessageFromBackend(data: string | ArrayBuffer) { + if (typeof (data) === "string") { + this.handleStringFromBackend(data); + } else { + this.handleBinaryFromBackend(data); } } - private static _decodeHeader(data: ArrayBuffer): number { - return new DataView(data, 0, 4).getUint32(0); + private handleStringFromBackend(data: string) { + if (this._partialFulfillment) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (already receiving response)."); + } + + const response = JSON.parse(data) as RpcRequestFulfillment; + this._partialFulfillment = response; + + if (!response.result.data.length) { + this.notifyResponse(); + } } - private static _decodeObject(length: number, data: ArrayBuffer): string { - return MobileRpcProtocol._decodeString(new Uint16Array(data, 4, length / 2)); + private handleBinaryFromBackend(data: ArrayBuffer) { + const fulfillment = this._partialFulfillment; + if (!fulfillment) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (no response received)."); + } + + this._partialData.push(new Uint8Array(data)); + if (this._partialData.length === fulfillment.result.data.length) { + this.notifyResponse(); + } } - private static _toBytes(data: ArrayBufferView): Uint8Array { - return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + private notifyResponse() { + const response = this._partialFulfillment; + if (!response) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (no response exists)."); + } + + ++this._capacity; + this.consumePartialData(response.result); + this._partialFulfillment = undefined; + + const request = this.requests.get(response.id) as MobileRpcRequest; + this.requests.delete(response.id); + request.notifyResponse(response); } - private static _encode(object: string, data: Uint8Array[]): Uint8Array[] { - const objectChars = MobileRpcProtocol._encodeString(object); + private consumePartialData(value: RpcSerializedValue) { + for (let i = 0, l = value.data.length; i !== l; ++i) { + value.data[i] = this._partialData[i]; + } - const header = new DataView(new ArrayBuffer(4)); - header.setUint32(0, objectChars.byteLength); + this._partialData.length = 0; + } + + private initializeBackend() { + const mobilegateway: MobileRpcGateway = interop as MobileRpcGateway; + if (mobilegateway === undefined || mobilegateway == null) { + throw new IModelError(BentleyStatus.ERROR, "MobileRpcProtocol on backend require native bridge to be setup"); + } - return [MobileRpcProtocol._toBytes(header), MobileRpcProtocol._toBytes(objectChars), ...data]; + mobilegateway.handler = (payload) => this.handleMessageFromFrontend(payload); + (self as any).__imodeljs_mobilegateway_handler__ = mobilegateway.handler; } - private static _decodeString(data: Uint16Array): string { - return String.fromCharCode.apply(null, data); + private handleMessageFromFrontend(data: string | ArrayBuffer) { + if (typeof (data) === "string") { + this.handleStringFromFrontend(data); + } else { + this.handleBinaryFromFrontend(data); + } } - private static _encodeString(value: string): Uint16Array { - const data = new Uint16Array(new ArrayBuffer(value.length * 2)); - for (let i = 0; i !== value.length; ++i) { - data[i] = value.charCodeAt(i); + private handleStringFromFrontend(data: string) { + if (this._partialRequest) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (already receiving request)."); } - return data; + const request = JSON.parse(data) as SerializedRpcRequest; + this._partialRequest = request; + + if (!request.parameters.data.length) { + this.notifyRequest(); // tslint:disable-line:no-floating-promises + } } - /** Constructs an Mobile protocol. */ - constructor(configuration: MobileRpcConfiguration, endPoint: RpcEndpoint) { - super(configuration); + private handleBinaryFromFrontend(data: ArrayBuffer) { + const request = this._partialRequest; + if (!request) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (no request received)."); + } - interface MobileGateway { - handler: (payload: ArrayBuffer) => void; - send: (message: Uint8Array[]) => void; - port: number; + this._partialData.push(new Uint8Array(data)); + if (this._partialData.length === request.parameters.data.length) { + this.notifyRequest(); // tslint:disable-line:no-floating-promises } + } - // Initialize for frontend - if (endPoint === RpcEndpoint.Frontend) { - if (typeof (WebSocket) === "undefined") { - throw new IModelError(BentleyStatus.ERROR, "MobileRpcProtocol on frontend require websocket to work"); - } + private async notifyRequest() { + const request = this._partialRequest; + if (!request) { + throw new IModelError(BentleyStatus.ERROR, "Invalid state (no request exists)."); + } - this.socket = new WebSocket(`ws://localhost:${window.location.hash.substr(1)}`); - this.socket.addEventListener("message", async (event) => { - const buf = await RpcMultipart.readFormBlob(event.data); - const response = MobileRpcProtocol.decodeResponse(buf); - const request = this.requests.get(response.id) as MobileRpcRequest; - this.requests.delete(response.id); - request.notifyResponse(response); - }); - - this.socket.addEventListener("open", (_event) => { - for (const pending of this.pending) { - this.socket.send(pending); - } - this.pending = []; - }); - } - - // Initialize for backend - if (endPoint === RpcEndpoint.Backend) { - const mobilegateway: MobileGateway = interop as MobileGateway; - if (mobilegateway === undefined || mobilegateway == null) { - throw new IModelError(BentleyStatus.ERROR, "MobileRpcProtocol on backend require native bridge to be setup"); - } + this.consumePartialData(request.parameters); + this._partialRequest = undefined; + + const fulfillment = await this.fulfill(request); + const response = MobileRpcProtocol.encodeResponse(fulfillment); + this.sendToFrontend(response); + } - mobilegateway.handler = async (payload) => { - const request: SerializedRpcRequest = MobileRpcProtocol.decodeRequest(payload); - const fulfillment = await this.fulfill(request); - const response = MobileRpcProtocol.encodeResponse(fulfillment); - mobilegateway.send(response); - }; + public sendToBackend(message: MobileRpcChunks): void { + this._pending.push(message); + this.scheduleSend(); + } + + private sendToFrontend(message: MobileRpcChunks): void { + const mobilegateway: MobileRpcGateway = interop as MobileRpcGateway; - (self as any).__imodeljs_mobilegateway_handler__ = mobilegateway.handler; + for (const chunk of message) { + if (typeof (chunk) === "string") { + mobilegateway.sendString(chunk); + } else { + mobilegateway.sendBinary(chunk); + } } } } diff --git a/core/common/src/rpc/mobile/MobileRpcRequest.ts b/core/common/src/rpc/mobile/MobileRpcRequest.ts index 161a9cb..4922895 100644 --- a/core/common/src/rpc/mobile/MobileRpcRequest.ts +++ b/core/common/src/rpc/mobile/MobileRpcRequest.ts @@ -15,21 +15,15 @@ export class MobileRpcRequest extends RpcRequest { public readonly protocol: MobileRpcProtocol = this.client.configuration.protocol as any; /** Sends the request. */ - protected send(): Promise { + protected async send(): Promise { this.protocol.requests.set(this.id, this); - const parts = new Blob(MobileRpcProtocol.encodeRequest(this), { type: "application/octet-stream" }); - - if (this.protocol.socket.readyState === WebSocket.OPEN) { - this.protocol.socket.send(parts); - } else { - this.protocol.pending.push(parts); - } - + const parts = MobileRpcProtocol.encodeRequest(this); + this.protocol.sendToBackend(parts); return new Promise((resolve) => { this._response = resolve; }); } /** Loads the request. */ - protected load(): Promise { + protected async load(): Promise { const fulfillment = this._fulfillment; if (!fulfillment) { return Promise.reject("No request fulfillment available."); diff --git a/core/common/src/rpc/web/BentleyCloudRpcProtocol.ts b/core/common/src/rpc/web/BentleyCloudRpcProtocol.ts index c72bc3b..8052475 100644 --- a/core/common/src/rpc/web/BentleyCloudRpcProtocol.ts +++ b/core/common/src/rpc/web/BentleyCloudRpcProtocol.ts @@ -12,6 +12,7 @@ import { RpcRequest } from "../core/RpcRequest"; import { IModelError } from "../../IModelError"; import { BentleyStatus, OpenMode } from "@bentley/bentleyjs-core"; import { Logger, assert } from "@bentley/bentleyjs-core"; +import { URL } from "url"; enum AppMode { MilestoneReview = "1", @@ -25,10 +26,11 @@ export abstract class BentleyCloudRpcProtocol extends WebAppRpcProtocol { /** Returns the operation specified by an OpenAPI-compatible URI path. */ public getOperationFromPath(path: string): SerializedRpcOperation { - const components = path.split("/"); - const operationIndex = (components.length % 2 === 0) ? -1 : -2; - const operationComponent = components.slice(operationIndex)[0]; - const encodedRequest = (operationIndex === -1) ? "" : components.slice(-1)[0]; + const url = new URL(path, "https://localhost/"); + const components = url.pathname.split("/"); + + const operationComponent = components.slice(-1)[0]; + const encodedRequest = url.searchParams.get("parameters") || ""; const firstHyphen = operationComponent.indexOf("-"); const lastHyphen = operationComponent.lastIndexOf("-"); diff --git a/core/common/src/rpc/web/RpcMultipart.ts b/core/common/src/rpc/web/RpcMultipart.ts index eca97ea..bcaf8dd 100644 --- a/core/common/src/rpc/web/RpcMultipart.ts +++ b/core/common/src/rpc/web/RpcMultipart.ts @@ -34,36 +34,10 @@ export class RpcMultipart { } /** Obtains the RPC value from a multipart HTTP request. */ - public static parseRequest(_req: HttpServerRequest): Promise { + public static async parseRequest(_req: HttpServerRequest): Promise { throw new IModelError(BentleyStatus.ERROR, "Not implemented."); } - /** Obtains the RPC value from a multipart form object. */ - public static parseForm(form: FormData) { - return new Promise(async (resolve, reject) => { - const value = RpcSerializedValue.create(form.get("objects") as string, []); - - let i = 0; - for (; ;) { - const data = form.get(`data-${i}`); - if (!data) { - break; - } - - try { - const buffer = await RpcMultipart.readFormBlob(data as Blob); - value.data.push(new Uint8Array(buffer)); - } catch (err) { - reject(err); - } - - ++i; - } - - resolve(value); - }); - } - /** @hidden */ public static writeValueToForm(form: FormDataCommon, value: RpcSerializedValue) { form.append("objects", value.objects); @@ -77,21 +51,4 @@ export class RpcMultipart { } } } - - /** @hidden */ - public static readFormBlob(data: Blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - - reader.addEventListener("load", () => { - resolve(reader.result as ArrayBuffer); - }); - - reader.addEventListener("error", () => { - reject(reader.error); - }); - - reader.readAsArrayBuffer(data as Blob); - }); - } } diff --git a/core/common/src/rpc/web/WebAppRpcProtocol.ts b/core/common/src/rpc/web/WebAppRpcProtocol.ts index 7901a95..6c53b03 100644 --- a/core/common/src/rpc/web/WebAppRpcProtocol.ts +++ b/core/common/src/rpc/web/WebAppRpcProtocol.ts @@ -90,9 +90,8 @@ export abstract class WebAppRpcProtocol extends RpcProtocol { /** Supplies the status corresponding to a protocol-specific code value. */ public getStatus(code: number): RpcRequestStatus { switch (code) { - case 202: return RpcRequestStatus.Provisioning; case 404: return RpcRequestStatus.NotFound; - case 409: return RpcRequestStatus.Pending; + case 202: return RpcRequestStatus.Pending; case 200: return RpcRequestStatus.Resolved; case 500: return RpcRequestStatus.Rejected; default: return RpcRequestStatus.Unknown; @@ -102,9 +101,8 @@ export abstract class WebAppRpcProtocol extends RpcProtocol { /** Supplies the protocol-specific code corresponding to a status value. */ public getCode(status: RpcRequestStatus): number { switch (status) { - case RpcRequestStatus.Provisioning: return 202; case RpcRequestStatus.NotFound: return 404; - case RpcRequestStatus.Pending: return 409; + case RpcRequestStatus.Pending: return 202; case RpcRequestStatus.Resolved: return 200; case RpcRequestStatus.Rejected: return 500; default: return 501; diff --git a/core/common/src/rpc/web/WebAppRpcRequest.ts b/core/common/src/rpc/web/WebAppRpcRequest.ts index ccbb82b..6274562 100644 --- a/core/common/src/rpc/web/WebAppRpcRequest.ts +++ b/core/common/src/rpc/web/WebAppRpcRequest.ts @@ -14,6 +14,7 @@ import { RpcSerializedValue, MarshalingBinaryMarker } from "../core/RpcMarshalin import { RpcMultipart } from "./RpcMultipart"; import { RpcMultipartParser } from "./multipart/RpcMultipartParser"; import { RpcResponseCacheControl, RpcContentType, RpcProtocolEvent, WEB_RPC_CONSTANTS } from "../core/RpcConstants"; +import URLSearchParams = require("url-search-params"); export type HttpMethod_T = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"; @@ -42,7 +43,7 @@ export class WebAppRpcRequest extends RpcRequest { /** Parses a request. */ public static async parseRequest(protocol: WebAppRpcProtocol, req: HttpServerRequest): Promise { - const operation = protocol.getOperationFromPath(req.path); + const operation = protocol.getOperationFromPath(req.url!); const request = { id: req.header(protocol.requestIdHeaderName) || "", @@ -53,7 +54,7 @@ export class WebAppRpcRequest extends RpcRequest { interfaceVersion: operation.interfaceVersion, }, method: req.method, - path: req.path, + path: req.url!, parameters: operation.encodedRequest ? WebAppRpcRequest.parseFromPath(operation) : await WebAppRpcRequest.parseFromBody(req), caching: operation.encodedRequest ? RpcResponseCacheControl.Immutable : RpcResponseCacheControl.None, }; @@ -104,11 +105,11 @@ export class WebAppRpcRequest extends RpcRequest { } /** Sends the request. */ - protected send(): Promise { + protected async send(): Promise { this._loading = true; this.setupTransport(); - return new Promise(async (resolve, reject) => { + return new Promise(async (resolve, reject) => { try { resolve(await this.performFetch()); } catch (reason) { @@ -117,8 +118,8 @@ export class WebAppRpcRequest extends RpcRequest { }); } - protected load(): Promise { - return new Promise(async (resolve, reject) => { + protected async load(): Promise { + return new Promise(async (resolve, reject) => { try { if (!this._loading) return; @@ -196,7 +197,7 @@ export class WebAppRpcRequest extends RpcRequest { return RpcSerializedValue.create(decoded); } - private static async parseFromBody(req: HttpServerRequest) { + private static async parseFromBody(req: HttpServerRequest): Promise { const contentType = WebAppRpcProtocol.computeContentType(req.header(WEB_RPC_CONSTANTS.CONTENT)); if (contentType === RpcContentType.Text) { return RpcSerializedValue.create(req.body as string); @@ -205,19 +206,21 @@ export class WebAppRpcRequest extends RpcRequest { const data = [req.body as Buffer]; return RpcSerializedValue.create(objects, data); } else if (contentType === RpcContentType.Multipart) { - return await RpcMultipart.parseRequest(req); + return RpcMultipart.parseRequest(req); } else { throw new IModelError(BentleyStatus.ERROR, `Unknown content type.`); } } private async performFetch(): Promise { - let path = this.path; + const path = new URL(this.path, location.origin); if (this._pathSuffix) { - path += `/${this._pathSuffix}`; + const params = new URLSearchParams(); + params.set("parameters", this._pathSuffix); + path.search = `?${params.toString()}`; } - const request = new Request(path, this._request); + const request = new Request(path.toString(), this._request); const response = await fetch(request); this._response = response; this.metadata.status = response.status; diff --git a/core/common/src/rpc/web/multipart/RpcMultipartParser.ts b/core/common/src/rpc/web/multipart/RpcMultipartParser.ts index c573454..b1ad8ec 100644 --- a/core/common/src/rpc/web/multipart/RpcMultipartParser.ts +++ b/core/common/src/rpc/web/multipart/RpcMultipartParser.ts @@ -210,7 +210,7 @@ export class RpcMultipartParser { break; case HEADERS_ALMOST_DONE: if (c !== LF) throw new Error("Expected LF Received " + c); - const err = this._onParseHeadersEnd(i + 1); + const err: any = this._onParseHeadersEnd(i + 1); if (err) throw err; state = PART_DATA_START; break; diff --git a/test-apps/testbed/frontend/FeatureIndex.test.ts b/core/common/src/test/FeatureIndex.test.ts similarity index 98% rename from test-apps/testbed/frontend/FeatureIndex.test.ts rename to core/common/src/test/FeatureIndex.test.ts index 27c1aaf..dcd50ad 100644 --- a/test-apps/testbed/frontend/FeatureIndex.test.ts +++ b/core/common/src/test/FeatureIndex.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import { assert } from "chai"; -import { ColorDef, ColorIndex } from "@bentley/imodeljs-common"; - +import { ColorDef } from "../ColorDef"; +import { ColorIndex } from "../FeatureIndex"; describe("ColorIndex", () => { it("should create, store and retrieve from ColorIndex", () => { const ci: ColorIndex = new ColorIndex(); diff --git a/test-apps/testbed/frontend/OctEncodedNormal.test.ts b/core/common/src/test/OctEncodedNormal.test.ts similarity index 96% rename from test-apps/testbed/frontend/OctEncodedNormal.test.ts rename to core/common/src/test/OctEncodedNormal.test.ts index 56f705c..fe0d051 100644 --- a/test-apps/testbed/frontend/OctEncodedNormal.test.ts +++ b/core/common/src/test/OctEncodedNormal.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import { assert } from "chai"; -import { OctEncodedNormal } from "@bentley/imodeljs-common"; +import { OctEncodedNormal } from "../OctEncodedNormal"; import { XYZ, Vector3d } from "@bentley/geometry-core"; function _expectSignsEqual(a: number, b: number) { diff --git a/test-apps/testbed/frontend/QPoint.test.ts b/core/common/src/test/QPoint.test.ts similarity index 96% rename from test-apps/testbed/frontend/QPoint.test.ts rename to core/common/src/test/QPoint.test.ts index c973589..21df159 100644 --- a/test-apps/testbed/frontend/QPoint.test.ts +++ b/core/common/src/test/QPoint.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ import { expect } from "chai"; -import { QParams3d, QPoint3d } from "@bentley/imodeljs-common"; +import { QParams3d, QPoint3d } from "../QPoint"; import { Point3d, Range3d } from "@bentley/geometry-core"; function expectPointsEqual(lhs: Point3d, rhs: Point3d, tolerance: number) { diff --git a/test-apps/testbed/frontend/Render.test.ts b/core/common/src/test/Render.test.ts similarity index 97% rename from test-apps/testbed/frontend/Render.test.ts rename to core/common/src/test/Render.test.ts index 9e9f319..94a55e5 100644 --- a/test-apps/testbed/frontend/Render.test.ts +++ b/core/common/src/test/Render.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { assert, expect } from "chai"; import { Id64 } from "@bentley/bentleyjs-core"; -import { Feature, GeometryClass, PolylineFlags } from "@bentley/imodeljs-common"; +import { Feature, GeometryClass, PolylineFlags } from "../Render"; describe("Feature", () => { it("constructor works as expected", () => { diff --git a/core/ecschema-metadata/CHANGELOG.json b/core/ecschema-metadata/CHANGELOG.json index bdc97e5..bcb97ea 100644 --- a/core/ecschema-metadata/CHANGELOG.json +++ b/core/ecschema-metadata/CHANGELOG.json @@ -1,6 +1,60 @@ { "name": "@bentley/ecschema-metadata", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/ecschema-metadata_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": {} + }, + { + "version": "0.170.0", + "tag": "@bentley/ecschema-metadata_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": {} + }, + { + "version": "0.169.0", + "tag": "@bentley/ecschema-metadata_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/ecschema-metadata_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/ecschema-metadata_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": {} + }, + { + "version": "0.166.0", + "tag": "@bentley/ecschema-metadata_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/ecschema-metadata_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/ecschema-metadata_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/ecschema-metadata_v0.163.0", diff --git a/core/ecschema-metadata/CHANGELOG.md b/core/ecschema-metadata/CHANGELOG.md index 7fe251d..bf90fca 100644 --- a/core/ecschema-metadata/CHANGELOG.md +++ b/core/ecschema-metadata/CHANGELOG.md @@ -1,6 +1,48 @@ # Change Log - @bentley/ecschema-metadata -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +*Version update only* + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +*Version update only* + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +*Version update only* + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/ecschema-metadata/package.json b/core/ecschema-metadata/package.json index 2680f15..33ea1e1 100644 --- a/core/ecschema-metadata/package.json +++ b/core/ecschema-metadata/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/ecschema-metadata", - "version": "0.163.0", + "version": "0.171.0", "description": "ECObjects core concepts in typescript", "license": "MIT", "main": "lib/index.js", @@ -29,28 +29,28 @@ "url": "http://www.bentley.com" }, "devDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/chai-as-promised": "^7", "@types/glob": "^5.0.35", "@types/mocha": "^5.2.5", - "@types/sinon": "^5.0.1", + "@types/sinon": "^5.0.5", "chai": "^4.1.2", "chai-as-promised": "^7", "mocha": "^5.2.0", "nyc": "^13.0.1", "rimraf": "^2.6.2", - "sinon": "^6.1.4", + "sinon": "^7.1.1", "ts-node": "^7.0.1", "typedoc": "^0.11.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0" }, "dependencies": { "glob": "^7.1.2" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0" }, "nyc": { "nycrc-path": "./node_modules/@bentley/build-tools/.nycrc" diff --git a/core/ecschema-metadata/src/Context.ts b/core/ecschema-metadata/src/Context.ts index b0531c3..6116f79 100644 --- a/core/ecschema-metadata/src/Context.ts +++ b/core/ecschema-metadata/src/Context.ts @@ -206,7 +206,7 @@ export class SchemaContext implements ISchemaLocater, ISchemaItemLocater { if (!schema) return undefined; - return await schema.getItem(schemaItemKey.name); + return schema.getItem(schemaItemKey.name); } public getSchemaItemSync(schemaItemKey: SchemaItemKey): T | undefined { diff --git a/core/ecschema-metadata/src/Deserialization/AbstractParser.ts b/core/ecschema-metadata/src/Deserialization/AbstractParser.ts index c534155..843ba48 100644 --- a/core/ecschema-metadata/src/Deserialization/AbstractParser.ts +++ b/core/ecschema-metadata/src/Deserialization/AbstractParser.ts @@ -4,38 +4,46 @@ *--------------------------------------------------------------------------------------------*/ import { - ClassProps, ConstantProps, CustomAttributeClassProps, EntityClassProps, EnumerationPropertyProps, EnumerationProps, FormatProps, InvertedUnitProps, KindOfQuantityProps, - MixinProps, NavigationPropertyProps, PhenomenonProps, PrimitiveArrayPropertyProps, PrimitiveOrEnumPropertyBaseProps, PrimitivePropertyProps, PropertyCategoryProps, - PropertyProps, RelationshipClassProps, RelationshipConstraintProps, SchemaItemProps, SchemaProps, StructArrayPropertyProps, StructPropertyProps, UnitProps, + ConstantProps, CustomAttributeClassProps, EntityClassProps, EnumerationPropertyProps, EnumerationProps, FormatProps, InvertedUnitProps, KindOfQuantityProps, + MixinProps, NavigationPropertyProps, PhenomenonProps, PrimitiveArrayPropertyProps, PrimitivePropertyProps, PropertyCategoryProps, + RelationshipClassProps, SchemaProps, StructArrayPropertyProps, StructPropertyProps, UnitProps, SchemaReferenceProps, StructClassProps, UnitSystemProps, } from "./../Deserialization/JsonProps"; -import { AnyClass } from "./../Interfaces"; +import { CustomAttribute } from "../Metadata/CustomAttribute"; + +type SchemaItemTuple = [string /** Name */, string /** SchemaItemType */, T]; +type PropertyTuple = [string /** Name */, string /** Property */, T]; /** @hidden */ -export abstract class AbstractParser { - public abstract parseSchemaProps(obj: T): SchemaProps; - public abstract parseSchemaItemProps(obj: T, schemaName: string, name: string): SchemaItemProps; - public abstract parseClassProps(obj: T, name: string): ClassProps; - public abstract parseEntityClassProps(obj: T, name: string): EntityClassProps; - public abstract parseMixinProps(obj: T, name: string): MixinProps; - public abstract parseCustomAttributeClassProps(obj: T, name: string): CustomAttributeClassProps; - public abstract parseRelationshipClassProps(obj: T, name: string): RelationshipClassProps; - public abstract parseRelationshipConstraintProps(relClassName: string, obj: T, isSource?: boolean): RelationshipConstraintProps; - public abstract parseEnumerationProps(obj: T, name: string): EnumerationProps; - public abstract parseKindOfQuantityProps(obj: T, name: string): KindOfQuantityProps; - public abstract parsePropertyCategoryProps(obj: T, name: string): PropertyCategoryProps; - public abstract parseUnitProps(obj: T, name: string): UnitProps; - public abstract parseInvertedUnitProps(obj: T, name: string): InvertedUnitProps; - public abstract parseConstantProps(obj: T, name: string): ConstantProps; - public abstract parsePhenomenonProps(obj: T, name: string): PhenomenonProps; - public abstract parseFormatProps(obj: T, name: string): FormatProps; - public abstract parsePropertyProps(obj: T, schemaName: string, className: string): PropertyProps; - public abstract parsePrimitiveOrEnumPropertyBaseProps(obj: T, schemaName: string, className: string): PrimitiveOrEnumPropertyBaseProps; - public abstract parsePrimitivePropertyProps(obj: T, schemaName: string, className: string): PrimitivePropertyProps; - public abstract parseStructPropertyProps(obj: T, schemaName: string, className: string): StructPropertyProps; - public abstract parseEnumerationPropertyProps(obj: T, schemaName: string, className: string): EnumerationPropertyProps; - public abstract parsePrimitiveArrayPropertyProps(obj: T, schemaName: string, className: string): PrimitiveArrayPropertyProps; - public abstract parseStructArrayPropertyProps(obj: T, schemaName: string, className: string): StructArrayPropertyProps; - public abstract parseNavigationPropertyProps(obj: T, name: string, classObj: AnyClass): NavigationPropertyProps; - public abstract parsePropertyTypes(obj: T, schemaName: string, className: string): PropertyProps; +export abstract class AbstractParser { + public abstract parseSchema(): SchemaProps; + public abstract getReferences(): Iterable; + public abstract getCustomAttributes(): Iterable; + + public abstract getItems(): Iterable>; + public abstract findItem(itemName: string): SchemaItemTuple | undefined; + public abstract parseEntityClass(data: TItem): EntityClassProps; + public abstract parseMixin(data: TItem): MixinProps; + public abstract parseStructClass(data: TItem): StructClassProps; + public abstract parseCustomAttributeClass(data: TItem): CustomAttributeClassProps; + public abstract parseRelationshipClass(data: TItem): RelationshipClassProps; + public abstract parseEnumeration(data: TItem): EnumerationProps; + public abstract parseKindOfQuantity(data: TItem): KindOfQuantityProps; + public abstract parsePropertyCategory(data: TItem): PropertyCategoryProps; + public abstract parseUnit(data: TItem): UnitProps; + public abstract parseInvertedUnit(data: TItem): InvertedUnitProps; + public abstract parseConstant(data: TItem): ConstantProps; + public abstract parsePhenomenon(data: TItem): PhenomenonProps; + public abstract parseFormat(data: TItem): FormatProps; + public abstract parseUnitSystem(data: TItem): UnitSystemProps; + + public abstract getProperties(data: TItem): Iterable>; + public abstract parsePrimitiveProperty(data: TProperty): PrimitivePropertyProps; + public abstract parseStructProperty(data: TProperty): StructPropertyProps; + public abstract parseEnumerationProperty(data: TProperty): EnumerationPropertyProps; + public abstract parsePrimitiveArrayProperty(data: TProperty): PrimitiveArrayPropertyProps; + public abstract parseStructArrayProperty(data: TProperty): StructArrayPropertyProps; + public abstract parseNavigationProperty(data: TProperty): NavigationPropertyProps; } + +export interface AbstractParserConstructor { new(obj: TSchema): AbstractParser; } diff --git a/core/ecschema-metadata/src/Deserialization/Helper.ts b/core/ecschema-metadata/src/Deserialization/Helper.ts index 373088b..de44e95 100644 --- a/core/ecschema-metadata/src/Deserialization/Helper.ts +++ b/core/ecschema-metadata/src/Deserialization/Helper.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { AbstractParser } from "./AbstractParser"; -import { AnySchemaItemProps, SchemaReferenceProps } from "./JsonProps"; +import { AbstractParser, AbstractParserConstructor } from "./AbstractParser"; +import { SchemaReferenceProps, ClassProps, RelationshipConstraintProps, PropertyProps } from "./JsonProps"; import { SchemaContext } from "./../Context"; import { parsePrimitiveType, parseSchemaItemType, SchemaItemType } from "./../ECObjects"; import { ECObjectsError, ECObjectsStatus } from "./../Exception"; @@ -26,37 +26,35 @@ import { SchemaKey, ECVersion, SchemaItemKey } from "./../SchemaKey"; /** * @hidden - * The purpose of this class is to properly order the deserialization of ECSchemas and SchemaItems from the JSON formats. + * The purpose of this class is to properly order the deserialization of ECSchemas and SchemaItems from serialized formats. * For example, when deserializing an ECClass most times all base class should be de-serialized before the given class. */ -export class SchemaReadHelper { +export class SchemaReadHelper { private _context: SchemaContext; private _visitor?: SchemaDeserializationVisitor; - private _parser: AbstractParser; + private _parserType: AbstractParserConstructor; + private _parser!: AbstractParser; // This is a cache of the schema we are loading. The schema also exists within the _context but in order // to not have to go back to the context every time we use this cache. private _schema?: Schema; - private _itemToRead: any; // This will be the json object of the Schema or SchemaItem to deserialize. Not sure if this is the best option.. Going to leave it for now. - - constructor(parser: AbstractParser, context?: SchemaContext, visitor?: SchemaDeserializationVisitor) { + constructor(parserType: AbstractParserConstructor, context?: SchemaContext, visitor?: SchemaDeserializationVisitor) { this._context = (undefined !== context) ? context : new SchemaContext(); this._visitor = visitor; - this._parser = parser; + this._parserType = parserType; } /** - * Populates the given Schema with the JSON. + * Populates the given Schema from a serialized representation. * @param schema The Schema to populate - * @param schemaJson The JSON to use to populate the Schema. + * @param rawSchema The serialized data to use to populate the Schema. */ - public async readSchema(schema: T, schemaJson: object | string): Promise { - this._itemToRead = typeof schemaJson === "string" ? JSON.parse(schemaJson) : schemaJson; - const parsedItem = this._parser.parseSchemaProps(this._itemToRead); + public async readSchema(schema: U, rawSchema: T): Promise { + this._parser = new this._parserType(rawSchema); // Loads all of the properties on the Schema object - await schema.deserialize(parsedItem); + await schema.deserialize(this._parser.parseSchema()); this._schema = schema; @@ -65,29 +63,29 @@ export class SchemaReadHelper { // Load schema references first // Need to figure out if other schemas are present. - if (undefined !== parsedItem.references) - await this.loadSchemaReferences(parsedItem.references); + for (const reference of this._parser.getReferences()) { + await this.loadSchemaReference(reference); + } if (this._visitor && this._visitor.visitEmptySchema) await this._visitor.visitEmptySchema(schema); // Load all schema items - if (undefined !== parsedItem.items) { - for (const itemName in parsedItem.items) { - // Make sure the item has not already been read. No need to check the SchemaContext because all SchemaItems are added to a Schema, - // which would be found when adding to the context. - if (await schema.getItem(itemName) !== undefined) - continue; - - const loadedItem = await this.loadSchemaItem(schema, parsedItem.items[itemName], itemName); - if (loadedItem && this._visitor) { - await loadedItem.accept(this._visitor); - } + for (const [itemName, itemType, rawItem] of this._parser.getItems()) { + // Make sure the item has not already been read. No need to check the SchemaContext because all SchemaItems are added to a Schema, + // which would be found when adding to the context. + if (await schema.getItem(itemName) !== undefined) + continue; + + const loadedItem = await this.loadSchemaItem(schema, itemName, itemType, rawItem); + if (loadedItem && this._visitor) { + await loadedItem.accept(this._visitor); } } - if (undefined !== parsedItem.customAttributes) - await this.loadCustomAttributes(parsedItem.customAttributes); + for (const customAttribute of this._parser.getCustomAttributes()) { + await this.loadCustomAttribute(customAttribute); + } if (this._visitor && this._visitor.visitFullSchema) await this._visitor.visitFullSchema(schema); @@ -96,16 +94,15 @@ export class SchemaReadHelper { } /** - * Populates the given Schema with the JSON. + * Populates the given Schema from a serialized representation. * @param schema The Schema to populate - * @param schemaJson The JSON to use to populate the Schema. + * @param rawSchema The serialized data to use to populate the Schema. */ - public readSchemaSync(schema: T, schemaJson: object | string): T { - this._itemToRead = typeof schemaJson === "string" ? JSON.parse(schemaJson) : schemaJson; - const parsedItem = this._parser.parseSchemaProps(this._itemToRead); + public readSchemaSync(schema: U, rawSchema: T): U { + this._parser = new this._parserType(rawSchema); // Loads all of the properties on the Schema object - schema.deserializeSync(parsedItem); + schema.deserializeSync(this._parser.parseSchema()); this._schema = schema; @@ -114,29 +111,29 @@ export class SchemaReadHelper { // Load schema references first // Need to figure out if other schemas are present. - if (undefined !== parsedItem.references) - this.loadSchemaReferencesSync(parsedItem.references); + for (const reference of this._parser.getReferences()) { + this.loadSchemaReferenceSync(reference); + } if (this._visitor && this._visitor.visitEmptySchema) this._visitor.visitEmptySchema(schema); // Load all schema items - if (undefined !== parsedItem.items) { - for (const itemName in parsedItem.items) { - // Make sure the item has not already been read. No need to check the SchemaContext because all SchemaItems are added to a Schema, - // which would be found when adding to the context. - if (schema.getItemSync(itemName) !== undefined) - continue; - - const loadedItem = this.loadSchemaItemSync(schema, parsedItem.items[itemName], itemName); - if (loadedItem && this._visitor) { - loadedItem.accept(this._visitor); - } + for (const [itemName, itemType, rawItem] of this._parser.getItems()) { + // Make sure the item has not already been read. No need to check the SchemaContext because all SchemaItems are added to a Schema, + // which would be found when adding to the context. + if (schema.getItemSync(itemName) !== undefined) + continue; + + const loadedItem = this.loadSchemaItemSync(schema, itemName, itemType, rawItem); + if (loadedItem && this._visitor) { + loadedItem.accept(this._visitor); } } - if (undefined !== parsedItem.customAttributes) - this.loadCustomAttributesSync(parsedItem.customAttributes); + for (const customAttribute of this._parser.getCustomAttributes()) { + this.loadCustomAttributeSync(customAttribute); + } if (this._visitor && this._visitor.visitFullSchema) this._visitor.visitFullSchema(schema); @@ -146,102 +143,100 @@ export class SchemaReadHelper { /** * Ensures that the SchemaReferences can be located and then loads the references. - * @param referencesJson The JSON to read the SchemaReference from. + * @param ref The object to read the SchemaReference's props from. */ - private async loadSchemaReferences(schemaRefProps: SchemaReferenceProps[]): Promise { - for (const ref of schemaRefProps) { - const schemaKey = new SchemaKey(ref.name, ECVersion.fromString(ref.version)); - const refSchema = await this._context.getSchema(schemaKey); - if (!refSchema) - throw new ECObjectsError(ECObjectsStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${ref.name}.${ref.version}, of ${this._schema!.schemaKey.name}`); + private async loadSchemaReference(ref: SchemaReferenceProps): Promise { + const schemaKey = new SchemaKey(ref.name, ECVersion.fromString(ref.version)); + const refSchema = await this._context.getSchema(schemaKey); + if (undefined === refSchema) + throw new ECObjectsError(ECObjectsStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${ref.name}.${ref.version}, of ${this._schema!.schemaKey.name}`); - await (this._schema as MutableSchema).addReference(refSchema); - } + await (this._schema as MutableSchema).addReference(refSchema); } /** * Ensures that the SchemaReferences can be located and then loads the references. - * @param referencesJson The JSON to read the SchemaReference from. + * @param ref The object to read the SchemaReference's props from. */ - private loadSchemaReferencesSync(schemaRefProps: SchemaReferenceProps[]): void { - for (const ref of schemaRefProps) { - const schemaKey = new SchemaKey(ref.name, ECVersion.fromString(ref.version)); - const refSchema = this._context.getSchemaSync(schemaKey); - if (!refSchema) - throw new ECObjectsError(ECObjectsStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${ref.name}.${ref.version}, of ${this._schema!.schemaKey.name}`); + private loadSchemaReferenceSync(ref: SchemaReferenceProps): void { + const schemaKey = new SchemaKey(ref.name, ECVersion.fromString(ref.version)); + const refSchema = this._context.getSchemaSync(schemaKey); + if (!refSchema) + throw new ECObjectsError(ECObjectsStatus.UnableToLocateSchema, `Could not locate the referenced schema, ${ref.name}.${ref.version}, of ${this._schema!.schemaKey.name}`); - (this._schema as MutableSchema).addReferenceSync(refSchema); - } + (this._schema as MutableSchema).addReferenceSync(refSchema); } /** * Given * @param schema The Schema to add this SchemaItem to. - * @param schemaItemJson The JSON to populate the SchemaItem with. - * @param name The name of the SchemaItem, only needed if the SchemaItem is being loaded outside the context of a Schema. + * @param name The name of the SchemaItem. + * @param itemType The SchemaItemType of the item to load. + * @param schemaItemObject The Object to populate the SchemaItem with. */ - private async loadSchemaItem(schema: Schema, schemaItemJson: AnySchemaItemProps, name: string): Promise { - const parsedItem = this._parser.parseSchemaItemProps(schemaItemJson, schema.name, name); + private async loadSchemaItem(schema: Schema, name: string, itemType: string, schemaItemObject: unknown): Promise { let schemaItem: AnySchemaItem | undefined; - switch (parseSchemaItemType(parsedItem.schemaItemType)) { + switch (parseSchemaItemType(itemType)) { case SchemaItemType.EntityClass: schemaItem = await (schema as MutableSchema).createEntityClass(name); - await this.loadEntityClass(schemaItem, schemaItemJson); + await this.loadEntityClass(schemaItem, schemaItemObject); break; case SchemaItemType.StructClass: schemaItem = await (schema as MutableSchema).createStructClass(name); - await this.loadClass(schemaItem, schemaItemJson); + const structProps = this._parser.parseStructClass(schemaItemObject); + await this.loadClass(schemaItem, structProps, schemaItemObject); break; case SchemaItemType.Mixin: schemaItem = await (schema as MutableSchema).createMixinClass(name); - await this.loadMixin(schemaItem, schemaItemJson); + await this.loadMixin(schemaItem, schemaItemObject); break; case SchemaItemType.CustomAttributeClass: schemaItem = await (schema as MutableSchema).createCustomAttributeClass(name); - await this.loadClass(schemaItem, schemaItemJson); + const caClassProps = this._parser.parseCustomAttributeClass(schemaItemObject); + await this.loadClass(schemaItem, caClassProps, schemaItemObject); break; case SchemaItemType.RelationshipClass: schemaItem = await (schema as MutableSchema).createRelationshipClass(name); - await this.loadRelationshipClass(schemaItem, schemaItemJson); + await this.loadRelationshipClass(schemaItem, schemaItemObject); break; case SchemaItemType.KindOfQuantity: schemaItem = await (schema as MutableSchema).createKindOfQuantity(name); - await this.loadKindOfQuantity(schemaItem, schemaItemJson); + await this.loadKindOfQuantity(schemaItem, schemaItemObject); break; case SchemaItemType.Unit: schemaItem = await (schema as MutableSchema).createUnit(name); - await this.loadUnit(schemaItem, schemaItemJson); + await this.loadUnit(schemaItem, schemaItemObject); break; case SchemaItemType.Constant: schemaItem = await (schema as MutableSchema).createConstant(name); - await this.loadConstant(schemaItem, schemaItemJson); + await this.loadConstant(schemaItem, schemaItemObject); break; case SchemaItemType.InvertedUnit: schemaItem = await (schema as MutableSchema).createInvertedUnit(name); - await this.loadInvertedUnit(schemaItem, schemaItemJson); + await this.loadInvertedUnit(schemaItem, schemaItemObject); break; case SchemaItemType.Format: schemaItem = await (schema as MutableSchema).createFormat(name); - await this.loadFormat(schemaItem, schemaItemJson); + await this.loadFormat(schemaItem, schemaItemObject); break; case SchemaItemType.Phenomenon: schemaItem = await (schema as MutableSchema).createPhenomenon(name); - const phenomenonProps = this._parser.parsePhenomenonProps(schemaItemJson, name); + const phenomenonProps = this._parser.parsePhenomenon(schemaItemObject); await schemaItem.deserialize(phenomenonProps); break; case SchemaItemType.UnitSystem: schemaItem = await (schema as MutableSchema).createUnitSystem(name); - await schemaItem.deserialize(parsedItem); + await schemaItem.deserialize(this._parser.parseUnitSystem(schemaItemObject)); break; case SchemaItemType.PropertyCategory: schemaItem = await (schema as MutableSchema).createPropertyCategory(name); - const propertyCategoryProps = this._parser.parsePropertyCategoryProps(schemaItemJson, name); + const propertyCategoryProps = this._parser.parsePropertyCategory(schemaItemObject); await schemaItem.deserialize(propertyCategoryProps); break; case SchemaItemType.Enumeration: schemaItem = await (schema as MutableSchema).createEnumeration(name); - const enumerationProps = this._parser.parseEnumerationProps(schemaItemJson, name); + const enumerationProps = this._parser.parseEnumeration(schemaItemObject); await schemaItem.deserialize(enumerationProps); break; // NOTE: we are being permissive here and allowing unknown types to silently fail. Not sure if we want to hard fail or just do a basic deserialization @@ -253,71 +248,73 @@ export class SchemaReadHelper { /** * Given * @param schema The Schema to add this SchemaItem to. - * @param schemaItemJson The JSON to populate the SchemaItem with. - * @param name The name of the SchemaItem, only needed if the SchemaItem is being loaded outside the context of a Schema. + * @param name The name of the SchemaItem. + * @param itemType The SchemaItemType of the item to load. + * @param schemaItemObject The Object to populate the SchemaItem with. */ - private loadSchemaItemSync(schema: Schema, schemaItemJson: AnySchemaItemProps, name: string): SchemaItem | undefined { - const parsedItem = this._parser.parseSchemaItemProps(schemaItemJson, schema.name, name); + private loadSchemaItemSync(schema: Schema, name: string, itemType: string, schemaItemObject: unknown): SchemaItem | undefined { let schemaItem: AnySchemaItem | undefined; - switch (parseSchemaItemType(parsedItem.schemaItemType)) { + switch (parseSchemaItemType(itemType)) { case SchemaItemType.EntityClass: schemaItem = (schema as MutableSchema).createEntityClassSync(name); - this.loadEntityClassSync(schemaItem, schemaItemJson); + this.loadEntityClassSync(schemaItem, schemaItemObject); break; case SchemaItemType.StructClass: schemaItem = (schema as MutableSchema).createStructClassSync(name); - this.loadClassSync(schemaItem, schemaItemJson); + const structProps = this._parser.parseStructClass(schemaItemObject); + this.loadClassSync(schemaItem, structProps, schemaItemObject); break; case SchemaItemType.Mixin: schemaItem = (schema as MutableSchema).createMixinClassSync(name); - this.loadMixinSync(schemaItem, schemaItemJson); + this.loadMixinSync(schemaItem, schemaItemObject); break; case SchemaItemType.CustomAttributeClass: schemaItem = (schema as MutableSchema).createCustomAttributeClassSync(name); - this.loadClassSync(schemaItem, schemaItemJson); + const caClassProps = this._parser.parseCustomAttributeClass(schemaItemObject); + this.loadClassSync(schemaItem, caClassProps, schemaItemObject); break; case SchemaItemType.RelationshipClass: schemaItem = (schema as MutableSchema).createRelationshipClassSync(name); - this.loadRelationshipClassSync(schemaItem, schemaItemJson); + this.loadRelationshipClassSync(schemaItem, schemaItemObject); break; case SchemaItemType.KindOfQuantity: schemaItem = (schema as MutableSchema).createKindOfQuantitySync(name); - this.loadKindOfQuantitySync(schemaItem, schemaItemJson); + this.loadKindOfQuantitySync(schemaItem, schemaItemObject); break; case SchemaItemType.Unit: schemaItem = (schema as MutableSchema).createUnitSync(name); - this.loadUnitSync(schemaItem, schemaItemJson); + this.loadUnitSync(schemaItem, schemaItemObject); break; case SchemaItemType.Constant: schemaItem = (schema as MutableSchema).createConstantSync(name); - this.loadConstantSync(schemaItem, schemaItemJson); + this.loadConstantSync(schemaItem, schemaItemObject); break; case SchemaItemType.InvertedUnit: schemaItem = (schema as MutableSchema).createInvertedUnitSync(name); - this.loadInvertedUnitSync(schemaItem, schemaItemJson); + this.loadInvertedUnitSync(schemaItem, schemaItemObject); break; case SchemaItemType.Format: schemaItem = (schema as MutableSchema).createFormatSync(name); - this.loadFormatSync(schemaItem, schemaItemJson); + this.loadFormatSync(schemaItem, schemaItemObject); break; case SchemaItemType.Phenomenon: schemaItem = (schema as MutableSchema).createPhenomenonSync(name); - const phenomenonProps = this._parser.parsePhenomenonProps(schemaItemJson, name); + const phenomenonProps = this._parser.parsePhenomenon(schemaItemObject); schemaItem.deserializeSync(phenomenonProps); break; case SchemaItemType.UnitSystem: schemaItem = (schema as MutableSchema).createUnitSystemSync(name); - schemaItem.deserializeSync(parsedItem); + schemaItem.deserializeSync(this._parser.parseUnitSystem(schemaItemObject)); break; case SchemaItemType.PropertyCategory: schemaItem = (schema as MutableSchema).createPropertyCategorySync(name); - const propertyCategoryProps = this._parser.parsePropertyCategoryProps(schemaItemJson, name); + const propertyCategoryProps = this._parser.parsePropertyCategory(schemaItemObject); schemaItem.deserializeSync(propertyCategoryProps); break; case SchemaItemType.Enumeration: schemaItem = (schema as MutableSchema).createEnumerationSync(name); - const enumerationProps = this._parser.parseEnumerationProps(schemaItemJson, name); + const enumerationProps = this._parser.parseEnumeration(schemaItemObject); schemaItem.deserializeSync(enumerationProps); break; // NOTE: we are being permissive here and allowing unknown types to silently fail. Not sure if we want to hard fail or just do a basic deserialization @@ -340,9 +337,9 @@ export class SchemaReadHelper { throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${fullName} is invalid without a schema name`); if (isInThisSchema && undefined === await this._schema!.getItem(itemName)) { - const itemJson: object | undefined = this._itemToRead.items[itemName]; - if (itemJson) { - const schemaItem = await this.loadSchemaItem(this._schema!, this._itemToRead.items[itemName], itemName); + const foundItem = this._parser.findItem(itemName); + if (foundItem) { + const schemaItem = await this.loadSchemaItem(this._schema!, ...foundItem); if (!skipVisitor && schemaItem && this._visitor) { await schemaItem.accept(this._visitor); } @@ -357,13 +354,45 @@ export class SchemaReadHelper { return undefined; } + /* + * Finds the a SchemaItem matching the fullName first by checking the schema that is being deserialized. If it does + * not exist within the schema the SchemaContext will be searched. + * @param fullName The full name of the SchemaItem to search for. + * @param skipVisitor Used to break Mixin -appliesTo-> Entity -extends-> Mixin cycle. + * @return The SchemaItem if it had to be loaded, otherwise undefined. + */ + private findSchemaItemSync(fullName: string, skipVisitor = false): SchemaItem | undefined { + const [schemaName, itemName] = SchemaItem.parseFullName(fullName); + const isInThisSchema = (this._schema && this._schema.name.toLowerCase() === schemaName.toLowerCase()); + + if (undefined === schemaName || schemaName.length === 0) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${fullName} is invalid without a schema name`); + + if (isInThisSchema && undefined === this._schema!.getItemSync(itemName)) { + const foundItem = this._parser.findItem(itemName); + if (foundItem) { + const schemaItem = this.loadSchemaItemSync(this._schema!, ...foundItem); + if (!skipVisitor && schemaItem && this._visitor) { + schemaItem.accept(this._visitor); + } + return schemaItem; + } + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Unable to locate SchemaItem ${fullName}.`); + } + + if (undefined === this._context.getSchemaItemSync(new SchemaItemKey(itemName, new SchemaKey(schemaName)))) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Unable to locate SchemaItem ${fullName}.`); + + return undefined; + } + /** - * Load dependencies on phenomenon and unitSystem for a Unit object and load the Unit from the json - * @param unit the Unit object that we are loading dependencies for - * @param unitJson The JSON containing the Unit to be added to the container + * Load dependencies on phenomenon and unitSystem for a Unit object and load the Unit from its serialized format. + * @param unit The Unit object that we are loading dependencies for and "deserializing into". + * @param rawUnit The serialized unit data */ - private async loadUnit(unit: Unit, unitJson: any): Promise { - const unitProps = this._parser.parseUnitProps(unitJson, unit.name); + private async loadUnit(unit: Unit, rawUnit: unknown): Promise { + const unitProps = this._parser.parseUnit(rawUnit); await this.findSchemaItem(unitProps.phenomenon, true); await this.findSchemaItem(unitProps.unitSystem, true); @@ -371,8 +400,13 @@ export class SchemaReadHelper { await unit.deserialize(unitProps); } - private loadUnitSync(unit: Unit, unitJson: any) { - const unitProps = this._parser.parseUnitProps(unitJson, unit.name); + /** + * Load dependencies on phenomenon and unitSystem for a Unit object and load the Unit from its serialized format. + * @param unit The Unit object that we are loading dependencies for and "deserializing into". + * @param rawUnit The serialized unit data + */ + private loadUnitSync(unit: Unit, rawUnit: unknown) { + const unitProps = this._parser.parseUnit(rawUnit); this.findSchemaItemSync(unitProps.phenomenon, true); this.findSchemaItemSync(unitProps.unitSystem, true); @@ -381,51 +415,56 @@ export class SchemaReadHelper { } /** - * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from the json - * @param koq the kind of quantity object that we are loading the persistence unit and presentation unit dependencies for - * @param koqJson The JSON containing the kind of quantity to be added to the container + * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from its serialized format. + * @param koq The KindOfQuantity object that we are loading dependencies for and "deserializing into". + * @param rawKoQ The serialized kind of quantity data */ - private async loadKindOfQuantity(koq: KindOfQuantity, koqJson: any): Promise { - const koqProps = this._parser.parseKindOfQuantityProps(koqJson, koq.name); + private async loadKindOfQuantity(koq: KindOfQuantity, rawKoQ: unknown): Promise { + const koqProps = this._parser.parseKindOfQuantity(rawKoQ); await koq.deserialize(koqProps); } /** - * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from the json - * @param koq the kind of quantity object that we are loading the persistence unit and presentation unit dependencies for - * @param koqJson The JSON containing the kind of quantity to be added to the container + * Load the persistence unit and presentation unit dependencies for a KindOfQuantity object and load the KoQ from its serialized format. + * @param koq The KindOfQuantity object that we are loading dependencies for and "deserializing into". + * @param rawKoQ The serialized kind of quantity data */ - private loadKindOfQuantitySync(koq: KindOfQuantity, koqJson: any) { - const koqProps = this._parser.parseKindOfQuantityProps(koqJson, koq.name); + private loadKindOfQuantitySync(koq: KindOfQuantity, rawKoQ: unknown) { + const koqProps = this._parser.parseKindOfQuantity(rawKoQ); koq.deserializeSync(koqProps); } /** - * Load the phenomenon dependency for a Constant object and load the Constant from the json - * @param constant the Constant object that we are loading the phenomenon dependency for - * @param constantJson The JSON containing the Constant to be added to the container + * Load the phenomenon dependency for a Constant object and load the Constant from its serialized format. + * @param constant The Constant object that we are loading the phenomenon dependency for + * @param rawConstant The serialized constant data */ - private async loadConstant(constant: Constant, constantJson: any): Promise { - const constantProps = this._parser.parseConstantProps(constantJson, constant.name); + private async loadConstant(constant: Constant, rawConstant: unknown): Promise { + const constantProps = this._parser.parseConstant(rawConstant); await this.findSchemaItem(constantProps.phenomenon, true); await constant.deserialize(constantProps); } - private loadConstantSync(constant: Constant, constantJson: any) { - const constantProps = this._parser.parseConstantProps(constantJson, constant.name); + /** + * Load the phenomenon dependency for a Constant object and load the Constant from its serialized format. + * @param constant The Constant object that we are loading dependencies for and "deserializing into". + * @param rawConstant The serialized constant data + */ + private loadConstantSync(constant: Constant, rawConstant: unknown) { + const constantProps = this._parser.parseConstant(rawConstant); this.findSchemaItemSync(constantProps.phenomenon, true); constant.deserializeSync(constantProps); } /** - * Load the unit system and invertsUnit dependencies for an Inverted Unit object and load the Inverted Unit from the json - * @param invertedUnit the inverted unit object that we are loading the unit system and invertsUnit dependencies for - * @param invertedUnitJson The JSON containing the InvertedUnit to be added to the container + * Load the unit system and invertsUnit dependencies for an Inverted Unit object and load the Inverted Unit from its serialized format. + * @param invertedUnit The InvertedUnit object that we are loading dependencies for and "deserializing into". + * @param rawInvertedUnit The serialized inverted unit data. */ - private async loadInvertedUnit(invertedUnit: InvertedUnit, invertedUnitJson: any): Promise { - const invertedUnitProps = this._parser.parseInvertedUnitProps(invertedUnitJson, invertedUnit.name); + private async loadInvertedUnit(invertedUnit: InvertedUnit, rawInvertedUnit: unknown): Promise { + const invertedUnitProps = this._parser.parseInvertedUnit(rawInvertedUnit); await this.findSchemaItem(invertedUnitProps.invertsUnit, true); await this.findSchemaItem(invertedUnitProps.unitSystem, true); @@ -433,8 +472,13 @@ export class SchemaReadHelper { await invertedUnit.deserialize(invertedUnitProps); } - private loadInvertedUnitSync(invertedUnit: InvertedUnit, invertedUnitJson: any) { - const invertedUnitProps = this._parser.parseInvertedUnitProps(invertedUnitJson, invertedUnit.name); + /** + * Load the unit system and invertsUnit dependencies for an Inverted Unit object and load the Inverted Unit from its serialized format. + * @param invertedUnit The InvertedUnit object that we are loading dependencies for and "deserializing into". + * @param rawInvertedUnit The serialized inverted unit data. + */ + private loadInvertedUnitSync(invertedUnit: InvertedUnit, rawInvertedUnit: unknown) { + const invertedUnitProps = this._parser.parseInvertedUnit(rawInvertedUnit); this.findSchemaItemSync(invertedUnitProps.invertsUnit, true); this.findSchemaItemSync(invertedUnitProps.unitSystem, true); @@ -443,12 +487,12 @@ export class SchemaReadHelper { } /** - * Load the unit dependencies for a Format object and load the Format from the json - * @param format the format object that we are loading the unit dependencies for - * @param formatJson The JSON containing the Format to be added to the container + * Load the unit dependencies for a Format object and load the Format from its serialized format. + * @param format The Format object that we are loading dependencies for and "deserializing into". + * @param rawFormat The serialized format data. */ - private async loadFormat(format: Format, formatJson: any): Promise { - const formatProps = this._parser.parseFormatProps(formatJson, format.name); + private async loadFormat(format: Format, rawFormat: unknown): Promise { + const formatProps = this._parser.parseFormat(rawFormat); if (undefined !== formatProps.composite) { const formatUnits = await formatProps.composite.units!; @@ -459,8 +503,13 @@ export class SchemaReadHelper { await format.deserialize(formatProps); } - private loadFormatSync(format: Format, formatJson: any) { - const formatProps = this._parser.parseFormatProps(formatJson, format.name); + /** + * Load the unit dependencies for a Format object and load the Format from its serialized format. + * @param format The Format object that we are loading dependencies for and "deserializing into". + * @param rawFormat The serialized format data. + */ + private loadFormatSync(format: Format, rawFormat: unknown) { + const formatProps = this._parser.parseFormat(rawFormat); if (undefined !== formatProps.composite) { const formatUnits = formatProps.composite.units!; @@ -472,69 +521,30 @@ export class SchemaReadHelper { format.deserializeSync(formatProps); } - /* - * Finds the a SchemaItem matching the fullName first by checking the schema that is being deserialized. If it does - * not exist within the schema the SchemaContext will be searched. - * @param fullName The full name of the SchemaItem to search for. - * @param skipVisitor Used to break Mixin -appliesTo-> Entity -extends-> Mixin cycle. - * @return The SchemaItem if it had to be loaded, otherwise undefined. - */ - private findSchemaItemSync(fullName: string, skipVisitor = false): SchemaItem | undefined { - const [schemaName, itemName] = SchemaItem.parseFullName(fullName); - const isInThisSchema = (this._schema && this._schema.name.toLowerCase() === schemaName.toLowerCase()); - - if (undefined === schemaName || schemaName.length === 0) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${fullName} is invalid without a schema name`); - - if (isInThisSchema && undefined === this._schema!.getItemSync(itemName)) { - const itemJson: object | undefined = this._itemToRead.items[itemName]; - if (itemJson) { - const schemaItem = this.loadSchemaItemSync(this._schema!, this._itemToRead.items[itemName], itemName); - if (!skipVisitor && schemaItem && this._visitor) { - schemaItem.accept(this._visitor); - } - return schemaItem; - } - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Unable to locate SchemaItem ${fullName}.`); - } - - if (undefined === this._context.getSchemaItemSync(new SchemaItemKey(itemName, new SchemaKey(schemaName)))) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Unable to locate SchemaItem ${fullName}.`); - - return undefined; - } - /** - * Given a CustomAttributeContainer and a CustomAttribute JSON it will make sure that all of the the CustomAttribute classes can be found within the context - * of the Container. - * @param caContainer The CustomAttribute Container to read the customAttributes within the context of. - * @param customAttributesJson The JSON containing the customAttributes that are to be added to the container + * Given a deserialized CustomAttribute object, make sure that the corresponding CustomAttributeClass can be found within the current context. + * @param customAttributeProps The deserialized CustomAttribute. */ - private async loadCustomAttributes(customAttributeProps: CustomAttribute[]): Promise { - this.loadCustomAttributesSync(customAttributeProps); + private async loadCustomAttribute(customAttributeProps: CustomAttribute): Promise { + await this.findSchemaItem(customAttributeProps.className); } /** - * Given a CustomAttributeContainer and a CustomAttribute JSON it will make sure that all of the the CustomAttribute classes can be found within the context - * of the Container. - * @param caContainer The CustomAttribute Container to read the customAttributes within the context of. - * @param customAttributesJson The JSON containing the customAttributes that are to be added to the container + * Given a deserialized CustomAttribute object, make sure that the corresponding CustomAttributeClass can be found within the current context. + * @param customAttributeProps The deserialized CustomAttribute. */ - private loadCustomAttributesSync(customAttributeProps: CustomAttribute[]): void { - for (const instance of customAttributeProps) { - this.findSchemaItem(instance.className); - } + private loadCustomAttributeSync(customAttributeProps: CustomAttribute): void { + this.findSchemaItemSync(customAttributeProps.className); } /** - * - * @param schemaJson The original json object of the schema. - * @param classJson The json object for this class - * @param schema The ECSchema this class exists in. + * Load the base class and property type dependencies for an ECClass object and load the ECClass (and its properties) from its serialized format. + * @param classObj The ECClass object that we are loading dependencies for and "deserializing into". + * @param classProps The parsed class props object. + * @param rawClass The serialized class data. */ - private async loadClass(classObj: AnyClass, classJson: any): Promise { + private async loadClass(classObj: AnyClass, classProps: ClassProps, rawClass: unknown): Promise { // Load base class first - const classProps = this._parser.parseClassProps(classJson, classObj.name); let baseClass: undefined | SchemaItem; if (undefined !== classProps.baseClass) { baseClass = await this.findSchemaItem(classProps.baseClass, true); @@ -544,10 +554,8 @@ export class SchemaReadHelper { // (We need to do this to break Entity -navProp-> Relationship -constraint-> Entity cycle.) await (classObj as ECClass).deserialize(classProps); - if (undefined !== classProps.properties) { - for (const property of classProps.properties) { - await this.loadPropertyTypes(classObj, property); - } + for (const [propName, propType, rawProp] of this._parser.getProperties(rawClass)) { + await this.loadPropertyTypes(classObj, propName, propType, rawProp); } if (baseClass && this._visitor) @@ -555,14 +563,13 @@ export class SchemaReadHelper { } /** - * - * @param schemaJson The original json object of the schema. - * @param classJson The json object for this class - * @param schema The ECSchema this class exists in. + * Load the base class and property type dependencies for an ECClass object and load the ECClass (and its properties) from its serialized format. + * @param classObj The ECClass object that we are loading dependencies for and "deserializing into". + * @param classProps The parsed class props object. + * @param rawClass The serialized class data. */ - private loadClassSync(classObj: AnyClass, classJson: any): void { + private loadClassSync(classObj: AnyClass, classProps: ClassProps, rawClass: unknown): void { // Load base class first - const classProps = this._parser.parseClassProps(classJson, classObj.name); let baseClass: undefined | SchemaItem; if (undefined !== classProps.baseClass) { baseClass = this.findSchemaItemSync(classProps.baseClass, true); @@ -572,218 +579,259 @@ export class SchemaReadHelper { // (We need to do this to break Entity -navProp-> Relationship -constraint-> Entity cycle.) (classObj as ECClass).deserializeSync(classProps); - if (undefined !== classProps.properties) { - for (const property of classProps.properties) { - this.loadPropertyTypesSync(classObj, property); - } + for (const [propName, propType, rawProp] of this._parser.getProperties(rawClass)) { + this.loadPropertyTypesSync(classObj, propName, propType, rawProp); } if (baseClass && this._visitor) baseClass.accept(this._visitor); } - private async loadEntityClass(entity: EntityClass, entityJson: any): Promise { + /** + * Load the mixin, base class, and property type dependencies for an EntityClass object and load the EntityClass (and properties) from its serialized format. + * @param entity The EntityClass that we are loading dependencies for and "deserializing into". + * @param rawEntity The serialized entity class data. + */ + private async loadEntityClass(entity: EntityClass, rawEntity: unknown): Promise { + const entityClassProps = this._parser.parseEntityClass(rawEntity); + // Load Mixin classes first - const entityClassProps = this._parser.parseEntityClassProps(entityJson, entity.name); if (undefined !== entityClassProps.mixins) { - for (const mixinName of entityClassProps.mixins) { + for (const mixinName of entityClassProps.mixins) await this.findSchemaItem(mixinName); - } } - await this.loadClass(entity, entityJson); + await this.loadClass(entity, entityClassProps, rawEntity); } - private loadEntityClassSync(entity: EntityClass, entityJson: any): void { + /** + * Load the mixin, base class, and property type dependencies for an EntityClass object and load the EntityClass (and properties) from its serialized format. + * @param entity The EntityClass that we are loading dependencies for and "deserializing into". + * @param rawEntity The serialized entity class data. + */ + private loadEntityClassSync(entity: EntityClass, rawEntity: unknown): void { + const entityClassProps = this._parser.parseEntityClass(rawEntity); + // Load Mixin classes first - const entityClassProps = this._parser.parseEntityClassProps(entityJson, entity.name); if (undefined !== entityClassProps.mixins) { - for (const mixinName of entityClassProps.mixins) { + for (const mixinName of entityClassProps.mixins) this.findSchemaItemSync(mixinName); - } } - this.loadClassSync(entity, entityJson); + this.loadClassSync(entity, entityClassProps, rawEntity); } - private async loadMixin(mixin: Mixin, mixinJson: any): Promise { - const mixinProps = this._parser.parseMixinProps(mixinJson, mixin.name); - let appliesToClass: undefined | SchemaItem; - appliesToClass = await this.findSchemaItem(mixinProps.appliesTo, true); + /** + * Load the appliesTo class, base class, and property type dependencies for a Mixin object and load the Mixin (and properties) from its serialized format. + * @param mixin The Mixin that we are loading dependencies for and "deserializing into". + * @param rawMixin The serialized mixin data. + */ + private async loadMixin(mixin: Mixin, rawMixin: unknown): Promise { + const mixinProps = this._parser.parseMixin(rawMixin); + const appliesToClass = await this.findSchemaItem(mixinProps.appliesTo, true); - await this.loadClass(mixin, mixinJson); + await this.loadClass(mixin, mixinProps, rawMixin); if (appliesToClass && this._visitor) await appliesToClass.accept(this._visitor); } - private loadMixinSync(mixin: Mixin, mixinJson: any): void { - const mixinProps = this._parser.parseMixinProps(mixinJson, mixin.name); - let appliesToClass: undefined | SchemaItem; - appliesToClass = this.findSchemaItemSync(mixinProps.appliesTo, true); + /** + * Load the appliesTo class, base class, and property type dependencies for a Mixin object and load the Mixin (and properties) from its serialized format. + * @param mixin The Mixin that we are loading dependencies for and "deserializing into". + * @param rawMixin The serialized mixin data. + */ + private loadMixinSync(mixin: Mixin, rawMixin: unknown): void { + const mixinProps = this._parser.parseMixin(rawMixin); + const appliesToClass = this.findSchemaItemSync(mixinProps.appliesTo, true); - this.loadClassSync(mixin, mixinJson); + this.loadClassSync(mixin, mixinProps, rawMixin); if (appliesToClass && this._visitor) appliesToClass.accept(this._visitor); } - private async loadRelationshipClass(rel: RelationshipClass, relJson: any): Promise { - const relationshipClassProps = this._parser.parseRelationshipClassProps(relJson, rel.name); - - await this.loadClass(rel, relJson); - - await this.loadRelationshipConstraint(rel.name, rel.source.isSource, rel.source, relationshipClassProps.source); - await this.loadRelationshipConstraint(rel.name, rel.source.isSource, rel.target, relationshipClassProps.target); + /** + * Load the relationship constraint, base class, and property type dependencies for a RelationshipClass object and load the RelationshipClass (and properties) from its serialized format. + * @param rel The RelationshipClass that we are loading dependencies for and "deserializing into". + * @param rawRel The serialized relationship class data. + */ + private async loadRelationshipClass(rel: RelationshipClass, rawRel: unknown): Promise { + const relationshipClassProps = this._parser.parseRelationshipClass(rawRel); + await this.loadClass(rel, relationshipClassProps, rawRel); + await this.loadRelationshipConstraint(rel.source, relationshipClassProps.source); + await this.loadRelationshipConstraint(rel.target, relationshipClassProps.target); } - private loadRelationshipClassSync(rel: RelationshipClass, relJson: any): void { - const relationshipClassProps = this._parser.parseRelationshipClassProps(relJson, rel.name); - - this.loadClassSync(rel, relJson); - - this.loadRelationshipConstraintSync(rel.name, rel.source.isSource, rel.source, relationshipClassProps.source); - this.loadRelationshipConstraintSync(rel.name, rel.target.isSource, rel.target, relationshipClassProps.target); + /** + * Load the relationship constraint, base class, and property type dependencies for a RelationshipClass object and load the RelationshipClass (and properties) from its serialized format. + * @param rel The RelationshipClass that we are loading dependencies for and "deserializing into". + * @param rawRel The serialized relationship class data. + */ + private loadRelationshipClassSync(rel: RelationshipClass, rawRel: unknown): void { + const relationshipClassProps = this._parser.parseRelationshipClass(rawRel); + this.loadClassSync(rel, relationshipClassProps, rawRel); + this.loadRelationshipConstraintSync(rel.source, relationshipClassProps.source); + this.loadRelationshipConstraintSync(rel.target, relationshipClassProps.target); } - private async loadRelationshipConstraint(relClassName: string, isSource: boolean, relConstraint: RelationshipConstraint, relConstraintJson: any): Promise { - const relationshipConstraintProps = this._parser.parseRelationshipConstraintProps(relClassName, relConstraintJson, isSource); - - if (undefined !== relationshipConstraintProps.abstractConstraint) { - await this.findSchemaItem(relationshipConstraintProps.abstractConstraint); + /** + * Load the abstract constraint and constraint class dependencies for a RelationshipConstraint object and load the RelationshipConstraint from its parsed props. + * @param relConstraint The RelationshipConstraint that we are loading dependencies for and "deserializing into". + * @param props The parsed relationship constraint props. + */ + private async loadRelationshipConstraint(relConstraint: RelationshipConstraint, props: RelationshipConstraintProps): Promise { + if (undefined !== props.abstractConstraint) { + await this.findSchemaItem(props.abstractConstraint); } - if (undefined !== relationshipConstraintProps.constraintClasses) { // TODO: this should be required - for (const constraintClass of relationshipConstraintProps.constraintClasses) { + if (undefined !== props.constraintClasses) { // TODO: this should be required + for (const constraintClass of props.constraintClasses) { await this.findSchemaItem(constraintClass); } } - await relConstraint.deserialize(relationshipConstraintProps); + await relConstraint.deserialize(props); } - private loadRelationshipConstraintSync(relClassName: string, isSource: boolean, relConstraint: RelationshipConstraint, relConstraintJson: any): void { - const relationshipConstraintProps = this._parser.parseRelationshipConstraintProps(relClassName, relConstraintJson, isSource); - - if (undefined !== relationshipConstraintProps.abstractConstraint) { - this.findSchemaItemSync(relationshipConstraintProps.abstractConstraint); + /** + * Load the abstract constraint and constraint class dependencies for a RelationshipConstraint object and load the RelationshipConstraint from its parsed props. + * @param relConstraint The RelationshipConstraint that we are loading dependencies for and "deserializing into". + * @param props The parsed relationship constraint props. + */ + private loadRelationshipConstraintSync(relConstraint: RelationshipConstraint, props: RelationshipConstraintProps): void { + if (undefined !== props.abstractConstraint) { + this.findSchemaItemSync(props.abstractConstraint); } - if (undefined !== relationshipConstraintProps.constraintClasses) { - for (const constraintClass of relationshipConstraintProps.constraintClasses) { + if (undefined !== props.constraintClasses) { + for (const constraintClass of props.constraintClasses) { this.findSchemaItemSync(constraintClass); } } - relConstraint.deserializeSync(relationshipConstraintProps); + relConstraint.deserializeSync(props); } /** - * Creates the property defined in the JSON in the given class. - * @param classObj - * @param propertyJson + * Load the type dependencies for a serialized property, then creates and deserialized the Property object in the given ECClass. + * @param classObj The ECClass that the Property should be created in. + * @param propName The name of the Property. + * @param propType The (serialized string) kind of property to create. + * @param rawProperty The serialized property class data. */ - private async loadPropertyTypes(classObj: AnyClass, propertyJson: any): Promise { - const propertyProps = this._parser.parsePropertyTypes(propertyJson, classObj.schema.name, classObj.name); - const propName = propertyProps.name; + private async loadPropertyTypes(classObj: AnyClass, propName: string, propType: string, rawProperty: unknown): Promise { - const loadTypeName = async () => { - if (undefined === parsePrimitiveType(propertyJson.typeName)) - await this.findSchemaItem(propertyJson.typeName); + const loadTypeName = async (typeName: string) => { + if (undefined === parsePrimitiveType(typeName)) + await this.findSchemaItem(typeName); }; - switch (propertyProps.type) { + switch (propType) { case "PrimitiveProperty": - await loadTypeName(); - const primPropertyProps = this._parser.parsePrimitivePropertyProps(propertyJson, classObj.schema.name, classObj.name); + const primPropertyProps = this._parser.parsePrimitiveProperty(rawProperty); + await loadTypeName(primPropertyProps.typeName); const primProp = await (classObj as MutableClass).createPrimitiveProperty(propName, primPropertyProps.typeName); - return this.loadProperty(primProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadProperty(primProp, primPropertyProps); case "StructProperty": - await loadTypeName(); - const structPropertyProps = this._parser.parseStructPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const structPropertyProps = this._parser.parseStructProperty(rawProperty); + await loadTypeName(structPropertyProps.typeName); const structProp = await (classObj as MutableClass).createStructProperty(propName, structPropertyProps.typeName); - return this.loadProperty(structProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadProperty(structProp, structPropertyProps); case "PrimitiveArrayProperty": - await loadTypeName(); - const primArrPropertyProps = this._parser.parsePrimitiveArrayPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const primArrPropertyProps = this._parser.parsePrimitiveArrayProperty(rawProperty); + await loadTypeName(primArrPropertyProps.typeName); const primArrProp = await (classObj as MutableClass).createPrimitiveArrayProperty(propName, primArrPropertyProps.typeName); - return this.loadProperty(primArrProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadProperty(primArrProp, primArrPropertyProps); case "StructArrayProperty": - await loadTypeName(); - const structArrPropertyProps = this._parser.parseStructArrayPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const structArrPropertyProps = this._parser.parseStructArrayProperty(rawProperty); + await loadTypeName(structArrPropertyProps.typeName); const structArrProp = await (classObj as MutableClass).createStructArrayProperty(propName, structArrPropertyProps.typeName); - return this.loadProperty(structArrProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadProperty(structArrProp, structArrPropertyProps); case "NavigationProperty": - const navPropertyProps = this._parser.parseNavigationPropertyProps(propertyJson, propName, classObj); - await this.findSchemaItem(propertyJson.relationshipName); + if (classObj.schemaItemType !== SchemaItemType.EntityClass && classObj.schemaItemType !== SchemaItemType.RelationshipClass && classObj.schemaItemType !== SchemaItemType.Mixin) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${propName} is invalid, because only EntityClasses, Mixins, and RelationshipClasses can have NavigationProperties.`); + + const navPropertyProps = this._parser.parseNavigationProperty(rawProperty); + await this.findSchemaItem(navPropertyProps.relationshipName); const navProp = await (classObj as MutableEntityClass).createNavigationProperty(propName, navPropertyProps.relationshipName, navPropertyProps.direction); - return this.loadProperty(navProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadProperty(navProp, navPropertyProps); } } /** - * Creates the property defined in the JSON in the given class. - * @param classObj - * @param propertyJson + * Load the type dependencies for a serialized property, then creates and deserialized the Property object in the given ECClass. + * @param classObj The ECClass that the Property should be created in. + * @param propName The name of the Property. + * @param propType The (serialized string) kind of property to create. + * @param rawProperty The serialized property class data. */ - private loadPropertyTypesSync(classObj: AnyClass, propertyJson: any): void { - const propertyProps = this._parser.parsePropertyTypes(propertyJson, classObj.schema.name, classObj.name); - const propName = propertyJson.name; - - const loadTypeName = () => { - if (undefined === parsePrimitiveType(propertyJson.typeName)) - this.findSchemaItemSync(propertyJson.typeName); + private loadPropertyTypesSync(classObj: AnyClass, propName: string, propType: string, rawProperty: unknown): void { + const loadTypeName = (typeName: string) => { + if (undefined === parsePrimitiveType(typeName)) + this.findSchemaItemSync(typeName); }; - switch (propertyProps.type) { + switch (propType) { case "PrimitiveProperty": - loadTypeName(); - const primPropertyProps = this._parser.parsePrimitivePropertyProps(propertyJson, classObj.schema.name, classObj.name); + const primPropertyProps = this._parser.parsePrimitiveProperty(rawProperty); + loadTypeName(primPropertyProps.typeName); const primProp = (classObj as MutableClass).createPrimitivePropertySync(propName, primPropertyProps.typeName); - return this.loadPropertySync(primProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadPropertySync(primProp, primPropertyProps); case "StructProperty": - loadTypeName(); - const structPropertyProps = this._parser.parseStructPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const structPropertyProps = this._parser.parseStructProperty(rawProperty); + loadTypeName(structPropertyProps.typeName); const structProp = (classObj as MutableClass).createStructPropertySync(propName, structPropertyProps.typeName); - return this.loadPropertySync(structProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadPropertySync(structProp, structPropertyProps); case "PrimitiveArrayProperty": - loadTypeName(); - const primArrPropertyProps = this._parser.parsePrimitiveArrayPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const primArrPropertyProps = this._parser.parsePrimitiveArrayProperty(rawProperty); + loadTypeName(primArrPropertyProps.typeName); const primArrProp = (classObj as MutableClass).createPrimitiveArrayPropertySync(propName, primArrPropertyProps.typeName); - return this.loadPropertySync(primArrProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadPropertySync(primArrProp, primArrPropertyProps); case "StructArrayProperty": - loadTypeName(); - const structArrPropertyProps = this._parser.parseStructArrayPropertyProps(propertyJson, classObj.schema.name, classObj.name); + const structArrPropertyProps = this._parser.parseStructArrayProperty(rawProperty); + loadTypeName(structArrPropertyProps.typeName); const structArrProp = (classObj as MutableClass).createStructArrayPropertySync(propName, structArrPropertyProps.typeName); - return this.loadPropertySync(structArrProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadPropertySync(structArrProp, structArrPropertyProps); case "NavigationProperty": - const navPropertyProps = this._parser.parseNavigationPropertyProps(propertyJson, propName, classObj); - this.findSchemaItemSync(propertyJson.relationshipName); + if (classObj.schemaItemType !== SchemaItemType.EntityClass && classObj.schemaItemType !== SchemaItemType.RelationshipClass && classObj.schemaItemType !== SchemaItemType.Mixin) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${propName} is invalid, because only EntityClasses, Mixins, and RelationshipClasses can have NavigationProperties.`); + + const navPropertyProps = this._parser.parseNavigationProperty(rawProperty); + this.findSchemaItemSync(navPropertyProps.relationshipName); const navProp = (classObj as MutableEntityClass).createNavigationPropertySync(propName, navPropertyProps.relationshipName, navPropertyProps.direction); - return this.loadPropertySync(navProp, propertyJson, classObj.schema.name, classObj.name); + return this.loadPropertySync(navProp, navPropertyProps); } } - private async loadProperty(prop: T, propertyJson: any, schemaName: string, className: string): Promise { - const propertyProps = this._parser.parsePropertyProps(propertyJson, schemaName, className); - if (undefined !== propertyProps.category) { - await this.findSchemaItem(propertyProps.category); + /** + * Load the propertyCategory, kindOfQuantity, and customAttribute dependencies for a Property object and load the Property from its parsed props. + * @param propertyObj The Property that we are loading dependencies for and "deserializing into". + * @param props The parsed property props. + */ + private async loadProperty(propertyObj: U, props: PropertyProps): Promise { + if (undefined !== props.category) { + await this.findSchemaItem(props.category); } - if (undefined !== propertyProps.kindOfQuantity) { - await this.findSchemaItem(propertyProps.kindOfQuantity); + if (undefined !== props.kindOfQuantity) { + await this.findSchemaItem(props.kindOfQuantity); } // TODO Load CustomAttributeClasses - await prop.deserialize(propertyProps); + await propertyObj.deserialize(props); } - private loadPropertySync(prop: T, propertyJson: any, schemaName: string, className: string): void { - const propertyProps = this._parser.parsePropertyProps(propertyJson, schemaName, className); + /** + * Load the propertyCategory, kindOfQuantity, and customAttribute dependencies for a Property object and load the Property from its parsed props. + * @param propertyObj The Property that we are loading dependencies for and "deserializing into". + * @param props The parsed property props. + */ + private loadPropertySync(prop: U, propertyProps: PropertyProps): void { if (undefined !== propertyProps.category) { this.findSchemaItemSync(propertyProps.category); } diff --git a/core/ecschema-metadata/src/Deserialization/JsonParser.ts b/core/ecschema-metadata/src/Deserialization/JsonParser.ts index bce461e..93e0dc2 100644 --- a/core/ecschema-metadata/src/Deserialization/JsonParser.ts +++ b/core/ecschema-metadata/src/Deserialization/JsonParser.ts @@ -5,198 +5,249 @@ import { AbstractParser } from "./AbstractParser"; import { - ClassProps, ConstantProps, CustomAttributeClassProps, EntityClassProps, EnumerationPropertyProps, EnumerationProps, EnumeratorProps, FormatProps, InvertedUnitProps, KindOfQuantityProps, + ConstantProps, CustomAttributeClassProps, EntityClassProps, EnumerationPropertyProps, EnumerationProps, FormatProps, InvertedUnitProps, KindOfQuantityProps, MixinProps, NavigationPropertyProps, PhenomenonProps, PrimitiveArrayPropertyProps, PrimitiveOrEnumPropertyBaseProps, PrimitivePropertyProps, PropertyCategoryProps, - PropertyProps, RelationshipClassProps, RelationshipConstraintProps, SchemaItemProps, SchemaProps, SchemaReferenceProps, StructArrayPropertyProps, StructPropertyProps, UnitProps, + PropertyProps, RelationshipClassProps, SchemaProps, SchemaReferenceProps, StructArrayPropertyProps, StructPropertyProps, UnitProps, } from "./JsonProps"; -import { parseStrength, parseStrengthDirection, SchemaItemType } from "../ECObjects"; +import { parseStrength, parseStrengthDirection } from "../ECObjects"; import { ECObjectsError, ECObjectsStatus } from "../Exception"; -import { AnyClass } from "../Interfaces"; import { ECName } from "../SchemaKey"; import { CustomAttribute } from "../Metadata/CustomAttribute"; -function isObject(x: unknown): x is { [name: string]: unknown } { +interface UnknownObject { [name: string]: unknown; } +function isObject(x: unknown): x is UnknownObject { return typeof (x) === "object"; } /** @hidden */ -export class JsonParser extends AbstractParser { +export class JsonParser extends AbstractParser { + private _rawSchema: UnknownObject; + private _schemaName?: string; + private _currentItemFullName?: string; + + constructor(rawSchema: unknown) { + super(); + + if (!isObject(rawSchema)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Invalid JSON object.`); + + this._rawSchema = rawSchema; + this._schemaName = rawSchema.name as string | undefined; + } /** * Type checks Schema and returns SchemaProps interface - * @param jsonObj + * @param this._rawSchema * @returns SchemaProps */ - public parseSchemaProps(jsonObj: unknown): SchemaProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Invalid JSON object.`); - if (undefined === jsonObj.$schema) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECSchema is missing the required '$schema' attribute.`); - if (typeof (jsonObj.$schema) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECSchema has an invalid '$schema' attribute. It should be of type 'string'.`); - - if (undefined === jsonObj.name) + public parseSchema(): SchemaProps { + if (undefined === this._rawSchema.name) throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECSchema is missing the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") + if (typeof (this._rawSchema.name) !== "string") throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECSchema has an invalid 'name' attribute. It should be of type 'string'.`); - if (undefined === jsonObj.version) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${jsonObj.name} is missing the required 'version' attribute.`); - if (typeof (jsonObj.version) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${jsonObj.name} has an invalid 'version' attribute. It should be of type 'string'.`); + if (undefined === this._rawSchema.$schema) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} is missing the required '$schema' attribute.`); + if (typeof (this._rawSchema.$schema) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} has an invalid '$schema' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.alias) { - if (typeof (jsonObj.alias) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${jsonObj.name} has an invalid 'alias' attribute. It should be of type 'string'.`); - } + if (undefined === this._rawSchema.version) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} is missing the required 'version' attribute.`); + if (typeof (this._rawSchema.version) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} has an invalid 'version' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.label) { - if (typeof (jsonObj.label) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${jsonObj.name} has an invalid 'label' attribute. It should be of type 'string'.`); + if (undefined !== this._rawSchema.alias) { + if (typeof (this._rawSchema.alias) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} has an invalid 'alias' attribute. It should be of type 'string'.`); } - if (undefined !== jsonObj.description) { - if (typeof (jsonObj.description) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${jsonObj.name} has an invalid 'description' attribute. It should be of type 'string'.`); + if (undefined !== this._rawSchema.label) { + if (typeof (this._rawSchema.label) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} has an invalid 'label' attribute. It should be of type 'string'.`); } - if (undefined !== jsonObj.references) { - if (!Array.isArray(jsonObj.references)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${jsonObj.name} has an invalid 'references' property. It should be of type 'object[]'.`); - const references = new Array(); - for (const ref of jsonObj.references) { - references.push(this.checkSchemaReference(ref, jsonObj.name)); - } - jsonObj.references = references; + if (undefined !== this._rawSchema.description) { + if (typeof (this._rawSchema.description) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECSchema ${this._schemaName} has an invalid 'description' attribute. It should be of type 'string'.`); } - if (undefined !== jsonObj.items) { - if (!isObject(jsonObj.items) || Array.isArray(jsonObj.items)) // fails if items is an array or otherwise not an object - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${jsonObj.name} has an invalid 'items' attribute. It should be of type 'object'.`); + return this._rawSchema as SchemaProps; + } + + public *getReferences(): Iterable { + if (undefined !== this._rawSchema.references) { + if (!Array.isArray(this._rawSchema.references)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._rawSchema.name} has an invalid 'references' attribute. It should be of type 'object[]'.`); - for (const itemName in jsonObj.items) { // tslint:disable-line:forin - const parsedItem = this.parseSchemaItemProps(jsonObj.items[itemName], jsonObj.name, itemName); // parse each item to ensure item props are valid - jsonObj.items[itemName] = parsedItem; + for (const ref of this._rawSchema.references) { + yield this.checkSchemaReference(ref); } } + } - if (undefined !== jsonObj.customAttributes) { - if (!Array.isArray(jsonObj.customAttributes)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Schema ${jsonObj.name} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); - const customAttributeSet = new Array(); - jsonObj.customAttributes.forEach((instance) => { + public *getCustomAttributes(): Iterable { + if (undefined !== this._rawSchema.customAttributes) { + if (!Array.isArray(this._rawSchema.customAttributes)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Schema ${this._rawSchema.name} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + + for (const instance of this._rawSchema.customAttributes) { if (!instance.className) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem ${jsonObj.name}.customAttributes instance is missing the required 'className' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem ${this._rawSchema.name}.customAttributes instance is missing the required 'className' attribute.`); if (typeof (instance.className) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem ${jsonObj.name}.customAttributes instance has an invalid 'className' property. It should be of type 'string'.`); - customAttributeSet.push(instance); - }); - jsonObj.customAttributes = customAttributeSet; - } + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem ${this._rawSchema.name}.customAttributes instance has an invalid 'className' attribute. It should be of type 'string'.`); - return jsonObj as SchemaProps; + yield instance; + } + } } - private checkSchemaReference(jsonObj: unknown, schemaName: string): SchemaReferenceProps { + private checkSchemaReference(jsonObj: unknown): SchemaReferenceProps { if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${schemaName} has an invalid 'references' property. It should be of type 'object[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'references' attribute. It should be of type 'object[]'.`); if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${schemaName} has an invalid 'references' property. One of the references is missing the required 'name' property.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'references' attribute. One of the references is missing the required 'name' attribute.`); if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${schemaName} has an invalid 'references' property. One of the references has an invalid 'name' property. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'references' attribute. One of the references has an invalid 'name' attribute. It should be of type 'string'.`); if (undefined === jsonObj.version) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${schemaName} has an invalid 'references' property. One of the references is missing the required 'version' property.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'references' attribute. One of the references is missing the required 'version' attribute.`); if (typeof (jsonObj.version) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${schemaName} has an invalid 'references' property. One of the references has an invalid 'version' property. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'references' attribute. One of the references has an invalid 'version' attribute. It should be of type 'string'.`); return jsonObj as SchemaReferenceProps; } - /** - * Type checks Schema Item and returns SchemaItemProps interface - * @param jsonObj - * @param name name of schema item - * @returns SchemaItemProps - */ - public parseSchemaItemProps(jsonObj: unknown, schemaName: string, name: string): SchemaItemProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem in ${schemaName} is an invalid JSON object.`); + public *getItems(): Iterable<[string, string, UnknownObject]> { + const items = this._rawSchema.items; + if (undefined !== items) { + if (!isObject(items) || Array.isArray(items)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'items' attribute. It should be of type 'object'.`); + + // tslint:disable-next-line:forin + for (const itemName in items) { + const item = items[itemName]; + if (!isObject(item)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem in ${this._schemaName} is an invalid JSON object.`); + + if (!ECName.validate(itemName)) + throw new ECObjectsError(ECObjectsStatus.InvalidECName, `A SchemaItem in ${this._schemaName} has an invalid 'name' attribute. '${itemName}' is not a valid ECName.`); + + if (undefined === item.schemaItemType) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._schemaName}.${itemName} is missing the required 'schemaItemType' attribute.`); + if (typeof (item.schemaItemType) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._schemaName}.${itemName} has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); + + this._currentItemFullName = this._schemaName + "." + itemName; + yield [itemName, item.schemaItemType, item]; + } + } + } + + public findItem(itemName: string): [string, string, UnknownObject] | undefined { + const items = this._rawSchema.items; + if (undefined !== items) { + if (!isObject(items) || Array.isArray(items)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The schema ${this._schemaName} has an invalid 'items' attribute. It should be of type 'object'.`); - if (!ECName.validate(name)) - throw new ECObjectsError(ECObjectsStatus.InvalidECName, `A SchemaItem in ${schemaName} has an invalid 'name' attribute. '${name}' is not a valid ECName.`); + const item = items[itemName]; + if (undefined !== item) { + if (!isObject(item)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `A SchemaItem in ${this._schemaName} is an invalid JSON object.`); - if (undefined === jsonObj.schemaItemType) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} is missing the required 'schemaItemType' attribute.`); - if (typeof (jsonObj.schemaItemType) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); + if (!ECName.validate(itemName)) + throw new ECObjectsError(ECObjectsStatus.InvalidECName, `A SchemaItem in ${this._schemaName} has an invalid 'name' attribute. '${itemName}' is not a valid ECName.`); + if (undefined === item.schemaItemType) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._schemaName}.${itemName} is missing the required 'schemaItemType' attribute.`); + if (typeof (item.schemaItemType) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._schemaName}.${itemName} has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); + + this._currentItemFullName = this._schemaName + "." + itemName; + return [itemName, item.schemaItemType, item]; + } + } + + return undefined; + } + + /** + * Type checks all Schema Item attributes. + * @param jsonObj The JSON object to check if it represents a Schema Item. + */ + private checkSchemaItemProps(jsonObj: UnknownObject): void { if (undefined !== jsonObj.description) { if (typeof (jsonObj.description) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} has an invalid 'description' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._currentItemFullName} has an invalid 'description' attribute. It should be of type 'string'.`); } if (undefined !== jsonObj.label) { if (typeof (jsonObj.label) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} has an invalid 'label' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this._currentItemFullName} has an invalid 'label' attribute. It should be of type 'string'.`); } + } - if (undefined !== jsonObj.schema) { - if (typeof (jsonObj.schema) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} has an invalid 'schema' attribute. It should be of type 'string'.`); - } else - jsonObj.schema = schemaName; - - if (undefined !== jsonObj.schemaVersion) { - if (typeof (jsonObj.schemaVersion) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${schemaName}.${name} has an invalid 'schemaVersion' attribute. It should be of type 'string'.`); + public *getProperties(jsonObj: UnknownObject): Iterable<[string, string, UnknownObject]> { + const properties = jsonObj.properties; + if (undefined !== properties) { + if (!Array.isArray(properties)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${this._currentItemFullName} has an invalid 'properties' attribute. It should be of type 'object[]'.`); + + for (const property of properties as Array) { + if (!isObject(property)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${this._currentItemFullName} is an invalid JSON object.`); + + if (undefined === property.name) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${this._currentItemFullName} is missing the required 'name' attribute.`); + if (typeof (property.name) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${this._currentItemFullName} has an invalid 'name' attribute. It should be of type 'string'.`); + + if (undefined === property.type) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${property.name} does not have the required 'type' attribute.`); + if (typeof (property.type) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${property.name} has an invalid 'type' attribute. It should be of type 'string'.`); + if (!this.isValidPropertyType(property.type)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${property.name} has an invalid 'type' attribute. '${property.type}' is not a valid type.`); + + yield [property.name, property.type, property]; + } } - return jsonObj as SchemaItemProps; } /** * Type checks Class and returns ClassProps interface - * @param jsonObj - * @param name name of class - * @returns ClassProps + * @param jsonObj The JSON object to check if it represents a Class. */ - public parseClassProps(jsonObj: unknown, name: string): ClassProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${name} is an invalid JSON object.`); + private checkClassProps(jsonObj: UnknownObject): void { + this.checkSchemaItemProps(jsonObj); + if (undefined !== jsonObj.modifier) { if (typeof (jsonObj.modifier) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${name} has an invalid 'modifier' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${this._currentItemFullName} has an invalid 'modifier' attribute. It should be of type 'string'.`); } if (undefined !== jsonObj.baseClass) { if (typeof (jsonObj.baseClass) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${name} has an invalid 'baseClass' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${this._currentItemFullName} has an invalid 'baseClass' attribute. It should be of type 'string'.`); } if (undefined !== jsonObj.customAttributes) { if (!Array.isArray(jsonObj.customAttributes)) { - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${name} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${this._currentItemFullName} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); } } - if (undefined !== jsonObj.properties) { - if (!Array.isArray(jsonObj.properties)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECClass ${name} has an invalid 'properties' attribute. It should be of type 'object[]'.`); - } - return jsonObj as ClassProps; } /** * Type checks entity class and returns EntityClassProps interface * @param jsonObj - * @param name name of EntityClass * @returns EntityClassProps */ - public parseEntityClassProps(jsonObj: unknown, name: string): EntityClassProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECEntityClass ${name} is an invalid JSON object.`); + public parseEntityClass(jsonObj: UnknownObject): EntityClassProps { + this.checkClassProps(jsonObj); + if (undefined !== jsonObj.mixins) { if (!Array.isArray(jsonObj.mixins)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECEntityClass ${jsonObj.schema}.${name} has an invalid 'mixins' attribute. It should be of type 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECEntityClass ${this._currentItemFullName} has an invalid 'mixins' attribute. It should be of type 'string[]'.`); for (const mixinName of jsonObj.mixins) { if (typeof (mixinName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECEntityClass ${jsonObj.schema}.${name} has an invalid 'mixins' attribute. It should be of type 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECEntityClass ${this._currentItemFullName} has an invalid 'mixins' attribute. It should be of type 'string[]'.`); } } return jsonObj as EntityClassProps; @@ -205,138 +256,133 @@ export class JsonParser extends AbstractParser { /** * Type checks mixin and returns MixinProps interface * @param jsonObj - * @param name name of Mixin * @returns MixinProps */ - public parseMixinProps(jsonObj: unknown, name: string): MixinProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Mixin ${name} is an invalid JSON object.`); + public parseMixin(jsonObj: UnknownObject): MixinProps { + this.checkClassProps(jsonObj); if (undefined === jsonObj.appliesTo) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Mixin ${name} is missing the required 'appliesTo' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Mixin ${this._currentItemFullName} is missing the required 'appliesTo' attribute.`); if (typeof (jsonObj.appliesTo) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Mixin ${name} has an invalid 'appliesTo' property. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Mixin ${this._currentItemFullName} has an invalid 'appliesTo' attribute. It should be of type 'string'.`); return jsonObj as MixinProps; } /** * Type checks custom attribute class and returns CustomAttributeClassProps interface * @param jsonObj - * @param name name of Custom Attribute Class * @returns CustomAttributeClassProps */ - public parseCustomAttributeClassProps(jsonObj: unknown, name: string): CustomAttributeClassProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The CustomAttributeClass ${name} is an invalid JSON object.`); + public parseCustomAttributeClass(jsonObj: UnknownObject): CustomAttributeClassProps { + this.checkClassProps(jsonObj); if (undefined === jsonObj.appliesTo) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The CustomAttributeClass ${name} is missing the required 'appliesTo' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The CustomAttributeClass ${this._currentItemFullName} is missing the required 'appliesTo' attribute.`); if (typeof (jsonObj.appliesTo) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The CustomAttributeClass ${name} has an invalid 'appliesTo' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The CustomAttributeClass ${this._currentItemFullName} has an invalid 'appliesTo' attribute. It should be of type 'string'.`); + return jsonObj as CustomAttributeClassProps; + } + + public parseStructClass(jsonObj: UnknownObject): CustomAttributeClassProps { + this.checkClassProps(jsonObj); + return jsonObj as CustomAttributeClassProps; + } + + public parseUnitSystem(jsonObj: UnknownObject): CustomAttributeClassProps { + this.checkSchemaItemProps(jsonObj); return jsonObj as CustomAttributeClassProps; } /** * Type checks Relationship Class and returns RelationshipClassProps interface * @param jsonObj - * @param name name of Relationship Class * @returns RelationshipClassProps */ - public parseRelationshipClassProps(jsonObj: unknown, name: string): RelationshipClassProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} is an invalid JSON object.`); - + public parseRelationshipClass(jsonObj: UnknownObject): RelationshipClassProps { + this.checkClassProps(jsonObj); if (undefined === jsonObj.strength) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} is missing the required 'strength' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} is missing the required 'strength' attribute.`); if (typeof (jsonObj.strength) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} has an invalid 'strength' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} has an invalid 'strength' attribute. It should be of type 'string'.`); const strength = parseStrength(jsonObj.strength); if (undefined === strength) - throw new ECObjectsError(ECObjectsStatus.InvalidStrength, `The RelationshipClass ${name} has an invalid 'strength' attribute. '${jsonObj.strength}' is not a valid StrengthType.`); + throw new ECObjectsError(ECObjectsStatus.InvalidStrength, `The RelationshipClass ${this._currentItemFullName} has an invalid 'strength' attribute. '${jsonObj.strength}' is not a valid StrengthType.`); jsonObj.strength = strength; if (undefined === jsonObj.strengthDirection) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} is missing the required 'strengthDirection' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} is missing the required 'strengthDirection' attribute.`); if (typeof (jsonObj.strengthDirection) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} has an invalid 'strengthDirection' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} has an invalid 'strengthDirection' attribute. It should be of type 'string'.`); const strengthDirection = parseStrengthDirection(jsonObj.strengthDirection); if (undefined === strengthDirection) - throw new ECObjectsError(ECObjectsStatus.InvalidStrength, `The RelationshipClass ${name} has an invalid 'strengthDirection' attribute. '${jsonObj.strengthDirection}' is not a valid StrengthDirection.`); + throw new ECObjectsError(ECObjectsStatus.InvalidStrength, `The RelationshipClass ${this._currentItemFullName} has an invalid 'strengthDirection' attribute. '${jsonObj.strengthDirection}' is not a valid StrengthDirection.`); jsonObj.strengthDirection = strengthDirection; if (undefined === jsonObj.source) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} is missing the required source constraint.`); - if (typeof (jsonObj.source) !== "object") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} has an invalid source constraint. It should be of type 'object'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} is missing the required source constraint.`); + if (!isObject(jsonObj.source)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} has an invalid source constraint. It should be of type 'object'.`); + this.checkRelationshipConstraintProps(jsonObj.source, true); if (undefined === jsonObj.target) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} is missing the required target constraint.`); - if (typeof (jsonObj.target) !== "object") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${name} has an invalid target constraint. It should be of type 'object'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} is missing the required target constraint.`); + if (!isObject(jsonObj.target)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The RelationshipClass ${this._currentItemFullName} has an invalid target constraint. It should be of type 'object'.`); + this.checkRelationshipConstraintProps(jsonObj.target, false); return jsonObj as RelationshipClassProps; } /** - * Type checks Relationship Constraint and returns RelationshipConstraintProps interface - * @param relClassName Relationship class name - * @param isSource For sake of error message, is this relationship constraint a source or target + * Type checks Relationship Constraint and returns RelationshipConstraintProps interface. * @param jsonObj + * @param isSource For sake of error message, is this relationship constraint a source or target * @returns RelationshipConstraintProps */ - public parseRelationshipConstraintProps(relClassName: string, jsonObj: unknown, isSource?: boolean): RelationshipConstraintProps { - const constraintName = `${(isSource) ? "Source" : "Target"} Constraint of ${relClassName}`; // most specific name to call RelationshipConstraint - const className = `RelationshipConstraint ${relClassName}`; // less specific name if isSource is undefined - - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} is an invalid JSON object.`); + private checkRelationshipConstraintProps(jsonObj: UnknownObject, isSource: boolean): void { + const constraintName = `${(isSource) ? "Source" : "Target"} Constraint of ${this._currentItemFullName}`; // most specific name to call RelationshipConstraint if (undefined === jsonObj.multiplicity) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} is missing the required 'multiplicity' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} is missing the required 'multiplicity' attribute.`); if (typeof (jsonObj.multiplicity) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'multiplicity' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'multiplicity' attribute. It should be of type 'string'.`); if (undefined === jsonObj.roleLabel) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} is missing the required 'roleLabel' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} is missing the required 'roleLabel' attribute.`); if (typeof (jsonObj.roleLabel) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'roleLabel' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'roleLabel' attribute. It should be of type 'string'.`); if (undefined === jsonObj.polymorphic) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} is missing the required 'polymorphic' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} is missing the required 'polymorphic' attribute.`); if (typeof (jsonObj.polymorphic) !== "boolean") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'polymorhpic' attribute. It should be of type 'boolean'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'polymorphic' attribute. It should be of type 'boolean'.`); if (undefined !== jsonObj.abstractConstraint && typeof (jsonObj.abstractConstraint) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'abstractConstraint' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'abstractConstraint' attribute. It should be of type 'string'.`); if (undefined === jsonObj.constraintClasses) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} is missing the required 'constraintClasses' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} is missing the required 'constraintClasses' attribute.`); if (!Array.isArray(jsonObj.constraintClasses)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); for (const constraintClassName of jsonObj.constraintClasses) { if (typeof (constraintClassName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); } if (undefined !== jsonObj.customAttributes && !Array.isArray(jsonObj.customAttributes)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${(isSource !== undefined) ? `${constraintName}` : `${className}`} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); - - return jsonObj as RelationshipConstraintProps; + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ${constraintName} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); } /** * Type checks Enumeration and returns EnumerationProps interface * @param jsonObj - * @param name name of enumeration * @returns EnumerationProps */ - public parseEnumerationProps(jsonObj: unknown, name: string): EnumerationProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} is an invalid JSON object.`); - + public parseEnumeration(jsonObj: UnknownObject): EnumerationProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} is missing the required 'type' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} is missing the required 'type' attribute.`); if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an invalid 'type' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'type' attribute. It should be of type 'string'.`); const isValidEnumerationType = (type: string): boolean => { type = type.toLowerCase(); @@ -345,48 +391,46 @@ export class JsonParser extends AbstractParser { (type === "string"); }; if (!isValidEnumerationType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an invalid 'type' attribute. It should be either "int" or "string".`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'type' attribute. It should be either "int" or "string".`); if (undefined !== jsonObj.isStrict) { // TODO: make required if (typeof (jsonObj.isStrict) !== "boolean") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an invalid 'isStrict' attribute. It should be of type 'boolean'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'isStrict' attribute. It should be of type 'boolean'.`); } if (undefined === jsonObj.enumerators) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} is missing the required 'enumerators' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} is missing the required 'enumerators' attribute.`); if (!Array.isArray(jsonObj.enumerators)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); - const enumeratorProps = Array(); - jsonObj.enumerators.forEach((enumerator: unknown) => { + for (const enumerator of jsonObj.enumerators) { if (!isObject(enumerator)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); if (undefined === enumerator.value) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an enumerator that is missing the required attribute 'value'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an enumerator that is missing the required attribute 'value'.`); + // TODO: Should this really be handled here? const expectedType = jsonObj.type as string; const receivedType = (typeof (enumerator.value) === "number") ? "int" : typeof (enumerator.value); if (!expectedType.includes(receivedType)) // is receivedType a substring of expectedType? - easiest way to check "int" === "int" | "integer" && "string" === "string" - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an incompatible type. It must be "${expectedType}", not "${receivedType}".`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an incompatible type. It must be "${expectedType}", not "${receivedType}".`); if (undefined === enumerator.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an enumerator that is missing the required attribute 'name'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an enumerator that is missing the required attribute 'name'.`); if (typeof (enumerator.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an enumerator with an invalid 'name' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an enumerator with an invalid 'name' attribute. It should be of type 'string'.`); if (undefined !== enumerator.label) { if (typeof (enumerator.label) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an enumerator with an invalid 'label' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an enumerator with an invalid 'label' attribute. It should be of type 'string'.`); } if (undefined !== enumerator.description) { if (typeof (enumerator.description) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${name} has an enumerator with an invalid 'description' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Enumeration ${this._currentItemFullName} has an enumerator with an invalid 'description' attribute. It should be of type 'string'.`); } - enumeratorProps.push(enumerator as EnumeratorProps); - }); - jsonObj.enumerators = enumeratorProps; + } return jsonObj as EnumerationProps; } @@ -394,31 +438,28 @@ export class JsonParser extends AbstractParser { /** * Type checks KindOfQuantity and returns KindOfQuantityProps interface * @param jsonObj - * @param name name of koq * @returns KindOfQuantityProps */ - public parseKindOfQuantityProps(jsonObj: unknown, name: string): KindOfQuantityProps { // TODO: func needs to throw errors for non-existent Formats - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} is an invalid JSON object.`); - + public parseKindOfQuantity(jsonObj: UnknownObject): KindOfQuantityProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.relativeError) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} is missing the required 'relativeError' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${this._currentItemFullName} is missing the required 'relativeError' attribute.`); if (typeof (jsonObj.relativeError) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} has an invalid 'relativeError' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${this._currentItemFullName} has an invalid 'relativeError' attribute. It should be of type 'number'.`); if (undefined !== jsonObj.presentationUnits) { if (!Array.isArray(jsonObj.presentationUnits)) { if (typeof (jsonObj.presentationUnits) === "string") // must be a string or an array jsonObj.presentationUnits = jsonObj.presentationUnits.split(";") as string[]; else - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} has an invalid 'presentationUnits' attribute. It should be of type 'string' or 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${this._currentItemFullName} has an invalid 'presentationUnits' attribute. It should be of type 'string' or 'string[]'.`); } } if (undefined === jsonObj.persistenceUnit) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} is missing the required 'persistenceUnit' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${this._currentItemFullName} is missing the required 'persistenceUnit' attribute.`); if (typeof (jsonObj.persistenceUnit) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${name} has an invalid 'persistenceUnit' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The KindOfQuantity ${this._currentItemFullName} has an invalid 'persistenceUnit' attribute. It should be of type 'string'.`); return jsonObj as KindOfQuantityProps; } @@ -426,15 +467,13 @@ export class JsonParser extends AbstractParser { /** * Type checks Property Category and returns PropertyCategoryProps interface * @param jsonObj - * @param name name of property category * @returns PropertyCategoryProps */ - public parsePropertyCategoryProps(jsonObj: unknown, name: string): PropertyCategoryProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The PropertyCategory ${name} is an invalid JSON object.`); + public parsePropertyCategory(jsonObj: UnknownObject): PropertyCategoryProps { + this.checkSchemaItemProps(jsonObj); if (undefined !== jsonObj.priority) { // TODO: make required if (typeof (jsonObj.priority) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The PropertyCategory ${name} has an invalid 'priority' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The PropertyCategory ${this._currentItemFullName} has an invalid 'priority' attribute. It should be of type 'number'.`); } return jsonObj as PropertyCategoryProps; @@ -443,40 +482,38 @@ export class JsonParser extends AbstractParser { /** * Type checks unit and returns UnitProps interface * @param jsonObj - * @param name name of unit * @returns UnitProps */ - public parseUnitProps(jsonObj: unknown, name: string): UnitProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} is an invalid JSON object.`); + public parseUnit(jsonObj: UnknownObject): UnitProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.phenomenon) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} does not have the required 'phenomenon' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} does not have the required 'phenomenon' attribute.`); if (typeof (jsonObj.phenomenon) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'phenomenon' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'phenomenon' attribute. It should be of type 'string'.`); if (undefined === jsonObj.unitSystem) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} does not have the required 'unitSystem' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} does not have the required 'unitSystem' attribute.`); if (typeof (jsonObj.unitSystem) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'unitSystem' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'unitSystem' attribute. It should be of type 'string'.`); if (undefined === jsonObj.definition) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} does not have the required 'definition' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} does not have the required 'definition' attribute.`); if (typeof (jsonObj.definition) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'definition' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'definition' attribute. It should be of type 'string'.`); if (undefined !== jsonObj.numerator) { if (typeof (jsonObj.numerator) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'numerator' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'numerator' attribute. It should be of type 'number'.`); } if (undefined !== jsonObj.denominator) { if (typeof (jsonObj.denominator) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'denominator' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'denominator' attribute. It should be of type 'number'.`); } if (undefined !== jsonObj.offset) { if (typeof (jsonObj.offset) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${name} has an invalid 'offset' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Unit ${this._currentItemFullName} has an invalid 'offset' attribute. It should be of type 'number'.`); } return jsonObj as UnitProps; } @@ -484,21 +521,19 @@ export class JsonParser extends AbstractParser { /** * Type checks inverted unit and returns InvertedUnitProps interface * @param jsonObj - * @param name name of inverted unit * @returns InvertedUnitProps */ - public parseInvertedUnitProps(jsonObj: unknown, name: string): InvertedUnitProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${name} is an invalid JSON object.`); + public parseInvertedUnit(jsonObj: UnknownObject): InvertedUnitProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.invertsUnit) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${name} does not have the required 'invertsUnit' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${this._currentItemFullName} does not have the required 'invertsUnit' attribute.`); if (typeof (jsonObj.invertsUnit) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${name} has an invalid 'invertsUnit' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${this._currentItemFullName} has an invalid 'invertsUnit' attribute. It should be of type 'string'.`); if (undefined === jsonObj.unitSystem) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${name} does not have the required 'unitSystem' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${this._currentItemFullName} does not have the required 'unitSystem' attribute.`); if (typeof (jsonObj.unitSystem) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${name} has an invalid 'unitSystem' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The InvertedUnit ${this._currentItemFullName} has an invalid 'unitSystem' attribute. It should be of type 'string'.`); return jsonObj as InvertedUnitProps; } @@ -506,30 +541,28 @@ export class JsonParser extends AbstractParser { /** * Type checks constant and returns ConstantProps interface * @param jsonObj - * @param name name of constant * @returns ConstantProps */ - public parseConstantProps(jsonObj: unknown, name: string): ConstantProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} is an invalid JSON object.`); + public parseConstant(jsonObj: UnknownObject): ConstantProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.phenomenon) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} does not have the required 'phenomenon' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} does not have the required 'phenomenon' attribute.`); if (typeof (jsonObj.phenomenon) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} has an invalid 'phenomenon' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} has an invalid 'phenomenon' attribute. It should be of type 'string'.`); if (undefined === jsonObj.definition) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} does not have the required 'definition' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} does not have the required 'definition' attribute.`); if (typeof (jsonObj.definition) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} has an invalid 'definition' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} has an invalid 'definition' attribute. It should be of type 'string'.`); if (undefined !== jsonObj.numerator) { if (typeof (jsonObj.numerator) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} has an invalid 'numerator' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} has an invalid 'numerator' attribute. It should be of type 'number'.`); } if (undefined !== jsonObj.denominator) { if (typeof (jsonObj.denominator) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${name} has an invalid 'denominator' attribute. It should be of type 'number'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Constant ${this._currentItemFullName} has an invalid 'denominator' attribute. It should be of type 'number'.`); } return jsonObj as ConstantProps; @@ -538,128 +571,91 @@ export class JsonParser extends AbstractParser { /** * Type checks phenomenon and returns PhenomenonProps interface * @param jsonObj - * @param name name of phenomenon * @returns PhenomenonProps */ - public parsePhenomenonProps(jsonObj: unknown, name: string): PhenomenonProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Phenomenon ${name} is an invalid JSON object.`); - if (!ECName.validate(name)) - throw new ECObjectsError(ECObjectsStatus.InvalidECName, `The Phenomenon ${jsonObj.schema}.${name} has an invalid 'name' attribute. '${name}' is not a valid ECName.`); + public parsePhenomenon(jsonObj: UnknownObject): PhenomenonProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.definition) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Phenomenon ${name} does not have the required 'definition' attribute.`); - else if (typeof (jsonObj.definition) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Phenomenon ${name} has an invalid 'definition' attribute. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Phenomenon ${this._currentItemFullName} does not have the required 'definition' attribute.`); + if (typeof (jsonObj.definition) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Phenomenon ${this._currentItemFullName} has an invalid 'definition' attribute. It should be of type 'string'.`); return jsonObj as PhenomenonProps; } /** * Type checks format and returns FormatProps interface * @param jsonObj - * @param name name of format * @returns FormatProps */ - public parseFormatProps(jsonObj: unknown, name: string): FormatProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} is an invalid JSON object.`); - if (!ECName.validate(name)) - throw new ECObjectsError(ECObjectsStatus.InvalidECName, `The Format ${jsonObj.schema}.${name} has an invalid 'name' attribute. '${name}' is not a valid ECName.`); - + public parseFormat(jsonObj: UnknownObject): FormatProps { + this.checkSchemaItemProps(jsonObj); if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} does not have the required 'type' attribute.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} does not have the required 'type' attribute.`); if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'type' attribute. It should be of type 'string'.`); - - if (undefined === jsonObj.precision) // precision is required - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} does not have the required 'precision' attribute.`); - else if (typeof (jsonObj.precision) !== "number") // must be a number - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'precision' attribute. It should be of type 'number'.`); - else if (!Number.isInteger(jsonObj.precision)) // must be an integer - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'precision' attribute. It should be an integer.`); - - if (undefined !== jsonObj.roundFactor) { - if (typeof (jsonObj.roundFactor) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'roundFactor' attribute. It should be of type 'number'.`); - } + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.minWidth) { // optional - if (typeof (jsonObj.minWidth) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'minWidth' attribute. It should be of type 'number'.`); - if (!Number.isInteger(jsonObj.minWidth) || jsonObj.minWidth < 0) // must be a positive int - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'minWidth' attribute. It should be a positive integer.`); - } + if (undefined !== jsonObj.precision && typeof (jsonObj.precision) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'precision' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.showSignOption) { // optional; default is "onlyNegative" - if (typeof (jsonObj.showSignOption) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'showSignOption' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.roundFactor && typeof (jsonObj.roundFactor) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'roundFactor' attribute. It should be of type 'number'.`); + + if (undefined !== jsonObj.minWidth && typeof (jsonObj.minWidth) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'minWidth' attribute. It should be of type 'number'.`); + + if (undefined !== jsonObj.showSignOption && typeof (jsonObj.showSignOption) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'showSignOption' attribute. It should be of type 'string'.`); if (undefined !== jsonObj.formatTraits) { if (!Array.isArray(jsonObj.formatTraits) && typeof (jsonObj.formatTraits) !== "string") // must be either an array of strings or a string - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'formatTraits' attribute. It should be of type 'string' or 'string[]'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'formatTraits' attribute. It should be of type 'string' or 'string[]'.`); } - if (undefined !== jsonObj.decimalSeparator) { // optional - if (typeof (jsonObj.decimalSeparator) !== "string") // not a string or not a one character string - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'decimalSeparator' attribute. It should be of type 'string'.`); - if (jsonObj.decimalSeparator.length !== 1) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'decimalSeparator' attribute. It must be a one character string.`); - } + if (undefined !== jsonObj.decimalSeparator && typeof (jsonObj.decimalSeparator) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'decimalSeparator' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.thousandSeparator) { // optional - if (typeof (jsonObj.thousandSeparator) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'thousandSeparator' attribute. It should be of type 'string'.`); - if (jsonObj.thousandSeparator.length !== 1) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'thousandSeparator' attribute. It must be a one character string.`); - } + if (undefined !== jsonObj.thousandSeparator && typeof (jsonObj.thousandSeparator) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'thousandSeparator' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.uomSeparator) { // optional; default is " " - if (typeof (jsonObj.uomSeparator) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'uomSeparator' attribute. It should be of type 'string'.`); - if (jsonObj.uomSeparator.length !== 1) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'uomSeparator' attribute. It must be a one character string.`); - } + if (undefined !== jsonObj.uomSeparator && typeof (jsonObj.uomSeparator) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'uomSeparator' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.scientificType) { - if (typeof (jsonObj.scientificType) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'scientificType' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.scientificType && typeof (jsonObj.scientificType) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'scientificType' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.stationOffsetSize) { - if (typeof (jsonObj.stationOffsetSize) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'stationOffsetSize' attribute. It should be of type 'number'.`); - if (!Number.isInteger(jsonObj.stationOffsetSize) || jsonObj.stationOffsetSize < 0) // must be a positive int > 0 - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); - } + if (undefined !== jsonObj.stationOffsetSize && typeof (jsonObj.stationOffsetSize) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'stationOffsetSize' attribute. It should be of type 'number'.`); + + if (undefined !== jsonObj.stationSeparator && typeof (jsonObj.stationSeparator) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'stationSeparator' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.stationSeparator) { // optional; default is "+" - if (typeof (jsonObj.stationSeparator) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'stationSeparator' attribute. It should be of type 'string'.`); - if (jsonObj.stationSeparator.length !== 1) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'stationSeparator' attribute. It must be a one character string.`); - } if (undefined !== jsonObj.composite) { // optional if (!isObject(jsonObj.composite)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid composite object.`); - if (jsonObj.composite.includeZero !== undefined) { - if (typeof (jsonObj.composite.includeZero) !== "boolean") // includeZero must be a boolean IF it is defined - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has a Composite with an invalid 'includeZero' attribute. It should be of type 'boolean'.`); - } - if (jsonObj.composite.spacer !== undefined) { // spacer must be a string IF it is defined - if (typeof (jsonObj.composite.spacer) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has a Composite with an invalid 'spacer' attribute. It must be of type 'string'.`); - if (jsonObj.composite.spacer.length !== 1) // spacer must be a one character string - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has a Composite with an invalid 'spacer' attribute. It must be a one character string.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'composite' object.`); + if (undefined !== jsonObj.composite.includeZero && typeof (jsonObj.composite.includeZero) !== "boolean") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'includeZero' attribute. It should be of type 'boolean'.`); + + if (undefined !== jsonObj.composite.spacer && typeof (jsonObj.composite.spacer) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'spacer' attribute. It should be of type 'string'.`); + + // if composite is defined + if (undefined === jsonObj.composite.units) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has an invalid 'Composite' attribute. It should have 1-4 units.`); + if (!Array.isArray(jsonObj.composite.units)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'units' attribute. It should be of type 'object[]'.`); + + for (let i = 0; i < jsonObj.composite.units.length; i++) { + if (!isObject(jsonObj.composite.units[i])) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'units' attribute. It should be of type 'object[]'.`); + + if (undefined === jsonObj.composite.units[i].name) // required + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'units' attribute. The object at position ${i} is missing the required 'name' attribute.`); + if (typeof (jsonObj.composite.units[i].name) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'units' attribute. The object at position ${i} has an invalid 'name' attribute. It should be of type 'string'.`); + + if (undefined !== jsonObj.composite.units[i].label && typeof (jsonObj.composite.units[i].label) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this._currentItemFullName} has a Composite with an invalid 'units' attribute. The object at position ${i} has an invalid 'label' attribute. It should be of type 'string'.`); } - if (jsonObj.composite.units !== undefined) { // if composite is defined, it must be an array with 1-4 units - if (!Array.isArray(jsonObj.composite.units)) { // must be an array - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has a Composite with an invalid 'units' attribute. It must be of type 'array'`); - } - if (jsonObj.composite.units.length <= 0 || jsonObj.composite.units.length > 4) { // Composite requires 1-4 units - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'Composite' attribute. It must have 1-4 units.`); - } - } else - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${name} has an invalid 'Composite' attribute. It must have 1-4 units.`); // if you have a composite without any units that is an error } return jsonObj as FormatProps; } @@ -676,353 +672,153 @@ export class JsonParser extends AbstractParser { /** * Type checks property and returns PropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns PropertyProps */ - public parsePropertyProps(jsonObj: unknown, schemaName: string, className: string): PropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - + private checkPropertyProps(jsonObj: UnknownObject): PropertyProps { const propName = jsonObj.name; - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); + if (undefined !== jsonObj.label && typeof (jsonObj.label) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'label' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.label) { - if (typeof (jsonObj.label) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'label' attribute. It should be of type 'string'.`); - } - - if (undefined !== jsonObj.description) { - if (typeof (jsonObj.description) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'description' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.description && typeof (jsonObj.description) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'description' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.priority) { - if (typeof (jsonObj.priority) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'priority' attribute. It should be of type 'number'.`); - } + if (undefined !== jsonObj.priority && typeof (jsonObj.priority) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'priority' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.isReadOnly) { - if (typeof (jsonObj.isReadOnly) !== "boolean") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'isReadOnly' attribute. It should be of type 'boolean'.`); - } + if (undefined !== jsonObj.isReadOnly && typeof (jsonObj.isReadOnly) !== "boolean") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'isReadOnly' attribute. It should be of type 'boolean'.`); - if (undefined !== jsonObj.category) { - if (typeof (jsonObj.category) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'category' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.category && typeof (jsonObj.category) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'category' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.kindOfQuantity) { - if (typeof (jsonObj.kindOfQuantity) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'kindOfQuantity' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.kindOfQuantity && typeof (jsonObj.kindOfQuantity) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'kindOfQuantity' attribute. It should be of type 'string'.`); - if (undefined !== jsonObj.inherited) { - if (typeof (jsonObj.inherited) !== "boolean") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'inherited' attribute. It should be of type 'boolean'.`); - } + if (undefined !== jsonObj.inherited && typeof (jsonObj.inherited) !== "boolean") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'inherited' attribute. It should be of type 'boolean'.`); - if (undefined !== jsonObj.customAttributes) { - if (!Array.isArray(jsonObj.customAttributes)) { - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); - } - } + if (undefined !== jsonObj.customAttributes && !Array.isArray(jsonObj.customAttributes)) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'customAttributes' attribute. It should be of type 'array'.`); return jsonObj as PropertyProps; } + private checkPropertyTypename(jsonObj: UnknownObject): void { + const propName = jsonObj.name; + if (undefined === jsonObj.typeName) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} is missing the required 'typeName' attribute.`); + if (typeof (jsonObj.typeName) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); + } + + private checkPropertyMinAndMaxOccurs(jsonObj: UnknownObject): void { + const propName = jsonObj.name; + if (undefined !== jsonObj.minOccurs && typeof (jsonObj.minOccurs) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minOccurs' attribute. It should be of type 'number'.`); + if (undefined !== jsonObj.maxOccurs && typeof (jsonObj.maxOccurs) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maxOccurs' attribute. It should be of type 'number'.`); + } + /** * Type checks PrimitiveOrEnumProperty and returns PrimitiveOrEnumPropertyBaseProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns PrimitiveOrEnumPropertyBaseProps */ - public parsePrimitiveOrEnumPropertyBaseProps(jsonObj: unknown, schemaName: string, className: string): PrimitiveOrEnumPropertyBaseProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - + private checkPrimitiveOrEnumPropertyBaseProps(jsonObj: UnknownObject): PrimitiveOrEnumPropertyBaseProps { + this.checkPropertyProps(jsonObj); + this.checkPropertyTypename(jsonObj); const propName = jsonObj.name; - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); + if (undefined !== jsonObj.minLength && typeof (jsonObj.minLength) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minLength' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.minLength) { - if (typeof (jsonObj.minLength) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'minLength' attribute. It should be of type 'number'.`); - } + if (undefined !== jsonObj.maxLength && typeof (jsonObj.maxLength) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maxLength' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.maxLength) { - if (typeof (jsonObj.maxLength) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'maxLength' attribute. It should be of type 'number'.`); - } + if (undefined !== jsonObj.minValue && typeof (jsonObj.minValue) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'minValue' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.minValue) { - if (typeof (jsonObj.minValue) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'minValue' attribute. It should be of type 'number'.`); - } + if (undefined !== jsonObj.maxValue && typeof (jsonObj.maxValue) !== "number") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'maxValue' attribute. It should be of type 'number'.`); - if (undefined !== jsonObj.maxValue) { - if (typeof (jsonObj.maxValue) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'maxValue' attribute. It should be of type 'number'.`); - } - - if (undefined !== jsonObj.extendedTypeName) { - if (typeof (jsonObj.extendedTypeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'extendedTypeName' attribute. It should be of type 'string'.`); - } + if (undefined !== jsonObj.extendedTypeName && typeof (jsonObj.extendedTypeName) !== "string") + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${this._currentItemFullName}.${propName} has an invalid 'extendedTypeName' attribute. It should be of type 'string'.`); return jsonObj as PrimitiveOrEnumPropertyBaseProps; } /** * Type checks PrimitiveProperty and returns PrimitivePropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns PrimitivePropertyProps */ - public parsePrimitivePropertyProps(jsonObj: unknown, schemaName: string, className: string): PrimitivePropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); - - if (undefined !== jsonObj.typeName) { - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); - } + public parsePrimitiveProperty(jsonObj: UnknownObject): PrimitivePropertyProps { + this.checkPrimitiveOrEnumPropertyBaseProps(jsonObj); return jsonObj as PrimitivePropertyProps; } /** * Type checks StructProperty and returns StructPropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns StructPropertyProps */ - public parseStructPropertyProps(jsonObj: unknown, schemaName: string, className: string): StructPropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); - - if (undefined !== jsonObj.typeName) { - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); - } + public parseStructProperty(jsonObj: UnknownObject): StructPropertyProps { + this.checkPropertyProps(jsonObj); + this.checkPropertyTypename(jsonObj); return jsonObj as StructPropertyProps; } /** * Type checks EnumerationProperty and returns EnumerationPropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns EnumerationPropertyProps */ - public parseEnumerationPropertyProps(jsonObj: unknown, schemaName: string, className: string): EnumerationPropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); - - if (undefined !== jsonObj.typeName) { - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); - } + public parseEnumerationProperty(jsonObj: UnknownObject): EnumerationPropertyProps { + this.checkPrimitiveOrEnumPropertyBaseProps(jsonObj); return jsonObj as EnumerationPropertyProps; } /** * Type checks PrimitiveArrayProperty and returns PrimitiveArrayPropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns PrimitiveArrayPropertyProps */ - public parsePrimitiveArrayPropertyProps(jsonObj: unknown, schemaName: string, className: string): PrimitiveArrayPropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); - - if (undefined !== jsonObj.typeName) { - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); - } - if (undefined !== jsonObj.minOccurs) { - if (typeof (jsonObj.minOccurs) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'minOccurs' attribute. It should be of type 'number'.`); - } - - if (undefined !== jsonObj.maxOccurs) { - if (typeof (jsonObj.maxOccurs) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'maxOccurs' attribute. It should be of type 'number'.`); - } + public parsePrimitiveArrayProperty(jsonObj: UnknownObject): PrimitiveArrayPropertyProps { + this.checkPrimitiveOrEnumPropertyBaseProps(jsonObj); + this.checkPropertyMinAndMaxOccurs(jsonObj); return jsonObj as PrimitiveArrayPropertyProps; } /** * Type checks StructArrayProperty and returns StructArrayPropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns StructArrayPropertyProps */ - public parseStructArrayPropertyProps(jsonObj: unknown, schemaName: string, className: string): StructArrayPropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} does not have the required 'name' attribute.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' attribute. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} does not have the required 'type' attribute.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. It should be of type 'string'.`); - if (!this.isValidPropertyType(jsonObj.type)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' attribute. '${jsonObj.type}' is not a valid type.`); - - if (undefined !== jsonObj.typeName) { - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' attribute. It should be of type 'string'.`); - } - if (undefined !== jsonObj.minOccurs) { - if (typeof (jsonObj.minOccurs) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'minOccurs' attribute. It should be of type 'number'.`); - } - - if (undefined !== jsonObj.maxOccurs) { - if (typeof (jsonObj.maxOccurs) !== "number") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'maxOccurs' attribute. It should be of type 'number'.`); - } + public parseStructArrayProperty(jsonObj: UnknownObject): StructArrayPropertyProps { + this.checkPropertyProps(jsonObj); + this.checkPropertyTypename(jsonObj); + this.checkPropertyMinAndMaxOccurs(jsonObj); return jsonObj as StructArrayPropertyProps; } /** * Type checks NavigationProperty and returns NavigationPropertyProps interface * @param jsonObj - * @param schemaName name of schema - * @param className name of class * @returns NavigationPropertyProps */ - public parseNavigationPropertyProps(jsonObj: unknown, name: string, classObj: AnyClass): NavigationPropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} is an invalid JSON object.`); - if (classObj.schemaItemType !== SchemaItemType.EntityClass && classObj.schemaItemType !== SchemaItemType.RelationshipClass && classObj.schemaItemType !== SchemaItemType.Mixin) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} is invalid, because only EntityClasses, Mixins, and RelationshipClasses can have NavigationProperties.`); + public parseNavigationProperty(jsonObj: UnknownObject): NavigationPropertyProps { + this.checkPropertyProps(jsonObj); + const fullname = `${this._currentItemFullName}.${jsonObj.name}`; + if (undefined === jsonObj.relationshipName) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} is missing the required 'relationshipName' property.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${fullname} is missing the required 'relationshipName' property.`); if (typeof (jsonObj.relationshipName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} has an invalid 'relationshipName' property. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${fullname} has an invalid 'relationshipName' property. It should be of type 'string'.`); if (undefined === jsonObj.direction) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} is missing the required 'direction' property.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${fullname} is missing the required 'direction' property.`); if (typeof (jsonObj.direction) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${classObj.name}.${name} has an invalid 'direction' property. It should be of type 'string'.`); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Navigation Property ${fullname} has an invalid 'direction' property. It should be of type 'string'.`); return jsonObj as NavigationPropertyProps; } - - /** - * Type checks PropertyTypes and returns PropertyProps interface - * @param jsonObj - * @param schemaName name of schema - * @param className name of class - * @returns PropertyProps - */ - public parsePropertyTypes(jsonObj: unknown, schemaName: string, className: string): PropertyProps { - if (!isObject(jsonObj)) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is an invalid JSON object.`); - if (undefined === jsonObj.name) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} is missing the required 'name' property.`); - if (typeof (jsonObj.name) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `An ECProperty in ${schemaName}.${className} has an invalid 'name' property. It should be of type 'string'.`); - - const propName = jsonObj.name; - - if (undefined === jsonObj.type) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} is missing the required 'type' property.`); - if (typeof (jsonObj.type) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'type' property. It should be of type 'string'.`); - if (jsonObj.type.toLowerCase() !== "navigationproperty") { // type name is required for all properties except Navigation Property - if (undefined === jsonObj.typeName) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} is missing the required 'typeName' property.`); - if (typeof (jsonObj.typeName) !== "string") - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The ECProperty ${schemaName}.${className}.${propName} has an invalid 'typeName' property. It should be of type 'string'.`); - } - return jsonObj as PropertyProps; - } } diff --git a/core/ecschema-metadata/src/Deserialization/JsonProps.ts b/core/ecschema-metadata/src/Deserialization/JsonProps.ts index 71f775b..30a2a62 100644 --- a/core/ecschema-metadata/src/Deserialization/JsonProps.ts +++ b/core/ecschema-metadata/src/Deserialization/JsonProps.ts @@ -20,9 +20,6 @@ export interface SchemaProps { description?: string; references?: SchemaReferenceProps[]; customAttributes?: CustomAttribute[]; - items?: { - [key: string]: AnySchemaItemProps, - }; } export interface SchemaReferenceProps { @@ -31,10 +28,10 @@ export interface SchemaReferenceProps { } export interface SchemaItemProps { - $schema?: string; // conditionally required + // NEEDSWORK: Still need to clarify how single-item deserialization works... schema?: string; // conditionally required schemaVersion?: string; - schemaItemType: string; + schemaItemType?: string; label?: string; description?: string; } @@ -42,7 +39,6 @@ export interface SchemaItemProps { export interface ClassProps extends SchemaItemProps { modifier?: string; baseClass?: string; - properties?: AnyPropertyProps[]; customAttributes?: object[]; } @@ -54,6 +50,8 @@ export interface MixinProps extends ClassProps { appliesTo: string; } +export type StructClassProps = ClassProps; + export interface CustomAttributeClassProps extends ClassProps { appliesTo: string; } @@ -170,7 +168,7 @@ export interface FormatProps extends SchemaItemProps { composite?: { spacer?: string; includeZero?: boolean; - units?: Array<{ + units: Array<{ name: string; label?: string; }> @@ -186,6 +184,8 @@ export interface PhenomenonProps extends SchemaItemProps { definition: string; } +export type UnitSystemProps = SchemaItemProps; + export interface UnitProps extends SchemaItemProps { phenomenon: string; unitSystem: string; diff --git a/core/ecschema-metadata/src/ECObjects.ts b/core/ecschema-metadata/src/ECObjects.ts index 1a778fc..fc921ba 100644 --- a/core/ecschema-metadata/src/ECObjects.ts +++ b/core/ecschema-metadata/src/ECObjects.ts @@ -316,7 +316,7 @@ export function containerTypeToString(type: CustomAttributeContainerType): strin if (containerType.length === 0) containerType = val; else - containerType += "," + val; + containerType += ", " + val; }; if (testContainerTypeValue(CustomAttributeContainerType.Schema, type)) diff --git a/core/ecschema-metadata/src/Metadata/Class.ts b/core/ecschema-metadata/src/Metadata/Class.ts index b3646ac..4ad7c4e 100644 --- a/core/ecschema-metadata/src/Metadata/Class.ts +++ b/core/ecschema-metadata/src/Metadata/Class.ts @@ -89,7 +89,7 @@ export abstract class ECClass extends SchemaItem implements CustomAttributeConta return undefined; } - return await this.getInheritedProperty(name); + return this.getInheritedProperty(name); } /** @@ -119,7 +119,7 @@ export abstract class ECClass extends SchemaItem implements CustomAttributeConta public async getInheritedProperty(name: string): Promise { if (this.baseClass) { const baseClassObj = await this.baseClass; - return await baseClassObj.getProperty(name, true); + return baseClassObj.getProperty(name, true); } return undefined; diff --git a/core/ecschema-metadata/src/Metadata/Format.ts b/core/ecschema-metadata/src/Metadata/Format.ts index 4f359d9..a6450a0 100644 --- a/core/ecschema-metadata/src/Metadata/Format.ts +++ b/core/ecschema-metadata/src/Metadata/Format.ts @@ -87,134 +87,158 @@ export class Format extends SchemaItem implements IFormat { get includeZero(): boolean | undefined { return this._includeZero; } get units(): Array<[Unit | InvertedUnit, string | undefined]> | undefined { return this._units; } - private verifyFormatTraitsOptions(formatTraitsFromJson: string | string[]) { - const formatTraits = (Array.isArray(formatTraitsFromJson)) ? formatTraitsFromJson : formatTraitsFromJson.split(/,|;|\|/); - formatTraits.forEach((formatTraitsString: string) => { // for each element in the string array - this._formatTraits = parseFormatTrait(formatTraitsString, this.formatTraits); - }); + private parseFormatTraits(formatTraitsFromJson: string | string[]) { + const formatTraits = Array.isArray(formatTraitsFromJson) ? formatTraitsFromJson : formatTraitsFromJson.split(/,|;|\|/); + for (const traitStr of formatTraits) { + const formatTrait = parseFormatTrait(traitStr); + if (undefined === formatTrait) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'formatTraits' attribute. The string '${traitStr}' is not a valid format trait.`); + this._formatTraits = this._formatTraits | formatTrait; + } } public hasFormatTrait(formatTrait: FormatTraits) { return (this._formatTraits & formatTrait) === formatTrait; } - protected setUnits(units: Array<[Unit | InvertedUnit, string | undefined]> | undefined) { - // TODO: Need to do validation - this._units = units; - } - protected setPrecision(precision: number) { this._precision = precision; } - /** - * Creates a Unit with the provided name and label and adds it to this unit array - * @param name The name of the Unit - * @param label A localized display label that is used instead of the name in a GUI. + * Adds a Unit, or InvertedUnit, with an optional label override. + * @param unit The Unit, or InvertedUnit, to add to this Format. + * @param label A label that overrides the label defined within the Unit when a value is formatted. */ - private createUnitSync(name: string, label?: string) { - let newUnit: Unit | InvertedUnit | undefined; - if (name === undefined || typeof (name) !== "string" || (label !== undefined && typeof (label) !== "string")) // throws if name is undefined or name isnt a string or if label is defined and isnt a string - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `This Composite has a unit with an invalid 'name' or 'label' attribute.`); - for (const unit of this.units!) { - const unitObj = unit[0].name; - if (unitObj.toLowerCase() === (name.split(".")[1]).toLowerCase()) // no duplicate names- take unit name after "." - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The unit ${unitObj} has a duplicate name.`); + protected addUnit(unit: Unit | InvertedUnit, label?: string) { + if (undefined === this._units) + this._units = []; + else { // Validate that a duplicate is not added. + for (const existingUnit of this._units) { + if (unit.fullName.toLowerCase() === existingUnit[0].fullName.toLowerCase()) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has duplicate units, '${unit.fullName}'.`); // TODO: Validation - this should be a validation error not a hard failure. + } } - newUnit = this.schema.lookupItemSync(name); - if (!newUnit) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); - this.units!.push([newUnit, label]); - } - private async createUnit(name: string, label?: string) { - let newUnit: Unit | InvertedUnit | undefined; - if (name === undefined || typeof (name) !== "string" || (label !== undefined && typeof (label) !== "string")) // throws if name is undefined or name isnt a string or if label is defined and isnt a string - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `This Composite has a unit with an invalid 'name' or 'label' attribute.`); - for (const unit of this.units!) { - const unitObj = unit[0].name; - if (unitObj.toLowerCase() === (name.split(".")[1]).toLowerCase()) // duplicate names are not allowed- take unit name after "." - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The unit ${unitObj} has a duplicate name.`); - } - newUnit = await this.schema.lookupItem(name); - if (!newUnit) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); - this.units!.push([newUnit, label]); + this._units.push([unit, label]); } + protected setPrecision(precision: number) { this._precision = precision; } + private typecheck(formatProps: FormatProps) { - this._type = parseFormatType(formatProps.type, this.name); - this._precision = parsePrecision(formatProps.precision!, this.name, this._type); + const formatType = parseFormatType(formatProps.type); + if (undefined === formatType) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'type' attribute.`); + this._type = formatType; - if (this.type === FormatType.Scientific) { - if (undefined === formatProps.scientificType) // if format type is scientific and scientific type is undefined, throw - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.name} has type 'Scientific' therefore attribute 'scientificType' is required.`); - this._scientificType = parseScientificType(formatProps.scientificType, this.name); + if (undefined !== formatProps.precision) { + if (!Number.isInteger(formatProps.precision)) // must be an integer + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'precision' attribute. It should be an integer.`); + const precision = parsePrecision(formatProps.precision, this._type); + if (undefined === precision) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'precision' attribute.`); + this._precision = precision; } - if (this.type === FormatType.Station) { - if (undefined === formatProps.stationOffsetSize) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.name} has type 'Station' therefore attribute 'stationOffsetSize' is required.`); - this._stationOffsetSize = formatProps.stationOffsetSize; + if (undefined !== formatProps.minWidth) { + if (!Number.isInteger(formatProps.minWidth) || formatProps.minWidth < 0) // must be a positive int + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'minWidth' attribute. It should be a positive integer.`); + this._minWidth = formatProps.minWidth; } - if (undefined !== formatProps.roundFactor) { - if (formatProps.roundFactor !== this.roundFactor) - this._roundFactor = formatProps.roundFactor; + if (FormatType.Scientific === this.type) { + if (undefined === formatProps.scientificType) // if format type is scientific and scientific type is undefined, throw + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} is 'Scientific' type therefore the attribute 'scientificType' is required.`); + const scientificType = parseScientificType(formatProps.scientificType); + if (undefined === scientificType) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'scientificType' attribute.`); + this._scientificType = scientificType; } - if (undefined !== formatProps.minWidth) { - this._minWidth = formatProps.minWidth; + if (FormatType.Station === this.type) { + if (undefined === formatProps.stationOffsetSize) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} is 'Station' type therefore the attribute 'stationOffsetSize' is required.`); + if (!Number.isInteger(formatProps.stationOffsetSize) || formatProps.stationOffsetSize < 0) // must be a positive int > 0 + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); + this._stationOffsetSize = formatProps.stationOffsetSize; } if (undefined !== formatProps.showSignOption) { - this._showSignOption = parseShowSignOption(formatProps.showSignOption, this.name); + const signOption = parseShowSignOption(formatProps.showSignOption); + if (undefined === signOption) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'showSignOption' attribute.`); + this._showSignOption = signOption; } - if (undefined !== formatProps.formatTraits && formatProps.formatTraits.length !== 0) { - this.verifyFormatTraitsOptions(formatProps.formatTraits); - } + if (undefined !== formatProps.formatTraits && formatProps.formatTraits.length !== 0) + this.parseFormatTraits(formatProps.formatTraits); + + if (undefined !== formatProps.roundFactor) + this._roundFactor = formatProps.roundFactor; if (undefined !== formatProps.decimalSeparator) { + if (formatProps.decimalSeparator.length > 1) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'decimalSeparator' attribute. It should be an empty or one character string.`); this._decimalSeparator = formatProps.decimalSeparator; } if (undefined !== formatProps.thousandSeparator) { + if (formatProps.thousandSeparator.length > 1) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'thousandSeparator' attribute. It should be an empty or one character string.`); this._thousandSeparator = formatProps.thousandSeparator; } if (undefined !== formatProps.uomSeparator) { + if (formatProps.uomSeparator.length > 1) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'uomSeparator' attribute. It should be an empty or one character string.`); this._uomSeparator = formatProps.uomSeparator; } if (undefined !== formatProps.stationSeparator) { + if (formatProps.stationSeparator.length > 1) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'stationSeparator' attribute. It should be an empty or one character string.`); this._stationSeparator = formatProps.stationSeparator; } - if (undefined !== formatProps.composite) { - this._units = new Array<[Unit | InvertedUnit, string | undefined]>(); - if (formatProps.composite.includeZero !== undefined) { + + if (undefined !== formatProps.composite) { // TODO: This is duplicated below when the units need to be processed... + if (undefined !== formatProps.composite.includeZero) this._includeZero = formatProps.composite.includeZero; - } - if (formatProps.composite.spacer !== undefined) { + + if (undefined !== formatProps.composite.spacer) { + if (formatProps.composite.spacer.length > 1) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has a composite with an invalid 'spacer' attribute. It should be an empty or one character string.`); this._spacer = formatProps.composite.spacer; } + + // Composite requires 1-4 units + if (formatProps.composite.units.length <= 0 || formatProps.composite.units.length > 4) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${this.fullName} has an invalid 'Composite' attribute. It should have 1-4 units.`); } } public deserializeSync(formatProps: FormatProps) { super.deserializeSync(formatProps); this.typecheck(formatProps); - if (undefined !== formatProps.composite) { - for (const unit of formatProps.composite.units!) { - this.createUnitSync(unit.name, unit.label); - } + if (undefined === formatProps.composite) + return; + + // Units are separated from the rest of the deserialization because of the need to have separate sync and async implementation + for (const unit of formatProps.composite.units) { + const newUnit = this.schema.lookupItemSync(unit.name); + if (undefined === newUnit) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); + this.addUnit(newUnit, unit.label); } } public async deserialize(formatProps: FormatProps) { super.deserialize(formatProps); this.typecheck(formatProps); - if (undefined !== formatProps.composite) { - for (const unit of formatProps.composite.units!) { - await this.createUnit(unit.name, unit.label); - } + if (undefined === formatProps.composite) + return; + + // Units are separated from the rest of the deserialization because of the need to have separate sync and async implementation + for (const unit of formatProps.composite.units) { + const newUnit = await this.schema.lookupItem(unit.name); + if (undefined === newUnit) + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); + this.addUnit(newUnit, unit.label); } } @@ -222,44 +246,46 @@ export class Format extends SchemaItem implements IFormat { const schemaJson = super.toJson(standalone, includeSchemaVersion); schemaJson.type = formatTypeToString(this.type!); schemaJson.precision = this.precision; - if (undefined !== this.roundFactor) - schemaJson.roundFactor = this.roundFactor; - if (undefined !== this.minWidth) - schemaJson.minWidth = this.minWidth; - if (undefined !== this.showSignOption) - schemaJson.showSignOption = showSignOptionToString(this.showSignOption); - if (undefined !== this.formatTraits) - schemaJson.formatTraits = formatTraitsToArray(this.formatTraits); - if (undefined !== this.decimalSeparator) - schemaJson.decimalSeparator = this.decimalSeparator; - if (undefined !== this.thousandSeparator) - schemaJson.thousandSeparator = this.thousandSeparator; - if (undefined !== this.uomSeparator) - schemaJson.uomSeparator = this.uomSeparator; - if (undefined !== this.scientificType) + + // this._spacer = " "; + // this._includeZero = true; + + // Serialize the minimal amount of information needed so anything that is the same as the default, do not serialize. + if (0.0 !== this.roundFactor) schemaJson.roundFactor = this.roundFactor; + if (ShowSignOption.OnlyNegative !== this.showSignOption) schemaJson.showSignOption = showSignOptionToString(this.showSignOption); + if (0x0 !== this.formatTraits) schemaJson.formatTraits = formatTraitsToArray(this.formatTraits); + if ("." !== this.decimalSeparator) schemaJson.decimalSeparator = this.decimalSeparator; + if ("," !== this.thousandSeparator) schemaJson.thousandSeparator = this.thousandSeparator; + if (" " !== this.uomSeparator) schemaJson.uomSeparator = this.uomSeparator; + + if (undefined !== this.minWidth) schemaJson.minWidth = this.minWidth; + + if (FormatType.Scientific === this.type && undefined !== this.scientificType) schemaJson.scientificType = scientificTypeToString(this.scientificType); - if (undefined !== this.stationOffsetSize) - schemaJson.stationOffsetSize = this.stationOffsetSize; - if (undefined !== this.stationSeparator) - schemaJson.stationSeparator = this.stationSeparator; - if (undefined !== this.units) { - const composite: { [value: string]: any } = {}; - composite.spacer = this.spacer; - composite.includeZero = this.includeZero; - composite.units = []; - this.units.forEach((unit: any) => { - if (undefined !== unit[1]) - composite.units.push({ - name: unit[0].name, - label: unit[1], - }); - else - composite.units.push({ - name: unit[0].name, - }); + + if (FormatType.Station === this.type) { + if (undefined !== this.stationOffsetSize) + schemaJson.stationOffsetSize = this.stationOffsetSize; + if (" " !== this.stationSeparator) + schemaJson.stationSeparator = this.stationSeparator; + } + + if (undefined === this.units) + return schemaJson; + + schemaJson.composite = {}; + + if (" " !== this.spacer) schemaJson.composite.spacer = this.spacer; + if (true !== this.includeZero) schemaJson.composite.includeZero = this.includeZero; + + schemaJson.composite.units = []; + for (const unit of this.units) { + schemaJson.composite.units.push({ + name: unit[0].fullName, + label: unit[1], }); - schemaJson.composite = composite; } + return schemaJson; } diff --git a/core/ecschema-metadata/src/Metadata/Schema.ts b/core/ecschema-metadata/src/Metadata/Schema.ts index 2e53c73..0234027 100644 --- a/core/ecschema-metadata/src/Metadata/Schema.ts +++ b/core/ecschema-metadata/src/Metadata/Schema.ts @@ -278,7 +278,7 @@ export class Schema implements CustomAttributeContainerProps { // This method is private at the moment, but there is really no reason it can't be public... Need to make sure this is the way we want to handle this private createClass(type: (new (schema: Schema, name: string, modifier?: ECClassModifier) => T), name: string, modifier?: ECClassModifier): T { const item = new type(this, name, modifier); - this.addItemSync(item); + this.addItem(item); return item; } @@ -321,7 +321,7 @@ export class Schema implements CustomAttributeContainerProps { } if (!schemaName || schemaName.toUpperCase() === this.name.toUpperCase()) { - return await this.getItem(itemName); + return this.getItem(itemName); // this._context. } @@ -330,7 +330,7 @@ export class Schema implements CustomAttributeContainerProps { return undefined; // TODO: use the context and not get the full schema here - return await refSchema.getItem(itemName); + return refSchema.getItem(itemName); } /** @@ -359,15 +359,7 @@ export class Schema implements CustomAttributeContainerProps { return refSchema.getItemSync(itemName); } - /** - * - * @param item - */ - protected async addItem(item: T): Promise { - this.addItemSync(item); - } - - protected addItemSync(item: T): void { + protected addItem(item: T): void { if (undefined !== this.getItemSync(item.name)) throw new ECObjectsError(ECObjectsStatus.DuplicateItem, `The SchemaItem ${item.name} cannot be added to the schema ${this.name} because it already exists`); @@ -452,31 +444,29 @@ export class Schema implements CustomAttributeContainerProps { } public deserializeSync(schemaProps: SchemaProps) { - if (SCHEMAURL3_2 !== schemaProps.$schema) - throw new ECObjectsError(ECObjectsStatus.MissingSchemaUrl, `Schema namespace '${schemaProps.$schema}' is not supported.`); + if (SCHEMAURL3_2 !== schemaProps.$schema) // TODO: Allow for 3.x URI versions to allow the API to read newer specs. (Start at 3.2 though) + throw new ECObjectsError(ECObjectsStatus.MissingSchemaUrl, `The Schema ${this.name} has an unsupported namespace '${schemaProps.$schema}'.`); - if (!this._schemaKey) { + if (undefined === this._schemaKey) { const schemaName = schemaProps.name; const version = ECVersion.fromString(schemaProps.version); this._schemaKey = new SchemaKey(schemaName, version); } else { if (schemaProps.name.toLowerCase() !== this.name.toLowerCase()) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Schema ${this.name} does not match the provided name, '${schemaProps.name}'.`); if (schemaProps.version !== this.schemaKey.version.toString()) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, ``); + throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Schema ${this.name} has the version '${this.schemaKey.version}' that does not match the provided version '${schemaProps.version}'.`); } - if (undefined !== schemaProps.alias) { + if (undefined !== schemaProps.alias) this._alias = schemaProps.alias; - } - if (undefined !== schemaProps.label) { + if (undefined !== schemaProps.label) this._label = schemaProps.label; - } - if (undefined !== schemaProps.description) { + if (undefined !== schemaProps.description) this._description = schemaProps.description; - } + this._customAttributes = processCustomAttributes(schemaProps.customAttributes, this.name, CustomAttributeContainerType.Schema); } @@ -487,8 +477,9 @@ export class Schema implements CustomAttributeContainerProps { public static async fromJson(jsonObj: object | string, context?: SchemaContext): Promise { let schema: Schema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), context); - schema = await reader.readSchema(schema, jsonObj); + const reader = new SchemaReadHelper(JsonParser, context); + const rawSchema = typeof jsonObj === "string" ? JSON.parse(jsonObj) : jsonObj; + schema = await reader.readSchema(schema, rawSchema); return schema; } @@ -496,8 +487,9 @@ export class Schema implements CustomAttributeContainerProps { public static fromJsonSync(jsonObj: object | string, context?: SchemaContext): Schema { let schema: Schema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), context); - schema = reader.readSchemaSync(schema, jsonObj); + const reader = new SchemaReadHelper(JsonParser, context); + const rawSchema = typeof jsonObj === "string" ? JSON.parse(jsonObj) : jsonObj; + schema = reader.readSchemaSync(schema, rawSchema); return schema; } @@ -538,8 +530,7 @@ export abstract class MutableSchema extends Schema { public abstract createUnitSystemSync(name: string): UnitSystem; public abstract async createPropertyCategory(name: string): Promise; public abstract createPropertyCategorySync(name: string): PropertyCategory; - public abstract async addItem(item: T): Promise; - public abstract addItemSync(item: T): void; + public abstract addItem(item: T): void; public abstract async addReference(refSchema: Schema): Promise; public abstract addReferenceSync(refSchema: Schema): void; } diff --git a/core/ecschema-metadata/src/Metadata/SchemaItem.ts b/core/ecschema-metadata/src/Metadata/SchemaItem.ts index 915f01a..6388866 100644 --- a/core/ecschema-metadata/src/Metadata/SchemaItem.ts +++ b/core/ecschema-metadata/src/Metadata/SchemaItem.ts @@ -5,7 +5,7 @@ import { Schema } from "./Schema"; import { SchemaItemProps } from "./../Deserialization/JsonProps"; -import { parseSchemaItemType, SchemaItemType, schemaItemTypeToString } from "./../ECObjects"; +import { SchemaItemType, schemaItemTypeToString } from "./../ECObjects"; import { ECObjectsError, ECObjectsStatus } from "./../Exception"; import { SchemaItemVisitor } from "./../Interfaces"; import { SchemaItemKey } from "./../SchemaKey"; @@ -29,7 +29,7 @@ export abstract class SchemaItem { get name() { return this.key.name; } - get fullName() { return this.key.schemaKey ? `${this.key.schemaKey}.${this.name}` : this.name; } + get fullName() { return this.key.schemaKey ? `${this.key.schemaName}.${this.name}` : this.name; } get key() { return this._key; } @@ -55,9 +55,6 @@ export abstract class SchemaItem { } public deserializeSync(schemaItemProps: SchemaItemProps) { - if (parseSchemaItemType(schemaItemProps.schemaItemType) !== this.schemaItemType) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this.name} has an incompatible schemaItemType. It must be "${schemaItemTypeToString(this.schemaItemType)}", not "${schemaItemProps.schemaItemType}".`); - if (undefined !== schemaItemProps.label) { this._label = schemaItemProps.label; } @@ -76,9 +73,6 @@ export abstract class SchemaItem { } public async deserialize(schemaItemProps: SchemaItemProps) { - if (parseSchemaItemType(schemaItemProps.schemaItemType) !== this.schemaItemType) - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The SchemaItem ${this.name} has an incompatible schemaItemType. It must be "${schemaItemTypeToString(this.schemaItemType)}", not "${schemaItemProps.schemaItemType}".`); - if (undefined !== schemaItemProps.label) { this._label = schemaItemProps.label; } diff --git a/core/ecschema-metadata/src/SchemaKey.ts b/core/ecschema-metadata/src/SchemaKey.ts index be3e401..fbd6154 100644 --- a/core/ecschema-metadata/src/SchemaKey.ts +++ b/core/ecschema-metadata/src/SchemaKey.ts @@ -66,7 +66,7 @@ export class ECVersion { /** * Compares two schema versions. * @param rhs The schema to compare. - * @return A negative number if this schema version is less than the given version, a positive number if greater, and 0 if are equalivalent. + * @return A negative number if this schema version is less than the given version, a positive number if greater, and 0 if are equivalent. */ public compare(rhv: ECVersion): number { if (this.read !== rhv.read) @@ -160,7 +160,7 @@ export class SchemaKey { /** * Compares two schema versions. * @param rhs The schema to compare. - * @return A negative number if this schema version is less than the given version, a positive number if greater, and 0 if are equalivalent. + * @return A negative number if this schema version is less than the given version, a positive number if greater, and 0 if are equivalent. */ public compareByVersion(rhs: SchemaKey): number { return this.version.compare(rhs.version); @@ -210,8 +210,6 @@ export class SchemaItemKey { private _name: ECName; protected _schemaKey: SchemaKey; - constructor(name: string, schema: SchemaKey); - constructor(name: string, schema: SchemaKey); // tslint:disable-line constructor(name: string, schema: SchemaKey) { this._name = new ECName(name); this._schemaKey = schema; diff --git a/core/ecschema-metadata/src/utils/FormatEnums.ts b/core/ecschema-metadata/src/utils/FormatEnums.ts index a96acb0..39b6c38 100644 --- a/core/ecschema-metadata/src/utils/FormatEnums.ts +++ b/core/ecschema-metadata/src/utils/FormatEnums.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { ECObjectsError, ECObjectsStatus } from "./../Exception"; - export const formatStringRgx = /([\w.:]+)(\(([^\)]+)\))?(\[([^\|\]]+)([\|])?([^\]]+)?\])?(\[([^\|\]]+)([\|])?([^\]]+)?\])?(\[([^\|\]]+)([\|])?([^\]]+)?\])?(\[([^\|\]]+)([\|])?([^\]]+)?\])?/; export const enum FormatTraits { @@ -69,16 +67,27 @@ export const enum ShowSignOption { // default is no sign // parse and toString methods +export function parseScientificType(scientificType: string): ScientificType | undefined { + switch (scientificType.toLowerCase()) { + case "normalized": return ScientificType.Normalized; + case "zeronormalized": return ScientificType.ZeroNormalized; + default: + return undefined; + } +} + export function scientificTypeToString(scientificType: ScientificType): string { return (scientificType === ScientificType.Normalized) ? "Normalized" : "ZeroNormalized"; } -export function parseScientificType(scientificType: string, formatName: string): ScientificType { - switch (scientificType.toLowerCase()) { - case "normalized": return ScientificType.Normalized; - case "zeronormalized": return ScientificType.ZeroNormalized; +export function parseShowSignOption(showSignOption: string): ShowSignOption | undefined { + switch (showSignOption.toLowerCase()) { + case "nosign": return ShowSignOption.NoSign; + case "onlynegative": return ShowSignOption.OnlyNegative; + case "signalways": return ShowSignOption.SignAlways; + case "negativeparentheses": return ShowSignOption.NegativeParentheses; default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} has an invalid 'scientificType' attribute.`); + return undefined; } } @@ -91,14 +100,23 @@ export function showSignOptionToString(showSign: ShowSignOption): string { } } -export function parseShowSignOption(showSignOption: string, formatName: string): ShowSignOption { - switch (showSignOption.toLowerCase()) { - case "nosign": return ShowSignOption.NoSign; - case "onlynegative": return ShowSignOption.OnlyNegative; - case "signalways": return ShowSignOption.SignAlways; - case "negativeparentheses": return ShowSignOption.NegativeParentheses; - default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} has an invalid 'showSignOption' attribute.`); +/** + * + * @param formatTraitsString + */ +export function parseFormatTrait(formatTraitsString: string): FormatTraits | undefined { + switch (formatTraitsString.toLowerCase()) { + case "trailzeroes": return FormatTraits.TrailZeroes; + case "keepsinglezero": return FormatTraits.KeepSingleZero; + case "zeroempty": return FormatTraits.ZeroEmpty; + case "keepdecimalpoint": return FormatTraits.KeepDecimalPoint; + case "applyrounding": return FormatTraits.ApplyRounding; + case "fractiondash": return FormatTraits.FractionDash; + case "showunitlabel": return FormatTraits.ShowUnitLabel; + case "prependunitlabel": return FormatTraits.PrependUnitLabel; + case "use1000separator": return FormatTraits.Use1000Separator; + case "exponentonlynegative": return FormatTraits.ExponentOnlyNegative; + default: return undefined; } } @@ -127,48 +145,15 @@ export function formatTraitsToArray(currentFormatTrait: FormatTraits): string[] return formatTraitsArr; } -/** - * - * @param formatTraitsString - * @param currentFormatTrait - */ -export function parseFormatTrait(formatTraitsString: string, currentFormatTrait: number): FormatTraits { - let formatTrait = currentFormatTrait; - switch (formatTraitsString.toLowerCase()) { - case "trailzeroes": - formatTrait = currentFormatTrait | FormatTraits.TrailZeroes; - break; - case "keepsinglezero": - formatTrait = currentFormatTrait | FormatTraits.KeepSingleZero; - break; - case "zeroempty": - formatTrait = currentFormatTrait | FormatTraits.ZeroEmpty; - break; - case "keepdecimalpoint": - formatTrait = currentFormatTrait | FormatTraits.KeepDecimalPoint; - break; - case "applyrounding": - formatTrait = currentFormatTrait | FormatTraits.ApplyRounding; - break; - case "fractiondash": - formatTrait = currentFormatTrait | FormatTraits.FractionDash; - break; - case "showunitlabel": - formatTrait = currentFormatTrait | FormatTraits.ShowUnitLabel; - break; - case "prependunitlabel": - formatTrait = currentFormatTrait | FormatTraits.PrependUnitLabel; - break; - case "use1000separator": - formatTrait = currentFormatTrait | FormatTraits.Use1000Separator; - break; - case "exponentonlynegative": - formatTrait = currentFormatTrait | FormatTraits.ExponentOnlyNegative; - break; +export function parseFormatType(jsonObjType: string): FormatType | undefined { + switch (jsonObjType.toLowerCase()) { + case "decimal": return FormatType.Decimal; + case "scientific": return FormatType.Scientific; + case "station": return FormatType.Station; + case "fractional": return FormatType.Fractional; default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `Format has an invalid 'formatTraits' option.`); + return undefined; } - return formatTrait; } export function formatTypeToString(type: FormatType): string { @@ -180,18 +165,7 @@ export function formatTypeToString(type: FormatType): string { } } -export function parseFormatType(jsonObjType: string, formatName: string): FormatType { - switch (jsonObjType.toLowerCase()) { - case "decimal": return FormatType.Decimal; - case "scientific": return FormatType.Scientific; - case "station": return FormatType.Station; - case "fractional": return FormatType.Fractional; - default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} has an invalid 'type' attribute.`); - } -} - -export function parseDecimalPrecision(jsonObjPrecision: number, formatName: string): DecimalPrecision { +export function parseDecimalPrecision(jsonObjPrecision: number): DecimalPrecision | undefined { switch (jsonObjPrecision) { case 0: return DecimalPrecision.Zero; case 1: return DecimalPrecision.One; @@ -207,11 +181,11 @@ export function parseDecimalPrecision(jsonObjPrecision: number, formatName: stri case 11: return DecimalPrecision.Eleven; case 12: return DecimalPrecision.Twelve; default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} contains a 'precision' attribute which must be an integer in the range 0-12.`); + return undefined; } } -export function parseFractionalPrecision(jsonObjPrecision: number, formatName: string): FractionalPrecision { +export function parseFractionalPrecision(jsonObjPrecision: number): FractionalPrecision | undefined { switch (jsonObjPrecision) { case 1: return FractionalPrecision.One; case 2: return FractionalPrecision.Two; @@ -223,19 +197,19 @@ export function parseFractionalPrecision(jsonObjPrecision: number, formatName: s case 128: return FractionalPrecision.OneHundredTwentyEight; case 256: return FractionalPrecision.TwoHundredFiftySix; default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} has an invalid 'precision' attribute.`); + return undefined; } } -export function parsePrecision(precision: number, formatName: string, type: FormatType): DecimalPrecision | FractionalPrecision { +export function parsePrecision(precision: number, type: FormatType): DecimalPrecision | FractionalPrecision | undefined { switch (type) { // type must be decimal, fractional, scientific, or station case FormatType.Decimal: case FormatType.Scientific: case FormatType.Station: - return parseDecimalPrecision(precision, formatName); + return parseDecimalPrecision(precision); case FormatType.Fractional: - return parseFractionalPrecision(precision, formatName); + return parseFractionalPrecision(precision); default: - throw new ECObjectsError(ECObjectsStatus.InvalidECJson, `The Format ${formatName} has an invalid 'type' attribute.`); + return undefined; } } diff --git a/core/ecschema-metadata/test/Deserialization/JsonParser.test.ts b/core/ecschema-metadata/test/Deserialization/JsonParser.test.ts new file mode 100644 index 0000000..9373313 --- /dev/null +++ b/core/ecschema-metadata/test/Deserialization/JsonParser.test.ts @@ -0,0 +1,710 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { assert } from "chai"; +import { ECObjectsError } from "../../src"; +import { JsonParser } from "../../src/Deserialization/JsonParser"; +import { createSchemaJsonWithItems } from "../TestUtils/DeserializationHelpers"; + +describe("JsonParser", () => { + let parser: JsonParser; + + describe("getItems/findItem", () => { + + it("should throw for missing schemaItemType", () => { + parser = new JsonParser(createSchemaJsonWithItems({ TestEntity: {} })); + assert.throws(() => parser.findItem("TestEntity"), ECObjectsError, `The SchemaItem TestSchema.TestEntity is missing the required 'schemaItemType' attribute.`); + + parser = new JsonParser(createSchemaJsonWithItems({ TestEntity: {} })); + assert.throws(() => [...parser.getItems()], ECObjectsError, `The SchemaItem TestSchema.TestEntity is missing the required 'schemaItemType' attribute.`); + }); + + it("should throw for invalid schemaItemType", () => { + const json = { + TestEntity: { schemaItemType: 0 }, + }; + + parser = new JsonParser(createSchemaJsonWithItems(json)); + assert.throws(() => parser.findItem("TestEntity"), ECObjectsError, `The SchemaItem TestSchema.TestEntity has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); + + parser = new JsonParser(createSchemaJsonWithItems(json)); + assert.throws(() => [...parser.getItems()], ECObjectsError, `The SchemaItem TestSchema.TestEntity has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid name", () => { + const json = { + 0: { schemaItemType: "EntityClass" }, + }; + + parser = new JsonParser(createSchemaJsonWithItems(json)); + assert.throws(() => parser.findItem("0"), ECObjectsError, `A SchemaItem in TestSchema has an invalid 'name' attribute. '0' is not a valid ECName.`); + + parser = new JsonParser(createSchemaJsonWithItems(json)); + assert.throws(() => [...parser.getItems()], ECObjectsError, `A SchemaItem in TestSchema has an invalid 'name' attribute. '0' is not a valid ECName.`); + }); + }); + + describe("parseCustomAttributeClass", () => { + const baseJson = { schemaItemType: "CustomAttributeClass" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestCustomAttribute: baseJson })); + parser.findItem("TestCustomAttribute"); + }); + + it("should throw for missing appliesTo", () => { + assert.throws(() => parser.parseCustomAttributeClass({ ...baseJson }), ECObjectsError, `The CustomAttributeClass TestSchema.TestCustomAttribute is missing the required 'appliesTo' attribute.`); + }); + + it("should throw for invalid appliesTo", () => { + const json = { + ...baseJson, + appliesTo: 0, + }; + assert.throws(() => parser.parseCustomAttributeClass(json), ECObjectsError, `The CustomAttributeClass TestSchema.TestCustomAttribute has an invalid 'appliesTo' attribute. It should be of type 'string'.`); + }); + }); + + describe("parseEntityClass", () => { + const baseJson = { schemaItemType: "EntityClass" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestEntity: baseJson })); + parser.findItem("TestEntity"); + }); + + function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { + const json: any = { + ...baseJson, + [attributeName]: value, + }; + assert.throws(() => parser.parseEntityClass(json), ECObjectsError, `The SchemaItem TestSchema.TestEntity has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); + } + + it("should throw for invalid description", () => testInvalidAttribute("description", "string", 0)); + it("should throw for invalid label", () => testInvalidAttribute("label", "string", 0)); + + // TODO: Item-only parsing?? How's this gonna work? + it.skip("should throw for invalid schema", () => testInvalidAttribute("schema", "string", 0)); + it.skip("should throw for invalid schemaVersion", () => testInvalidAttribute("schemaVersion", "string", 0)); + + it("should throw for invalid modifier", async () => { + let json: any = { ...baseJson, modifier: 0 }; + assert.throws(() => parser.parseEntityClass(json), ECObjectsError, `The ECClass TestSchema.TestEntity has an invalid 'modifier' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid baseClass", async () => { + let json: any = { ...baseJson, baseClass: 0 }; + assert.throws(() => parser.parseEntityClass(json), ECObjectsError, `The ECClass TestSchema.TestEntity has an invalid 'baseClass' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid mixins", () => { + let json: any = { ...baseJson, mixins: 0 }; + assert.throws(() => parser.parseEntityClass(json), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); + + json = { ...baseJson, mixins: [0] }; + assert.throws(() => parser.parseEntityClass(json), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); + }); + }); + + describe("parseEnumeration", () => { + const baseJson = { schemaItemType: "Enumeration" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestEnumeration: baseJson })); + parser.findItem("TestEnumeration"); + }); + + it("should throw for missing type", () => { + const json = { ...baseJson }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration is missing the required 'type' attribute.`); + }); + + it("should throw for invalid type", () => { + const json = { ...baseJson, type: 0 }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an invalid 'type' attribute. It should be of type 'string'.`); + }); + + it("should throw for type not int or string", () => { + const json = { ...baseJson, type: "ThisIsNotRight" }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an invalid 'type' attribute. It should be either "int" or "string".`); + }); + + it("should throw for invalid isStrict", () => { + const json = { ...baseJson, type: "int", isStrict: 0 }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an invalid 'isStrict' attribute. It should be of type 'boolean'.`); + }); + + it("should throw for mismatched type", () => { + let json: any = { + ...baseJson, + type: "string", + enumerators: [ + { + name: "testEnumerator", + value: 0, // should throw as typeof(value) !== string + }, + ], + }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an incompatible type. It must be "string", not "int".`); + + json = { + ...baseJson, + type: "int", + enumerators: [ + { + name: "testEnumerator", + value: "test", // should throw as typeof(value) !== int + }, + ], + }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an incompatible type. It must be "int", not "string".`); + }); + + it("should throw for enumerators not an array", () => { + const json: any = { ...baseJson, type: "int", enumerators: 0 }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); + }); + + it("should throw for enumerators not an array of objects", () => { + const json: any = { ...baseJson, type: "int", enumerators: [0] }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); + }); + + it("should throw for enumerator description not a string", () => { + const json = { + ...baseJson, + type: "string", + isStrict: false, + label: "SomeDisplayLabel", + description: "A really long description...", + enumerators: [ + { name: "ONEVALUE", value: "one", label: "Label for the first value", description: 1 }, + ], + }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an enumerator with an invalid 'description' attribute. It should be of type 'string'.`); + }); + + it("should throw for enumerator with missing name", () => { + const json = { + ...baseJson, + type: "string", + isStrict: false, + label: "SomeDisplayLabel", + description: "A really long description...", + enumerators: [ + { value: "one", label: "Label for the first value", description: "Description for the first value" }, + ], + }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an enumerator that is missing the required attribute 'name'.`); + }); + + it("should throw for enumerator with missing value", () => { + const json = { + ...baseJson, + type: "string", + isStrict: false, + label: "SomeDisplayLabel", + description: "A really long description...", + enumerators: [ + { name: "one", label: "Label for the first value", description: "Description for the first value" }, + ], + }; + assert.throws(() => parser.parseEnumeration(json), ECObjectsError, `The Enumeration TestSchema.TestEnumeration has an enumerator that is missing the required attribute 'value'.`); + }); + }); + + describe("parseFormat", () => { + const baseJson = { + schemaItemType: "Format", + type: "fractional", + label: "myfi4", + description: "A format description", + roundFactor: 0.0, + showSignOption: "onlyNegative", + formatTraits: "keepSingleZero|trailZeroes", + precision: 4, + decimalSeparator: ".", + thousandSeparator: ",", + uomSeparator: " ", + }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ AmerMYFI4: baseJson })); + parser.findItem("AmerMYFI4"); + }); + + it("should throw for description not a string", () => { + const json = { + ...baseJson, + description: 12345678, + }; + assert.throws(() => parser.parseFormat(json), ECObjectsError, `The SchemaItem TestSchema.AmerMYFI4 has an invalid 'description' attribute. It should be of type 'string'.`); + }); + + it("should throw for missing type", () => { + const json = { + ...baseJson + }; + delete json.type; + assert.throws(() => parser.parseFormat(json), ECObjectsError, `The Format TestSchema.AmerMYFI4 does not have the required 'type' attribute.`); + }); + + it("should throw for invalid showSignOption", () => { + const json = { + ...baseJson, + showSignOption: 456, + }; + assert.throws(() => parser.parseFormat(json), ECObjectsError, `The Format TestSchema.AmerMYFI4 has an invalid 'showSignOption' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid composite", () => { + const json = { + includeZero: false, + schemaItemType: "Format", + name: "AmerMYFI4", + type: "fractional", + precision: 4, + composite: { + includeZero: false, + spacer: 1, + units: [ + { + name: "TestSchema.MILE", + label: "mile(s)", + }, + ], + }, + }; + assert.throws(() => parser.parseFormat(json), ECObjectsError, `The Format TestSchema.AmerMYFI4 has a Composite with an invalid 'spacer' attribute.`); + }); + }); + + describe("parseInvertedUnit", () => { + const baseJson = { schemaItemType: "InvertedUnit" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ HORIZONTAL_PER_VERTICAL: baseJson })); + parser.findItem("HORIZONTAL_PER_VERTICAL"); + }); + + it("should throw for invalid label", () => { + const json = { + ...baseJson, + label: 5, + unitSystem: "ExampleSchema.INTERNATIONAL", + invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", + }; + assert.throws(() => parser.parseInvertedUnit(json), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'label' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid description", () => { + const json = { + ...baseJson, + description: 5, + unitSystem: "ExampleSchema.INTERNATIONAL", + invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", + }; + assert.throws(() => parser.parseInvertedUnit(json), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'description' attribute. It should be of type 'string'.`); + }); + + it("should throw for missing invertsUnit", () => { + const json = { + ...baseJson, + unitSystem: "ExampleSchema.INTERNATIONAL", + }; + assert.throws(() => parser.parseInvertedUnit(json), ECObjectsError, `The InvertedUnit TestSchema.HORIZONTAL_PER_VERTICAL does not have the required 'invertsUnit' attribute.`); + }); + + it("should throw for missing unitSystem", () => { + const json = { + ...baseJson, + invertsUnit: "TestSchema.VERTICAL_PER_HORIZONTAL", + }; + assert.throws(() => parser.parseInvertedUnit(json), ECObjectsError, `The InvertedUnit TestSchema.HORIZONTAL_PER_VERTICAL does not have the required 'unitSystem' attribute.`); + }); + }); + + describe("parseKindOfQuantity", () => { + const baseJson = { + schemaItemType: "KindOfQuantity", + name: "TestKindOfQuantity", + label: "SomeDisplayLabel", + description: "A really long description...", + }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestKindOfQuantity: baseJson })); + parser.findItem("TestKindOfQuantity"); + }); + + function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { + const json: any = { + ...baseJson, + relativeError: 0, + presentationUnits: ["Formats.CM"], + persistenceUnit: "Formats.DefaultReal", + [attributeName]: value, // will overwrite previously defined objects + }; + assert.throws(() => parser.parseKindOfQuantity(json), ECObjectsError, `The KindOfQuantity TestSchema.TestKindOfQuantity has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); + } + + it("should throw for invalid relativeError", () => { testInvalidAttribute("relativeError", "number", false); }); + it("should throw for invalid presentationUnits", () => { testInvalidAttribute("presentationUnits", `string' or 'string[]`, false); }); + it("should throw for invalid persistenceUnit", () => { testInvalidAttribute("persistenceUnit", "string", false); }); + + // should throw for missing relativeError + const missingRelativeError = { + ...baseJson, + presentationUnits: ["Formats.IN"], + persistenceUnit: "Formats.IN", + }; + it("should throw for missing relativeError", () => { + assert.throws(() => parser.parseKindOfQuantity(missingRelativeError), ECObjectsError, `The KindOfQuantity TestSchema.TestKindOfQuantity is missing the required 'relativeError' attribute.`); + }); + + // should throw for missing persistenceUnit + const missingPersistenceUnit = { + ...baseJson, + relativeError: 1.234, + presentationUnits: ["Formats.IN"], + }; + it("should throw for missing persistenceUnit", () => { + assert.throws(() => parser.parseKindOfQuantity(missingPersistenceUnit), ECObjectsError, `The KindOfQuantity TestSchema.TestKindOfQuantity is missing the required 'persistenceUnit' attribute.`); + }); + }); + + describe("parseMixin", () => { + const baseJson = { schemaItemType: "Mixin" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestMixin: baseJson })); + parser.findItem("TestMixin"); + }); + + it("should throw for missing appliesTo", () => { + assert.throws(() => parser.parseMixin({ ...baseJson }), ECObjectsError, `The Mixin TestSchema.TestMixin is missing the required 'appliesTo' attribute.`); + }); + + it("should throw for invalid appliesTo", () => { + const json = { ...baseJson, appliesTo: 0 }; + assert.throws(() => parser.parseMixin(json), ECObjectsError, `The Mixin TestSchema.TestMixin has an invalid 'appliesTo' attribute. It should be of type 'string'.`); + }); + }); + + describe("parsePhenomenon", () => { + const baseJson = { schemaItemType: "Phenomenon" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ AREA: baseJson })); + parser.findItem("AREA"); + }); + + it("should throw for invalid label", () => { + const json = { + ...baseJson, + label: 48, + definition: "Units.LENGTH(2)", + }; + assert.throws(() => parser.parsePhenomenon(json), ECObjectsError, `The SchemaItem TestSchema.AREA has an invalid 'label' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid description", () => { + const json = { + ...baseJson, + description: 5, + definition: "Units.LENGTH(2)", + }; + assert.throws(() => parser.parsePhenomenon(json), ECObjectsError, `The SchemaItem TestSchema.AREA has an invalid 'description' attribute. It should be of type 'string'.`); + }); + + it("should throw for missing definition", () => { + const json = { + ...baseJson, + }; + assert.throws(() => parser.parsePhenomenon(json), ECObjectsError, `The Phenomenon TestSchema.AREA does not have the required 'definition' attribute.`); + }); + + it("should throw for invalid definition", () => { + const json = { + ...baseJson, + definition: 2, + }; + assert.throws(() => parser.parsePhenomenon(json), ECObjectsError, `The Phenomenon TestSchema.AREA has an invalid 'definition' attribute. It should be of type 'string'.`); + }); + }); + + describe("parsePrimitiveProperty", () => { + const baseJson = { schemaItemType: "EntityClass" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestClass: baseJson })); + parser.findItem("TestClass"); + }); + + const mustBeArrayJson = { + name: "BadProp", + label: "SomeDisplayLabel", + type: "PrimitiveArrayProperty", + description: "A really long description...", + customAttributes: "CoreCustomAttributes.HiddenSchema", + }; + + it("should throw for invalid customAttributes", () => { + assert.throws(() => parser.parsePrimitiveProperty(mustBeArrayJson), ECObjectsError, "The ECProperty TestSchema.TestClass.BadProp has an invalid 'customAttributes' attribute. It should be of type 'array'."); + }); + + function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { + const json: any = { + name: "TestProp", + type: "PrimitiveProperty", + typeName: "string", + label: "Some display label", + description: "Some really long description...", + priority: 0, + isReadOnly: true, + category: "TestCategory", + kindOfQuantity: "TestKindOfQuantity", + inherited: false, + customAttributes: [], + [attributeName]: value, // overwrites previously defined objects + } + let err = (typeof (json.name) !== "string") ? `An ECProperty in TestSchema.TestClass ` : `The ECProperty TestSchema.TestClass.TestProp `; + err += `has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`; + assert.throws(() => parser.parsePrimitiveProperty(json), ECObjectsError, err); + } + + it("should throw for invalid label", () => { testInvalidAttribute("label", "string", false); }); + it("should throw for invalid description", () => { testInvalidAttribute("description", "string", false); }); + it("should throw for invalid priority", () => { testInvalidAttribute("priority", "number", false); }); + it("should throw for invalid isReadOnly", () => { testInvalidAttribute("isReadOnly", "boolean", 1.234); }); + it("should throw for invalid category", () => { testInvalidAttribute("category", "string", false); }); + it("should throw for invalid kindOfQuantity", () => { testInvalidAttribute("kindOfQuantity", "string", false); }); + it("should throw for invalid inherited", () => { testInvalidAttribute("inherited", "boolean", 1.234); }); + it("should throw for invalid customAttributes", () => { testInvalidAttribute("category", "string", false); }); + it("should throw for invalid typeName", () => { testInvalidAttribute("typeName", "string", 0); }); + it("should throw for invalid minLength", () => { testInvalidAttribute("minLength", "number", "0"); }); + it("should throw for invalid maxLength", () => { testInvalidAttribute("maxLength", "number", "0"); }); + it("should throw for invalid minValue", () => { testInvalidAttribute("minValue", "number", "0"); }); + it("should throw for invalid maxValue", () => { testInvalidAttribute("maxValue", "number", "0"); }); + it("should throw for invalid extendedTypeName", () => { testInvalidAttribute("extendedTypeName", "string", 0); }); + }); + + describe("parseEnumerationProperty", () => { + const baseJson = { schemaItemType: "EntityClass" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestClass: baseJson })); + parser.findItem("TestClass"); + }); + + it("should throw for invalid typeName", () => { + const json: any = { + name: 0, + type: "PrimitiveProperty", + typeName: 0, + }; + assert.throws(() => parser.parseEnumerationProperty(json), ECObjectsError); + }); + }); + + describe("parsePrimitiveArrayProperty", () => { + const baseJson = { schemaItemType: "EntityClass" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestClass: baseJson })); + parser.findItem("TestClass"); + }); + + it("should throw for invalid minOccurs", () => { + const json = { + name: "TestProperty", + type: "PrimitiveProperty", + minOccurs: "0", + }; + assert.throws(() => parser.parsePrimitiveArrayProperty(json), ECObjectsError); + }); + + it("should throw for invalid maxOccurs", () => { + const json = { + name: "TestProperty", + type: "PrimitiveProperty", + maxOccurs: "0", + }; + assert.throws(() => parser.parsePrimitiveArrayProperty(json), ECObjectsError); + }); + }); + + describe("parsePropertyCategory", () => { + const baseJson = { schemaItemType: "PropertyCategory" }; + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestCategory: baseJson })); + parser.findItem("TestCategory"); + }); + + it("should throw for invalid priority", () => { + const json = { + ...baseJson, + priority: "1", + }; + assert.throws(() => parser.parsePropertyCategory(json), ECObjectsError, `The PropertyCategory TestSchema.TestCategory has an invalid 'priority' attribute. It should be of type 'number'.`); + }); + }); + + describe("parseRelationshipClass", () => { + const validConstraintJson = { + polymorphic: true, + multiplicity: "(1..1)", + roleLabel: "owns", + constraintClasses: [ + "TestSchema.TestTargetEntity", + ], + }; + + const baseJson = { + schemaItemType: "RelationshipClass", + strength: "holding", + strengthDirection: "backward", + source: validConstraintJson, + target: validConstraintJson, + }; + + function withInvalidConstraint(end: "source" | "target", json: any) { + const badJson = { ...baseJson }; + badJson[end] = { ...validConstraintJson, ...json }; + return badJson; + } + + beforeEach(() => { + parser = new JsonParser(createSchemaJsonWithItems({ TestRelationship: baseJson })); + parser.findItem("TestRelationship"); + }); + + it("should throw for invalid strength", () => { + const json = { + ...baseJson, + strength: 0, + }; + assert.throws(() => parser.parseRelationshipClass(json), ECObjectsError, `The RelationshipClass TestSchema.TestRelationship has an invalid 'strength' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid strengthDirection", () => { + const json = { + ...baseJson, + strengthDirection: 0, + }; + assert.throws(() => parser.parseRelationshipClass(json), ECObjectsError, `The RelationshipClass TestSchema.TestRelationship has an invalid 'strengthDirection' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid roleLabel", () => { + const json = { roleLabel: 0 }; + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", json)), ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'roleLabel' attribute. It should be of type 'string'.`); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", json)), ECObjectsError, `The Target Constraint of TestSchema.TestRelationship has an invalid 'roleLabel' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid polymorphic", () => { + const json = { polymorphic: "0" }; + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", json)), ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'polymorphic' attribute. It should be of type 'boolean'.`); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", json)), ECObjectsError, `The Target Constraint of TestSchema.TestRelationship has an invalid 'polymorphic' attribute. It should be of type 'boolean'.`); + }); + + it("should throw for invalid multiplicity", () => { + const json = { multiplicity: 0 }; + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", json)), ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'multiplicity' attribute. It should be of type 'string'.`); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", json)), ECObjectsError, `The Target Constraint of TestSchema.TestRelationship has an invalid 'multiplicity' attribute. It should be of type 'string'.`); + }); + + it("should throw for invalid abstractConstraint", () => { + const json = { abstractConstraint: 0 }; + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", json)), ECObjectsError); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", json)), ECObjectsError); + }); + + it("should throw for invalid constraintClasses", () => { + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", { constraintClasses: 0 })), ECObjectsError); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", { constraintClasses: [0] })), ECObjectsError); + + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", { constraintClasses: 0 })), ECObjectsError); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", { constraintClasses: [0] })), ECObjectsError); + }); + + it("should throw for invalid constraint customAttributes", () => { + const json = { + customAttributes: "array", + }; + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("source", json)), ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + assert.throws(() => parser.parseRelationshipClass(withInvalidConstraint("target", json)), ECObjectsError, `The Target Constraint of TestSchema.TestRelationship has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + }); + + }); + + describe("parseSchema", () => { + it("should throw for missing name", () => { + const json = createSchemaJsonWithItems({}); + delete json.name; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, "An ECSchema is missing the required 'name' attribute."); + }); + + it("should throw for invalid name", () => { + const json = createSchemaJsonWithItems({}); + json.name = 0; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, `An ECSchema has an invalid 'name' attribute. It should be of type 'string'.`); + }); + + it("should throw for missing $schema", () => { + const json = createSchemaJsonWithItems({}); + delete json.$schema; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, `The ECSchema TestSchema is missing the required \'$schema\' attribute.`); + }); + + it("should throw for invalid version", () => { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "BadSchema", + version: 0, + }; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, `The ECSchema BadSchema has an invalid 'version' attribute. It should be of type 'string'.`); + }); + + it("should throw for missing version", () => { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "BadSchema", + }; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, "The ECSchema BadSchema is missing the required 'version' attribute."); + }); + + function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/31/draft-01/ecschema", + name: "TestSchema", + version: "1.2.3", + [attributeName]: value, + }; + parser = new JsonParser(json); + assert.throws(() => parser.parseSchema(), ECObjectsError, `The ECSchema TestSchema has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); + } + + it("should throw for invalid alias", () => testInvalidAttribute("alias", "string", 0)); + it("should throw for invalid label", () => testInvalidAttribute("label", "string", 0)); + it("should throw for invalid description", () => testInvalidAttribute("description", "string", 0)); + }); + + describe("getCustomAttributes", () => { + it("should throw for invalid customAttributes", () => { + const json = createSchemaJsonWithItems({}); + json.customAttributes = "CoreCustomAttributes.HiddenSchema"; + + parser = new JsonParser(json); + assert.throws(() => [...parser.getCustomAttributes()], ECObjectsError, "The Schema TestSchema has an invalid 'customAttributes' attribute. It should be of type 'array'."); + }); + }); +}); diff --git a/core/ecschema-metadata/test/Metadata/Class.test.ts b/core/ecschema-metadata/test/Metadata/Class.test.ts index 2965563..fa4e568 100644 --- a/core/ecschema-metadata/test/Metadata/Class.test.ts +++ b/core/ecschema-metadata/test/Metadata/Class.test.ts @@ -13,12 +13,9 @@ import { SchemaContext } from "../../src/Context"; import { DelayedPromiseWithProps } from "../../src/DelayedPromise"; import { ECClass, MutableClass } from "../../src/Metadata/Class"; import { ECObjectsError } from "../../src/Exception"; -import { SchemaItemType } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("ECClass", () => { let schema: Schema; - let parser = new JsonParser(); describe("get properties", () => { beforeEach(() => { @@ -143,6 +140,24 @@ describe("ECClass", () => { assert.isDefined(await testClass!.baseClass); assert.isTrue(await testClass!.baseClass === refBaseClass); }); + + it("should throw for missing base class", () => { + const schemaJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "TestSchema", + version: "1.2.3", + items: { + testClass: { + schemaItemType: "EntityClass", + baseClass: "TestSchema.ClassDoesNotExist", + }, + }, + }; + + const context = new SchemaContext(); + expect(Schema.fromJson(schemaJson, context)).to.be.rejectedWith(ECObjectsError); + }); + const oneCustomAttributeJson = { $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", name: "TestSchema", @@ -228,10 +243,10 @@ describe("ECClass", () => { }, }; it("async - Custom Attributes must be an array", async () => { - await expect(Schema.fromJson(mustBeAnArrayJson)).to.be.rejectedWith(ECObjectsError, `The ECClass testClass has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + await expect(Schema.fromJson(mustBeAnArrayJson)).to.be.rejectedWith(ECObjectsError, `The ECClass TestSchema.testClass has an invalid 'customAttributes' attribute. It should be of type 'array'.`); }); it("sync - Custom Attributes must be an array", async () => { - assert.throws(() => Schema.fromJsonSync(mustBeAnArrayJson), ECObjectsError, `The ECClass testClass has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + assert.throws(() => Schema.fromJsonSync(mustBeAnArrayJson), ECObjectsError, `The ECClass TestSchema.testClass has an invalid 'customAttributes' attribute. It should be of type 'array'.`); }); it("sync - Deserialize Multiple Custom Attributes with additional properties", () => { const classJson = { @@ -445,36 +460,6 @@ describe("ECClass", () => { }); }); - describe("parseProps", () => { - let testClass: ECClass; - class MockECClass extends ECClass { - public readonly schemaItemType!: SchemaItemType.EntityClass; // tslint:disable-line - constructor(newSchema: Schema, name: string) { - super(newSchema, name); - this.schemaItemType = SchemaItemType.EntityClass; - } - public async accept() { } - } - - beforeEach(() => { - testClass = new MockECClass(schema, "TestClass"); - }); - - it("should throw for invalid modifier", async () => { - expect(testClass).to.exist; - const invalidModifierJson = { schemaItemType: "EntityClass", modifier: 0 }; - assert.throws(() => parser.parseClassProps(invalidModifierJson, testClass.name), ECObjectsError, `The ECClass TestClass has an invalid 'modifier' attribute. It should be of type 'string'.`); - }); - - it("should throw for invalid baseClass", async () => { - expect(testClass).to.exist; - const invalidBaseClassJson = { schemaItemType: "EntityClass", baseClass: 0 }; - assert.throws(() => parser.parseClassProps(invalidBaseClassJson, testClass.name), ECObjectsError, `The ECClass TestClass has an invalid 'baseClass' attribute. It should be of type 'string'.`); - - const unloadedBaseClassJson = { schemaItemType: "EntityClass", baseClass: "ThisClassDoesNotExist" }; - await expect(testClass.deserialize(parser.parseClassProps(unloadedBaseClassJson, testClass.name))).to.be.rejectedWith(ECObjectsError); - }); - }); describe("toJson", () => { const schemaJsonOne = { $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", diff --git a/core/ecschema-metadata/test/Metadata/Constant.test.ts b/core/ecschema-metadata/test/Metadata/Constant.test.ts index ca553aa..57dcee2 100644 --- a/core/ecschema-metadata/test/Metadata/Constant.test.ts +++ b/core/ecschema-metadata/test/Metadata/Constant.test.ts @@ -136,10 +136,10 @@ describe("Constant", () => { definition: "testing", }; it("async - should throw for missing phenomenon", async () => { - await expect(Schema.fromJson(createSchemaJson(missingPhenomenon))).to.be.rejectedWith(ECObjectsError, `The Constant TestConstant does not have the required 'phenomenon' attribute.`); + await expect(Schema.fromJson(createSchemaJson(missingPhenomenon))).to.be.rejectedWith(ECObjectsError, `The Constant TestSchema.TestConstant does not have the required 'phenomenon' attribute.`); }); it("sync - should throw for missing phenomenon", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingPhenomenon)), ECObjectsError, `The Constant TestConstant does not have the required 'phenomenon' attribute.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingPhenomenon)), ECObjectsError, `The Constant TestSchema.TestConstant does not have the required 'phenomenon' attribute.`); }); // Invalid phenomenon @@ -148,10 +148,10 @@ describe("Constant", () => { phenomenon: 5, }; it("async - should throw for invalid phenomenon", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidPhenomenon))).to.be.rejectedWith(ECObjectsError, `The Constant TestConstant has an invalid 'phenomenon' attribute. It should be of type 'string'.`); + await expect(Schema.fromJson(createSchemaJson(invalidPhenomenon))).to.be.rejectedWith(ECObjectsError, `The Constant TestSchema.TestConstant has an invalid 'phenomenon' attribute. It should be of type 'string'.`); }); it("sync - should throw for invalid phenomenon", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidPhenomenon)), ECObjectsError, `The Constant TestConstant has an invalid 'phenomenon' attribute. It should be of type 'string'.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidPhenomenon)), ECObjectsError, `The Constant TestSchema.TestConstant has an invalid 'phenomenon' attribute. It should be of type 'string'.`); }); // Not found phenomenon @@ -171,10 +171,10 @@ describe("Constant", () => { phenomenon: "TestSchema.TestPhenomenon", }; it("async - should throw for missing definition", async () => { - await expect(Schema.fromJson(createSchemaJson(missingDefinition))).to.be.rejectedWith(ECObjectsError, `The Constant TestConstant does not have the required 'definition' attribute.`); + await expect(Schema.fromJson(createSchemaJson(missingDefinition))).to.be.rejectedWith(ECObjectsError, `The Constant TestSchema.TestConstant does not have the required 'definition' attribute.`); }); it("sync - should throw for missing definition", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingDefinition)), ECObjectsError, `The Constant TestConstant does not have the required 'definition' attribute.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingDefinition)), ECObjectsError, `The Constant TestSchema.TestConstant does not have the required 'definition' attribute.`); }); // Invalid definition @@ -183,10 +183,10 @@ describe("Constant", () => { definition: 5, }; it("async - should throw for invalid definition", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidDefinition))).to.be.rejectedWith(ECObjectsError, `The Constant TestConstant has an invalid 'definition' attribute. It should be of type 'string'.`); + await expect(Schema.fromJson(createSchemaJson(invalidDefinition))).to.be.rejectedWith(ECObjectsError, `The Constant TestSchema.TestConstant has an invalid 'definition' attribute. It should be of type 'string'.`); }); it("sync - should throw for invalid definition", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDefinition)), ECObjectsError, `The Constant TestConstant has an invalid 'definition' attribute. It should be of type 'string'.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDefinition)), ECObjectsError, `The Constant TestSchema.TestConstant has an invalid 'definition' attribute. It should be of type 'string'.`); }); }); describe("toJson", () => { diff --git a/core/ecschema-metadata/test/Metadata/CustomAttributeClass.test.ts b/core/ecschema-metadata/test/Metadata/CustomAttributeClass.test.ts index 4ff7686..62ffa1d 100644 --- a/core/ecschema-metadata/test/Metadata/CustomAttributeClass.test.ts +++ b/core/ecschema-metadata/test/Metadata/CustomAttributeClass.test.ts @@ -11,10 +11,8 @@ import { ECObjectsError } from "../../src/Exception"; import { CustomAttributeClass } from "../../src/Metadata/CustomAttributeClass"; import { ECClassModifier } from "../../src/ECObjects"; import { CustomAttributeContainerType } from "../../src"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("CustomAttributeClass", () => { - let parser = new JsonParser(); describe("deserialization", () => { function createSchemaJson(caClassJson: any): any { return createSchemaJsonWithItems({ @@ -55,28 +53,11 @@ describe("CustomAttributeClass", () => { }); describe("fromJson", () => { - let testClass: CustomAttributeClass; - const baseJson = { schemaItemType: "CustomAttributeClass" }; - - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testClass = new CustomAttributeClass(schema, "TestCustomAttribute"); - }); - - it("should throw for missing appliesTo", async () => { - expect(testClass).to.exist; - assert.throws(() => parser.parseCustomAttributeClassProps({ ...baseJson }, testClass.name), ECObjectsError, `The CustomAttributeClass TestCustomAttribute is missing the required 'appliesTo' attribute.`); - }); - - it("should throw for invalid appliesTo", async () => { - expect(testClass).to.exist; - const json = { - ...baseJson, - appliesTo: 0, - }; - assert.throws(() => parser.parseCustomAttributeClassProps(json, testClass.name), ECObjectsError, `The CustomAttributeClass TestCustomAttribute has an invalid 'appliesTo' attribute. It should be of type 'string'.`); + it("TODO", () => { + // TODO: Need a test... }); }); + describe("toJson", () => { let testClass: CustomAttributeClass; @@ -96,7 +77,7 @@ describe("CustomAttributeClass", () => { appliesTo: "Schema, AnyProperty", }; - await testClass.deserialize(parser.parseCustomAttributeClassProps(schemaJson, testClass.name)); + await testClass.deserialize(schemaJson); const caJson = testClass!.toJson(true, true); assert(caJson.$schema, "https://dev.bentley.com/json_schemas/ec/31/draft-01/schemaitem"); assert(caJson.appliesTo, "Schema,AnyProperty"); @@ -117,7 +98,7 @@ describe("CustomAttributeClass", () => { appliesTo: "Schema, AnyProperty", }; - testClass.deserializeSync(parser.parseCustomAttributeClassProps(schemaJson, testClass.name)); + testClass.deserializeSync(schemaJson); const caJson = testClass!.toJson(true, true); assert(caJson.$schema, "https://dev.bentley.com/json_schemas/ec/31/draft-01/schemaitem"); assert(caJson.appliesTo, "Schema,AnyProperty"); @@ -152,7 +133,7 @@ describe("CustomAttributeClass", () => { const customAttributeClass = testCustomAttribute as CustomAttributeClass; const caSerialization = customAttributeClass!.toJson(false, true); assert.isDefined(caSerialization); - expect(caSerialization.appliesTo).eql("Schema,AnyProperty"); + expect(caSerialization.appliesTo).eql("Schema, AnyProperty"); expect(caSerialization.modifier).eql("Sealed"); }); it("sync - should succeed with fully defined without standalone", () => { @@ -180,7 +161,7 @@ describe("CustomAttributeClass", () => { const customAttributeClass = testCustomAttribute as CustomAttributeClass; const caSerialization = customAttributeClass!.toJson(false, false); assert.isDefined(caSerialization); - expect(caSerialization.appliesTo).eql("Schema,AnyProperty"); + expect(caSerialization.appliesTo).eql("Schema, AnyProperty"); expect(caSerialization.modifier).eql("Sealed"); }); }); diff --git a/core/ecschema-metadata/test/Metadata/Deserialization.test.ts b/core/ecschema-metadata/test/Metadata/Deserialization.test.ts index cef9d18..f64f653 100644 --- a/core/ecschema-metadata/test/Metadata/Deserialization.test.ts +++ b/core/ecschema-metadata/test/Metadata/Deserialization.test.ts @@ -132,10 +132,10 @@ describe("Full Schema Deserialization", () => { it("should throw for invalid references attribute", async () => { let json: any = { ...baseJson, references: 0 }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. It should be of type 'object[]'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. It should be of type 'object[]'.`); json = { ...baseJson, references: [0] }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. It should be of type 'object[]'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. It should be of type 'object[]'.`); }); it("should throw for missing reference name", async () => { @@ -143,7 +143,7 @@ describe("Full Schema Deserialization", () => { ...baseJson, references: [{ version: "1.0.5" }], }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. One of the references is missing the required 'name' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. One of the references is missing the required 'name' attribute.`); }); it("should throw for invalid reference name", async () => { @@ -151,7 +151,7 @@ describe("Full Schema Deserialization", () => { ...baseJson, references: [{ name: 0, version: "1.0.5" }], }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. One of the references has an invalid 'name' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. One of the references has an invalid 'name' attribute. It should be of type 'string'.`); }); it("should throw for missing reference version", async () => { @@ -159,7 +159,7 @@ describe("Full Schema Deserialization", () => { ...baseJson, references: [{ name: "RefSchema" }], }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. One of the references is missing the required 'version' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. One of the references is missing the required 'version' attribute.`); }); it("should throw for invalid reference version", async () => { @@ -167,7 +167,7 @@ describe("Full Schema Deserialization", () => { ...baseJson, references: [{ name: "RefSchema", version: 0 }], }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' property. One of the references has an invalid 'version' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The schema TestSchema has an invalid 'references' attribute. One of the references has an invalid 'version' attribute. It should be of type 'string'.`); }); }); @@ -267,7 +267,7 @@ describe("Full Schema Deserialization", () => { }, }; let testSchema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), undefined, mockVisitor); + const reader = new SchemaReadHelper(JsonParser, undefined, mockVisitor); testSchema = await reader.readSchema(testSchema, schemaJson); expect(testSchema).to.exist; expect(mockVisitor!.visitEmptySchema!.calledOnce).to.be.true; @@ -333,7 +333,7 @@ describe("Full Schema Deserialization", () => { }; let testSchema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), undefined, mockVisitor); + const reader = new SchemaReadHelper(JsonParser, undefined, mockVisitor); testSchema = await reader.readSchema(testSchema, schemaJson); expect(testSchema).to.exist; @@ -383,7 +383,7 @@ describe("Full Schema Deserialization", () => { }; let testSchema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), undefined, mockVisitor); + const reader = new SchemaReadHelper(JsonParser, undefined, mockVisitor); testSchema = await reader.readSchema(testSchema, schemaJson); expect(testSchema).to.exist; @@ -455,7 +455,7 @@ describe("Full Schema Deserialization", () => { }; let testSchema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), undefined, mockVisitor); + const reader = new SchemaReadHelper(JsonParser, undefined, mockVisitor); testSchema = await reader.readSchema(testSchema, schemaJson); expect(testSchema).to.exist; @@ -527,7 +527,7 @@ describe("Full Schema Deserialization", () => { }; let testSchema = new Schema(); - const reader = new SchemaReadHelper(new JsonParser(), undefined, mockVisitor); + const reader = new SchemaReadHelper(JsonParser, undefined, mockVisitor); testSchema = await reader.readSchema(testSchema, schemaJson); expect(testSchema).to.exist; diff --git a/core/ecschema-metadata/test/Metadata/EntityClass.test.ts b/core/ecschema-metadata/test/Metadata/EntityClass.test.ts index 79de997..f21da2a 100644 --- a/core/ecschema-metadata/test/Metadata/EntityClass.test.ts +++ b/core/ecschema-metadata/test/Metadata/EntityClass.test.ts @@ -14,10 +14,8 @@ import { RelationshipClass } from "./../../src/Metadata/RelationshipClass"; import { ECClassModifier } from "./../../src/ECObjects"; import { DelayedPromiseWithProps } from "./../../src/DelayedPromise"; import { ECObjectsError } from "./../../src/Exception"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("EntityClass", () => { - let parser = new JsonParser(); describe("get inherited properties", () => { let schema: Schema; @@ -322,7 +320,7 @@ describe("EntityClass", () => { it("should throw for invalid baseClass", async () => { const json = createSchemaJson({ baseClass: 0 }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECClass TestEntityClass has an invalid 'baseClass' attribute. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECClass TestSchema.TestEntityClass has an invalid 'baseClass' attribute. It should be of type 'string'.`); }); it("should throw for invalid mixins", async () => { @@ -335,7 +333,7 @@ describe("EntityClass", () => { it("should throw for invalid properties", async () => { let json: any = createSchemaJson({ properties: 0 }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECClass TestEntityClass has an invalid 'properties' attribute. It should be of type 'object[]'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECClass TestSchema.TestEntityClass has an invalid 'properties' attribute. It should be of type 'object[]'.`); json = createSchemaJson({ properties: [0], @@ -347,42 +345,42 @@ describe("EntityClass", () => { const json = createSchemaJson({ properties: [{ type: "PrimitiveProperty" }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `An ECProperty in TestSchema.TestEntityClass is missing the required 'name' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `An ECProperty in TestSchema.TestEntityClass is missing the required 'name' attribute.`); }); it("should throw for property with invalid name", async () => { const json = createSchemaJson({ properties: [{ type: "PrimitiveProperty", name: 0 }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `An ECProperty in TestSchema.TestEntityClass has an invalid 'name' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `An ECProperty in TestSchema.TestEntityClass has an invalid 'name' attribute. It should be of type 'string'.`); }); it("should throw for property with missing type", async () => { const json = createSchemaJson({ properties: [{ name: "badProp" }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp is missing the required 'type' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp does not have the required 'type' attribute.`); }); it("should throw for property with invalid type", async () => { const json = createSchemaJson({ properties: [{ name: "badProp", type: 0 }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp has an invalid 'type' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp has an invalid 'type' attribute. It should be of type 'string'.`); }); it("should throw for property with missing typeName", async () => { const json = createSchemaJson({ properties: [{ name: "badProp", type: "PrimitiveProperty" }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp is missing the required 'typeName' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp is missing the required 'typeName' attribute.`); }); it("should throw for property with invalid typeName", async () => { const json = createSchemaJson({ properties: [{ name: "badProp", type: "PrimitiveProperty", typeName: 0 }], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp has an invalid 'typeName' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The ECProperty TestSchema.TestEntityClass.badProp has an invalid 'typeName' attribute. It should be of type 'string'.`); }); it("should throw for property with invalid category", async () => { @@ -423,7 +421,7 @@ describe("EntityClass", () => { }, ], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestEntityClass.testNavProp is missing the required 'relationshipName' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestSchema.TestEntityClass.testNavProp is missing the required 'relationshipName' property.`); }); it("should throw for navigation property with invalid relationshipName", async () => { @@ -437,7 +435,7 @@ describe("EntityClass", () => { }, ], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestEntityClass.testNavProp has an invalid 'relationshipName' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestSchema.TestEntityClass.testNavProp has an invalid 'relationshipName' property. It should be of type 'string'.`); }); it("should throw for navigation property with nonexistent relationship", async () => { @@ -464,7 +462,7 @@ describe("EntityClass", () => { }, ], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestEntityClass.testNavProp is missing the required 'direction' property.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestSchema.TestEntityClass.testNavProp is missing the required 'direction' property.`); }); it("should throw for navigation property with invalid direction", async () => { @@ -478,7 +476,7 @@ describe("EntityClass", () => { }, ], }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestEntityClass.testNavProp has an invalid 'direction' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Navigation Property TestSchema.TestEntityClass.testNavProp has an invalid 'direction' property. It should be of type 'string'.`); }); }); @@ -496,26 +494,14 @@ describe("EntityClass", () => { it("should throw for invalid mixins", async () => { expect(testClass).to.exist; - let json: any = { ...baseJson, mixins: 0 }; - assert.throws(() => parser.parseEntityClassProps(json, testClass.name), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); - - json = { ...baseJson, mixins: [0] }; - assert.throws(() => parser.parseEntityClassProps(json, testClass.name), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); - - json = { ...baseJson, mixins: ["DoesNotExist"] }; - await expect(testClass.deserialize(parser.parseEntityClassProps(json, testClass.name))).to.be.rejectedWith(ECObjectsError, `Unable to find the referenced SchemaItem DoesNotExist.`); + const props = { ...baseJson, mixins: ["DoesNotExist"] }; + await expect(testClass.deserialize(props)).to.be.rejectedWith(ECObjectsError, `Unable to find the referenced SchemaItem DoesNotExist.`); }); it("should throw for invalid mixins synchronously", () => { expect(testClass).to.exist; - let json: any = { ...baseJson, mixins: 0 }; - assert.throws(() => parser.parseEntityClassProps(json, testClass.name), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); - - json = { ...baseJson, mixins: [0] }; - assert.throws(() => parser.parseEntityClassProps(json, testClass.name), ECObjectsError, `The ECEntityClass TestSchema.TestEntity has an invalid 'mixins' attribute. It should be of type 'string[]'.`); - - json = { ...baseJson, mixins: ["DoesNotExist"] }; - expect(() => testClass.deserializeSync(parser.parseEntityClassProps(json, testClass.name))).to.throw(ECObjectsError, `Unable to find the referenced SchemaItem DoesNotExist.`); + const props = { ...baseJson, mixins: ["DoesNotExist"] }; + expect(() => testClass.deserializeSync(props)).to.throw(ECObjectsError, `Unable to find the referenced SchemaItem DoesNotExist.`); }); }); describe("toJson", () => { @@ -529,7 +515,7 @@ describe("EntityClass", () => { baseClass: "TestSchema.testBaseClass", }; it("async - Simple serialization", async () => { - await testEntityClass.deserialize(parser.parseEntityClassProps(schemaJsonOne, testEntityClass.name)); + await testEntityClass.deserialize(schemaJsonOne); const serialized = testEntityClass.toJson(true, true); assert(serialized.baseClass, "TestSchema.testBaseClass"); assert(serialized.modifier, "None"); @@ -537,7 +523,7 @@ describe("EntityClass", () => { assert(serialized.name, "testClass"); }); it("sync - Simple serialization", () => { - testEntityClass.deserializeSync(parser.parseEntityClassProps(schemaJsonOne, testEntityClass.name)); + testEntityClass.deserializeSync(schemaJsonOne); const serialized = testEntityClass.toJson(true, true); assert(serialized.baseClass, "TestSchema.testBaseClass"); assert(serialized.modifier, "None"); diff --git a/core/ecschema-metadata/test/Metadata/Enumeration.test.ts b/core/ecschema-metadata/test/Metadata/Enumeration.test.ts index f63cd2a..b5656da 100644 --- a/core/ecschema-metadata/test/Metadata/Enumeration.test.ts +++ b/core/ecschema-metadata/test/Metadata/Enumeration.test.ts @@ -10,10 +10,8 @@ import { Schema } from "../../src/Metadata/Schema"; import { Enumeration, MutableEnumeration } from "./../../src/Metadata/Enumeration"; import { ECObjectsError } from "./../../src/Exception"; import { PrimitiveType } from "./../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Enumeration", () => { - let parser = new JsonParser(); describe("accept", () => { let testEnum: Enumeration; @@ -185,7 +183,7 @@ describe("Enumeration", () => { { name: "EightValue", value: 8, label: "An enumerator label" }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); assertValidEnumeration(testEnumSansPrimType); }); @@ -201,7 +199,7 @@ describe("Enumeration", () => { { name: "EightValue", value: 8, label: "An enumerator label" }, ], }; - await testEnum.deserialize(parser.parseEnumerationProps(json, testEnum.name)); + await testEnum.deserialize(json); assertValidEnumeration(testEnum); }); it(`with type="string"`, async () => { @@ -216,74 +214,11 @@ describe("Enumeration", () => { { name: "EightValue", value: "8", label: "An enumerator label" }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); assertValidEnumeration(testEnumSansPrimType); }); }); - it("should throw for missing type", async () => { - expect(testEnumSansPrimType).to.exist; - const json: any = { ...baseJson }; - assert.throws(() => parser.parseEnumerationProps(json, testEnumSansPrimType.name), ECObjectsError, `The Enumeration TestEnumeration is missing the required 'type' attribute.`); - }); - - it("should throw for invalid type", async () => { - expect(testEnum).to.exist; - const json: any = { ...baseJson, type: 0 }; - assert.throws(() => parser.parseEnumerationProps(json, testEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an invalid 'type' attribute. It should be of type 'string'.`); - }); - - it("should throw for type not int or string", async () => { - expect(testEnumSansPrimType).to.exist; - const json = { ...baseJson, type: "ThisIsNotRight" }; - assert.throws(() => parser.parseEnumerationProps(json, testEnumSansPrimType.name), ECObjectsError, `The Enumeration TestEnumeration has an invalid 'type' attribute. It should be either "int" or "string".`); - }); - - it("should throw for invalid isStrict", async () => { - expect(testEnum).to.exist; - const json: any = { ...baseJson, type: "int", isStrict: 0 }; - assert.throws(() => parser.parseEnumerationProps(json, testEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an invalid 'isStrict' attribute. It should be of type 'boolean'.`); - }); - - it("should throw for mismatched type", async () => { - expect(testEnum).to.exist; - let json: any = { - ...baseJson, - type: "string", - enumerators: [ - { - name: "testEnumerator", - value: 0, // should throw as typeof(value) !== string - }, - ], - }; - assert.throws(() => parser.parseEnumerationProps(json, testEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an incompatible type. It must be "string", not "int".`); - - expect(testStringEnum).to.exist; - json = { - ...baseJson, - type: "int", - enumerators: [ - { - name: "testEnumerator", - value: "test", // should throw as typeof(value) !== int - }, - ], - }; - assert.throws(() => parser.parseEnumerationProps(json, testStringEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an incompatible type. It must be "int", not "string".`); - }); - - it("should throw for enumerators not an array", async () => { - expect(testEnum).to.exist; - const json: any = { ...baseJson, type: "int", enumerators: 0 }; - assert.throws(() => parser.parseEnumerationProps(json, testEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); - }); - - it("should throw for enumerators not an array of objects", async () => { - expect(testEnum).to.exist; - const json: any = { ...baseJson, type: "int", enumerators: [0] }; - assert.throws(() => parser.parseEnumerationProps(json, testEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an invalid 'enumerators' attribute. It should be of type 'object[]'.`); - }); it("Duplicate name", async () => { const json = { ...baseJson, @@ -296,8 +231,9 @@ describe("Enumeration", () => { { name: "SixValue", value: 8, label: "An enumerator label" }, ], }; - await expect(testEnum.deserialize(parser.parseEnumerationProps(json, testEnum.name))).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with name 'SixValue'.`); + await expect(testEnum.deserialize(json)).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with name 'SixValue'.`); }); + it("Duplicate value", async () => { const json = { ...baseJson, @@ -310,8 +246,9 @@ describe("Enumeration", () => { { name: "EightValue", value: 6 }, ], }; - await expect(testEnum.deserialize(parser.parseEnumerationProps(json, testEnum.name))).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with value '6'.`); + await expect(testEnum.deserialize(json)).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with value '6'.`); }); + it("Basic test with number values", async () => { const json = { ...baseJson, @@ -327,13 +264,14 @@ describe("Enumeration", () => { { name: "FiveValue", value: 5, label: "Label for the fifth value", description: "description for the fifth value" }, ], }; - await testEnum.deserialize(parser.parseEnumerationProps(json, testEnum.name)); + await testEnum.deserialize(json); assertValidEnumerator(testEnum, 1, "Label for the first value", "description for the first value"); assertValidEnumerator(testEnum, 2, "Label for the second value", "description for the second value"); assertValidEnumerator(testEnum, 3, "Label for the third value", "description for the third value"); assertValidEnumerator(testEnum, 4, "Label for the fourth value", "description for the fourth value"); assertValidEnumerator(testEnum, 5, "Label for the fifth value", "description for the fifth value"); }); + it("Basic test with string values", async () => { const json = { ...baseJson, @@ -349,13 +287,14 @@ describe("Enumeration", () => { { name: "FiveValue", value: "five", label: "Label for the fifth value", description: "description for the fifth value" }, ], }; - await testStringEnum.deserialize(parser.parseEnumerationProps(json, testStringEnum.name)); + await testStringEnum.deserialize(json); assertValidEnumerator(testStringEnum, "one", "Label for the first value", "description for the first value"); assertValidEnumerator(testStringEnum, "two", "Label for the second value", "description for the second value"); assertValidEnumerator(testStringEnum, "three", "Label for the third value", "description for the third value"); assertValidEnumerator(testStringEnum, "four", "Label for the fourth value", "description for the fourth value"); assertValidEnumerator(testStringEnum, "five", "Label for the fifth value", "description for the fifth value"); }); + it("ECName comparison is case insensitive", async () => { const json = { ...baseJson, @@ -368,21 +307,9 @@ describe("Enumeration", () => { { name: "onevalue", value: "two", label: "Label for the second value", description: "description for the second value" }, ], }; - await expect(testStringEnum.deserialize(parser.parseEnumerationProps(json, testStringEnum.name))).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with name 'onevalue'.`); - }); - it("Description is not a string", async () => { - const json = { - ...baseJson, - type: "string", - isStrict: false, - label: "SomeDisplayLabel", - description: "A really long description...", - enumerators: [ - { name: "ONEVALUE", value: "one", label: "Label for the first value", description: 1 }, - ], - }; - assert.throws(() => parser.parseEnumerationProps(json, testStringEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an enumerator with an invalid 'description' attribute. It should be of type 'string'.`); + await expect(testStringEnum.deserialize(json)).to.be.rejectedWith(ECObjectsError, `The Enumeration TestEnumeration has a duplicate Enumerator with name 'onevalue'.`); }); + it("Get enumerator by name", async () => { const json = { ...baseJson, @@ -398,37 +325,12 @@ describe("Enumeration", () => { { name: "FiveValue", value: "five", label: "Label for the fifth value", description: "description for the fifth value" }, ], }; - await testStringEnum.deserialize(parser.parseEnumerationProps(json, testStringEnum.name)); + await testStringEnum.deserialize(json); expect(testStringEnum.getEnumeratorByName("OneValue")).to.exist; expect(testStringEnum.getEnumeratorByName("onevalue")!.description).to.eql("description for the first value"); expect(testStringEnum.getEnumeratorByName("fourVALUE")!.label).to.eql("Label for the fourth value"); }); - it("Name is required", async () => { - const json = { - ...baseJson, - type: "string", - isStrict: false, - label: "SomeDisplayLabel", - description: "A really long description...", - enumerators: [ - { value: "one", label: "Label for the first value", description: "Description for the first value" }, - ], - }; - assert.throws(() => parser.parseEnumerationProps(json, testStringEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an enumerator that is missing the required attribute 'name'.`); - }); - it("Value is required", async () => { - const json = { - ...baseJson, - type: "string", - isStrict: false, - label: "SomeDisplayLabel", - description: "A really long description...", - enumerators: [ - { name: "one", label: "Label for the first value", description: "Description for the first value" }, - ], - }; - assert.throws(() => parser.parseEnumerationProps(json, testStringEnum.name), ECObjectsError, `The Enumeration TestEnumeration has an enumerator that is missing the required attribute 'value'.`); - }); + it("Invalid ECName", async () => { const json = { ...baseJson, @@ -440,7 +342,7 @@ describe("Enumeration", () => { { name: "5FiveValue", value: "five", label: "Label for the fifth value", description: "description for the fifth value" }, ], }; - await expect(testStringEnum.deserialize(parser.parseEnumerationProps(json, testStringEnum.name))).to.be.rejectedWith(ECObjectsError, ``); + await expect(testStringEnum.deserialize(json)).to.be.rejectedWith(ECObjectsError, ``); }); }); describe("toJson", () => { @@ -470,7 +372,7 @@ describe("Enumeration", () => { { name: "EightValue", value: 8, label: "An enumerator label" }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); const serialization = testEnumSansPrimType.toJson(true, true); assert.isDefined(serialization); expect(serialization.type).eql("int"); @@ -494,7 +396,7 @@ describe("Enumeration", () => { { name: "EightValue", value: "eight", label: "Eight label", description: "EightValue enumerator description" }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); const serialization = testEnumSansPrimType.toJson(true, true); assert.isDefined(serialization); expect(serialization.type).eql("string"); @@ -521,7 +423,7 @@ describe("Enumeration", () => { { name: "BValue", value: "B" }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); const serialization = testEnumSansPrimType.toJson(true, true); assert.isDefined(serialization); expect(serialization.enumerators[0].value).eql("A"); @@ -541,7 +443,7 @@ describe("Enumeration", () => { { name: "FourValue", value: 4 }, ], }; - await testEnumSansPrimType.deserialize(parser.parseEnumerationProps(json, testEnumSansPrimType.name)); + await testEnumSansPrimType.deserialize(json); const serialization = testEnumSansPrimType.toJson(true, true); assert.isDefined(serialization); expect(serialization.enumerators[0].value).eql(2); diff --git a/core/ecschema-metadata/test/Metadata/Format.test.ts b/core/ecschema-metadata/test/Metadata/Format.test.ts index b5b5519..888ce25 100644 --- a/core/ecschema-metadata/test/Metadata/Format.test.ts +++ b/core/ecschema-metadata/test/Metadata/Format.test.ts @@ -5,21 +5,39 @@ import { assert, expect } from "chai"; import * as sinon from "sinon"; +import { TestSchemaLocater } from "../TestUtils/FormatTestHelper"; +import { createSchemaJsonWithItems } from "../TestUtils/DeserializationHelpers"; -import { Schema } from "./../../src/Metadata/Schema"; +import { Schema, MutableSchema } from "./../../src/Metadata/Schema"; import { Format } from "./../../src/Metadata/Format"; -import { ShowSignOption, FormatType, FormatTraits, FractionalPrecision } from "./../../src/utils/FormatEnums"; +import { ShowSignOption, FormatType, FormatTraits, DecimalPrecision } from "./../../src/utils/FormatEnums"; import { ECObjectsError } from "./../../src/Exception"; -import { Unit } from "./../../src/Metadata/Unit"; -import { schemaItemTypeToString, SchemaItemType } from "./../../src/ECObjects"; +import { FormatProps } from "../../src/Deserialization/JsonProps"; import { JsonParser } from "../../src/Deserialization/JsonParser"; +import { SchemaContext } from "../../src"; -describe("Format tests", () => { +function createSchemaJson(koq: any) { + return createSchemaJsonWithItems({ + TestFormat: { + schemaItemType: "Format", + ...koq, + }, + }, { + references: [ + { + name: "Formats", + version: "1.0.0", + }, + ], + }); +}; + +describe("Format", () => { + let schema: Schema; let testFormat: Format; - let parser = new JsonParser(); describe("accept", () => { beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); + schema = new Schema("TestSchema", 1, 0, 0); testFormat = new Format(schema, "TestFormat"); }); @@ -37,2211 +55,741 @@ describe("Format tests", () => { }); }); - describe("SchemaItemType", () => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "Test"); - it("should return correct item type and string", () => { - assert.equal(testFormat.schemaItemType, SchemaItemType.Format); - assert.equal(schemaItemTypeToString(testFormat.schemaItemType), "Format"); - }); - }); + describe("type checking json", () => { + let jsonParser: JsonParser; // This is an easy way to test the logic directly in the parser without having to go through deserialization every time. + + const rawSchema = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "TestSchema", + version: "1.2.3", + items: { + TestFormat: { + schemaItemType: "Format", + } + } + }; + + function createFormatJson(extraStuff: any): any { + return { + schemaItemType: "Format", + type: "Decimal", + ...extraStuff + } + } - describe("Async Tests without Composite", () => { beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "AmerMYFI4"); + jsonParser = new JsonParser(rawSchema); + jsonParser.findItem("TestFormat"); // Hack for testing to force the Format name cache to populated within the Parser to allow for valid error messages. }); - describe("fromJson", () => { - it("Basic test", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.name, "AmerMYFI4"); - assert(testFormat.label, "myfi4"); - assert(testFormat.description === ""); - assert(testFormat.roundFactor === 0.0); - assert(testFormat.type === FormatType.Fractional); - assert(testFormat.showSignOption === ShowSignOption.OnlyNegative); - assert(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)); - assert(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)); - assert(testFormat.hasFormatTrait(FormatTraits.ApplyRounding) === false); - assert(testFormat.precision === FractionalPrecision.Four); - assert(testFormat.decimalSeparator, "."); - assert(testFormat.thousandSeparator, ","); - assert(testFormat.uomSeparator, " "); - assert(testFormat.stationSeparator, "+"); - }); - it("Name must be a valid ECName", async () => { - const json = { - schema: "TestSchema", - schemaItemType: "Format", - name: "10AmerMYFI4", - label: "myfi4", - description: "", - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserialize(parser.parseFormatProps(json, json.name)), ECObjectsError, `The Format TestSchema.10AmerMYFI4 has an invalid 'name' attribute. '10AmerMYFI4' is not a valid ECName.`); - }); - it("Description must be a string", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: 12345678, - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testFormat.schema.name, testFormat.name), ECObjectsError, `The SchemaItem TestSchema.AmerMYFI4 has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("Round factor is not default value", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - roundFactor: 20, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.roundFactor === 20); - }); - it("Type is required", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 does not have the required 'type' attribute.`); - }); - it("Type value is invalid", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fraction", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'type' attribute.`); - }); - it("Type is fractional; Precision value is invalid", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fractional", - description: "", - showSignOption: "onlyNegative", - precision: 3, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'precision' attribute.`); - }); - it("Type is fractional; Precision value is valid", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fractional", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 16, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.precision === 16); - }); - it("Type is decimal, scientific, or station; Precision value is invalid", async () => { - const jsonDecimal = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 13, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonScientific = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 30, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStation = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: -1, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(jsonDecimal, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 contains a 'precision' attribute which must be an integer in the range 0-12.`); - await expect(testFormat.deserialize(parser.parseFormatProps(jsonScientific, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 contains a 'precision' attribute which must be an integer in the range 0-12.`); - await expect(testFormat.deserialize(parser.parseFormatProps(jsonStation, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 contains a 'precision' attribute which must be an integer in the range 0-12.`); - }); - it("Type is decimal, scientific, or station; Precision value is valid", async () => { - const jsonDecimal = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 3, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonScientific = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 0, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStation = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(jsonDecimal, testFormat.name)); - assert(testFormat.precision === 3); - await testFormat.deserialize(parser.parseFormatProps(jsonScientific, testFormat.name)); - assert(testFormat.precision === 0); - await testFormat.deserialize(parser.parseFormatProps(jsonStation, testFormat.name)); - assert(testFormat.precision === 12); - }); - it("MinWidth is not an int", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - minWidth: 3.3, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'minWidth' attribute.`); - }); - it("MinWidth is not positive", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - minWidth: -3, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'minWidth' attribute.`); - }); - it("Type is scientific; ScientificType is required", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "scientific", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has type 'Scientific' therefore attribute 'scientificType' is required.`); - }); - it("ScientificType value is not valid", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "scientific", - showSignOption: "onlyNegative", - scientificType: "normal", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'scientificType' attribute.`); - }); - it("Type is not scientific; ScientificType is provided and should be ignored", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.scientificType === undefined); - }); - it("showSignOption must be a string", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: 456, - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'showSignOption' attribute. It should be of type 'string'.`); - }); - it("showSignOption is not default value", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: "noSign", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.showSignOption === ShowSignOption.NoSign); - }); - it("showSignOption is invalid", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: "noSigned", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'showSignOption' attribute.`); - }); - it("UOMSeparator is not default", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: "-", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.uomSeparator, "-"); - }); - it("StationSeparator is not default", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - stationSeparator: "-", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.stationSeparator, "-"); - }); - it("StationOffsetSize is not an int", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - stationOffsetSize: 3.3, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); - }); - it("StationOffsetSize is not positive", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - stationOffsetSize: -3, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); - }); - it("Type is station; StationOffsetSize is required", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has type 'Station' therefore attribute 'stationOffsetSize' is required.`); - }); - it("Type is not station; StationOffsetSize is ignored", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - stationOffsetSize: 3, - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.stationOffsetSize === undefined); - }); - it("decimalSeparator, thousandSeparator, uomSeparator, stationSeparator cannot be more than one character", async () => { - const jsonDecimalSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 3, - decimalSeparator: "..", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonThousandSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 0, - decimalSeparator: ".", - thousandSeparator: ",.", - uomSeparator: " ", - }; - const jsonUOMSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStationSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - stationSeparator: "++", - }; - assert.throws(() => parser.parseFormatProps(jsonDecimalSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'decimalSeparator' attribute.`); - - assert.throws(() => parser.parseFormatProps(jsonThousandSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'thousandSeparator' attribute.`); - - assert.throws(() => parser.parseFormatProps(jsonUOMSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'uomSeparator' attribute.`); - - assert.throws(() => parser.parseFormatProps(jsonStationSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationSeparator' attribute.`); - }); + + it("check valid Format ECJSON", () => { + const correctFormat = { + type: "Decimal", + precision: 5, + roundFactor: 5, + minWidth: 5, + showSignOption: "", + formatTraits: "", + decimalSeparator: "", + thousandSeparator: "", + uomSeparator: "", + scientificType: "", + stationOffsetSize: 4, + stationSeparator: "", + composite: { + spacer: "", + includeZero: true, + units: [ + { + name: "", + label: "" + } + ] + } + } + + const formatProps = jsonParser.parseFormat(correctFormat); + assert.isDefined(formatProps); }); - describe("fromJson FormatTraits Tests", () => { - it("String with valid options", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes|keepSingleZero|zeroEmpty|keepDecimalPoint|applyRounding|fractionDash|showUnitLabel|prependUnitLabel|use1000Separator|exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); - }); - it("Valid options with multiple separators", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes;keepSingleZero|zeroEmpty|keepDecimalPoint,applyRounding|fractionDash;showUnitLabel,prependUnitLabel;use1000Separator,exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); - }); - it("Valid options with invalid separator", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes;keepSingleZero|zeroEmpty|keepDecimalPoint,applyRounding\fractionDash;showUnitLabel,prependUnitLabel;use1000Separator,exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `Format has an invalid 'formatTraits' option.`); - }); - it("String with invalid option", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZero|keepSingleZero|zeroEmpty", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `Format has an invalid 'formatTraits' option.`); - }); - it("Empty string should make formatTraits undefined", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.formatTraits === 0); - }); - it("String[] with valid options", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZeroes", - "keepSingleZero", - "zeroEmpty", - "keepDecimalPoint", - "applyRounding", - "fractionDash", - "showUnitLabel", - "prependUnitLabel", - "use1000Separator", - "exponentOnlyNegative", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); - }); - it("String[] with one valid option", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZeroes", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero) === false); - assert(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)); - assert(testFormat.hasFormatTrait(FormatTraits.ApplyRounding) === false); - }); - it("String[] with invalid option", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZero", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - await expect(testFormat.deserialize(parser.parseFormatProps(json, testFormat.name))).to.be.rejectedWith(ECObjectsError, `Format has an invalid 'formatTraits' option.`); - }); + + it("missing type attribute", () => { + const missingType = { schemaItemType: "Format" }; + assert.throws(() => jsonParser.parseFormat(missingType), ECObjectsError, `The Format TestSchema.TestFormat does not have the required 'type' attribute.`); }); - }); - describe("Sync Tests without Composite", () => { + + it("invalid type attribute", () => { + const invalidType = { schemaItemType: "Format", type: true }; + assert.throws(() => jsonParser.parseFormat(invalidType), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'type' attribute. It should be of type 'string'.`); + }); + + it("invalid precision attribute", () => { + const invalidPrecision = { precision: "" }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidPrecision)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute. It should be of type 'number'.`); + }); + + it("invalid roundFactor attribute", () => { + const invalidRoundFactor = { roundFactor: "" }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidRoundFactor)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'roundFactor' attribute. It should be of type 'number'.`); + }); + + it("invalid minWidth attribute", () => { + const invalidMinWidth = { minWidth: "" }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidMinWidth)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'minWidth' attribute. It should be of type 'number'.`); + }); + + it("invalid showSignOption attribute", () => { + const invalidShowSignOption = { showSignOption: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidShowSignOption)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'showSignOption' attribute. It should be of type 'string'.`); + }); + + it("invalid formatTraits attribute", () => { + const invalidFormatTraits = { formatTraits: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidFormatTraits)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. It should be of type 'string' or 'string[]'.`); + }); + + it("invalid decimalSeparator attribute", () => { + const invalidDecimalSeparator = { decimalSeparator: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidDecimalSeparator)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'decimalSeparator' attribute. It should be of type 'string'.`); + }); + + it("invalid thousandSeparator attribute", () => { + const invalidThousandSeparator = { thousandSeparator: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidThousandSeparator)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'thousandSeparator' attribute. It should be of type 'string'.`); + }); + + it("invalid uomSeparator attribute", () => { + const invalidUOMSeparator = { uomSeparator: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidUOMSeparator)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'uomSeparator' attribute. It should be of type 'string'.`); + }); + + it("invalid scientificType attribute", () => { + const invalidScientificType = { scientificType: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidScientificType)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'scientificType' attribute. It should be of type 'string'.`); + }); + + it("invalid stationOffsetSize attribute", () => { + const invalidStationOffsetSize = { stationOffsetSize: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidStationOffsetSize)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationOffsetSize' attribute. It should be of type 'number'.`); + }); + + it("invalid stationSeparator attribute", () => { + const invalidStationSeparator = { stationSeparator: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidStationSeparator)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationSeparator' attribute. It should be of type 'string'.`); + }); + + it("invalid composite attribute", () => { + const invalidComposite = { composite: true }; + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidComposite)), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'composite' object.`); + }); + + const invalidCompositeSpacer = { + composite: { + spacer: true + } + }; + it("invalid composite spacer attribute", () => { + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidCompositeSpacer)), ECObjectsError, `The Format TestSchema.TestFormat has a Composite with an invalid 'spacer' attribute. It should be of type 'string'.`); + }); + + const invalidCompositeIncludeZero = { + composite: { + includeZero: "" + } + }; + it("invalid composite include zero attribute", () => { + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidCompositeIncludeZero)), ECObjectsError, `The Format TestSchema.TestFormat has a Composite with an invalid 'includeZero' attribute. It should be of type 'boolean'.`); + }); + + const invalidCompositeUnits = { + composite: { + units: true + } + }; + it("invalid composite units attribute", () => { + assert.throws(() => jsonParser.parseFormat(createFormatJson(invalidCompositeUnits)), ECObjectsError, `The Format TestSchema.TestFormat has a Composite with an invalid 'units' attribute. It should be of type 'object[]'.`); + }); + }); // type checking json + + describe("deserialize formatted ECJSON", () => { beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "AmerMYFI4"); + schema = new Schema("TestSchema", 1, 0, 0); + testFormat = (schema as MutableSchema).createFormatSync("TestFormat"); }); - describe("fromJson", () => { - it("Basic test", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.name, "AmerMYFI4"); - assert(testFormat.label, "myfi4"); - assert(testFormat.description === ""); - assert(testFormat.roundFactor === 0.0); - assert(testFormat.type === FormatType.Fractional); - assert(testFormat.showSignOption === ShowSignOption.OnlyNegative); - assert(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)); - assert(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)); - assert(testFormat.hasFormatTrait(FormatTraits.ApplyRounding) === false); - assert(testFormat.precision === FractionalPrecision.Four); - assert(testFormat.decimalSeparator, "."); - assert(testFormat.thousandSeparator, ","); - assert(testFormat.uomSeparator, " "); - assert(testFormat.stationSeparator, "+"); - }); - it("Name must be a valid ECName", () => { - const json = { - schema: "TestSchema", - schemaItemType: "Format", - name: "10AmerMYFI4", - label: "myfi4", - description: "", - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, json.name)), ECObjectsError, `The Format TestSchema.10AmerMYFI4 has an invalid 'name' attribute. '10AmerMYFI4' is not a valid ECName.`); - }); - it("Description must be a string", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: 12345678, - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testFormat.schema.name, testFormat.name), ECObjectsError, `The SchemaItem TestSchema.AmerMYFI4 has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("Round factor is not default value", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - roundFactor: 20, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.roundFactor === 20); - }); - it("Type is required", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 does not have the required 'type' attribute.`); - }); - it("Type value is invalid", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fraction", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'type' attribute.`); - }); - it("Type is fractional; Precision value is invalid", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fractional", - description: "", - showSignOption: "onlyNegative", - precision: 3, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'precision' attribute.`); - }); - it("Type is fractional; Precision value is valid", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "fractional", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 16, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.precision === 16); - }); - it("Type is decimal, scientific, or station; Precision value is invalid", () => { - const jsonDecimal = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 13, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonScientific = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 30, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStation = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: -1, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(jsonDecimal, testFormat.name)), ECObjectsError, `The Format ${testFormat.name} contains a 'precision' attribute which must be an integer in the range 0-12.`); - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(jsonScientific, testFormat.name)), ECObjectsError, `The Format ${testFormat.name} contains a 'precision' attribute which must be an integer in the range 0-12.`); - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(jsonStation, testFormat.name)), ECObjectsError, `The Format ${testFormat.name} contains a 'precision' attribute which must be an integer in the range 0-12.`); - }); - it("Type is decimal, scientific, or station; Precision value is valid", () => { - const jsonDecimal = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 3, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonScientific = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 0, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStation = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(jsonDecimal, testFormat.name)); - assert(testFormat.precision === 3); - testFormat.deserializeSync(parser.parseFormatProps(jsonScientific, testFormat.name)); - assert(testFormat.precision === 0); - testFormat.deserializeSync(parser.parseFormatProps(jsonStation, testFormat.name)); - assert(testFormat.precision === 12); - }); - it("MinWidth is not an int", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - minWidth: 3.3, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'minWidth' attribute.`); - }); - it("MinWidth is not positive", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - minWidth: -3, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'minWidth' attribute.`); - }); - it("Type is scientific; ScientificType is required", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "scientific", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has type 'Scientific' therefore attribute 'scientificType' is required.`); - }); - it("ScientificType value is not valid", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "scientific", - showSignOption: "onlyNegative", - scientificType: "normal", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'scientificType' attribute.`); - }); - it("Type is not scientific; ScientificType is provided and should be ignored", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.scientificType === undefined); - }); - it("showSignOption must be a string", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: 456, - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'showSignOption' attribute. It should be of type 'string'.`); - }); - it("showSignOption is not default value", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: "noSign", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.showSignOption === ShowSignOption.NoSign); - }); - it("showSignOption is invalid", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "decimal", - showSignOption: "noSigned", - scientificType: "normalized", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has an invalid 'showSignOption' attribute.`); + + const validPropsWithoutUnits: FormatProps = { + schemaItemType: "Format", + label: "myfi4", + description: "Some tests description", + roundFactor: 15.0, // Non-default value + type: "decimal", + showSignOption: "noSign", + formatTraits: "keepSingleZero|trailZeroes", + precision: 12, + decimalSeparator: "-", + thousandSeparator: "!", + uomSeparator: "$", + }; + it("sync - valid decimal format", () => { + testFormat.deserializeSync(validPropsWithoutUnits); + expect(testFormat.label, "myfi4"); + expect(testFormat.description).eq("Some tests description"); + expect(testFormat.roundFactor).eq(15.0); + expect(testFormat.type).eq(FormatType.Decimal); + expect(testFormat.showSignOption).eq(ShowSignOption.NoSign); + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).false; + expect(testFormat.precision).eq(DecimalPrecision.Twelve); + expect(testFormat.decimalSeparator).eq("-"); + expect(testFormat.thousandSeparator).eq("!"); + expect(testFormat.uomSeparator).eq("$"); + }); + it("async - valid decimal format", async () => { + await testFormat.deserialize(validPropsWithoutUnits); + expect(testFormat.label, "myfi4"); + expect(testFormat.description).eq("Some tests description"); + expect(testFormat.roundFactor).eq(15.0); + expect(testFormat.type).eq(FormatType.Decimal); + expect(testFormat.showSignOption).eq(ShowSignOption.NoSign); + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).false; + expect(testFormat.precision).eq(DecimalPrecision.Twelve); + expect(testFormat.decimalSeparator).eq("-"); + expect(testFormat.thousandSeparator).eq("!"); + expect(testFormat.uomSeparator).eq("$"); + }); + + const invalidTypeAttributeValue: FormatProps = { + schemaItemType: "Format", + type: "BadType" + }; + it("sync - invalid type attribute value", () => { + assert.throws(() => testFormat.deserializeSync(invalidTypeAttributeValue), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'type' attribute.`); + }); + it("async - invalid type attribute value", async () => { + await expect(testFormat.deserialize(invalidTypeAttributeValue)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'type' attribute.`); + }); + + const invalidPrecisionDecimal: FormatProps = { + schemaItemType: "Format", + type: "decimal", + precision: 13, + }; + const invalidPrecisionScientific: FormatProps = { + schemaItemType: "Format", + type: "scientific", + scientificType: "normalized", + precision: 30, + }; + const invalidPrecisionStation: FormatProps = { + schemaItemType: "Format", + type: "station", + stationOffsetSize: 3, + precision: -1, + }; + it("sync - precision value is invalid with different format types", () => { + assert.throws(() => testFormat.deserializeSync(invalidPrecisionDecimal), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + assert.throws(() => testFormat.deserializeSync(invalidPrecisionScientific), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + assert.throws(() => testFormat.deserializeSync(invalidPrecisionStation), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + }); + it("async - precision value is invalid with different format types", async () => { + await expect(testFormat.deserialize(invalidPrecisionDecimal)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + await expect(testFormat.deserialize(invalidPrecisionScientific)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + await expect(testFormat.deserialize(invalidPrecisionStation)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'precision' attribute.`); + }); + + const validPrecisionDecimal: FormatProps = { + schemaItemType: "Format", + type: "decimal", + precision: 3, + }; + const validPrecisionScientific: FormatProps = { + schemaItemType: "Format", + type: "scientific", + scientificType: "normalized", + precision: 0, + }; + const validPrecisionStation: FormatProps = { + schemaItemType: "Format", + type: "station", + stationOffsetSize: 3, + precision: 12, + }; + it("sync - precision value is valid with different format types", () => { + testFormat.deserializeSync(validPrecisionDecimal); + assert(testFormat.precision === 3); + + testFormat = (schema as MutableSchema).createFormatSync("TestFormatA"); + testFormat.deserializeSync(validPrecisionScientific); + assert(testFormat.precision === 0); + + testFormat = (schema as MutableSchema).createFormatSync("TestFormatB"); + testFormat.deserializeSync(validPrecisionStation); + assert(testFormat.precision === 12); + }); + + it("async - precision value is valid with different format types", async () => { + await testFormat.deserialize(validPrecisionDecimal); + assert(testFormat.precision === 3); + + testFormat = (schema as MutableSchema).createFormatSync("TestFormatA"); + await testFormat.deserialize(validPrecisionScientific); + assert(testFormat.precision === 0); + + testFormat = (schema as MutableSchema).createFormatSync("TestFormatB"); + await testFormat.deserialize(validPrecisionStation); + assert(testFormat.precision === 12); + }); + + const invalidMinWidth: FormatProps = { + schemaItemType: "Format", + type: "Decimal", + minWidth: 5.5 + }; + it("sync - minWidth value is invalid", () => { + assert.throws(() => testFormat.deserializeSync(invalidMinWidth), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'minWidth' attribute. It should be a positive integer.`); + + invalidMinWidth.minWidth = -1; + assert.throws(() => testFormat.deserializeSync(invalidMinWidth), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'minWidth' attribute. It should be a positive integer.`); + }); + it("async - minWidth value is invalid", async () => { + invalidMinWidth.minWidth = 5.5; // TODO fix this + await expect(testFormat.deserialize(invalidMinWidth)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'minWidth' attribute. It should be a positive integer.`); + + invalidMinWidth.minWidth = -1; + await expect(testFormat.deserialize(invalidMinWidth)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'minWidth' attribute. It should be a positive integer.`); + }); + + const missingScientificType: FormatProps = { + schemaItemType: "Format", + type: "Scientific" + } + it("sync - scientific type is required when type is scientific", () => { + assert.throws(() => testFormat.deserializeSync(missingScientificType), ECObjectsError, `The Format TestSchema.TestFormat is 'Scientific' type therefore the attribute 'scientificType' is required.`); + }); + it("async - scientific type is required when type is scientific", async () => { + await expect(testFormat.deserialize(missingScientificType)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat is 'Scientific' type therefore the attribute 'scientificType' is required.`); + }); + + const invalidScientificType: FormatProps = { + schemaItemType: "Format", + type: "Scientific", + scientificType: "badType" + } + it("sync - scientific type is not supported", () => { + assert.throws(() => testFormat.deserializeSync(invalidScientificType), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'scientificType' attribute.`); + }); + it("async - scientific type is not supported", async () => { + await expect(testFormat.deserialize(invalidScientificType)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'scientificType' attribute.`); + }); + + const missingStationOffsetSize: FormatProps = { + schemaItemType: "Format", + type: "station", + }; + it("sync - stationOffsetSize is required when type is station", () => { + assert.throws(() => testFormat.deserializeSync(missingStationOffsetSize), ECObjectsError, `The Format TestSchema.TestFormat is 'Station' type therefore the attribute 'stationOffsetSize' is required.`); + }); + it("async - stationOffsetSize is required when type is station", async () => { + await expect(testFormat.deserialize(missingStationOffsetSize)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat is 'Station' type therefore the attribute 'stationOffsetSize' is required.`); + }); + + const invalidStationOffsetSize: FormatProps = { + schemaItemType: "Format", + type: "station", + stationOffsetSize: -1 + }; + it("sync - stationOffsetSize is invalid value", () => { + assert.throws(() => testFormat.deserializeSync(invalidStationOffsetSize), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); + }); + it("async - stationOffsetSize is invalid value", async () => { + await expect(testFormat.deserialize(invalidStationOffsetSize)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); + }); + + const invalidShowSignOption: FormatProps = { + schemaItemType: "Format", + type: "decimal", + showSignOption: "noSigned", + }; + it("sync - scientific type is not supported", () => { + assert.throws(() => testFormat.deserializeSync(invalidShowSignOption), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'showSignOption' attribute.`); + }); + it("async - scientific type is not supported", async () => { + await expect(testFormat.deserialize(invalidShowSignOption)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'showSignOption' attribute.`); + }); + + const invalidDecimalSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + decimalSeparator: "badSeparator" + }; + it("sync - decimal separator cannot be larger than 1 character", () => { + assert.throws(() => testFormat.deserializeSync(invalidDecimalSeparator), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'decimalSeparator' attribute.`); + }); + it("async - decimal separator cannot be larger than 1 character", async () => { + await expect(testFormat.deserialize(invalidDecimalSeparator)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'decimalSeparator' attribute.`); + }); + + const invalidThousandSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + thousandSeparator: "badSeparator" + }; + it("sync - thousand separator cannot be larger than 1 character", () => { + assert.throws(() => testFormat.deserializeSync(invalidThousandSeparator), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'thousandSeparator' attribute.`); + }); + it("async - thousand separator cannot be larger than 1 character", async () => { + await expect(testFormat.deserialize(invalidThousandSeparator)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'thousandSeparator' attribute.`); + }); + + const invalidUOMSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + uomSeparator: "badSeparator" + }; + it("sync - UOM separator cannot be larger than 1 character", () => { + assert.throws(() => testFormat.deserializeSync(invalidUOMSeparator), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'uomSeparator' attribute.`); + }); + it("async - UOM separator cannot be larger than 1 character", async () => { + await expect(testFormat.deserialize(invalidUOMSeparator)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'uomSeparator' attribute.`); + }); + + const invalidStationSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + stationSeparator: "badSeparator" + }; + it("sync - station separator cannot be larger than 1 character", () => { + assert.throws(() => testFormat.deserializeSync(invalidStationSeparator), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationSeparator' attribute.`); + }); + it("async - station separator cannot be larger than 1 character", async () => { + await expect(testFormat.deserialize(invalidStationSeparator)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'stationSeparator' attribute.`); + }); + + describe("format traits", () => { + const validEmptyFormatTraitSring: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: "" + } + it("sync - ", () => { + testFormat.deserializeSync(validEmptyFormatTraitSring); + assert.isTrue(testFormat.hasFormatTrait(0x0)); }); - it("UOMSeparator is not default", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: "-", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.uomSeparator, "-"); + it("async - ", async () => { + await testFormat.deserialize(validEmptyFormatTraitSring); + assert.isTrue(testFormat.hasFormatTrait(0x0)); }); - it("StationSeparator is not default", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - stationSeparator: "-", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.stationSeparator, "-"); + + const validFormatTraitString: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: "trailZeroes|keepSingleZero|zeroEmpty|keepDecimalPoint|applyRounding|fractionDash|showUnitLabel|prependUnitLabel|use1000Separator|exponentOnlyNegative", + }; + it("sync - all valid options defined in a string", () => { + testFormat.deserializeSync(validFormatTraitString); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepDecimalPoint)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; + expect(testFormat.hasFormatTrait(FormatTraits.FractionDash)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ShowUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.PrependUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.Use1000Separator)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ExponentOnlyNegative)).true; + }); + it("async - all valid options defined in a string", async () => { + await testFormat.deserialize(validFormatTraitString); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepDecimalPoint)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; + expect(testFormat.hasFormatTrait(FormatTraits.FractionDash)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ShowUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.PrependUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.Use1000Separator)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ExponentOnlyNegative)).true; }); - it("StationOffsetSize is not an int", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - stationOffsetSize: 3.3, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); + + // TODO: Consolidate this and above test to reduce code... + const validFormatTraitArray: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: [ + "trailZeroes", + "keepSingleZero", + "zeroEmpty", + "keepDecimalPoint", + "applyRounding", + "fractionDash", + "showUnitLabel", + "prependUnitLabel", + "use1000Separator", + "exponentOnlyNegative", + ], + }; + it("sync - all valid options defined in a array", () => { + testFormat.deserializeSync(validFormatTraitArray); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepDecimalPoint)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; + expect(testFormat.hasFormatTrait(FormatTraits.FractionDash)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ShowUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.PrependUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.Use1000Separator)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ExponentOnlyNegative)).true; + }); + it("async - all valid options defined in a array", async () => { + await testFormat.deserialize(validFormatTraitArray); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepDecimalPoint)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; + expect(testFormat.hasFormatTrait(FormatTraits.FractionDash)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ShowUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.PrependUnitLabel)).true; + expect(testFormat.hasFormatTrait(FormatTraits.Use1000Separator)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ExponentOnlyNegative)).true; }); - it("StationOffsetSize is not positive", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - stationOffsetSize: -3, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationOffsetSize' attribute. It should be a positive integer.`); + + const validFormatTraitSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: "trailZeroes;keepSingleZero|zeroEmpty,applyRounding", + }; + it("sync - valid multiple separators", () => { + testFormat.deserializeSync(validFormatTraitSeparator); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; + }); + it("async - valid multiple separators", async () => { + await testFormat.deserialize(validFormatTraitSeparator); + expect(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)).true; + expect(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ZeroEmpty)).true; + expect(testFormat.hasFormatTrait(FormatTraits.ApplyRounding)).true; }); - it("Type is station; StationOffsetSize is required", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "station", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `The Format AmerMYFI4 has type 'Station' therefore attribute 'stationOffsetSize' is required.`); + + const invalidSeparator: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: "applyRounding\fractionDash;showUnitLabel", + }; + it("sync - invalid format trait separator", () => { + assert.throws(() => testFormat.deserializeSync(invalidSeparator), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'applyRounding\fractionDash' is not a valid format trait.`); }); - it("Type is not station; StationOffsetSize is ignored", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - stationOffsetSize: 3, - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.stationOffsetSize === undefined); + it("async - invalid format trait separator", async () => { + await expect(testFormat.deserialize(invalidSeparator)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'applyRounding\fractionDash' is not a valid format trait.`); }); - it("decimalSeparator, thousandSeparator, uomSeparator, stationSeparator cannot be more than one character", () => { - const jsonDecimalSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "decimal", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 3, - decimalSeparator: "..", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonThousandSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "scientific", - scientificType: "normalized", - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 0, - decimalSeparator: ".", - thousandSeparator: ",.", - uomSeparator: " ", - }; - const jsonUOMSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - const jsonStationSeparator = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - type: "station", - stationOffsetSize: 3, - description: "", - showSignOption: "onlyNegative", - formatTraits: "keepSingleZero|trailZeroes", - precision: 12, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - stationSeparator: "++", - }; - assert.throws(() => parser.parseFormatProps(jsonDecimalSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'decimalSeparator' attribute.`); - assert.throws(() => parser.parseFormatProps(jsonThousandSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'thousandSeparator' attribute.`); - assert.throws(() => parser.parseFormatProps(jsonUOMSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'uomSeparator' attribute.`); - assert.throws(() => parser.parseFormatProps(jsonStationSeparator, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has an invalid 'stationSeparator' attribute.`); + + const invalidFormatTraitInString: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: "badTraits", + }; + it("sync - invalid format trait within a string", () => { + assert.throws(() => testFormat.deserializeSync(invalidFormatTraitInString), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'badTraits' is not a valid format trait.`); }); - }); - describe("fromJson FormatTraits Tests", () => { - it("String with valid options", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes|keepSingleZero|zeroEmpty|keepDecimalPoint|applyRounding|fractionDash|showUnitLabel|prependUnitLabel|use1000Separator|exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); + it("async - invalid format trait within a string", async () => { + await expect(testFormat.deserialize(invalidFormatTraitInString)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'badTraits' is not a valid format trait.`); }); - it("Valid options with multiple separators", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes;keepSingleZero|zeroEmpty|keepDecimalPoint,applyRounding|fractionDash;showUnitLabel,prependUnitLabel;use1000Separator,exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); + + const invalidFormatTraitInArray: FormatProps = { + schemaItemType: "Format", + type: "decimal", + formatTraits: [ + "badTraits" + ] + }; + it("sync - invalid format trait within a array", () => { + assert.throws(() => testFormat.deserializeSync(invalidFormatTraitInArray), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'badTraits' is not a valid format trait.`); }); - it("Valid options with invalid separator", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes;keepSingleZero|zeroEmpty|keepDecimalPoint,applyRounding\fractionDash;showUnitLabel,prependUnitLabel;use1000Separator,exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `Format has an invalid 'formatTraits' option.`); + it("async - invalid format trait within a array", async () => { + await expect(testFormat.deserialize(invalidFormatTraitInArray)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'formatTraits' attribute. The string 'badTraits' is not a valid format trait.`); }); - it("String with invalid option", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZero|keepSingleZero|zeroEmpty", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `Format has an invalid 'formatTraits' option.`); + }); // formatTraits + + describe("composite", () => { + let context: SchemaContext; + beforeEach(() => { + context = new SchemaContext(); + context.addLocater(new TestSchemaLocater()); }); - it("Empty string should make formatTraits undefined", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.formatTraits === 0); + + const invalidSpacer = { + type: "fractional", + composite: { + includeZero: false, + spacer: "spacer", + units: [ + { name: "Formats.MILE" } + ] + }, + }; + it("sync - spacer must be a one character string", () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidSpacer), context), ECObjectsError, `The Format TestSchema.TestFormat has a composite with an invalid 'spacer' attribute. It should be an empty or one character string.`); }); - it("String[] with valid options", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZeroes", - "keepSingleZero", - "zeroEmpty", - "keepDecimalPoint", - "applyRounding", - "fractionDash", - "showUnitLabel", - "prependUnitLabel", - "use1000Separator", - "exponentOnlyNegative", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert((testFormat!.formatTraits & 0x3FF) === testFormat!.formatTraits); + it("async - spacer must be a one character string", async () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidSpacer), context), ECObjectsError, `The Format TestSchema.TestFormat has a composite with an invalid 'spacer' attribute. It should be an empty or one character string.`); }); - it("String[] with one valid option", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZeroes", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - assert(testFormat.hasFormatTrait(FormatTraits.KeepSingleZero) === false); - assert(testFormat.hasFormatTrait(FormatTraits.TrailZeroes)); - assert(testFormat.hasFormatTrait(FormatTraits.ApplyRounding) === false); + + const invalidCompositeWithoutUnits = { + type: "fractional", + composite: {}, + }; + it("sync - invalid composite without units", () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidCompositeWithoutUnits), context), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); }); - it("String[] with invalid option", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: [ - "trailZero", - ], - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - assert.throws(() => testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)), ECObjectsError, `Format has an invalid 'formatTraits' option.`); + it("async - invalid composite without units", async () => { + await expect(Schema.fromJson(createSchemaJson(invalidCompositeWithoutUnits), context)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); }); - }); - }); - describe("Async Tests with Composite", () => { - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "AmerMYFI4"); - }); - it("Basic test", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - const ecSchema = await Schema.fromJson(testSchema); - assert.isDefined(ecSchema); - const testItem = await ecSchema.getItem("AmerMYFI4"); - assert.isDefined(testItem); - assert.isTrue(testItem instanceof Format); - const formatTest: Format = testItem as Format; - assert.isDefined(formatTest); - expect(formatTest.type === FormatType.Fractional); - const testUnitItem = await ecSchema.getItem("MILE"); - assert.isDefined(testUnitItem); - const unitTest: Unit = testUnitItem as Unit; - assert(unitTest!.name, "MILE"); - }); - it("Throw for Composite with missing units attribute", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - await expect(Schema.fromJson(testSchema)).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - it("Throw for Composite with empty units array", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - await expect(Schema.fromJson(testSchema)).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - it("includeZero must be boolean", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: "false", - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - await expect(Schema.fromJson(testSchema)).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'includeZero' attribute. It should be of type 'boolean'.`); - }); - it("spacer must be a one character string", async () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", + const invalidCompositeEmptyUnits = { type: "fractional", - precision: 4, composite: { - includeZero: false, - spacer: "space", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], + units: [] }, }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'spacer' attribute.`); - }); - it("spacer must be a string", async () => { - const json = { - includeZero: false, - schemaItemType: "Format", - name: "AmerMYFI4", + it("sync - invalid composite without units", () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidCompositeEmptyUnits), context), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); + }); + it("async - invalid composite without units", async () => { + await expect(Schema.fromJson(createSchemaJson(invalidCompositeEmptyUnits), context)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); + }); + + const invalidCompositeTooManyUnits = { type: "fractional", - precision: 4, composite: { - includeZero: false, - spacer: 1, units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], + { name: "Formats.MILE" }, + { name: "Formats.YRD" }, + { name: "Formats.FT" }, + { name: "Formats.IN" }, + { name: "Formats.MILLIINCH" } + ] }, }; - assert.throws(() => parser.parseFormatProps(json, testFormat.name), ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'spacer' attribute.`); - }); - it("Unit names must be unique", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - { - name: "TestSchema.MILE", - label: "yrd(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - await expect(Schema.fromJson(testSchema)).to.be.rejectedWith(ECObjectsError, `The unit MILE has a duplicate name.`); + it("sync - invalid composite with too many units", () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidCompositeTooManyUnits), context), ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); + }); + it("async - invalid composite with too many units", async () => { + await expect(Schema.fromJson(createSchemaJson(invalidCompositeTooManyUnits), context)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has an invalid 'Composite' attribute. It should have 1-4 units.`); + }); - }); - it("Cannot have more than 4 units", async () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "MILE", - label: "mile(s)", - }, - { - name: "YRD", - label: "yrd(s)", - }, - { - name: "FT", - label: "'", - }, - { - name: "IN", - label: "\"", - }, - { - name: "METER", - label: "meter(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, + const invalidCompositeDuplicateUnits = { + type: "fractional", + composite: { + units: [ + { name: "Formats.MILE" }, + { name: "Formats.MILE" } + ] + } }; - await expect(Schema.fromJson(testSchema)).to.be.rejectedWith(ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - }); - describe("Sync Tests with Composite", () => { - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "AmerMYFI4"); - }); + it("sync - invalid composite with duplicate units", () => { + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidCompositeDuplicateUnits), context), ECObjectsError, `The Format TestSchema.TestFormat has duplicate units, 'Formats.MILE'.`); + }); + it("async - invalid composite with duplicate units", async () => { + await expect(Schema.fromJson(createSchemaJson(invalidCompositeDuplicateUnits), context)).to.be.rejectedWith(ECObjectsError, `The Format TestSchema.TestFormat has duplicate units, 'Formats.MILE'.`); + }); - it("Basic test", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - const ecSchema = Schema.fromJsonSync(testSchema); - assert.isDefined(ecSchema); - const testItem = ecSchema.getItemSync("AmerMYFI4"); - assert.isDefined(testItem); - assert.isTrue(testItem instanceof Format); - const formatTest: Format = testItem as Format; - assert.isDefined(formatTest); - expect(formatTest.type === FormatType.Fractional); - const testUnitItem = ecSchema.getItemSync("MILE"); - assert.isDefined(testUnitItem); - const unitTest: Unit = testUnitItem as Unit; - assert(unitTest!.name, "MILE"); - }); - it("Throw for Composite with missing units attribute", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - it("Throw for Composite with empty units array", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - it("includeZero must be boolean", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: "false", - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], + const validComposite = { + type: "decimal", + composite: { + includeZero: false, + spacer: "-", + units: [ + { + name: "Formats.MILE", + label: "mile(s)" }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'includeZero' attribute. It should be of type 'boolean'.`); - }); - it("spacer must be a one character string", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "spacer", + { + name: "Formats.YRD", + label: "yrd(s)" }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'spacer' attribute.`); - }); - it("spacer must be a string", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: 8, + { + name: "Formats.FT", + label: "'" }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has a Composite with an invalid 'spacer' attribute.`); - }); - it("Unit names must be unique", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - { - name: "TestSchema.MILE", - label: "yrd(s)", - }, - ], + { + name: "Formats.IN", + label: "\"" }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, + ] + } }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The unit MILE has a duplicate name.`); + function validateTestFormat(testFormat: Format | undefined) { + assert.isDefined(testFormat); + + expect(testFormat!.includeZero).false; + expect(testFormat!.spacer).eq("-"); + + assert.isDefined(testFormat!.units); + expect(testFormat!.units!.length).eq(4); + expect(testFormat!.units![0][0].fullName).eq("Formats.MILE"); + expect(testFormat!.units![0][1]).eq("mile(s)"); + + expect(testFormat!.units![1][0].fullName).eq("Formats.YRD"); + expect(testFormat!.units![1][1]).eq("yrd(s)"); + + expect(testFormat!.units![2][0].fullName).eq("Formats.FT"); + expect(testFormat!.units![2][1]).eq("'"); + + expect(testFormat!.units![3][0].fullName).eq("Formats.IN"); + expect(testFormat!.units![3][1]).eq("\""); + } + it("sync - ", () => { + const schema = Schema.fromJsonSync(createSchemaJson(validComposite), context) + assert.isDefined(schema); + const testFormat = schema.getItemSync("TestFormat"); + validateTestFormat(testFormat); + }); + it("async - ", async () => { + const schema = await Schema.fromJson(createSchemaJson(validComposite), context) + assert.isDefined(schema); + const testFormat = await schema.getItem("TestFormat"); + validateTestFormat(testFormat); + }); + }); // composite + + }); // deserialize properly formatted ECJSON - }); - it("Cannot have more than 4 units", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "MILE", - label: "mile(s)", - }, - { - name: "YRD", - label: "yrd(s)", - }, - { - name: "FT", - label: "'", - }, - { - name: "IN", - label: "\"", - }, - { - name: "METER", - label: "meter(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(testSchema), ECObjectsError, `The Format AmerMYFI4 has an invalid 'Composite' attribute. It must have 1-4 units.`); - }); - }); describe("toJson", () => { + let context: SchemaContext; beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testFormat = new Format(schema, "AmerMYFI4"); + context = new SchemaContext(); + context.addLocater(new TestSchemaLocater()); }); it("Basic test I", () => { - const testSchema = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - AmerMYFI4: { - schemaItemType: "Format", - type: "fractional", - precision: 4, - composite: { - includeZero: false, - spacer: "-", - units: [ - { - name: "TestSchema.MILE", - label: "mile(s)", - }, - ], - }, - }, - Length: { - schemaItemType: "Phenomenon", - definition: "LENGTH(1)", - }, - Imperial: { - schemaItemType: "UnitSystem", - }, - MILE: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.Imperial", - definition: "mile", - }, - }, - }; - const ecSchema = Schema.fromJsonSync(testSchema); - assert.isDefined(ecSchema); - const testItem = ecSchema.getItemSync("AmerMYFI4"); - assert.isDefined(testItem); - assert.isTrue(testItem instanceof Format); - const formatTest: Format = testItem as Format; - assert.isDefined(formatTest); - const formatSerialization = formatTest.toJson(false, true); - assert.isDefined(formatSerialization); - expect(formatSerialization.type).equals("Fractional"); - expect(formatSerialization.precision).equals(4); - expect(formatSerialization.decimalSeparator).equals("."); - expect(formatSerialization.roundFactor).equals(0); - expect(formatSerialization.showSignOption).equals("OnlyNegative"); - expect(formatSerialization.stationSeparator).equals("+"); - expect(formatSerialization.thousandSeparator).equals(","); - expect(formatSerialization.uomSeparator).equals(" "); - expect(formatSerialization.composite.includeZero).equals(false); - expect(formatSerialization.composite.spacer).equals("-"); - expect(formatSerialization.composite.units).to.deep.equal([{ name: "MILE", label: "mile(s)" }]); - - }); - it("Basic test with formatTraits", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - formatTraits: "keepSingleZero|trailZeroes", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - const formatSerialization = testFormat.toJson(false, true); - assert.isDefined(formatSerialization); - assert(formatSerialization.formatTraits.indexOf("KeepSingleZero") !== -1); - assert(formatSerialization.formatTraits.indexOf("TrailZeroes") !== -1); - }); - it("String with valid options", () => { - const json = { - schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "trailZeroes|keepSingleZero|zeroEmpty|keepDecimalPoint|applyRounding|fractionDash|showUnitLabel|prependUnitLabel|use1000Separator|exponentOnlyNegative", - precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", - }; - testFormat.deserializeSync(parser.parseFormatProps(json, testFormat.name)); - const formatSerialization = testFormat.toJson(false, true); - assert.isDefined(formatSerialization); - assert(formatSerialization.formatTraits.indexOf("TrailZeroes") !== -1); - assert(formatSerialization.formatTraits.indexOf("KeepSingleZero") !== -1); - assert(formatSerialization.formatTraits.indexOf("ZeroEmpty") !== -1); - assert(formatSerialization.formatTraits.indexOf("KeepDecimalPoint") !== -1); - assert(formatSerialization.formatTraits.indexOf("ApplyRounding") !== -1); - assert(formatSerialization.formatTraits.indexOf("FractionDash") !== -1); - assert(formatSerialization.formatTraits.indexOf("ShowUnitLabel") !== -1); - assert(formatSerialization.formatTraits.indexOf("PrependUnitLabel") !== -1); - assert(formatSerialization.formatTraits.indexOf("Use1000Separator") !== -1); - assert(formatSerialization.formatTraits.indexOf("ExponentOnlyNegative") !== -1); - }); - it("Empty string should make formatTraits undefined", () => { - const json = { + const testFormatJson = { schemaItemType: "Format", - name: "AmerMYFI4", - label: "myfi4", - description: "", - roundFactor: 0.0, - type: "fractional", - showSignOption: "onlyNegative", - formatTraits: "", + type: "Fractional", precision: 4, - decimalSeparator: ".", - thousandSeparator: ",", - uomSeparator: " ", + composite: { + includeZero: false, + spacer: "-", + units: [ + { + name: "Formats.MILE", + label: "mile(s)", + }, + ], + } }; - testFormat.deserialize(parser.parseFormatProps(json, testFormat.name)); - const formatSerialization = testFormat.toJson(false, true); - assert.isDefined(formatSerialization); - assert(formatSerialization.formatTraits.length === 0); + const ecSchema = Schema.fromJsonSync(createSchemaJson(testFormatJson), context); + assert.isDefined(ecSchema); + const testFormat = ecSchema.getItemSync("TestFormat"); + assert.isDefined(testFormat); + const formatSerialization = testFormat!.toJson(false, true); + expect(formatSerialization).to.deep.equal(testFormatJson); }); - }); + }); // toJson }); diff --git a/core/ecschema-metadata/test/Metadata/InvertedUnit.test.ts b/core/ecschema-metadata/test/Metadata/InvertedUnit.test.ts index e645837..363abdd 100644 --- a/core/ecschema-metadata/test/Metadata/InvertedUnit.test.ts +++ b/core/ecschema-metadata/test/Metadata/InvertedUnit.test.ts @@ -12,11 +12,9 @@ import { ECObjectsError } from "../../src/Exception"; import { UnitSystem } from "../../src/Metadata/UnitSystem"; import { Unit } from "../../src/Metadata/Unit"; import { schemaItemTypeToString, SchemaItemType } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Inverted Unit tests", () => { let testUnit: InvertedUnit; - let parser = new JsonParser(); describe("accept", () => { beforeEach(() => { const schema = new Schema("TestSchema", 1, 0, 0); @@ -129,41 +127,6 @@ describe("Inverted Unit tests", () => { assert(testInvertedUnit.unitSystem, "TestSchema.INTERNATIONAL"); assert(testInvertedUnit.invertsUnit, "TestSchema.VERTICAL_PER_HORIZONTAL"); }); - it("Label must be string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: 5, - description: "A unit representing run over rise", - unitSystem: "ExampleSchema.INTERNATIONAL", - invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnit.schema.name, testUnit.name), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'label' attribute. It should be of type 'string'.`); - }); - it("Description must be string", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: "Horizontal/Vertical", - description: 5, - unitSystem: "ExampleSchema.INTERNATIONAL", - invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnit.schema.name, testUnit.name), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("invertsUnit is required", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: "Horizontal/Vertical", - description: "A unit representing run over rise", - unitSystem: "ExampleSchema.INTERNATIONAL", - }; - assert.throws(() => parser.parseInvertedUnitProps(json, testUnit.name), ECObjectsError, `The InvertedUnit HORIZONTAL_PER_VERTICAL does not have the required 'invertsUnit' attribute.`); - }); it("unitSystem is required", async () => { const json = { $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", @@ -192,7 +155,7 @@ describe("Inverted Unit tests", () => { }, }, }; - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The InvertedUnit HORIZONTAL_PER_VERTICAL does not have the required 'unitSystem' attribute.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The InvertedUnit TestSchema.HORIZONTAL_PER_VERTICAL does not have the required 'unitSystem' attribute.`); }); it("Resolve all dependencies for inverts unit and unit system", async () => { const json = { @@ -329,71 +292,6 @@ describe("Inverted Unit tests", () => { assert(testInvertedUnit.unitSystem, "TestSchema.INTERNATIONAL"); assert(testInvertedUnit.invertsUnit, "TestSchema.VERTICAL_PER_HORIZONTAL"); }); - it("Label must be string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: 5, - description: "A unit representing run over rise", - unitSystem: "ExampleSchema.INTERNATIONAL", - invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnit.schema.name, testUnit.name), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'label' attribute. It should be of type 'string'.`); - }); - it("Description must be string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: "Horizontal/Vertical", - description: 5, - unitSystem: "ExampleSchema.INTERNATIONAL", - invertsUnit: "ExampleSchema.VERTICAL_PER_HORIZONTAL", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnit.schema.name, testUnit.name), ECObjectsError, `The SchemaItem TestSchema.HORIZONTAL_PER_VERTICAL has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("invertsUnit is required", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "InvertedUnit", - name: "HORIZONTAL_PER_VERTICAL", - label: "Horizontal/Vertical", - description: "A unit representing run over rise", - unitSystem: "ExampleSchema.INTERNATIONAL", - }; - assert.throws(() => parser.parseInvertedUnitProps(json, testUnit.name), ECObjectsError, `The InvertedUnit HORIZONTAL_PER_VERTICAL does not have the required 'invertsUnit' attribute.`); - }); - it("unitSystem is required", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - version: "1.0.0", - name: "TestSchema", - items: { - HORIZONTAL_PER_VERTICAL: { - schemaItemType: "InvertedUnit", - invertsUnit: "TestSchema.VERTICAL_PER_HORIZONTAL", - label: "Horizontal/Vertical", - }, - INTERNATIONAL: { - schemaItemType: "UnitSystem", - label: "Imperial", - description: "Units of measure from the british imperial empire", - }, - VERTICAL_PER_HORIZONTAL: { - schemaItemType: "Unit", - phenomenon: "TestSchema.Length", - unitSystem: "TestSchema.INTERNATIONAL", - definition: "Vert/Horizontal", - }, - Length: { - schemaItemType: "Phenomenon", - definition: "TestSchema.Length", - }, - }, - }; - assert.throws(() => Schema.fromJsonSync(json), ECObjectsError, `The InvertedUnit HORIZONTAL_PER_VERTICAL does not have the required 'unitSystem' attribute.`); - }); it("Resolve all dependencies for inverts unit and unit system", async () => { const json = { $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", diff --git a/core/ecschema-metadata/test/Metadata/KindOfQuantity.test.ts b/core/ecschema-metadata/test/Metadata/KindOfQuantity.test.ts index 47e8a66..aab52ad 100644 --- a/core/ecschema-metadata/test/Metadata/KindOfQuantity.test.ts +++ b/core/ecschema-metadata/test/Metadata/KindOfQuantity.test.ts @@ -14,8 +14,6 @@ import { Schema } from "../../src/Metadata/Schema"; import { Format } from "../../src/Metadata/Format"; import { SchemaContext } from "../../src/Context"; import { DecimalPrecision } from "../../src/utils/FormatEnums"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; -import { KindOfQuantityProps } from "../../src/Deserialization/JsonProps"; import { createSchemaJsonWithItems } from "../TestUtils/DeserializationHelpers"; import { TestSchemaLocater } from "../TestUtils/FormatTestHelper"; @@ -36,7 +34,6 @@ function createSchemaJson(koq: any) { } describe("KindOfQuantity", () => { - let parser = new JsonParser(); const baseJson = { schemaItemType: "KindOfQuantity", name: "TestKindOfQuantity", @@ -75,7 +72,7 @@ describe("KindOfQuantity", () => { context.addLocater(new TestSchemaLocater()); }); it("should successfully deserialize valid JSON", async () => { - const koqJson = { + const koqProps = { ...baseJson, relativeError: 1.234, persistenceUnit: "Formats.DefaultReal", @@ -84,9 +81,8 @@ describe("KindOfQuantity", () => { "Formats.DefaultReal", ], }; - const koqProps: KindOfQuantityProps = parser.parseKindOfQuantityProps(koqJson, koqJson.name); schema = await Schema.fromJson(createSchemaJson(koqProps), context); - const testKoq = await schema.getItem(koqJson.name); + const testKoq = await schema.getItem(koqProps.name); expect(testKoq!).to.exist; expect(testKoq!.name).to.eql("TestKindOfQuantity"); @@ -124,41 +120,6 @@ describe("KindOfQuantity", () => { expect(testKoq!.persistenceUnit!.fullName).to.eql(testUnit!.key.fullName); // Formats.IN === Formats.IN }); - async function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { - const json: any = { - ...baseJson, - relativeError: 0, - presentationUnits: ["Formats.CM"], - persistenceUnit: "Formats.DefaultReal", - [attributeName]: value, // will overwrite previously defined objects - }; - assert.throws(() => parser.parseKindOfQuantityProps(json, json.name), ECObjectsError, `The KindOfQuantity TestKindOfQuantity has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); - } - - it("should throw for invalid relativeError", () => { testInvalidAttribute("relativeError", "number", false); }); - it("should throw for invalid presentationUnits", () => { testInvalidAttribute("presentationUnits", `string' or 'string[]`, false); }); - it("should throw for invalid persistenceUnit", () => { testInvalidAttribute("persistenceUnit", "string", false); }); - - // should throw for missing relativeError - const missingRelativeError = { - ...baseJson, - presentationUnits: ["Formats.IN"], - persistenceUnit: "Formats.IN", - }; - it("should throw for missing relativeError", () => { - assert.throws(() => parser.parseKindOfQuantityProps(missingRelativeError, missingRelativeError.name), ECObjectsError, `The KindOfQuantity TestKindOfQuantity is missing the required 'relativeError' attribute.`); - }); - - // should throw for missing persistenceUnit - const missingPersistenceUnit = { - ...baseJson, - relativeError: 1.234, - presentationUnits: ["Formats.IN"], - }; - it("should throw for missing persistenceUnit", () => { - assert.throws(() => parser.parseKindOfQuantityProps(missingPersistenceUnit, missingPersistenceUnit.name), ECObjectsError, `The KindOfQuantity TestKindOfQuantity is missing the required 'persistenceUnit' attribute.`); - }); - // should throw for presentationUnit with non-existent format const presentationUnitsNonExistentFormat = { ...baseJson, @@ -169,10 +130,10 @@ describe("KindOfQuantity", () => { ], }; it("async - should throw for presentationUnit having a non-existent format", async () => { - await expect(Schema.fromJson(createSchemaJson(presentationUnitsNonExistentFormat), context)).to.be.rejectedWith(ECObjectsError, `Unable to locate format 'TestSchema.NonexistentFormat' for the presentation unit on KindOfQuantity TestSchema.1.2.3.TestKindOfQuantity.`); + await expect(Schema.fromJson(createSchemaJson(presentationUnitsNonExistentFormat), context)).to.be.rejectedWith(ECObjectsError, `Unable to locate format 'TestSchema.NonexistentFormat' for the presentation unit on KindOfQuantity TestSchema.TestKindOfQuantity.`); }); it("sync - should throw for presentationUnit having a non-existent format", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(presentationUnitsNonExistentFormat), context), ECObjectsError, `Unable to locate format 'TestSchema.NonexistentFormat' for the presentation unit on KindOfQuantity TestSchema.1.2.3.TestKindOfQuantity.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(presentationUnitsNonExistentFormat), context), ECObjectsError, `Unable to locate format 'TestSchema.NonexistentFormat' for the presentation unit on KindOfQuantity TestSchema.TestKindOfQuantity.`); }); // should throw for persistenceUnit with non-existent format diff --git a/core/ecschema-metadata/test/Metadata/Mixin.test.ts b/core/ecschema-metadata/test/Metadata/Mixin.test.ts index eae20e0..e7ad64f 100644 --- a/core/ecschema-metadata/test/Metadata/Mixin.test.ts +++ b/core/ecschema-metadata/test/Metadata/Mixin.test.ts @@ -12,10 +12,8 @@ import { Mixin } from "../../src/Metadata/Mixin"; import { ECObjectsError } from "../../src/Exception"; import { NavigationProperty } from "../../src/Metadata/Property"; import { StrengthDirection } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Mixin", () => { - let parser = new JsonParser(); describe("deserialization", () => { function createSchemaJson(mixinJson: any): any { return createSchemaJsonWithItems({ @@ -131,7 +129,7 @@ describe("Mixin", () => { const json = createSchemaJson({ appliesTo: 0, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Mixin TestMixin has an invalid 'appliesTo' property. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Mixin TestSchema.TestMixin has an invalid 'appliesTo' attribute. It should be of type 'string'.`); }); }); @@ -152,23 +150,14 @@ describe("Mixin", () => { appliesTo: "TestSchema.TestEntity", }; expect(testMixin).to.exist; - await testMixin.deserialize(parser.parseMixinProps(json, testMixin.name)); - + await testMixin.deserialize(json); expect(await testMixin.appliesTo).to.eql(testEntity); }); - it("should throw for missing appliesTo", async () => { - expect(testMixin).to.exist; - assert.throws(() => parser.parseMixinProps({ ...baseJson }, testMixin.name), ECObjectsError, `The Mixin TestMixin is missing the required 'appliesTo' attribute.`); - }); - it("should throw for invalid appliesTo", async () => { expect(testMixin).to.exist; - const invalidAppliesToJson = { ...baseJson, appliesTo: 0 }; - assert.throws(() => parser.parseMixinProps(invalidAppliesToJson, testMixin.name), ECObjectsError, `The Mixin TestMixin has an invalid 'appliesTo' property. It should be of type 'string'.`); - const unloadedAppliesToJson = { ...baseJson, appliesTo: "ThisClassDoesNotExist" }; - await expect(testMixin.deserialize(parser.parseMixinProps(unloadedAppliesToJson, testMixin.name))).to.be.rejectedWith(ECObjectsError); + await expect(testMixin.deserialize(unloadedAppliesToJson)).to.be.rejectedWith(ECObjectsError); }); }); describe("Sync fromJson", () => { @@ -188,23 +177,14 @@ describe("Mixin", () => { appliesTo: "TestSchema.TestEntity", }; expect(testMixin).to.exist; - testMixin.deserializeSync(parser.parseMixinProps(json, testMixin.name)); + testMixin.deserializeSync(json); expect(await testMixin.appliesTo).to.eql(testEntity); }); - - it("should throw for missing appliesTo", async () => { - expect(testMixin).to.exist; - assert.throws(() => parser.parseMixinProps({ ...baseJson }, testMixin.name), ECObjectsError, `The Mixin TestMixin is missing the required 'appliesTo' attribute.`); - }); - it("should throw for invalid appliesTo", async () => { expect(testMixin).to.exist; - const invalidAppliesToJson = { ...baseJson, appliesTo: 0 }; - assert.throws(() => parser.parseMixinProps(invalidAppliesToJson, testMixin.name), ECObjectsError, `The Mixin TestMixin has an invalid 'appliesTo' property. It should be of type 'string'.`); - - const unloadedAppliesToJson = { ...baseJson, appliesTo: "ThisClassDoesNotExist" }; - assert.throws(() => testMixin.deserializeSync(parser.parseMixinProps(unloadedAppliesToJson, testMixin.name)), ECObjectsError); + const json = { ...baseJson, appliesTo: "ThisClassDoesNotExist" }; + assert.throws(() => testMixin.deserializeSync(json), ECObjectsError); }); }); }); diff --git a/core/ecschema-metadata/test/Metadata/Phenomenon.test.ts b/core/ecschema-metadata/test/Metadata/Phenomenon.test.ts index b8c1970..7ed8fc2 100644 --- a/core/ecschema-metadata/test/Metadata/Phenomenon.test.ts +++ b/core/ecschema-metadata/test/Metadata/Phenomenon.test.ts @@ -6,12 +6,9 @@ import { assert, expect } from "chai"; import { Schema } from "../../src/Metadata/Schema"; import { Phenomenon } from "../../src/Metadata/Phenomenon"; -import { ECObjectsError } from "../../src/Exception"; import * as sinon from "sinon"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Phenomenon tests", () => { - let parser = new JsonParser(); let testPhenomenon: Phenomenon; describe("accept", () => { beforeEach(() => { @@ -45,61 +42,10 @@ describe("Phenomenon tests", () => { label: "Area", definition: "Units.LENGTH(2)", }; - await testPhenomenon.deserialize(parser.parsePhenomenonProps(json, testPhenomenon.name)); + await testPhenomenon.deserialize(json); assert(testPhenomenon.label, "Area"); assert(testPhenomenon.definition, "Units.LENGTH(2)"); }); - it("Name must be a valid ECName", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schema: "TestSchema", - schemaItemType: "Phenomenon", - name: "12AREA", - definition: "Units.LENGTH(2)", - }; - assert.throws(() => testPhenomenon.deserialize(parser.parsePhenomenonProps(json, json.name)), ECObjectsError, `The Phenomenon TestSchema.12AREA has an invalid 'name' attribute. '12AREA' is not a valid ECName.`); - }); - it("Label must be a string", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: 48, - definition: "Units.LENGTH(2)", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testPhenomenon.schema.name, testPhenomenon.name), ECObjectsError, `The SchemaItem ExampleSchema.AREA has an invalid 'label' attribute. It should be of type 'string'.`); - - }); - it("Description must be a string", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - description: 5, - definition: "Units.LENGTH(2)", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testPhenomenon.schema.name, testPhenomenon.name), ECObjectsError, `The SchemaItem ExampleSchema.AREA has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("Definition is required", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - }; - assert.throws(() => parser.parsePhenomenonProps(json, testPhenomenon.name), ECObjectsError, `The Phenomenon AREA does not have the required 'definition' attribute.`); - }); - it("Definition must be a string", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - definition: 2, - }; - assert.throws(() => parser.parsePhenomenonProps(json, testPhenomenon.name), ECObjectsError, `The Phenomenon AREA has an invalid 'definition' attribute. It should be of type 'string'.`); - }); }); describe("Sync fromJson", () => { beforeEach(() => { @@ -114,61 +60,10 @@ describe("Phenomenon tests", () => { label: "Area", definition: "Units.LENGTH(2)", }; - testPhenomenon.deserializeSync(parser.parsePhenomenonProps(json, testPhenomenon.name)) + testPhenomenon.deserializeSync(json) assert(testPhenomenon.label, "Area"); assert(testPhenomenon.definition, "Units.LENGTH(2)"); }); - it("Name must be a valid ECName", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schema: "TestSchema", - schemaItemType: "Phenomenon", - name: "12AREA", - definition: "Units.LENGTH(2)", - }; - assert.throws(() => testPhenomenon.deserialize(parser.parsePhenomenonProps(json, json.name)), ECObjectsError, `The Phenomenon TestSchema.12AREA has an invalid 'name' attribute. '12AREA' is not a valid ECName.`); - }); - it("Label must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: 48, - definition: "Units.LENGTH(2)", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testPhenomenon.schema.name, testPhenomenon.name), ECObjectsError, `The SchemaItem ExampleSchema.AREA has an invalid 'label' attribute. It should be of type 'string'.`); - - }); - it("Description must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - description: 5, - definition: "Units.LENGTH(2)", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testPhenomenon.schema.name, testPhenomenon.name), ECObjectsError, `The SchemaItem ExampleSchema.AREA has an invalid 'description' attribute. It should be of type 'string'.`); - }); - it("Definition is required", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - }; - assert.throws(() => parser.parsePhenomenonProps(json, testPhenomenon.name), ECObjectsError, `The Phenomenon AREA does not have the required 'definition' attribute.`); - }); - it("Definition must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "Phenomenon", - name: "AREA", - label: "Area", - definition: 2, - }; - assert.throws(() => parser.parsePhenomenonProps(json, testPhenomenon.name), ECObjectsError, `The Phenomenon AREA has an invalid 'definition' attribute. It should be of type 'string'.`); - }); }); describe("toJson", () => { beforeEach(() => { @@ -182,7 +77,7 @@ describe("Phenomenon tests", () => { name: "AREA", definition: "Units.LENGTH(2)", }; - await testPhenomenon.deserialize(parser.parsePhenomenonProps(json, testPhenomenon.name)); + await testPhenomenon.deserialize(json); const phenomSerialization = testPhenomenon.toJson(true, true); assert(phenomSerialization.definition, "Units.LENGTH(2)"); }); @@ -193,7 +88,7 @@ describe("Phenomenon tests", () => { name: "AREA", definition: "Units.LENGTH(2)", }; - testPhenomenon.deserializeSync(parser.parsePhenomenonProps(json, testPhenomenon.name)); + testPhenomenon.deserializeSync(json); const phenomSerialization = testPhenomenon.toJson(true, true); assert(phenomSerialization.definition, "Units.LENGTH(2)"); }); diff --git a/core/ecschema-metadata/test/Metadata/Property.test.ts b/core/ecschema-metadata/test/Metadata/Property.test.ts index 78a6fcf..2149f53 100644 --- a/core/ecschema-metadata/test/Metadata/Property.test.ts +++ b/core/ecschema-metadata/test/Metadata/Property.test.ts @@ -20,10 +20,8 @@ import { KindOfQuantity } from "../../src/Metadata/KindOfQuantity"; import { RelationshipClass } from "../../src/Metadata/RelationshipClass"; import { DelayedPromiseWithProps } from "../../src/DelayedPromise"; import { PrimitiveType } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Property", () => { - let parser = new JsonParser(); let testClass: EntityClass; let testCategory: PropertyCategory; let testKindOfQuantity: KindOfQuantity; @@ -132,7 +130,7 @@ describe("Property", () => { }; const testProp = new MockProperty("TestProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); expect(testProp.name).to.eql("TestProp"); expect(testProp.label).to.eql("SomeDisplayLabel"); expect(testProp.description).to.eql("A really long description..."); @@ -155,7 +153,7 @@ describe("Property", () => { const testProp = new MockProperty("TestProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(oneCustomAttributeJson, testClass.name, testProp.name)); + await testProp.deserialize(oneCustomAttributeJson); expect(testProp.name).to.eql("TestProp"); expect(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; assert(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"].ExampleAttribute === 1234); @@ -163,7 +161,7 @@ describe("Property", () => { it("sync - Deserialize One Custom Attribute", () => { const testProp = new MockProperty("TestProp"); expect(testProp).to.exist; - testProp.deserializeSync(parser.parsePropertyProps(oneCustomAttributeJson, testClass.name, testProp.name)); + testProp.deserializeSync(oneCustomAttributeJson); expect(testProp.name).to.eql("TestProp"); expect(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; assert(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"].ExampleAttribute === 1234); @@ -184,7 +182,7 @@ describe("Property", () => { const testProp = new MockProperty("TestProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(twoCustomAttributesJson, testClass.name, testProp.name)); + await testProp.deserialize(twoCustomAttributesJson); expect(testProp.name).to.eql("TestProp"); expect(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; expect(testProp.customAttributes!["ExampleCustomAttributes.ExampleSchema"]).to.exist; @@ -192,28 +190,11 @@ describe("Property", () => { it("sync - Deserialize Two Custom Attributes", () => { const testProp = new MockProperty("TestProp"); expect(testProp).to.exist; - testProp.deserializeSync(parser.parsePropertyProps(twoCustomAttributesJson, testClass.name, testProp.name)); + testProp.deserializeSync(twoCustomAttributesJson); expect(testProp.name).to.eql("TestProp"); expect(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; expect(testProp.customAttributes!["ExampleCustomAttributes.ExampleSchema"]).to.exist; }); - const mustBeArrayJson = { - name: "BadProp", - label: "SomeDisplayLabel", - type: "PrimitiveArrayProperty", - description: "A really long description...", - customAttributes: "CoreCustomAttributes.HiddenSchema", - }; - it("async - Custom Attributes must be an array", async () => { - const testProp = new MockProperty("BadProp"); - expect(testProp).to.exist; - assert.throws(() => parser.parsePropertyProps(mustBeArrayJson, testClass.schema.name, testClass.name), ECObjectsError, "The ECProperty TestSchema.TestClass.BadProp has an invalid 'customAttributes' attribute. It should be of type 'array'."); - }); - it("sync - Custom Attributes must be an array", async () => { - const testProp = new MockProperty("BadProp"); - expect(testProp).to.exist; - assert.throws(() => parser.parsePropertyProps(mustBeArrayJson, testClass.schema.name, testClass.name), ECObjectsError, "The ECProperty TestSchema.TestClass.BadProp has an invalid 'customAttributes' attribute. It should be of type 'array'."); - }); it("sync - Deserialize Multiple Custom Attributes with additional properties", () => { const propertyJson = { name: "Prop", @@ -235,42 +216,12 @@ describe("Property", () => { }; const testProp = new MockProperty("Prop"); expect(testProp).to.exist; - testProp.deserializeSync(parser.parsePropertyProps(propertyJson, testClass.schema.name, testClass.name)); + testProp.deserializeSync(propertyJson); assert(testProp.customAttributes!["CoreCustomAttributes.HiddenSchema"].ShowClasses === 1.2); assert(testProp.customAttributes!["ExampleCustomAttributes.ExampleSchema"].ExampleAttribute === true); assert(testProp.customAttributes!["AnotherCustomAttributes.ExampleSchema1"].Example2Attribute === "example"); }); - async function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { - const json: any = { - name: "TestProp", - type: "PrimitiveProperty", - label: "Some display label", - description: "Some really long description...", - priority: 0, - isReadOnly: true, - category: "TestCategory", - kindOfQuantity: "TestKindOfQuantity", - inherited: false, - customAttributes: [], - [attributeName]: value, // overwrites previously defined objects - } - let err = (typeof (json.name) !== "string") ? `An ECProperty in TestSchema.TestClass ` : `The ECProperty TestSchema.TestClass.TestProp `; - err += `has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`; - assert.throws(() => parser.parsePropertyProps(json, testClass.schema.name, testClass.name), ECObjectsError, err); - } - - it("should throw for invalid name", () => { testInvalidAttribute("name", "string", false); }); - it("should throw for invalid type", () => { testInvalidAttribute("type", "string", false); }); - it("should throw for invalid label", () => { testInvalidAttribute("label", "string", false); }); - it("should throw for invalid description", () => { testInvalidAttribute("description", "string", false); }); - it("should throw for invalid priority", () => { testInvalidAttribute("priority", "number", false); }); - it("should throw for invalid isReadOnly", () => { testInvalidAttribute("isReadOnly", "boolean", 1.234); }); - it("should throw for invalid category", () => { testInvalidAttribute("category", "string", false); }); - it("should throw for invalid kindOfQuantity", () => { testInvalidAttribute("kindOfQuantity", "string", false); }); - it("should throw for invalid inherited", () => { testInvalidAttribute("inherited", "boolean", 1.234); }); - it("should throw for invalid customAttributes", () => { testInvalidAttribute("category", "string", false); }); - it("should throw for non-existent category", async () => { const testProp = new MockProperty("BadProp"); // Also test for a PropertyCategory that doesn't exist @@ -279,7 +230,7 @@ describe("Property", () => { type: "PrimitiveProperty", category: "TestSchema.NonExistentPropertyCategory" }; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); await expect(testProp.category).to.be.rejectedWith(ECObjectsError, `The Property BadProp has a 'category' ("TestSchema.NonExistentPropertyCategory") that cannot be found.`); }); @@ -291,7 +242,7 @@ describe("Property", () => { type: "PrimitiveProperty", kindOfQuantity: "TestSchema.NonExistentKindOfQuantity" }; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); await expect(testProp.kindOfQuantity).to.be.rejectedWith(ECObjectsError, `The Property BadProp has a 'kindOfQuantity' ("TestSchema.NonExistentKindOfQuantity") that cannot be found.`); }); }); @@ -308,7 +259,7 @@ describe("Property", () => { }; const testProp = new MockProperty("ValidProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); const serialized = testProp.toJson(); assert(serialized.name, "ValidProp"); assert(serialized.description, "A really long description..."); @@ -330,7 +281,7 @@ describe("Property", () => { }; const testProp = new MockProperty("ValidProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); const serialized = testProp.toJson(); assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); }); @@ -348,7 +299,7 @@ describe("Property", () => { }; const testProp = new MockProperty("ValidProp"); expect(testProp).to.exist; - testProp.deserializeSync(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + testProp.deserializeSync(propertyJson); const serialized = testProp.toJson(); assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); assert(serialized.customAttributes[0].ShowClasses === true); @@ -372,7 +323,7 @@ describe("Property", () => { }; const testProp = new MockProperty("ValidProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); const serialized = testProp.toJson(); assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); assert(serialized.customAttributes[1].className === "CoreAttributes.HiddenSchema"); @@ -400,7 +351,7 @@ describe("Property", () => { }; const testProp = new MockProperty("ValidProp"); expect(testProp).to.exist; - await testProp.deserialize(parser.parsePropertyProps(propertyJson, testClass.name, testProp.name)); + await testProp.deserialize(propertyJson); const serialized = testProp.toJson(); assert(serialized.customAttributes[0].ShowClasses === true); assert(serialized.customAttributes[1].FloatValue === 1.2); @@ -410,7 +361,6 @@ describe("Property", () => { }); describe("PrimitiveProperty", () => { - let parser = new JsonParser(); describe("fromJson", () => { let testProperty: PrimitiveProperty; @@ -432,7 +382,7 @@ describe("PrimitiveProperty", () => { extendedTypeName: "SomeExtendedType", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); expect(testProperty.minLength).to.eql(2); expect(testProperty.maxLength).to.eql(4); @@ -448,62 +398,12 @@ describe("PrimitiveProperty", () => { typeName: "string" }; expect(testProperty).to.exist; - await expect(testProperty.deserialize(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name))).to.be.rejectedWith(ECObjectsError); - }); - - it("should throw for invalid typeName", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - typeName: 0, - }; - assert.throws(() => parser.parsePrimitivePropertyProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid minLength", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - minLength: "0", - }; - assert.throws(() => parser.parsePrimitiveOrEnumPropertyBaseProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid maxLength", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - maxLength: "0", - }; - assert.throws(() => parser.parsePrimitiveOrEnumPropertyBaseProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid minValue", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - minValue: "0", - }; - assert.throws(() => parser.parsePrimitiveOrEnumPropertyBaseProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid maxValue", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - maxValue: "0", - }; - assert.throws(() => parser.parsePrimitiveOrEnumPropertyBaseProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid extendedTypeName", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - extendedTypeName: 0, - }; - assert.throws(() => parser.parsePrimitiveOrEnumPropertyBaseProps(json, testProperty.class.name, testProperty.name), ECObjectsError); + await expect(testProperty.deserialize(propertyJson)).to.be.rejectedWith(ECObjectsError); }); }); describe("KindOfQuantity in referenced schema", () => { let testProperty: PrimitiveProperty; - let parser = new JsonParser(); beforeEach(() => { const referencedSchema = new Schema("Reference", 1, 0, 0) as MutableSchema; referencedSchema.createKindOfQuantitySync("MyKindOfQuantity"); @@ -523,14 +423,14 @@ describe("PrimitiveProperty", () => { }; it("Should load KindOfQuantity synchronously", () => { - testProperty.deserializeSync(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + testProperty.deserializeSync(propertyJson); const koq = testProperty.getKindOfQuantitySync(); assert(koq !== undefined); assert(koq!.name === "MyKindOfQuantity"); }); it("Should load KindOfQuantity", async () => { - await testProperty.deserialize(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); const koq = await testProperty.kindOfQuantity; assert(koq !== undefined); assert(koq!.name === "MyKindOfQuantity"); @@ -558,14 +458,14 @@ describe("PrimitiveProperty", () => { }; it("Should load PropertyCategory synchronously", () => { - testProperty.deserializeSync(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + testProperty.deserializeSync(propertyJson); const cat = testProperty.getCategorySync(); assert(cat !== undefined); assert(cat!.name === "MyCategory"); }); it("Should load PropertyCategory", async () => { - await testProperty.deserialize(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); const cat = await testProperty.category; assert(cat !== undefined); assert(cat!.name === "MyCategory"); @@ -592,7 +492,7 @@ describe("PrimitiveProperty", () => { extendedTypeName: "SomeExtendedType", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parsePrimitivePropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); const testPropSerialization = testProperty.toJson(); expect(testPropSerialization.minLength).to.eql(2); expect(testPropSerialization.maxLength).to.eql(4); @@ -604,7 +504,6 @@ describe("PrimitiveProperty", () => { }); describe("EnumerationProperty", () => { - let parser = new JsonParser(); describe("fromJson", () => { let testProperty: EnumerationProperty; let testEnum: Enumeration; @@ -623,25 +522,10 @@ describe("EnumerationProperty", () => { typeName: "TestSchema.TestEnum", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parseEnumerationPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); - expect(await testProperty.enumeration).to.eql(testEnum); - - // Should also work if typeName is not specified - await testProperty.deserialize(parser.parseEnumerationPropertyProps({ - name: "TestProperty", - type: "PrimitiveProperty", - }, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); expect(await testProperty.enumeration).to.eql(testEnum); }); - it("should throw for invalid typeName", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - typeName: 0, - }; - assert.throws(() => parser.parseEnumerationPropertyProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); it("should throw for mismatched typeName", async () => { const propertyJson = { name: "TestProperty", @@ -649,7 +533,7 @@ describe("EnumerationProperty", () => { typeName: "ThisDoesNotMatch" }; expect(testProperty).to.exist; - await expect(testProperty.deserialize(parser.parseEnumerationPropertyProps(propertyJson, testProperty.class.name, testProperty.name))).to.be.rejectedWith(ECObjectsError); + await expect(testProperty.deserialize(propertyJson)).to.be.rejectedWith(ECObjectsError); }); }); describe("toJson", () => { @@ -670,7 +554,7 @@ describe("EnumerationProperty", () => { typeName: "TestSchema.TestEnum", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parseEnumerationPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); const testPropSerialization = testProperty.toJson(); assert(testPropSerialization.typeName, "TestSchema.TestEnum"); }); @@ -678,7 +562,6 @@ describe("EnumerationProperty", () => { }); describe("StructProperty", () => { - let parser = new JsonParser(); describe("fromJson", () => { let testProperty: StructProperty; let testStruct: StructClass; @@ -697,25 +580,10 @@ describe("StructProperty", () => { typeName: "TestSchema.TestStruct", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parseStructPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); - expect(await testProperty.structClass).to.eql(testStruct); - - // Should also work if typeName is not specified - await testProperty.deserialize(parser.parseStructPropertyProps({ - name: "TestProperty", - type: "StructProperty", - }, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); expect(await testProperty.structClass).to.eql(testStruct); }); - it("should throw for invalid typeName", () => { - const json: any = { - name: 0, - type: "StructProperty", - typeName: 0, - }; - assert.throws(() => parser.parseStructPropertyProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); it("should throw for mismatched typeName", async () => { const propertyJson = { name: "TestProperty", @@ -723,7 +591,7 @@ describe("StructProperty", () => { typeName: "ThisDoesNotMatch" }; expect(testProperty).to.exist; - await expect(testProperty.deserialize(parser.parseStructPropertyProps(propertyJson, testProperty.class.name, testProperty.name))).to.be.rejectedWith(ECObjectsError); + await expect(testProperty.deserialize(propertyJson)).to.be.rejectedWith(ECObjectsError); }); }); describe("toJson", () => { @@ -744,7 +612,7 @@ describe("StructProperty", () => { typeName: "TestSchema.TestStruct", }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parseStructPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); const testPropSerialization = testProperty.toJson(); assert(testPropSerialization.typeName, "TestStruct"); }); @@ -752,7 +620,6 @@ describe("StructProperty", () => { }); describe("PrimitiveArrayProperty", () => { - let parser = new JsonParser(); describe("fromJson", () => { let testProperty: PrimitiveArrayProperty; @@ -770,50 +637,34 @@ describe("PrimitiveArrayProperty", () => { maxOccurs: 4, }; expect(testProperty).to.exist; - await testProperty.deserialize(parser.parsePrimitiveArrayPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); + await testProperty.deserialize(propertyJson); expect(testProperty.minOccurs).to.eql(2); expect(testProperty.maxOccurs).to.eql(4); }); - it("should throw for invalid minOccurs", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - minOccurs: "0", - }; - assert.throws(() => parser.parsePrimitiveArrayPropertyProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - it("should throw for invalid maxOccurs", () => { - const json: any = { - name: 0, - type: "PrimitiveProperty", - maxOccurs: "0", - }; - assert.throws(() => parser.parsePrimitiveArrayPropertyProps(json, testProperty.class.name, testProperty.name), ECObjectsError); - }); - }); - describe("toJson", () => { - let testProperty: PrimitiveArrayProperty; - - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - const testClass = new EntityClass(schema, "TestClass"); - testProperty = new PrimitiveArrayProperty(testClass, "TestProperty"); - }); - - it("should successfully serialize valid JSON", async () => { - const propertyJson = { - name: "TestProperty", - type: "PrimitiveArrayProperty", - minOccurs: 2, - maxOccurs: 4, - }; - expect(testProperty).to.exist; - await testProperty.deserialize(parser.parsePrimitiveArrayPropertyProps(propertyJson, testProperty.class.name, testProperty.name)); - const testPropSerialization = testProperty.toJson(); - expect(testPropSerialization.minOccurs).to.eql(2); - expect(testPropSerialization.maxOccurs).to.eql(4); + describe("toJson", () => { + let testProperty: PrimitiveArrayProperty; + + beforeEach(() => { + const schema = new Schema("TestSchema", 1, 0, 0); + const testClass = new EntityClass(schema, "TestClass"); + testProperty = new PrimitiveArrayProperty(testClass, "TestProperty"); + }); + + it("should successfully serialize valid JSON", async () => { + const propertyJson = { + name: "TestProperty", + type: "PrimitiveArrayProperty", + minOccurs: 2, + maxOccurs: 4, + }; + expect(testProperty).to.exist; + await testProperty.deserialize(propertyJson); + const testPropSerialization = testProperty.toJson(); + expect(testPropSerialization.minOccurs).to.eql(2); + expect(testPropSerialization.maxOccurs).to.eql(4); + }); }); }); }); diff --git a/core/ecschema-metadata/test/Metadata/PropertyCategory.test.ts b/core/ecschema-metadata/test/Metadata/PropertyCategory.test.ts index 077d0f5..853dd46 100644 --- a/core/ecschema-metadata/test/Metadata/PropertyCategory.test.ts +++ b/core/ecschema-metadata/test/Metadata/PropertyCategory.test.ts @@ -5,10 +5,8 @@ import { assert, expect } from "chai"; import { Schema } from "../../src/Metadata/Schema"; -import { ECObjectsError } from "../../src/Exception"; import { PropertyCategory } from "../../src/Metadata/PropertyCategory"; import * as sinon from "sinon"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("PropertyCategory", () => { describe("deserialization", () => { @@ -42,23 +40,11 @@ describe("PropertyCategory", () => { }); describe("fromJson", () => { - let testCategory: PropertyCategory; - let parser = new JsonParser(); - - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testCategory = new PropertyCategory(schema, "TestCategory"); - }); - - it("should throw for invalid priority", async () => { - expect(testCategory).to.exist; - const json = { - schemaItemType: "PropertyCategory", - priority: "1", - }; - assert.throws(() => parser.parsePropertyCategoryProps(json, testCategory.name), ECObjectsError, `The PropertyCategory TestCategory has an invalid 'priority' attribute. It should be of type 'number'.`); + it("TODO", async () => { + // TODO: Implement test... }); }); + describe("toJson", () => { it("fully defined", async () => { const testSchema = { diff --git a/core/ecschema-metadata/test/Metadata/Relationship.test.ts b/core/ecschema-metadata/test/Metadata/Relationship.test.ts index 59acfd8..0589b09 100644 --- a/core/ecschema-metadata/test/Metadata/Relationship.test.ts +++ b/core/ecschema-metadata/test/Metadata/Relationship.test.ts @@ -9,7 +9,6 @@ import { EntityClass } from "../../src/Metadata/EntityClass"; import { RelationshipClass, RelationshipMultiplicity } from "../../src/Metadata/RelationshipClass"; import { StrengthType, StrengthDirection } from "../../src/ECObjects"; import { ECObjectsError } from "../../src/Exception"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("RelationshipMultiplicity", () => { describe("fromString", () => { @@ -50,8 +49,6 @@ describe("RelationshipMultiplicity", () => { }); describe("RelationshipClass", () => { - let parser = new JsonParser(); - describe("deserialization", () => { function createSchemaJson(relClassJson: any): any { @@ -237,22 +234,31 @@ describe("RelationshipClass", () => { } }); + const validConstraint = { + polymorphic: true, + multiplicity: "(0..*)", + roleLabel: "owns", + constraintClasses: [ + "TestSchema.TestSourceEntity", + ], + }; + it("should throw for missing source constraint", async () => { const json = createSchemaJson({ strength: "holding", strengthDirection: "backward", - target: {}, + target: validConstraint, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestRelationship is missing the required source constraint.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestSchema.TestRelationship is missing the required source constraint.`); }); it("should throw for missing target constraint", async () => { const json = createSchemaJson({ strength: "embedding", strengthDirection: "forward", - source: {}, + source: validConstraint, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestRelationship is missing the required target constraint.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestSchema.TestRelationship is missing the required target constraint.`); }); it("should throw for invalid source constraint", async () => { @@ -260,19 +266,19 @@ describe("RelationshipClass", () => { strength: "holding", strengthDirection: "forward", source: 0, - target: {}, + target: validConstraint, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestRelationship has an invalid source constraint. It should be of type 'object'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestSchema.TestRelationship has an invalid source constraint. It should be of type 'object'.`); }); it("should throw for invalid target constraint", async () => { const json = createSchemaJson({ strength: "referencing", strengthDirection: "forward", - source: {}, + source: validConstraint, target: 0, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestRelationship has an invalid target constraint. It should be of type 'object'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The RelationshipClass TestSchema.TestRelationship has an invalid target constraint. It should be of type 'object'.`); }); it("should throw for invalid abstractConstraint", async () => { @@ -290,7 +296,7 @@ describe("RelationshipClass", () => { }, target: {}, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Source Constraint of TestRelationship has an invalid 'abstractConstraint' attribute. It should be of type 'string'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'abstractConstraint' attribute. It should be of type 'string'.`); }); it("should throw for invalid constraintClasses", async () => { @@ -305,37 +311,13 @@ describe("RelationshipClass", () => { }, target: {}, }); - await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Source Constraint of TestRelationship has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); + await expect(Schema.fromJson(json)).to.be.rejectedWith(ECObjectsError, `The Source Constraint of TestSchema.TestRelationship has an invalid 'constraintClasses' attribute. It should be of type 'string[]'.`); }); }); describe("fromJson", () => { - const baseJson = { schemaItemType: "RelationshipClass" }; - let testRelationship: RelationshipClass; - - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - testRelationship = new RelationshipClass(schema, "TestRelationship"); - }); - - it("should throw for invalid strength", async () => { - expect(testRelationship).to.exist; - const json = { - ...baseJson, - strength: 0, - strengthDirection: "backward", - }; - assert.throws(() => parser.parseRelationshipClassProps(json, testRelationship.name), ECObjectsError, `The RelationshipClass TestRelationship has an invalid 'strength' attribute. It should be of type 'string'.`); - }); - - it("should throw for invalid strengthDirection", async () => { - expect(testRelationship).to.exist; - const json = { - ...baseJson, - strength: "holding", - strengthDirection: 0, - }; - assert.throws(() => parser.parseRelationshipClassProps(json, testRelationship.name), ECObjectsError, `The RelationshipClass TestRelationship has an invalid 'strengthDirection' attribute. It should be of type 'string'.`); + it("TODO", async () => { + // TODO: Implement test... }); }); describe("toJson", () => { diff --git a/core/ecschema-metadata/test/Metadata/RelationshipConstraint.test.ts b/core/ecschema-metadata/test/Metadata/RelationshipConstraint.test.ts index 5e06850..e888a69 100644 --- a/core/ecschema-metadata/test/Metadata/RelationshipConstraint.test.ts +++ b/core/ecschema-metadata/test/Metadata/RelationshipConstraint.test.ts @@ -2,14 +2,12 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ - import { assert, expect } from "chai"; import { Schema } from "../../src/Metadata/Schema"; import { ECObjectsError } from "../../src/Exception"; import { RelationshipClass, RelationshipConstraint } from "../../src/Metadata/RelationshipClass"; import { RelationshipEnd } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; import { createSchemaJsonWithItems } from "../TestUtils/DeserializationHelpers"; function createSchemaJson(sourceConst: any, targetConst: any) { @@ -43,7 +41,6 @@ function createSchemaJson(sourceConst: any, targetConst: any) { } describe("RelationshipConstraint", () => { - let parser = new JsonParser(); describe("fromJson", () => { let testConstraint: RelationshipConstraint; @@ -52,76 +49,14 @@ describe("RelationshipConstraint", () => { const relClass = new RelationshipClass(schema, "TestRelationship"); testConstraint = new RelationshipConstraint(relClass, RelationshipEnd.Source); }); - - it("should throw for invalid roleLabel", async () => { - const json: any = { - polymorphic: true, - multiplicity: "(1..1)", - roleLabel: 0, - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, json), ECObjectsError, `The RelationshipConstraint TestRelationship has an invalid 'roleLabel' attribute. It should be of type 'string'.`); - }); - it("should throw for invalid polymorphic", async () => { - const json: any = { - polymorphic: "0", - multiplicity: "(0..1)", - roleLabel: "test roleLabel", - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, json), ECObjectsError, `The RelationshipConstraint TestRelationship has an invalid 'polymorhpic' attribute. It should be of type 'boolean'.`); - }); - - it("should throw for invalid multiplicity", async () => { - const badMultiplicityJson = { - polymorphic: true, - multiplicity: 0, - roleLabel: "test roleLabel", - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, badMultiplicityJson), ECObjectsError, `The RelationshipConstraint TestRelationship has an invalid 'multiplicity' attribute. It should be of type 'string'.`); - }); - - it("should throw for invalid abstractConstraint", async () => { - const unloadedAbstractConstraintJson = { - polymorphic: true, - multiplicity: 0, - roleLabel: "test roleLabel", - abstractConstraint: 0, - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, unloadedAbstractConstraintJson), ECObjectsError); - }); it("should throw for invalid constraintClasses", async () => { const json: any = { polymorphic: true, multiplicity: "(0..1)", roleLabel: "test roleLabel", }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, { ...json, constraintClass: 0 }), ECObjectsError); - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, { ...json, constraintClass: [0] }), ECObjectsError); const unloadedConstraintClassesJson = { ...json, constraintClasses: ["ThisClassDoesNotExist"] }; - await expect(testConstraint.deserialize(parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, unloadedConstraintClassesJson))).to.be.rejectedWith(ECObjectsError); - }); - it("should throw for invalid customAttributes", async () => { - const json: any = { - polymorphic: true, - multiplicity: "(0..1)", - roleLabel: "test roleLabel", - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - customAttributes: "array", - }; - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, json), ECObjectsError, `The RelationshipConstraint TestRelationship has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + await expect(testConstraint.deserialize(unloadedConstraintClassesJson)).to.be.rejectedWith(ECObjectsError); }); const targetStubJson = { @@ -218,19 +153,5 @@ describe("RelationshipConstraint", () => { assert(testConstraint.customAttributes!["CoreCustomAttributes.HiddenSchema"].ShowClasses === false); assert(testConstraint.customAttributes!["ExampleCustomAttributes.ExampleSchema"].ShowClasses === true); }); - const mustBeAnArrayJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "InvalidSchema", - polymorphic: true, - multiplicity: "(0..1)", - roleLabel: "test roleLabel", - constraintClasses: [ - "TestSchema.TestTargetEntity", - ], - customAttributes: "CoreCustomAttributes.HiddenSchema", - }; - it("Custom Attributes must be an array", () => { - assert.throws(() => parser.parseRelationshipConstraintProps(testConstraint.relationshipClass.name, mustBeAnArrayJson, true), ECObjectsError, `The Source Constraint of TestRelationship has an invalid 'customAttributes' attribute. It should be of type 'array'.`); - }); }); }); diff --git a/core/ecschema-metadata/test/Metadata/Schema.test.ts b/core/ecschema-metadata/test/Metadata/Schema.test.ts index 9cf53af..003aa63 100644 --- a/core/ecschema-metadata/test/Metadata/Schema.test.ts +++ b/core/ecschema-metadata/test/Metadata/Schema.test.ts @@ -14,10 +14,8 @@ import { StructClass } from "./../../src/Metadata/Class"; import { ECObjectsError } from "./../../src/Exception"; import { SchemaMatchType } from "./../../src/ECObjects"; import { SchemaKey } from "./../../src/SchemaKey"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("Schema", () => { - const parser = new JsonParser(); describe("api creation of schema", () => { it("with only the essentials", () => { const testSchema = new Schema("TestSchemaCreation", 10, 99, 15); @@ -92,7 +90,7 @@ describe("Schema", () => { }; const testSchema = new Schema(); expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); + await testSchema.deserialize(propertyJson); assertValidSchema(testSchema); }); @@ -107,7 +105,7 @@ describe("Schema", () => { }; const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); + await testSchema.deserialize(propertyJson); assertValidSchema(testSchema); }); const oneCustomAttributeJson = { @@ -125,14 +123,14 @@ describe("Schema", () => { it("async - Deserialize One Custom Attribute", async () => { const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(oneCustomAttributeJson)); + await testSchema.deserialize(oneCustomAttributeJson); expect(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; assert(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"].ShowClasses === true); }); it("sync - Deserialize One Custom Attribute", () => { const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - testSchema.deserializeSync(parser.parseSchemaProps(oneCustomAttributeJson)); + testSchema.deserializeSync(oneCustomAttributeJson); expect(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; assert(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"].ShowClasses === true); }); @@ -153,14 +151,14 @@ describe("Schema", () => { it("async - Deserialize Two Custom Attributes", async () => { const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(twoCustomAttributeJson)); + await testSchema.deserialize(twoCustomAttributeJson); expect(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; expect(testSchema.customAttributes!["ExampleCustomAttributes.ExampleSchema"]).to.exist; }); it("sync - Deserialize Two Custom Attributes", () => { const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - testSchema.deserializeSync(parser.parseSchemaProps(twoCustomAttributeJson)); + testSchema.deserializeSync(twoCustomAttributeJson); expect(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"]).to.exist; expect(testSchema.customAttributes!["ExampleCustomAttributes.ExampleSchema"]).to.exist; }); @@ -185,501 +183,429 @@ describe("Schema", () => { }; const testSchema = new Schema("ValidSchema", 1, 2, 3); expect(testSchema).to.exist; - testSchema.deserializeSync(parser.parseSchemaProps(propertyJson)); + testSchema.deserializeSync(propertyJson); assertValidSchema(testSchema); assert(testSchema.customAttributes!["CoreCustomAttributes.HiddenSchema"].ShowClasses === false); assert(testSchema.customAttributes!["ExampleCustomAttributes.ExampleSchema"].ShowClasses === true); }); - const mustBeArrayJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "InvalidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - customAttributes: "CoreCustomAttributes.HiddenSchema", - }; - it("async - Custom Attributes must be an array", async () => { - const testSchema = new Schema("InvalidSchema", 1, 2, 3); + + it("should throw for invalid $schema", async () => { + const schemaJson = { + $schema: "https://badmetaschema.com", + name: "InvalidSchema", + version: "1.2.3", + }; + const testSchema = new Schema("BadSchema", 1, 2, 3); expect(testSchema).to.exist; - assert.throws(() => parser.parseSchemaProps(mustBeArrayJson), ECObjectsError, `The Schema InvalidSchema has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + await expect(testSchema.deserialize(schemaJson as any)).to.be.rejectedWith(ECObjectsError); }); - it("sync - Custom Attributes must be an array", () => { - const testSchema = new Schema("InvalidSchema", 1, 2, 3); + + it("should throw for mismatched name", async () => { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ThisDoesNotMatch", + version: "1.2.3", + alias: "bad", + }; + const testSchema = new Schema("BadSchema", 1, 2, 3); expect(testSchema).to.exist; - assert.throws(() => parser.parseSchemaProps(mustBeArrayJson), ECObjectsError, `The Schema InvalidSchema has an invalid 'customAttributes' attribute. It should be of type 'array'.`); + await expect(testSchema.deserialize(json)).to.be.rejectedWith(ECObjectsError); }); - }); - - async function testInvalidAttribute(schema: Schema, attributeName: string, expectedType: string, value: any) { - expect(schema).to.exist; - const json: any = { - $schema: "https://dev.bentley.com/json_schemas/ec/31/draft-01/ecschema", - name: schema.name, - version: "1.2.3", - [attributeName]: value, - }; - assert.throws(() => parser.parseSchemaProps(json), ECObjectsError, `The ECSchema ${schema.name} has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); - } - - it("should throw for missing $schema", async () => { - const testSchema = new Schema("BadSchema", 1, 2, 3); - expect(testSchema).to.exist; - assert.throws(() => parser.parseSchemaProps({}), ECObjectsError, `An ECSchema is missing the required \'$schema\' attribute.`); - }); - - it("should throw for invalid $schema", async () => { - const schemaJson = { - $schema: "https://badmetaschema.com", - name: "InvalidSchema", - version: "1.2.3", - }; - const testSchema = new Schema("BadSchema", 1, 2, 3); - expect(testSchema).to.exist; - await expect(testSchema.deserialize(parser.parseSchemaProps(schemaJson))).to.be.rejectedWith(ECObjectsError); - }); - - it("should throw for missing name", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - }; - assert.throws(() => parser.parseSchemaProps(json), ECObjectsError, "An ECSchema is missing the required 'name' attribute."); - }); - - it("should throw for mismatched name", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ThisDoesNotMatch", - version: "1.2.3", - }; - const testSchema = new Schema("BadSchema", 1, 2, 3); - expect(testSchema).to.exist; - await expect(testSchema.deserialize(parser.parseSchemaProps(json))).to.be.rejectedWith(ECObjectsError); - }); - - it("should throw for invalid name", async () => { - const json: any = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: 0, - }; - assert.throws(() => parser.parseSchemaProps(json), ECObjectsError, `An ECSchema has an invalid 'name' attribute. It should be of type 'string'.`); - }); - - it("should throw for invalid version", async () => { - const json: any = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "BadSchema", - version: 0, - }; - assert.throws(() => parser.parseSchemaProps(json), ECObjectsError, `The ECSchema BadSchema has an invalid 'version' attribute. It should be of type 'string'.`); - }); - it("should throw for missing version", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "BadSchema", - }; - const testSchema = new Schema(); - expect(testSchema).to.exist; - assert.throws(() => parser.parseSchemaProps(json), ECObjectsError, "The ECSchema BadSchema is missing the required 'version' attribute."); - }); - - it("should throw for mismatched version", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "BadSchema", - version: "1.2.6", - }; - const testSchema = new Schema("BadSchema", 1, 2, 3); - expect(testSchema).to.exist; - await expect(testSchema.deserialize(parser.parseSchemaProps(json))).to.be.rejectedWith(ECObjectsError); - }); - - it("should throw for invalid alias", async () => testInvalidAttribute(new Schema("BadSchema", 1, 2, 3), "alias", "string", 0)); - it("should throw for invalid label", async () => testInvalidAttribute(new Schema("BadSchema", 1, 2, 3), "label", "string", 0)); - it("should throw for invalid description", async () => testInvalidAttribute(new Schema("BadSchema", 1, 2, 3), "description", "string", 0)); - }); - describe("toJSON", () => { - it("Simple serialization", async () => { - const propertyJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - }; - const testSchema = new Schema("ValidSchema", 1, 2, 3); - expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); - const serialized = testSchema.toJson(); - assert(serialized.$schema, "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema"); - assert(serialized.name, "ValidSchema"); - assert(serialized.version, "01.02.03"); - assert(serialized.alias, "vs"); - assert(serialized.label, "SomeDisplayLabel"); - assert(serialized.description, "A really long description..."); - }); - it("Serialization with one custom attribute- only class name", async () => { - const propertyJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - customAttributes: [ - { - className: "CoreCustomAttributes.HiddenSchema", - }, - ], - }; - const testSchema = new Schema("ValidSchema", 1, 2, 3); - expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); - const serialized = testSchema.toJson(); - assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); - }); - it("Serialization with one custom attribute- additional properties", () => { - const propertyJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - customAttributes: [ - { - className: "CoreCustomAttributes.HiddenSchema", - ShowClasses: true, - }, - ], - }; - const testSchema = new Schema("ValidSchema", 1, 2, 3); - expect(testSchema).to.exist; - testSchema.deserializeSync(parser.parseSchemaProps(propertyJson)); - const serialized = testSchema.toJson(); - assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); - assert(serialized.customAttributes[0].ShowClasses === true); - }); - it("Serialization with multiple custom attributes- only class name", async () => { - const propertyJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - customAttributes: [ - { - className: "CoreCustomAttributes.HiddenSchema", - }, - { - className: "CoreAttributes.HiddenSchema", - }, - { - className: "CoreCustom.HiddenSchema", - }, - ], - }; - const testSchema = new Schema("ValidSchema", 1, 2, 3); - expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); - const serialized = testSchema.toJson(); - assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); - assert(serialized.customAttributes[1].className === "CoreAttributes.HiddenSchema"); - assert(serialized.customAttributes[2].className === "CoreCustom.HiddenSchema"); - }); - it("Serialization with multiple custom attributes- additional properties", async () => { - const propertyJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - customAttributes: [ - { - className: "CoreCustomAttributes.HiddenSchema", - ShowClasses: true, - }, - { - className: "CoreAttributes.HiddenSchema", - FloatValue: 1.2, - }, - { - className: "CoreCustom.HiddenSchema", - IntegerValue: 5, - }, - ], - }; - const testSchema = new Schema("ValidSchema", 1, 2, 3); - expect(testSchema).to.exist; - await testSchema.deserialize(parser.parseSchemaProps(propertyJson)); - const serialized = testSchema.toJson(); - assert(serialized.customAttributes[0].ShowClasses === true); - assert(serialized.customAttributes[1].FloatValue === 1.2); - assert(serialized.customAttributes[2].IntegerValue === 5); - }); - it("Serialization with one reference", async () => { - const schemaJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - references: [ - { - name: "RefSchema", - version: "1.0.0", - }, - ], - }; - const refSchema = new Schema("RefSchema", 1, 0, 0); - const context = new SchemaContext(); - await context.addSchema(refSchema); - let testSchema = new Schema("ValidSchema", 1, 2, 3); - testSchema = await Schema.fromJson(schemaJson, context); - expect(testSchema).to.exist; - const entityClassJson = testSchema.toJson(); - assert.isDefined(entityClassJson); - assert(entityClassJson.references[0].name === "RefSchema"); - assert(entityClassJson.references[0].version === "01.00.00"); - }); - it("Serialization with multiple references", () => { - const schemaJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "ValidSchema", - version: "1.2.3", - alias: "vs", - label: "SomeDisplayLabel", - description: "A really long description...", - references: [ - { - name: "RefSchema", - version: "1.0.0", - }, - { - name: "AnotherRefSchema", - version: "1.0.2", - }, - ], - }; - const refSchema = new Schema("RefSchema", 1, 0, 0); - const anotherRefSchema = new Schema("AnotherRefSchema", 1, 0, 2); - const context = new SchemaContext(); - context.addSchemaSync(refSchema); - context.addSchemaSync(anotherRefSchema); - let testSchema = new Schema("ValidSchema", 1, 2, 3); - testSchema = Schema.fromJsonSync(schemaJson, context); - expect(testSchema).to.exist; - const entityClassJson = testSchema.toJson(); - assert.isDefined(entityClassJson); - assert(entityClassJson.references[0].name === "RefSchema"); - assert(entityClassJson.references[0].version === "01.00.00"); - assert(entityClassJson.references[1].name === "AnotherRefSchema"); - assert(entityClassJson.references[1].version === "01.00.02"); + it("should throw for mismatched version", async () => { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "BadSchema", + version: "1.2.6", + alias: "bad", + }; + const testSchema = new Schema("BadSchema", 1, 2, 3); + expect(testSchema).to.exist; + await expect(testSchema.deserialize(json)).to.be.rejectedWith(ECObjectsError); + }); }); - it("Serialization with one reference and item", async () => { - const schemaJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "TestSchema", - version: "1.2.3", - references: [ - { - name: "RefSchema", - version: "1.0.5", - }, - ], - items: { - testClass: { - schemaItemType: "EntityClass", - label: "ExampleEntity", - description: "An example entity class.", + describe("toJSON", () => { + it("Simple serialization", async () => { + const propertyJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + }; + const testSchema = new Schema("ValidSchema", 1, 2, 3); + expect(testSchema).to.exist; + await testSchema.deserialize(propertyJson); + const serialized = testSchema.toJson(); + assert(serialized.$schema, "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema"); + assert(serialized.name, "ValidSchema"); + assert(serialized.version, "01.02.03"); + assert(serialized.alias, "vs"); + assert(serialized.label, "SomeDisplayLabel"); + assert(serialized.description, "A really long description..."); + }); + it("Serialization with one custom attribute- only class name", async () => { + const propertyJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + customAttributes: [ + { + className: "CoreCustomAttributes.HiddenSchema", + }, + ], + }; + const testSchema = new Schema("ValidSchema", 1, 2, 3); + expect(testSchema).to.exist; + await testSchema.deserialize(propertyJson); + const serialized = testSchema.toJson(); + assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); + }); + it("Serialization with one custom attribute- additional properties", () => { + const propertyJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + customAttributes: [ + { + className: "CoreCustomAttributes.HiddenSchema", + ShowClasses: true, + }, + ], + }; + const testSchema = new Schema("ValidSchema", 1, 2, 3); + expect(testSchema).to.exist; + testSchema.deserializeSync(propertyJson); + const serialized = testSchema.toJson(); + assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); + assert(serialized.customAttributes[0].ShowClasses === true); + }); + it("Serialization with multiple custom attributes- only class name", async () => { + const propertyJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + customAttributes: [ + { + className: "CoreCustomAttributes.HiddenSchema", + }, + { + className: "CoreAttributes.HiddenSchema", + }, + { + className: "CoreCustom.HiddenSchema", + }, + ], + }; + const testSchema = new Schema("ValidSchema", 1, 2, 3); + expect(testSchema).to.exist; + await testSchema.deserialize(propertyJson); + const serialized = testSchema.toJson(); + assert(serialized.customAttributes[0].className === "CoreCustomAttributes.HiddenSchema"); + assert(serialized.customAttributes[1].className === "CoreAttributes.HiddenSchema"); + assert(serialized.customAttributes[2].className === "CoreCustom.HiddenSchema"); + }); + it("Serialization with multiple custom attributes- additional properties", async () => { + const propertyJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + customAttributes: [ + { + className: "CoreCustomAttributes.HiddenSchema", + ShowClasses: true, + }, + { + className: "CoreAttributes.HiddenSchema", + FloatValue: 1.2, + }, + { + className: "CoreCustom.HiddenSchema", + IntegerValue: 5, + }, + ], + }; + const testSchema = new Schema("ValidSchema", 1, 2, 3); + expect(testSchema).to.exist; + await testSchema.deserialize(propertyJson); + const serialized = testSchema.toJson(); + assert(serialized.customAttributes[0].ShowClasses === true); + assert(serialized.customAttributes[1].FloatValue === 1.2); + assert(serialized.customAttributes[2].IntegerValue === 5); + }); + it("Serialization with one reference", async () => { + const schemaJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + references: [ + { + name: "RefSchema", + version: "1.0.0", + }, + ], + }; + const refSchema = new Schema("RefSchema", 1, 0, 0); + const context = new SchemaContext(); + await context.addSchema(refSchema); + let testSchema = new Schema("ValidSchema", 1, 2, 3); + testSchema = await Schema.fromJson(schemaJson, context); + expect(testSchema).to.exist; + const entityClassJson = testSchema.toJson(); + assert.isDefined(entityClassJson); + assert(entityClassJson.references[0].name === "RefSchema"); + assert(entityClassJson.references[0].version === "01.00.00"); + }); + it("Serialization with multiple references", () => { + const schemaJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "ValidSchema", + version: "1.2.3", + alias: "vs", + label: "SomeDisplayLabel", + description: "A really long description...", + references: [ + { + name: "RefSchema", + version: "1.0.0", + }, + { + name: "AnotherRefSchema", + version: "1.0.2", + }, + ], + }; + const refSchema = new Schema("RefSchema", 1, 0, 0); + const anotherRefSchema = new Schema("AnotherRefSchema", 1, 0, 2); + const context = new SchemaContext(); + context.addSchemaSync(refSchema); + context.addSchemaSync(anotherRefSchema); + let testSchema = new Schema("ValidSchema", 1, 2, 3); + testSchema = Schema.fromJsonSync(schemaJson, context); + expect(testSchema).to.exist; + const entityClassJson = testSchema.toJson(); + assert.isDefined(entityClassJson); + assert(entityClassJson.references[0].name === "RefSchema"); + assert(entityClassJson.references[0].version === "01.00.00"); + assert(entityClassJson.references[1].name === "AnotherRefSchema"); + assert(entityClassJson.references[1].version === "01.00.02"); + }); + it("Serialization with one reference and item", async () => { + const schemaJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "TestSchema", + version: "1.2.3", + references: [ + { + name: "RefSchema", + version: "1.0.5", + }, + ], + items: { + testClass: { + schemaItemType: "EntityClass", + label: "ExampleEntity", + description: "An example entity class.", + }, }, - }, - }; + }; - const refSchema = new Schema("RefSchema", 1, 0, 5); - const refBaseClass = await (refSchema as MutableSchema).createEntityClass("BaseClassInRef"); - assert.isDefined(refBaseClass); - const context = new SchemaContext(); - await context.addSchema(refSchema); - let testSchema = new Schema("TestSchema", 1, 2, 3); - testSchema = await Schema.fromJson(schemaJson, context); - const entityClassJson = testSchema.toJson(); - assert.isDefined(entityClassJson); - assert.isDefined(entityClassJson.items.testClass); - assert(entityClassJson.items.testClass.schemaItemType, "EntityClass"); - assert(entityClassJson.items.testClass.label, "ExampleEntity"); - assert(entityClassJson.items.testClass.description, "An example entity class."); - }); - it("Serialization with one reference and multiple items", async () => { - const schemaJson = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", - name: "TestSchema", - version: "1.2.3", - references: [ - { - name: "RefSchema", - version: "1.0.5", - }, - ], - items: { - testEnum: { - schemaItemType: "Enumeration", - type: "int", - enumerators: [ - { - name: "ZeroValue", - value: 0, - label: "None", - }, - ], - }, - testClass: { - schemaItemType: "EntityClass", - label: "ExampleEntity", - description: "An example entity class.", - }, - ExampleMixin: { - schemaItemType: "Mixin", - appliesTo: "TestSchema.testClass", - }, - ExampleStruct: { - schemaItemType: "StructClass", - name: "ExampleStruct", - modifier: "sealed", - properties: [ - { - type: "PrimitiveArrayProperty", - name: "ExamplePrimitiveArray", - typeName: "TestSchema.testEnum", - minOccurs: 7, - maxOccurs: 20, - }, - ], + const refSchema = new Schema("RefSchema", 1, 0, 5); + const refBaseClass = await (refSchema as MutableSchema).createEntityClass("BaseClassInRef"); + assert.isDefined(refBaseClass); + const context = new SchemaContext(); + await context.addSchema(refSchema); + let testSchema = new Schema("TestSchema", 1, 2, 3); + testSchema = await Schema.fromJson(schemaJson, context); + const entityClassJson = testSchema.toJson(); + assert.isDefined(entityClassJson); + assert.isDefined(entityClassJson.items.testClass); + assert(entityClassJson.items.testClass.schemaItemType, "EntityClass"); + assert(entityClassJson.items.testClass.label, "ExampleEntity"); + assert(entityClassJson.items.testClass.description, "An example entity class."); + }); + it("Serialization with one reference and multiple items", async () => { + const schemaJson = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/ecschema", + name: "TestSchema", + version: "1.2.3", + references: [ + { + name: "RefSchema", + version: "1.0.5", + }, + ], + items: { + testEnum: { + schemaItemType: "Enumeration", + type: "int", + enumerators: [ + { + name: "ZeroValue", + value: 0, + label: "None", + }, + ], + }, + testClass: { + schemaItemType: "EntityClass", + label: "ExampleEntity", + description: "An example entity class.", + }, + ExampleMixin: { + schemaItemType: "Mixin", + appliesTo: "TestSchema.testClass", + }, + ExampleStruct: { + schemaItemType: "StructClass", + name: "ExampleStruct", + modifier: "sealed", + properties: [ + { + type: "PrimitiveArrayProperty", + name: "ExamplePrimitiveArray", + typeName: "TestSchema.testEnum", + minOccurs: 7, + maxOccurs: 20, + }, + ], + }, }, - }, - }; + }; - const refSchema = new Schema("RefSchema", 1, 0, 5); - const refBaseClass = await (refSchema as MutableSchema).createEntityClass("BaseClassInRef"); - assert.isDefined(refBaseClass); - const context = new SchemaContext(); - await context.addSchema(refSchema); - let testSchema = new Schema("TestSchema", 1, 2, 3); - testSchema = await Schema.fromJson(schemaJson, context); - const entityClassJson = testSchema.toJson(); - assert.isDefined(entityClassJson); - - assert.isDefined(entityClassJson.items.testClass); - assert(entityClassJson.items.testClass.schemaItemType, "EntityClass"); - assert(entityClassJson.items.testClass.label, "ExampleEntity"); - assert(entityClassJson.items.testClass.description, "An example entity class."); - - assert.isDefined(entityClassJson.items.ExampleMixin); - assert(entityClassJson.items.ExampleMixin.schemaItemType, "Mixin"); - - assert.isDefined(entityClassJson.items.ExampleStruct); - assert(entityClassJson.items.ExampleMixin.schemaItemType, "Mixin"); - - assert.isDefined(entityClassJson.items.testEnum); - assert(entityClassJson.items.testEnum.schemaItemType, "Enumeration"); - }); - }); -}); // Schema tests - -describe("SchemaKey ", () => { - describe("matches", () => { - it("should correctly handle SchemaMatchType.Identical", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0))).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0))).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0))).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 1))).false; - - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Identical)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Identical)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 2, 0, 0), SchemaMatchType.Identical)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 1), SchemaMatchType.Identical)).false; + const refSchema = new Schema("RefSchema", 1, 0, 5); + const refBaseClass = await (refSchema as MutableSchema).createEntityClass("BaseClassInRef"); + assert.isDefined(refBaseClass); + const context = new SchemaContext(); + await context.addSchema(refSchema); + let testSchema = new Schema("TestSchema", 1, 2, 3); + testSchema = await Schema.fromJson(schemaJson, context); + const entityClassJson = testSchema.toJson(); + assert.isDefined(entityClassJson); + + assert.isDefined(entityClassJson.items.testClass); + assert(entityClassJson.items.testClass.schemaItemType, "EntityClass"); + assert(entityClassJson.items.testClass.label, "ExampleEntity"); + assert(entityClassJson.items.testClass.description, "An example entity class."); + + assert.isDefined(entityClassJson.items.ExampleMixin); + assert(entityClassJson.items.ExampleMixin.schemaItemType, "Mixin"); + + assert.isDefined(entityClassJson.items.ExampleStruct); + assert(entityClassJson.items.ExampleMixin.schemaItemType, "Mixin"); + + assert.isDefined(entityClassJson.items.testEnum); + assert(entityClassJson.items.testEnum.schemaItemType, "Enumeration"); + }); }); + }); // Schema tests - it("should correctly handle SchemaMatchType.Exact", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Exact)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Exact)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 2, 0, 0), SchemaMatchType.Exact)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 1), SchemaMatchType.Exact)).false; - }); + describe("SchemaKey ", () => { + describe("matches", () => { + it("should correctly handle SchemaMatchType.Identical", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0))).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0))).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0))).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 1))).false; - it("should correctly handle SchemaMatchType.Latest", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Latest)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Latest)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.Latest)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Latest)).true; - }); + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Identical)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Identical)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 2, 0, 0), SchemaMatchType.Identical)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 1), SchemaMatchType.Identical)).false; + }); - it("should correctly handle SchemaMatchType.LatestWriteCompatible", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.LatestWriteCompatible)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 1), SchemaMatchType.LatestWriteCompatible)).false; - }); + it("should correctly handle SchemaMatchType.Exact", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Exact)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Exact)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 2, 0, 0), SchemaMatchType.Exact)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 1), SchemaMatchType.Exact)).false; + }); - it("should correctly handle SchemaMatchType.LatestReadCompatible", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.LatestReadCompatible)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 1, 0), SchemaMatchType.LatestReadCompatible)).false; - expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).true; - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 1, 1), SchemaMatchType.LatestReadCompatible)).false; - }); + it("should correctly handle SchemaMatchType.Latest", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Latest)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.Latest)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.Latest)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.Latest)).true; + }); - it("should correctly handle invalid SchemaMatchType", () => { - expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), -1)).false; - }); - }); + it("should correctly handle SchemaMatchType.LatestWriteCompatible", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.LatestWriteCompatible)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestWriteCompatible)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 1), SchemaMatchType.LatestWriteCompatible)).false; + }); - describe("parseString", () => { - it("should throw for invalid string", () => { - expect(() => SchemaKey.parseString("invalid")).to.throw(ECObjectsError); - }); + it("should correctly handle SchemaMatchType.LatestReadCompatible", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaNotTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 2, 0, 0), SchemaMatchType.LatestReadCompatible)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 1, 0), SchemaMatchType.LatestReadCompatible)).false; + expect(new SchemaKey("SchemaTest", 1, 0, 1).matches(new SchemaKey("SchemaTest", 1, 0, 0), SchemaMatchType.LatestReadCompatible)).true; + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 1, 1), SchemaMatchType.LatestReadCompatible)).false; + }); - it("should correctly parse a valid schema full name", () => { - const key = SchemaKey.parseString("SchemaName.1.2.3"); - expect(key.name).to.equal("SchemaName"); - expect(key.readVersion).to.equal(1); - expect(key.writeVersion).to.equal(2); - expect(key.minorVersion).to.equal(3); + it("should correctly handle invalid SchemaMatchType", () => { + expect(new SchemaKey("SchemaTest", 1, 0, 0).matches(new SchemaKey("SchemaTest", 1, 0, 0), -1)).false; + }); }); - }); - describe("compareByName", () => { - it("should compare against a string", () => { - const key = new SchemaKey("SchemaName", 1, 2, 3); - expect(key.compareByName("SchemaName")).to.be.true; - expect(key.compareByName("WrongSchemaName")).to.be.false; + describe("parseString", () => { + it("should throw for invalid string", () => { + expect(() => SchemaKey.parseString("invalid")).to.throw(ECObjectsError); + }); + + it("should correctly parse a valid schema full name", () => { + const key = SchemaKey.parseString("SchemaName.1.2.3"); + expect(key.name).to.equal("SchemaName"); + expect(key.readVersion).to.equal(1); + expect(key.writeVersion).to.equal(2); + expect(key.minorVersion).to.equal(3); + }); }); - it("should compare against another SchemaKey", () => { - const key = new SchemaKey("SchemaName", 1, 2, 3); - const matchingKey = new SchemaKey("SchemaName", 1, 2, 3); - const incompatibleKey = new SchemaKey("WrongSchemaName", 1, 2, 3); - expect(key.compareByName(matchingKey)).to.be.true; - expect(key.compareByName(incompatibleKey)).to.be.false; + describe("compareByName", () => { + it("should compare against a string", () => { + const key = new SchemaKey("SchemaName", 1, 2, 3); + expect(key.compareByName("SchemaName")).to.be.true; + expect(key.compareByName("WrongSchemaName")).to.be.false; + }); + + it("should compare against another SchemaKey", () => { + const key = new SchemaKey("SchemaName", 1, 2, 3); + const matchingKey = new SchemaKey("SchemaName", 1, 2, 3); + const incompatibleKey = new SchemaKey("WrongSchemaName", 1, 2, 3); + expect(key.compareByName(matchingKey)).to.be.true; + expect(key.compareByName(incompatibleKey)).to.be.false; + }); }); - }); - // Tests to ensure the schemaKey compareByVersion exists - // and calls into ECVersion.compare. See ECVersion.test.ts - // for more comprehensive cases. - describe("compareByVersion", () => { - it("exact match, returns zero", async () => { - const leftSchema = new Schema("LeftSchema", 1, 2, 3); - const rightSchema = new Schema("RightSchema", 1, 2, 3); - const result = leftSchema.schemaKey.compareByVersion(rightSchema.schemaKey); - assert.equal(result, 0); + // Tests to ensure the schemaKey compareByVersion exists + // and calls into ECVersion.compare. See ECVersion.test.ts + // for more comprehensive cases. + describe("compareByVersion", () => { + it("exact match, returns zero", async () => { + const leftSchema = new Schema("LeftSchema", 1, 2, 3); + const rightSchema = new Schema("RightSchema", 1, 2, 3); + const result = leftSchema.schemaKey.compareByVersion(rightSchema.schemaKey); + assert.equal(result, 0); + }); }); }); }); diff --git a/core/ecschema-metadata/test/Metadata/SchemaItem.test.ts b/core/ecschema-metadata/test/Metadata/SchemaItem.test.ts index e4c5371..4827a12 100644 --- a/core/ecschema-metadata/test/Metadata/SchemaItem.test.ts +++ b/core/ecschema-metadata/test/Metadata/SchemaItem.test.ts @@ -6,77 +6,10 @@ import { assert, expect } from "chai"; import { Schema } from "./../../src/Metadata/Schema"; -import { ECObjectsError } from "./../../src/Exception"; -import { SchemaItem } from "./../../src/Metadata/SchemaItem"; -import { SchemaItemType } from "./../../src/ECObjects"; import { SchemaKey, SchemaItemKey } from "./../../src/SchemaKey"; import { EntityClass } from "./../../src/Metadata/EntityClass"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("SchemaItem", () => { - const parser = new JsonParser(); - describe("fromJson", () => { - let testItem: SchemaItem; - - beforeEach(() => { - const schema = new Schema("TestSchema", 1, 0, 0); - class MockSchemaItem extends SchemaItem { - public readonly schemaItemType!: SchemaItemType.EntityClass; // tslint:disable-line - constructor(name: string) { - super(schema, name); - this.schemaItemType = SchemaItemType.EntityClass; - } - public async accept() { } - } - testItem = new MockSchemaItem("BadSchemaItem"); - }); - it("should throw for missing schemaItemType", async () => { - expect(testItem).to.exist; - assert.throws(() => parser.parseSchemaItemProps({}, testItem.schema.name, testItem.name), ECObjectsError, `The SchemaItem TestSchema.BadSchemaItem is missing the required 'schemaItemType' attribute.`); - }); - it("should throw for invalid schemaItemType", async () => { - expect(testItem).to.exist; - const json: any = { - $schema: "https://dev.bentley.com/json_schemas/ec/31/draft-01/ecschema", - name: "BadSchemaItem", - schemaItemType: 0, - }; - assert.throws(() => parser.parseSchemaItemProps(json, testItem.schema.name, testItem.name), ECObjectsError, `The SchemaItem TestSchema.BadSchemaItem has an invalid 'schemaItemType' attribute. It should be of type 'string'.`); - }); - - it("should throw for mismatched schemaItemType", async () => { - expect(testItem).to.exist; - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/31/draft-01/ecschema", - name: "BadSchemaItem", - schemaItemType: "Mixin", - }; - await expect(testItem.deserialize(parser.parseSchemaItemProps(json, testItem.schema.name, testItem.name))).to.be.rejectedWith(ECObjectsError, `The SchemaItem BadSchemaItem has an incompatible schemaItemType. It must be "EntityClass", not "Mixin".`); - }); - - it("should throw for invalid name", async () => { - expect(testItem).to.exist; - const json: any = { - schemaItemType: "EntityClass", - name: 0, - } - assert.throws(() => parser.parseSchemaItemProps(json, testItem.schema.name, json.name), ECObjectsError, `A SchemaItem in TestSchema has an invalid 'name' attribute. '0' is not a valid ECName.`); - }); - - async function testInvalidAttribute(attributeName: string, expectedType: string, value: any) { - expect(testItem).to.exist; - const json: any = { - schemaItemType: "EntityClass", - [attributeName]: value, - }; - assert.throws(() => parser.parseSchemaItemProps(json, testItem.schema.name, "BadSchemaItem"), ECObjectsError, `The SchemaItem TestSchema.BadSchemaItem has an invalid '${attributeName}' attribute. It should be of type '${expectedType}'.`); - } - - it("should throw for invalid description", async () => testInvalidAttribute("description", "string", 0)); - it("should throw for invalid label", async () => testInvalidAttribute("label", "string", 0)); - it("should throw for invalid schema", async () => testInvalidAttribute("schema", "string", 0)); - it("should throw for invalid schemaVersion", async () => testInvalidAttribute("schemaVersion", "string", 0)); - }); describe("toJson", () => { let baseClass: any; let schema; @@ -94,7 +27,7 @@ describe("SchemaItem", () => { label: "ExampleEntity", description: "An example entity class.", }; - await (baseClass as EntityClass).deserialize(parser.parseSchemaItemProps(propertyJson, propertyJson.schema, propertyJson.name)); + await (baseClass as EntityClass).deserialize(propertyJson); const testClass = await (baseClass as EntityClass).toJson(true, true); expect(testClass).to.exist; assert(testClass.$schema, "https://dev.bentley.com/json_schemas/ec/31/draft-01/schemaitem"); diff --git a/core/ecschema-metadata/test/Metadata/Unit.test.ts b/core/ecschema-metadata/test/Metadata/Unit.test.ts index 51dac77..e1df192 100644 --- a/core/ecschema-metadata/test/Metadata/Unit.test.ts +++ b/core/ecschema-metadata/test/Metadata/Unit.test.ts @@ -141,10 +141,10 @@ describe("Unit", () => { definition: "[MILLI]*Units.M", }; it("async - should throw for missing phenomenon", async () => { - await expect(Schema.fromJson(createSchemaJson(missingPhenomenonJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit does not have the required 'phenomenon' attribute.`); + await expect(Schema.fromJson(createSchemaJson(missingPhenomenonJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'phenomenon' attribute.`); }); it("sync - should throw for missing phenomenon", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingPhenomenonJson)), ECObjectsError, `The Unit TestUnit does not have the required 'phenomenon' attribute.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingPhenomenonJson)), ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'phenomenon' attribute.`); }); // Invalid phenomenon @@ -154,10 +154,10 @@ describe("Unit", () => { definition: "[MILLI]*Units.M", }; it("async - should throw for invalid phenomenon", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidPhenomenonJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'phenomenon' attribute. It should be of type 'string'`); + await expect(Schema.fromJson(createSchemaJson(invalidPhenomenonJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'phenomenon' attribute. It should be of type 'string'`); }); it("sync - should throw for invalid phenomenon", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidPhenomenonJson)), ECObjectsError, `The Unit TestUnit has an invalid 'phenomenon' attribute. It should be of type 'string'`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidPhenomenonJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'phenomenon' attribute. It should be of type 'string'`); }); // Missing UnitSystem @@ -166,10 +166,10 @@ describe("Unit", () => { definition: "[MILLI]*Units.M", }; it("async - should throw for missing unit system", async () => { - await expect(Schema.fromJson(createSchemaJson(missingUnitSystemJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit does not have the required 'unitSystem' attribute.`); + await expect(Schema.fromJson(createSchemaJson(missingUnitSystemJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'unitSystem' attribute.`); }); it("sync - should throw for missing unit system", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingUnitSystemJson)), ECObjectsError, `The Unit TestUnit does not have the required 'unitSystem' attribute.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingUnitSystemJson)), ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'unitSystem' attribute.`); }); // Invalid UnitSystem @@ -179,10 +179,10 @@ describe("Unit", () => { definition: "[MILLI]*Units.M", }; it("async - should throw for invalid unit system", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidUnitSystemJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'unitSystem' attribute. It should be of type 'string'`); + await expect(Schema.fromJson(createSchemaJson(invalidUnitSystemJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'unitSystem' attribute. It should be of type 'string'`); }); it("sync - should throw for invalid unit system", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidUnitSystemJson)), ECObjectsError, `The Unit TestUnit has an invalid 'unitSystem' attribute. It should be of type 'string'`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidUnitSystemJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'unitSystem' attribute. It should be of type 'string'`); }); // Missing Definition @@ -191,10 +191,10 @@ describe("Unit", () => { unitSystem: "TestSchema.TestUnitSystem", }; it("async - should throw for missing definition", async () => { - await expect(Schema.fromJson(createSchemaJson(missingDefinitionJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit does not have the required 'definition' attribute.`); + await expect(Schema.fromJson(createSchemaJson(missingDefinitionJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'definition' attribute.`); }); it("sync - should throw for missing definition", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingDefinitionJson)), ECObjectsError, `The Unit TestUnit does not have the required 'definition' attribute.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(missingDefinitionJson)), ECObjectsError, `The Unit TestSchema.TestUnit does not have the required 'definition' attribute.`); }); // Missing Definition @@ -204,10 +204,10 @@ describe("Unit", () => { unitSystem: "TestSchema.TestUnitSystem", }; it("async - should throw for invalid definition", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidDefinitionJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'definition' attribute. It should be of type 'string'`); + await expect(Schema.fromJson(createSchemaJson(invalidDefinitionJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'definition' attribute. It should be of type 'string'`); }); it("sync - should throw for invalid definition", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDefinitionJson)), ECObjectsError, `The Unit TestUnit has an invalid 'definition' attribute. It should be of type 'string'`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDefinitionJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'definition' attribute. It should be of type 'string'`); }); // Invalid numerator @@ -218,10 +218,10 @@ describe("Unit", () => { numerator: "5", }; it("async - should throw for invalid numerator", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidNumeratorJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'numerator' attribute. It should be of type 'number'.`); + await expect(Schema.fromJson(createSchemaJson(invalidNumeratorJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'numerator' attribute. It should be of type 'number'.`); }); it("sync - should throw for invalid numerator", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidNumeratorJson)), ECObjectsError, `The Unit TestUnit has an invalid 'numerator' attribute. It should be of type 'number'.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidNumeratorJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'numerator' attribute. It should be of type 'number'.`); }); // Invalid denominator @@ -232,10 +232,10 @@ describe("Unit", () => { denominator: "5", }; it("async - should throw for invalid denominator", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidDenominatorJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'denominator' attribute. It should be of type 'number'.`); + await expect(Schema.fromJson(createSchemaJson(invalidDenominatorJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'denominator' attribute. It should be of type 'number'.`); }); it("sync - should throw for invalid denominator", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDenominatorJson)), ECObjectsError, `The Unit TestUnit has an invalid 'denominator' attribute. It should be of type 'number'.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidDenominatorJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'denominator' attribute. It should be of type 'number'.`); }); // Invalid offset @@ -246,10 +246,10 @@ describe("Unit", () => { offset: "5", }; it("async - should throw for invalid offset", async () => { - await expect(Schema.fromJson(createSchemaJson(invalidOffsetJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestUnit has an invalid 'offset' attribute. It should be of type 'number'.`); + await expect(Schema.fromJson(createSchemaJson(invalidOffsetJson))).to.be.rejectedWith(ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'offset' attribute. It should be of type 'number'.`); }); it("sync - should throw for invalid offset", () => { - assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidOffsetJson)), ECObjectsError, `The Unit TestUnit has an invalid 'offset' attribute. It should be of type 'number'.`); + assert.throws(() => Schema.fromJsonSync(createSchemaJson(invalidOffsetJson)), ECObjectsError, `The Unit TestSchema.TestUnit has an invalid 'offset' attribute. It should be of type 'number'.`); }); }); describe("toJson", () => { diff --git a/core/ecschema-metadata/test/Metadata/UnitSystem.test.ts b/core/ecschema-metadata/test/Metadata/UnitSystem.test.ts index cbf6f2e..3ff7ec5 100644 --- a/core/ecschema-metadata/test/Metadata/UnitSystem.test.ts +++ b/core/ecschema-metadata/test/Metadata/UnitSystem.test.ts @@ -7,13 +7,10 @@ import { assert, expect } from "chai"; import * as sinon from "sinon"; import { Schema } from "../../src/Metadata/Schema"; import { UnitSystem } from "../../src/Metadata/UnitSystem"; -import { ECObjectsError } from "../../src/Exception"; import { schemaItemTypeToString, SchemaItemType } from "../../src/ECObjects"; -import { JsonParser } from "../../src/Deserialization/JsonParser"; describe("UnitSystem tests", () => { let testUnitSystem: UnitSystem; - const parser = new JsonParser(); describe("accept", () => { beforeEach(() => { const schema = new Schema("TestSchema", 1, 0, 0); @@ -55,87 +52,27 @@ describe("UnitSystem tests", () => { name: "IMPERIAL", label: "Imperial", }; - await testUnitSystem.deserialize(parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name)); + await testUnitSystem.deserialize(json); assert(testUnitSystem.label, "Imperial"); assert(testUnitSystem.description === undefined); }); - it("Name must be a valid ECName", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "12IMPERIAL", - label: "Imperial", - description: "Units of measure from the british imperial empire", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name), ECObjectsError, `A SchemaItem in ExampleSchema has an invalid 'name' attribute. '12IMPERIAL' is not a valid ECName.`); - }); - it("Label must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "IMPERIAL", - label: 1, - description: "Units of measure from the british imperial empire", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name), ECObjectsError, `The SchemaItem ExampleSchema.IMPERIAL has an invalid 'label' attribute. It should be of type 'string'.`); - }); - it("Description must be a string", async () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "IMPERIAL", - label: "Imperial", - description: 1, - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name), ECObjectsError, `The SchemaItem ExampleSchema.IMPERIAL has an invalid 'description' attribute. It should be of type 'string'.`); - }); - }); - describe("Sync fromJson", () => { - beforeEach(() => { - const schema = new Schema("ExampleSchema", 1, 0, 0); - testUnitSystem = new UnitSystem(schema, "IMPERIAL"); - }); - it("Basic test", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "IMPERIAL", - label: "Imperial", - }; - testUnitSystem.deserializeSync(parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name)); - assert(testUnitSystem.label, "Imperial"); - assert(testUnitSystem.description === undefined); - }); - it("Name must be a valid ECName", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "12IMPERIAL", - label: "Imperial", - description: "Units of measure from the british imperial empire", - }; - assert.throws(() => testUnitSystem.deserializeSync(parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name)), ECObjectsError, ``); - }); - it("Label must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "IMPERIAL", - label: 1, - description: "Units of measure from the british imperial empire", - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name), ECObjectsError, `The SchemaItem ExampleSchema.IMPERIAL has an invalid 'label' attribute. It should be of type 'string'.`); - }); - it("Description must be a string", () => { - const json = { - $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", - schemaItemType: "UnitSystem", - name: "IMPERIAL", - label: "Imperial", - description: 1, - }; - assert.throws(() => parser.parseSchemaItemProps(json, testUnitSystem.schema.name, json.name), ECObjectsError, `The SchemaItem ExampleSchema.IMPERIAL has an invalid 'description' attribute. It should be of type 'string'.`); + describe("Sync fromJson", () => { + beforeEach(() => { + const schema = new Schema("ExampleSchema", 1, 0, 0); + testUnitSystem = new UnitSystem(schema, "IMPERIAL"); + }); + it("Basic test", () => { + const json = { + $schema: "https://dev.bentley.com/json_schemas/ec/32/draft-01/schemaitem", + schemaItemType: "UnitSystem", + name: "IMPERIAL", + label: "Imperial", + }; + testUnitSystem.deserializeSync(json); + assert(testUnitSystem.label, "Imperial"); + assert(testUnitSystem.description === undefined); + }); }); }); }); diff --git a/core/ecschema-metadata/test/ParseUtils.test.ts b/core/ecschema-metadata/test/ParseUtils.test.ts index 0b2bdc3..809530c 100644 --- a/core/ecschema-metadata/test/ParseUtils.test.ts +++ b/core/ecschema-metadata/test/ParseUtils.test.ts @@ -109,7 +109,7 @@ describe("Parsing/ToString Functions", () => { | CustomAttributeContainerType.AnyClass | CustomAttributeContainerType.TargetRelationshipConstraint | CustomAttributeContainerType.StructProperty; - expect(containerTypeToString(combo)).to.equal("Schema,AnyClass,StructProperty,TargetRelationshipConstraint"); + expect(containerTypeToString(combo)).to.equal("Schema, AnyClass, StructProperty, TargetRelationshipConstraint"); }); it("parseRelationshipEnd", () => { diff --git a/core/ecschema-metadata/tslint.json b/core/ecschema-metadata/tslint.json index 12a5c0c..82d9fde 100644 --- a/core/ecschema-metadata/tslint.json +++ b/core/ecschema-metadata/tslint.json @@ -1,6 +1,9 @@ { "extends": "@bentley/build-tools/tslint.json", "rules": { + "await-promise": false, // WIP: should fix code so that await-promise=true can be inherited from @bentley/build-tools/tslint.json + "promise-function-async": false, // WIP: should fix code so that promise-function-async=true can be inherited from @bentley/build-tools/tslint.json + "no-floating-promises": false, // WIP: should fix code so that no-floating-promises=true can be inherited from @bentley/build-tools/tslint.json "no-conditional-assignment": true } } \ No newline at end of file diff --git a/core/frontend/CHANGELOG.json b/core/frontend/CHANGELOG.json index 5868ff4..d95d440 100644 --- a/core/frontend/CHANGELOG.json +++ b/core/frontend/CHANGELOG.json @@ -1,6 +1,165 @@ { "name": "@bentley/imodeljs-frontend", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-frontend_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "WIP: add support for schedule animation (symbology)." + }, + { + "comment": "geometry coverage" + }, + { + "comment": "geometry coverage" + }, + { + "comment": "Fix incorrect length used to create Uint32Array from Uint8Array." + }, + { + "comment": "Fix incorrect display of raster text." + }, + { + "comment": "Fix bug in which the frustum of a spatial view was always expanded to include the ground plane even if the ground plane was not displayed." + }, + { + "comment": "Fix exception when attempting to create a SubCategoryAppearance from an empty string." + }, + { + "comment": "Locate circle should be initialized to off." + }, + { + "comment": "Enable locate and hilite for point clouds." + }, + { + "comment": "Rename SimpleViewTest to display-test-app" + }, + { + "comment": "SnapStatus and LocateFailure cleanup" + }, + { + "comment": "Front end \"read pixels\" can now provide subCategoryId and GeometryClass to backend." + }, + { + "comment": "Check SubCategoryAppearance dontLocate and dontSnap now that HitDetail has subCategoryId." + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-frontend_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Fix missing uniform error in Edge browser." + }, + { + "comment": "Optimize 'pick buffer' portion of renderer." + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-frontend_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-frontend_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-frontend_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Add support for finding reality models that overlap project extent." + }, + { + "comment": "Refactor ContextRealityModelState" + }, + { + "comment": "Numerous shader program optimizations." + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-frontend_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": { + "none": [ + { + "comment": "Hydrated briefcases for ReadOnly cases from the latest checkpoint, rather than the seed files. This significantly improves performance of IModelDb/IModelConnection.open() for typical cases. " + } + ] + } + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-frontend_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": { + "none": [ + { + "comment": "Fix SelectionSet broadcasting excessive selection change events" + }, + { + "comment": "Add support for Context Reality Models" + } + ] + } + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-frontend_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "AccuDraw/AccuSnap markdown/examples" + }, + { + "comment": "Fix edge animation of PolyfaceAuxData" + }, + { + "comment": "Updated frontend performance testing" + }, + { + "comment": "Change filterHit on InteractiveTool to async to support backend queries" + }, + { + "comment": "Fix JSON representation of DisplayStyleState." + }, + { + "comment": "Fix links in tool docs" + }, + { + "comment": "Added an option to Viewport.readImage() to flip the resultant image vertically." + }, + { + "comment": "PrimitiveTool isValidLocation shouldn't require write, want check for measure tools too" + }, + { + "comment": "Add Comments" + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-frontend_v0.163.0", diff --git a/core/frontend/CHANGELOG.md b/core/frontend/CHANGELOG.md index 234706d..4facf1a 100644 --- a/core/frontend/CHANGELOG.md +++ b/core/frontend/CHANGELOG.md @@ -1,6 +1,83 @@ # Change Log - @bentley/imodeljs-frontend -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- WIP: add support for schedule animation (symbology). +- geometry coverage +- geometry coverage +- Fix incorrect length used to create Uint32Array from Uint8Array. +- Fix incorrect display of raster text. +- Fix bug in which the frustum of a spatial view was always expanded to include the ground plane even if the ground plane was not displayed. +- Fix exception when attempting to create a SubCategoryAppearance from an empty string. +- Locate circle should be initialized to off. +- Enable locate and hilite for point clouds. +- Rename SimpleViewTest to display-test-app +- SnapStatus and LocateFailure cleanup +- Front end "read pixels" can now provide subCategoryId and GeometryClass to backend. +- Check SubCategoryAppearance dontLocate and dontSnap now that HitDetail has subCategoryId. + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Fix missing uniform error in Edge browser. +- Optimize 'pick buffer' portion of renderer. + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Add support for finding reality models that overlap project extent. +- Refactor ContextRealityModelState +- Numerous shader program optimizations. + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +### Updates + +- Hydrated briefcases for ReadOnly cases from the latest checkpoint, rather than the seed files. This significantly improves performance of IModelDb/IModelConnection.open() for typical cases. + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +### Updates + +- Fix SelectionSet broadcasting excessive selection change events +- Add support for Context Reality Models + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- AccuDraw/AccuSnap markdown/examples +- Fix edge animation of PolyfaceAuxData +- Updated frontend performance testing +- Change filterHit on InteractiveTool to async to support backend queries +- Fix JSON representation of DisplayStyleState. +- Fix links in tool docs +- Added an option to Viewport.readImage() to flip the resultant image vertically. +- PrimitiveTool isValidLocation shouldn't require write, want check for measure tools too +- Add Comments +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/frontend/package.json b/core/frontend/package.json index 92b52d4..a552ef0 100644 --- a/core/frontend/package.json +++ b/core/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-frontend", - "version": "0.163.0", + "version": "0.171.0", "description": "iModel.js frontend components", "main": "lib/frontend.js", "typings": "lib/frontend", @@ -28,25 +28,25 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0", - "@bentley/imodeljs-common": "0.163.0", - "@bentley/imodeljs-i18n": "0.163.0", - "@bentley/imodeljs-quantity": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0", + "@bentley/imodeljs-common": "0.171.0", + "@bentley/imodeljs-i18n": "0.171.0", + "@bentley/imodeljs-quantity": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", - "@bentley/geometry-core": "0.163.0", - "@bentley/imodeljs-clients": "0.163.0", - "@bentley/imodeljs-common": "0.163.0", - "@bentley/imodeljs-i18n": "0.163.0", - "@bentley/imodeljs-quantity": "0.163.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", + "@bentley/geometry-core": "0.171.0", + "@bentley/imodeljs-clients": "0.171.0", + "@bentley/imodeljs-common": "0.171.0", + "@bentley/imodeljs-i18n": "0.171.0", + "@bentley/imodeljs-quantity": "0.171.0", "@types/i18next": "^8.4.2", "@types/i18next-browser-languagedetector": "^2.0.1", "@types/i18next-xhr-backend": "^1.4.1", @@ -57,7 +57,7 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0" }, "//dependencies": [ "NOTE: these dependencies should be only for things that DO NOT APPEAR IN THE API", diff --git a/core/frontend/src/AccuDraw.ts b/core/frontend/src/AccuDraw.ts index 0f01f98..b9938cc 100644 --- a/core/frontend/src/AccuDraw.ts +++ b/core/frontend/src/AccuDraw.ts @@ -186,6 +186,7 @@ export class ThreeAxes { /** * Accudraw is an aide for entering coordinate data. + * @see [Using AccuDraw]($docs/learning/frontend/primitivetools.md#AccuDraw) */ export class AccuDraw { public currentState = CurrentState.NotEnabled; // Compass state @@ -754,9 +755,9 @@ export class AccuDraw { ev.initEvent(pt, pt, vp.worldToView(pt), vp, CoordSource.User); // Send both down and up events... - IModelApp.toolAdmin.sendButtonEvent(ev); + IModelApp.toolAdmin.sendButtonEvent(ev); // tslint:disable-line:no-floating-promises ev.isDown = false; - IModelApp.toolAdmin.sendButtonEvent(ev); + IModelApp.toolAdmin.sendButtonEvent(ev); // tslint:disable-line:no-floating-promises } public clearTentative(): boolean { diff --git a/core/frontend/src/AccuSnap.ts b/core/frontend/src/AccuSnap.ts index 94d355b..80e47b8 100644 --- a/core/frontend/src/AccuSnap.ts +++ b/core/frontend/src/AccuSnap.ts @@ -17,7 +17,9 @@ import { BeDuration } from "@bentley/bentleyjs-core"; import { Decorator } from "./ViewManager"; import { SnapRequestProps, DecorationGeometryProps } from "@bentley/imodeljs-common"; -/** AccuSnap is an aide for snapping to interesting points on elements or decorations as the cursor moves over them. */ +/** AccuSnap is an aide for snapping to interesting points on elements or decorations as the cursor moves over them. + * @see [Using AccuSnap]($docs/learning/frontend/primitivetools.md#AccuSnap) + */ export class AccuSnap implements Decorator { /** Currently active hit */ public currHit?: HitDetail; @@ -290,13 +292,14 @@ export class AccuSnap implements Decorator { } /** try to indicate what's wrong with the current point (why we're not snapping). */ - private showSnapError(status: SnapStatus, ev: BeButtonEvent) { + private showSnapError(out: LocateResponse, ev: BeButtonEvent) { + this.explanation = out.explanation; + this.errorKey = out.reason; this.errorIcon.deactivate(); const vp = ev.viewport!; let errorSprite: Sprite | undefined; - switch (status) { - case SnapStatus.FilteredByUser: + switch (out.snapStatus) { case SnapStatus.FilteredByApp: errorSprite = IconSprites.getSpriteFromUrl("SnapAppFiltered.png"); break; @@ -309,11 +312,6 @@ export class AccuSnap implements Decorator { errorSprite = IconSprites.getSpriteFromUrl("SnapNotSnappable.png"); this.errorKey = ElementLocateManager.getFailureMessageKey("NotSnappable"); break; - - case SnapStatus.ModelNotSnappable: - errorSprite = IconSprites.getSpriteFromUrl("SnapNotSnappable.png"); - this.errorKey = ElementLocateManager.getFailureMessageKey("ModelNotAllowed"); - break; } if (!errorSprite) @@ -439,6 +437,14 @@ export class AccuSnap implements Decorator { } public static async requestSnap(thisHit: HitDetail, snapModes: SnapMode[], hotDistanceInches: number, keypointDivisor: number, hitList?: HitList, out?: LocateResponse): Promise { + if (undefined !== thisHit.subCategoryId) { + const appearance = thisHit.viewport.view.getSubCategoryAppearance(thisHit.subCategoryId); + if (appearance.dontSnap) { + if (out) out.snapStatus = SnapStatus.NotSnappable; + return undefined; + } + } + const requestProps: SnapRequestProps = { id: thisHit.sourceId, testPoint: thisHit.testPoint, @@ -448,6 +454,8 @@ export class AccuSnap implements Decorator { snapModes, snapAperture: thisHit.viewport.pixelsFromInches(hotDistanceInches), snapDivisor: keypointDivisor, + subCategoryId: thisHit.subCategoryId, + geometryClass: thisHit.geometryClass, }; if (!thisHit.isElementHit) { @@ -491,51 +499,12 @@ export class AccuSnap implements Decorator { } } - let result = await thisHit.viewport.iModel.requestSnap(requestProps); + const result = await thisHit.viewport.iModel.requestSnap(requestProps); if (out) out.snapStatus = result.status; if (result.status !== SnapStatus.Success) return undefined; - if (undefined !== result.category && undefined !== result.subCategory) { - const viewState = thisHit.viewport.view; - const appearance = viewState.getSubCategoryAppearance(result.subCategory); - - if (appearance.invisible) { - const subCategories = viewState.subCategories.getSubCategories(result.category); - if (undefined !== subCategories) { - requestProps.offSubCategories = [result.subCategory]; - for (const thisSub of subCategories) { - if (thisSub !== result.subCategory && viewState.getSubCategoryAppearance(thisSub).invisible) - requestProps.offSubCategories.push(thisSub); - } - } - - if (undefined === requestProps.offSubCategories) { - if (out) out.snapStatus = SnapStatus.NoSnapPossible; - return undefined; - } - - result = await thisHit.viewport.iModel.requestSnap(requestProps); // Retry snap with list of unacceptable subcategories... - - if (result.status === SnapStatus.Success && undefined !== result.subCategory) { - const appearanceRetry = viewState.getSubCategoryAppearance(result.subCategory); - if (appearanceRetry.invisible) - result.status = SnapStatus.NoSnapPossible; - else if (appearanceRetry.dontSnap) - result.status = SnapStatus.NotSnappable; - } - - if (out) out.snapStatus = result.status; - if (result.status !== SnapStatus.Success) - return undefined; - - } else if (appearance.dontSnap) { - if (out) out.snapStatus = SnapStatus.NotSnappable; - return undefined; - } - } - const snap = new SnapDetail(thisHit, result.snapMode!, result.heat!, result.snapPoint!); snap.setCurvePrimitive(undefined !== result.curve ? GeomJson.Reader.parse(result.curve) : undefined, undefined, result.geomType); if (undefined !== result.parentGeomType) @@ -564,6 +533,12 @@ export class AccuSnap implements Decorator { if (undefined === thisHit) return undefined; + const filterStatus = (this.isLocateEnabled ? IModelApp.locateManager.filterHit(thisHit, LocateAction.AutoLocate, out) : LocateFilterStatus.Accept); + if (LocateFilterStatus.Accept !== filterStatus) { + out.snapStatus = SnapStatus.FilteredByApp; + return undefined; + } + let snapModes: SnapMode[]; if (IModelApp.tentativePoint.isActive) { snapModes = []; @@ -572,17 +547,10 @@ export class AccuSnap implements Decorator { snapModes = this.getActiveSnapModes(); // Get the list of point snap modes to consider } - this.explanation = ""; const thisSnap = await AccuSnap.requestSnap(thisHit, snapModes, this._hotDistanceInches, this.keypointDivisor, hitList, out); if (undefined === thisSnap) return undefined; - const filterStatus = (this.isLocateEnabled ? IModelApp.locateManager.filterHit(thisSnap, LocateAction.AutoLocate, out) : LocateFilterStatus.Accept); - if (LocateFilterStatus.Accept !== filterStatus) { - out.snapStatus = SnapStatus.FilteredByApp; - return undefined; - } - if (IModelApp.tentativePoint.isActive) { const tpSnap = IModelApp.tentativePoint.getCurrSnap(); if (undefined === tpSnap) @@ -641,7 +609,7 @@ export class AccuSnap implements Decorator { return SnapStatus.Success; } - private findLocatableHit(ev: BeButtonEvent, newSearch: boolean, out: LocateResponse): HitDetail | undefined { + private async findLocatableHit(ev: BeButtonEvent, newSearch: boolean, out: LocateResponse): Promise { out.snapStatus = SnapStatus.NoElements; if (newSearch) { @@ -661,7 +629,7 @@ export class AccuSnap implements Decorator { const ignore = new LocateResponse(); // keep looking through hits until we find one that is accu-snappable. while (undefined !== (thisHit = thisList.getNextHit())) { - if (LocateFilterStatus.Accept === IModelApp.locateManager.filterHit(thisHit, LocateAction.AutoLocate, out)) + if (LocateFilterStatus.Accept === await IModelApp.locateManager.filterHit(thisHit, LocateAction.AutoLocate, out)) return thisHit; // we only care about the status of the first hit. @@ -696,7 +664,7 @@ export class AccuSnap implements Decorator { hit = await this.getAccuSnapDetail(this.aSnapHits, out); } } else if (this.isLocateEnabled) { - hit = this.findLocatableHit(ev, false, out); // get next AccuSnap path (or undefined) + hit = await this.findLocatableHit(ev, false, out); // get next AccuSnap path (or undefined) } // set the current hit @@ -704,7 +672,7 @@ export class AccuSnap implements Decorator { this.setCurrHit(hit); // indicate errors - this.showSnapError(out.snapStatus, ev); + this.showSnapError(out, ev); return out.snapStatus; } @@ -720,7 +688,7 @@ export class AccuSnap implements Decorator { out.snapStatus = this.findHits(ev); hit = (SnapStatus.Success !== out.snapStatus) ? undefined : await this.getAccuSnapDetail(this.aSnapHits!, out); } else if (this.isLocateEnabled) { - hit = this.findLocatableHit(ev, true, out); + hit = await this.findLocatableHit(ev, true, out); } } @@ -729,7 +697,7 @@ export class AccuSnap implements Decorator { this.setCurrHit(hit); // indicate errors - this.showSnapError(out.snapStatus, ev); + this.showSnapError(out, ev); } public onMotionStopped(_ev: BeButtonEvent): void { this._motionStopTime = Date.now(); } diff --git a/core/frontend/src/ContextRealityModelState.ts b/core/frontend/src/ContextRealityModelState.ts new file mode 100644 index 0000000..7e0fbcc --- /dev/null +++ b/core/frontend/src/ContextRealityModelState.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Views */ +import { ContextRealityModelProps, CartographicRange } from "@bentley/imodeljs-common"; +import { IModelConnection } from "./IModelConnection"; +import { TileTreeModelState } from "./ModelState"; +import { TileTree, TileTreeState } from "./tile/TileTree"; +import { RealityModelTileTree, RealityModelTileClient, RealityModelTileUtils } from "./tile/RealityModelTileTree"; + +export class ContextRealityModelState implements TileTreeModelState { + protected _tilesetUrl: string; + protected _name: string; + protected _tileTreeState: TileTreeState; + protected _iModel: IModelConnection; + constructor(props: ContextRealityModelProps, iModel: IModelConnection) { + this._name = props.name ? props.name : ""; + this._tilesetUrl = props.tilesetUrl; + this._tileTreeState = new TileTreeState(iModel, true, ""); + this._iModel = iModel; + } + public get name() { return this._name; } + public get url() { return this._tilesetUrl; } + public get tileTree(): TileTree | undefined { return this._tileTreeState.tileTree; } + public get loadStatus(): TileTree.LoadStatus { return this._tileTreeState.loadStatus; } + public loadTileTree(_asClassifier?: boolean, _classifierExpansion?: number): TileTree.LoadStatus { + const tileTreeState = this._tileTreeState; + if (TileTree.LoadStatus.NotLoaded !== tileTreeState.loadStatus) + return tileTreeState.loadStatus; + + tileTreeState.loadStatus = TileTree.LoadStatus.Loading; + + RealityModelTileTree.loadRealityModelTileTree(this._tilesetUrl, undefined, tileTreeState); + return tileTreeState.loadStatus; + } + public async intersectsProjectExtents(): Promise { + if (undefined === this._iModel.ecefLocation) + return false; + const client = new RealityModelTileClient(this._tilesetUrl); + const json = await client.getRootDocument(this._tilesetUrl); + let tileTreeRange, tileTreeTransform; + if (json === undefined || + undefined === json.root || + undefined === (tileTreeRange = RealityModelTileUtils.rangeFromBoundingVolume(json.root.boundingVolume)) || + undefined === (tileTreeTransform = RealityModelTileUtils.transformFromJson(json.root.transform))) + return false; + + const treeCartographicRange = new CartographicRange(tileTreeRange, tileTreeTransform); + const projectCartographicRange = new CartographicRange(this._iModel.projectExtents, this._iModel.ecefLocation.getTransform()); + + return treeCartographicRange.intersectsRange(projectCartographicRange); + } + public matches(other: ContextRealityModelState) { + return other.name === this.name && other.url === this.url; + } + public static findAvailableRealityModels(): ContextRealityModelProps[] { + const availableRealityModels: ContextRealityModelProps[] = []; + + /* This is location to query all reality models available for this project. They will be filtered spatially to only those + that overlap the project extents later.... */ + + /* Testing... should be quering RDS to find models. + availableRealityModels.push({ name: "Clark Island", tilesetUrl: "http://localhost:8080/clarkIsland/74/TileRoot.json" }); + availableRealityModels.push({ name: "Philadelphia LoRes", tilesetUrl: "http://localhost:8080/PhiladelphiaLoResClassification/80/TileRoot.json" }); + availableRealityModels.push({ name: "Philadelphia HiRes", tilesetUrl: "http://localhost:8080/PhiladelphiaHiResClassification/80/TileRoot.json" }); + */ + + return availableRealityModels; + } +} diff --git a/core/frontend/src/DisplayStyleState.ts b/core/frontend/src/DisplayStyleState.ts index f6a900b..3fd705d 100644 --- a/core/frontend/src/DisplayStyleState.ts +++ b/core/frontend/src/DisplayStyleState.ts @@ -4,27 +4,29 @@ *--------------------------------------------------------------------------------------------*/ /** @module Views */ import { - Light, - LightType, ViewFlags, - HiddenLine, - ColorDefProps, ColorDef, - ColorByName, DisplayStyleProps, RenderTexture, RenderMaterial, - Gradient, SubCategoryOverride, - AnalysisStyle, + SkyBoxProps, + SkyBoxImageType, + SkyCubeProps, + EnvironmentProps, + GroundPlane, + DisplayStyleSettings, + DisplayStyle3dSettings, + BackgroundMapProps, } from "@bentley/imodeljs-common"; import { ElementState } from "./EntityState"; import { IModelConnection } from "./IModelConnection"; import { JsonUtils, Id64, Id64String } from "@bentley/bentleyjs-core"; -import { Vector3d } from "@bentley/geometry-core"; import { RenderSystem, TextureImage } from "./render/System"; import { BackgroundMapState } from "./tile/WebMercatorTileTree"; +import { TileTreeModelState } from "./ModelState"; import { Plane3dByOriginAndUnitNormal } from "@bentley/geometry-core"; +import { ContextRealityModelState } from "./ContextRealityModelState"; /** A DisplayStyle defines the parameters for 'styling' the contents of a [[ViewState]] * @note If the DisplayStyle is associated with a [[ViewState]] which is being rendered inside a [[Viewport]], modifying @@ -32,95 +34,75 @@ import { Plane3dByOriginAndUnitNormal } from "@bentley/geometry-core"; * [[ViewState]] provides APIs which forward to the DisplayStyle API and also ensure the screen is updated promptly. */ export abstract class DisplayStyleState extends ElementState implements DisplayStyleProps { - private readonly _viewFlags: ViewFlags; - private readonly _background: ColorDef; - private readonly _monochrome: ColorDef; - private readonly _subCategoryOverrides: Map = new Map(); - private readonly _analysisStyle?: AnalysisStyle; private _backgroundMap: BackgroundMapState; + private _contextRealityModels: ContextRealityModelState[]; + + public abstract get settings(): DisplayStyleSettings; constructor(props: DisplayStyleProps, iModel: IModelConnection) { super(props, iModel); - this._viewFlags = ViewFlags.fromJSON(this.getStyle("viewflags")); - this._background = ColorDef.fromJSON(this.getStyle("backgroundColor")); - this._analysisStyle = AnalysisStyle.fromJSON(this.styles.analysisStyle); - const monoName = "monochromeColor"; // because tslint: "object access via string literals is disallowed"... - const monoJson = this.styles[monoName]; - this._monochrome = undefined !== monoJson ? ColorDef.fromJSON(monoJson) : ColorDef.white.clone(); - this._backgroundMap = new BackgroundMapState(this.getStyle("backgroundMap"), iModel); - - // Read subcategory overrides. - // ###TODO: overrideSubCategory() and dropSubCategoryOverride() should be updating this element's JSON properties... - // NB: Not using this.getStyle() because it inserts the style as an object if not found, but this style is supposed to be an array... - const jsonProps = JsonUtils.asObject(props.jsonProperties); - const styles = undefined !== jsonProps ? JsonUtils.asObject(jsonProps.styles) : undefined; - const ovrsArray = undefined !== styles ? JsonUtils.asArray(styles.subCategoryOvr) : undefined; - if (undefined !== ovrsArray) { - for (const ovrJson of ovrsArray) { - const subCatId = Id64.fromJSON(ovrJson.subCategory); - if (Id64.isValid(subCatId)) { - const subCatOvr = SubCategoryOverride.fromJSON(ovrJson); - if (subCatOvr.anyOverridden) - this.overrideSubCategory(subCatId, subCatOvr); - } - } - } + const styles = this.jsonProperties.styles; + const backgroundMap = undefined !== styles ? styles.backgroundMap : undefined; + const mapProps = undefined !== backgroundMap ? backgroundMap : {}; + this._backgroundMap = new BackgroundMapState(mapProps, iModel); + this._contextRealityModels = []; + + if (styles && styles.ContextRealityModels) + for (const contextRealityModel of styles.contextRealityModels) + this._contextRealityModels.push(new ContextRealityModelState(contextRealityModel, this.iModel)); } /** @hidden */ - public syncBackgroundMapState() { - this._backgroundMap = new BackgroundMapState(this.getStyle("backgroundMap"), this.iModel); + public setBackgroundMap(mapProps: BackgroundMapProps): void { + if (!this.backgroundMap.equalsProps(mapProps)) { + this._backgroundMap = new BackgroundMapState(mapProps, this.iModel); + this.settings.backgroundMap = mapProps; + } + } + /** @hidden */ + public forEachContextRealityModel(func: (model: TileTreeModelState) => void): void { + for (const contextRealityModel of this._contextRealityModels) { func(contextRealityModel); } } - public equalState(other: DisplayStyleState): boolean { - return JSON.stringify(this.styles) === JSON.stringify(other.styles); + return JSON.stringify(this.settings) === JSON.stringify(other.settings); } /** @hidden */ public get backgroundMap() { return this._backgroundMap; } - public get AnalysisStyle() { return this._analysisStyle; } /** Get the name of this DisplayStyle */ public get name(): string { return this.code.getValue(); } - /** The ViewFlags associated with this style. - * @note If this style is associated with a [[ViewState]] attached to a [[Viewport]], use [[ViewState.viewFlags]] to modify the ViewFlags to ensure - * the changes are promptly visible on the screen. - */ - public get viewFlags(): ViewFlags { return this._viewFlags; } - public set viewFlags(flags: ViewFlags) { - flags.clone(this._viewFlags); - this.setStyle("viewflags", flags); - } + /** @hidden */ + public get contextRealityModels(): ContextRealityModelState[] { return this._contextRealityModels; } + /** @hidden */ + public set contextRealityModels(contextRealityModels: ContextRealityModelState[]) { this._contextRealityModels = contextRealityModels; } /** @hidden */ - public get styles(): any { - const p = this.jsonProperties as any; - if (undefined === p.styles) - p.styles = new Object(); + public containsContextRealityModel(contextRealityModel: ContextRealityModelState) { + for (const curr of this._contextRealityModels) + if (curr.matches(contextRealityModel)) + return true; - return p.styles; + return false; } - /** @hidden */ - public getStyle(name: string): any { - const style: object = this.styles[name]; - return style ? style : {}; - } - /** @hidden */ - public setStyle(name: string, value: any): void { this.styles[name] = value; } - /** @hidden */ - public removeStyle(name: string) { delete this.styles[name]; } + /** The ViewFlags associated with this style. + * @note If this style is associated with a [[ViewState]] attached to a [[Viewport]], use [[ViewState.viewFlags]] to modify the ViewFlags to ensure + * the changes are promptly visible on the screen. + */ + public get viewFlags(): ViewFlags { return this.settings.viewFlags; } + public set viewFlags(flags: ViewFlags) { this.settings.viewFlags = flags; } /** The background color for this DisplayStyle */ - public get backgroundColor(): ColorDef { return this._background; } - public set backgroundColor(val: ColorDef) { this._background.setFrom(val); this.setStyle("backgroundColor", val); } + public get backgroundColor(): ColorDef { return this.settings.backgroundColor; } + public set backgroundColor(val: ColorDef) { this.settings.backgroundColor = val; } /** The color used to draw geometry in monochrome mode. * @see [[ViewFlags.monochrome]] for enabling monochrome mode. */ - public get monochromeColor(): ColorDef { return this._monochrome; } - public set monochromeColor(val: ColorDef) { this._monochrome.setFrom(val); this.setStyle("monochromeColor", val); } + public get monochromeColor(): ColorDef { return this.settings.monochromeColor; } + public set monochromeColor(val: ColorDef) { this.settings.monochromeColor = val; } /** @hidden */ public get backgroundMapPlane(): Plane3dByOriginAndUnitNormal | undefined { return this.viewFlags.backgroundMap ? this.backgroundMap.getPlane() : undefined; } @@ -134,9 +116,7 @@ export abstract class DisplayStyleState extends ElementState implements DisplayS * the changes are promptly visible on the screen. * @see [[dropSubCategoryOverride]] */ - public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride) { - this._subCategoryOverrides.set(id.toString(), ovr); - } + public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride) { this.settings.overrideSubCategory(id, ovr); } /** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style. * @param id The ID of the [[SubCategory]]. @@ -144,153 +124,31 @@ export abstract class DisplayStyleState extends ElementState implements DisplayS * the changes are promptly visible on the screen. * @see [[overrideSubCategory]] */ - public dropSubCategoryOverride(id: Id64String) { - this._subCategoryOverrides.delete(id.toString()); - } + public dropSubCategoryOverride(id: Id64String) { this.settings.dropSubCategoryOverride(id); } /** Returns true if an [[SubCategoryOverride]s are defined by this style. */ - public get hasSubCategoryOverride() { return this._subCategoryOverrides.entries.length > 0; } + public get hasSubCategoryOverride() { return this.settings.hasSubCategoryOverride; } /** Obtain the overrides applied to a [[SubCategoryAppearance]] by this style. * @param id The ID of the [[SubCategory]]. * @returns The corresponding SubCategoryOverride, or undefined if the SubCategory's appearance is not overridden. * @see [[overrideSubCategory]] */ - public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined { - return this._subCategoryOverrides.get(id.toString()); - } + public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined { return this.settings.getSubCategoryOverride(id); } } /** A DisplayStyle for 2d views */ export class DisplayStyle2dState extends DisplayStyleState { - constructor(props: DisplayStyleProps, iModel: IModelConnection) { super(props, iModel); } -} - -/** JSON representation of a [[GroundPlane]]. */ -export interface GroundPlaneProps { - /** Whether the ground plane should be displayed. Defaults to false. */ - display?: boolean; - /** The Z height at which to draw the ground plane. */ - elevation?: number; - /** The color in which to draw the ground plane when viewed from above. */ - aboveColor?: ColorDefProps; - /** The color in which to draw the ground plane when viewed from below. */ - belowColor?: ColorDefProps; -} + private readonly _settings: DisplayStyleSettings; -/** A circle drawn at a Z elevation, whose diameter is the the XY diagonal of the project extents, used to represent the ground as a reference point within a spatial view. */ -export class GroundPlane implements GroundPlaneProps { - /** Whether the ground plane should be displayed. */ - public display: boolean = false; - /** The Z height at which to draw the plane. */ - public elevation: number = 0.0; - /** The color in which to draw the ground plane when viewed from above. */ - public aboveColor: ColorDef; - /** The color in which to draw the ground plane when viewed from below. */ - public belowColor: ColorDef; - private _aboveSymb?: Gradient.Symb; - private _belowSymb?: Gradient.Symb; - - public constructor(ground?: GroundPlaneProps) { - ground = ground ? ground : {}; - this.display = JsonUtils.asBool(ground.display, false); - this.elevation = JsonUtils.asDouble(ground.elevation, -.01); - this.aboveColor = (undefined !== ground.aboveColor) ? ColorDef.fromJSON(ground.aboveColor) : new ColorDef(ColorByName.darkGreen); - this.belowColor = (undefined !== ground.belowColor) ? ColorDef.fromJSON(ground.belowColor) : new ColorDef(ColorByName.darkBrown); - } + public get settings(): DisplayStyleSettings { return this._settings; } - /** - * Returns and locally stores gradient symbology for the ground plane texture depending on whether we are looking from above or below. - * Will store the ground colors used in the optional ColorDef array provided. - * @hidden - */ - public getGroundPlaneGradient(aboveGround: boolean): Gradient.Symb { - let gradient = aboveGround ? this._aboveSymb : this._belowSymb; - if (undefined !== gradient) - return gradient; - - const values = [0, .25, .5]; // gradient goes from edge of rectangle (0.0) to center (1.0)... - const color = aboveGround ? this.aboveColor : this.belowColor; - const alpha = aboveGround ? 0x80 : 0x85; - const groundColors = [color.clone(), color.clone(), color.clone()]; - groundColors[0].setTransparency(0xff); - groundColors[1].setTransparency(alpha); - groundColors[2].setTransparency(alpha); - - // Get the possibly cached gradient from the system, specific to whether or not we want ground from above or below. - gradient = new Gradient.Symb(); - gradient.mode = Gradient.Mode.Spherical; - gradient.keys = [{ color: groundColors[0], value: values[0] }, { color: groundColors[1], value: values[1] }, { color: groundColors[2], value: values[2] }]; - - // Store the gradient for possible future use - if (aboveGround) - this._aboveSymb = gradient; - else - this._belowSymb = gradient; - - return gradient; + constructor(props: DisplayStyleProps, iModel: IModelConnection) { + super(props, iModel); + this._settings = new DisplayStyleSettings(this.jsonProperties); } } -/** Enumerates the supported types of [[SkyBox]] images. */ -export const enum SkyBoxImageType { - None, - /** A single image mapped to the surface of a sphere. @see [[SkySphere]] */ - Spherical, - /** 6 images mapped to the faces of a cube. @see [[SkyCube]] */ - Cube, - /** @hidden not yet supported */ - Cylindrical, -} - -/** JSON representation of a set of images used by a [[SkyCube]]. Each property specifies the element ID of a texture associated with one face of the cube. */ -export interface SkyCubeProps { - front?: Id64String; - back?: Id64String; - top?: Id64String; - bottom?: Id64String; - right?: Id64String; - left?: Id64String; -} - -/** JSON representation of an image or images used by a [[SkySphere]] or [[SkyCube]]. */ -export interface SkyBoxImageProps { - /** The type of skybox image. */ - type?: SkyBoxImageType; - /** For [[SkyBoxImageType.Spherical]], the element ID of the texture to be drawn as the "sky". */ - texture?: Id64String; - /** For [[SkyBoxImageType.Cube]], the IDs of the texture elements drawn on each face of the cube. */ - textures?: SkyCubeProps; -} - -/** JSON representation of a [[SkyBox]]. */ -export interface SkyBoxProps { - /** Whether or not the skybox should be displayed. Defaults to false. */ - display?: boolean; - /** @hidden ###TODO Figure out how this is used... */ - twoColor?: boolean; - /** @hidden ###TODO Figure out how this is used... */ - groundExponent?: number; - /** @hidden ###TODO Figure out how this is used... */ - skyExponent?: number; - /** For a [[SkyGradient]], the color of the ground. */ - groundColor?: ColorDefProps; - /** @hidden ###TODO Figure out how this is used... */ - zenithColor?: ColorDefProps; - /** @hidden ###TODO Figure out how this is used... */ - nadirColor?: ColorDefProps; - /** For a [[SkyGradient]], the color of the sky. */ - skyColor?: ColorDefProps; - /** For a [[SkySphere]] or [[SkyCube]], the skybox image(s). */ - image?: SkyBoxImageProps; -} - -/** JSON representation of the environment setup of a [[DisplayStyle3dState]]. */ -export interface EnvironmentProps { - ground?: GroundPlaneProps; - sky?: SkyBoxProps; -} - /** The SkyBox is part of an [[Environment]] drawn in the background of spatial views to provide context. * Several types of skybox are supported: * - A cube with a texture image mapped to each face; @@ -553,7 +411,7 @@ export class Environment implements EnvironmentProps { public toJSON(): EnvironmentProps { return { sky: this.sky.toJSON(), - ground: this.ground, // ###TODO GroundPlane.toJSON missing...but lots of JSON inconsistencies associated with DisplayStyle...fix them all up later? + ground: this.ground.toJSON(), }; } } @@ -565,67 +423,27 @@ export class DisplayStyle3dState extends DisplayStyleState { private _skyBoxParams?: SkyBox.CreateParams; private _skyBoxParamsLoaded?: boolean; private _environment?: Environment; + private _settings: DisplayStyle3dSettings; - public constructor(props: DisplayStyleProps, iModel: IModelConnection) { super(props, iModel); } - - public getHiddenLineParams(): HiddenLine.Settings { return HiddenLine.Settings.fromJSON(this.getStyle("hline")); } - public setHiddenLineParams(params: HiddenLine.Settings) { this.setStyle("hline", params.toJSON()); } - - /** change one of the scene light specifications (Ambient, Flash, or Portrait) for this display style - * @hidden - */ - public setSceneLight(light: Light) { - if (!light.isValid) - return; - - const sceneLights = this.getStyle("sceneLights"); - switch (light.lightType) { - case LightType.Ambient: - sceneLights.ambient = light; - break; - - case LightType.Flash: - sceneLights.flash = light; - break; - - case LightType.Portrait: - sceneLights.portrait = light; - break; - } - this.setStyle("sceneLights", sceneLights); - } + public get settings(): DisplayStyle3dSettings { return this._settings; } - /** change the light specification and direction of the solar light for this display style - * @hidden - */ - public setSolarLight(light: Light, direction: Vector3d) { - const sceneLights = this.getStyle("sceneLights"); - if (light.lightType !== LightType.Solar || !light.isValid) { - delete sceneLights.sunDir; - } else { - sceneLights.sun = light; - sceneLights.sunDir = direction; - } - this.setStyle("sceneLights", sceneLights); + public constructor(props: DisplayStyleProps, iModel: IModelConnection) { + super(props, iModel); + this._settings = new DisplayStyle3dSettings(this.jsonProperties); } /** The [[SkyBox]] and [[GroundPlane]] settings for this style. */ public get environment(): Environment { if (undefined === this._environment) - this._environment = new Environment(this.getStyle("environment")); + this._environment = new Environment(this.settings.environment); return this._environment; } public set environment(env: Environment) { - this.setStyle("environment", env.toJSON()); + this.settings.environment = env.toJSON(); this._environment = undefined; } - /** @hidden */ - public setSceneBrightness(fstop: number): void { fstop = Math.max(-3.0, Math.min(fstop, 3.0)); this.getStyle("sceneLights").fstop = fstop; } - /** @hidden */ - public getSceneBrightness(): number { return JsonUtils.asDouble(this.getStyle("sceneLights").fstop, 0.0); } - /** Attempts to create textures for the sky of the environment, and load it into the sky. Returns true on success, and false otherwise. * @hidden */ diff --git a/core/frontend/src/ElementLocateManager.ts b/core/frontend/src/ElementLocateManager.ts index bbfff67..394d3b3 100644 --- a/core/frontend/src/ElementLocateManager.ts +++ b/core/frontend/src/ElementLocateManager.ts @@ -34,9 +34,6 @@ export const enum SnapStatus { Disabled = 100, NoSnapPossible = 200, NotSnappable = 300, - ModelNotSnappable = 301, - FilteredByCategory = 400, - FilteredByUser = 500, FilteredByApp = 600, FilteredByAppQuietly = 700, } @@ -143,45 +140,49 @@ export class ElementPicker { const testPointView = new Point2d(Math.floor(pickPointView.x + 0.5), Math.floor(pickPointView.y + 0.5)); const pixelRadius = Math.floor(pickRadiusView + 0.5); const rect = new ViewRect(testPointView.x - pixelRadius, testPointView.y - pixelRadius, testPointView.x + pixelRadius, testPointView.y + pixelRadius); - const pixels = vp.readPixels(rect, Pixel.Selector.All); - if (undefined === pixels) - return 0; - - const elmHits = new Map(); - const testPoint = Point2d.createZero(); - for (testPoint.x = testPointView.x - pixelRadius; testPoint.x <= testPointView.x + pixelRadius; ++testPoint.x) { - for (testPoint.y = testPointView.y - pixelRadius; testPoint.y <= testPointView.y + pixelRadius; ++testPoint.y) { - const pixel = pixels.getPixel(testPoint.x, testPoint.y); - if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) - continue; // no geometry at this location... - const distXY = testPointView.distance(testPoint); - if (distXY > pixelRadius) - continue; // ignore corners. it's a locate circle not square... - const oldPoint = elmHits.get(pixel.elementId.toString()); - if (undefined !== oldPoint) { - if (this.comparePixel(pixel, pixels.getPixel(oldPoint.x, oldPoint.y), distXY, testPointView.distance(oldPoint)) < 0) - oldPoint.setFrom(testPoint); // new hit is better, update location... - } else { - elmHits.set(pixel.elementId.toString(), testPoint.clone()); + let result: number = 0; + vp.readPixels(rect, Pixel.Selector.All, (pixels) => { + if (undefined === pixels) + return; + + const elmHits = new Map(); + const testPoint = Point2d.createZero(); + for (testPoint.x = testPointView.x - pixelRadius; testPoint.x <= testPointView.x + pixelRadius; ++testPoint.x) { + for (testPoint.y = testPointView.y - pixelRadius; testPoint.y <= testPointView.y + pixelRadius; ++testPoint.y) { + const pixel = pixels.getPixel(testPoint.x, testPoint.y); + if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) + continue; // no geometry at this location... + const distXY = testPointView.distance(testPoint); + if (distXY > pixelRadius) + continue; // ignore corners. it's a locate circle not square... + const oldPoint = elmHits.get(pixel.elementId); + if (undefined !== oldPoint) { + if (this.comparePixel(pixel, pixels.getPixel(oldPoint.x, oldPoint.y), distXY, testPointView.distance(oldPoint)) < 0) + oldPoint.setFrom(testPoint); // new hit is better, update location... + } else { + elmHits.set(pixel.elementId, testPoint.clone()); + } } } - } - if (0 === elmHits.size) - return 0; - - for (const elmPoint of elmHits.values()) { - const pixel = pixels.getPixel(elmPoint.x, elmPoint.y); - if (undefined === pixel || undefined === pixel.elementId) - continue; - const hitPointWorld = vp.getPixelDataWorldPoint(pixels, elmPoint.x, elmPoint.y); - if (undefined === hitPointWorld) - continue; - const hit = new HitDetail(pickPointWorld, vp, options.hitSource, hitPointWorld, pixel.elementId.toString(), this.getPixelPriority(pixel), testPointView.distance(elmPoint), pixel.distanceFraction); - this.hitList!.addHit(hit); - if (this.hitList!.hits.length > options.maxHits) - this.hitList!.hits.length = options.maxHits; // truncate array... - } - return this.hitList!.length; + if (0 === elmHits.size) + return; + + for (const elmPoint of elmHits.values()) { + const pixel = pixels.getPixel(elmPoint.x, elmPoint.y); + if (undefined === pixel || undefined === pixel.elementId) + continue; + const hitPointWorld = vp.getPixelDataWorldPoint(pixels, elmPoint.x, elmPoint.y); + if (undefined === hitPointWorld) + continue; + const hit = new HitDetail(pickPointWorld, vp, options.hitSource, hitPointWorld, pixel.elementId, this.getPixelPriority(pixel), testPointView.distance(elmPoint), pixel.distanceFraction, pixel.subCategoryId, pixel.geometryClass); + this.hitList!.addHit(hit); + if (this.hitList!.hits.length > options.maxHits) + this.hitList!.hits.length = options.maxHits; // truncate array... + } + result = this.hitList!.length; + }); + + return result; } public testHit(hit: HitDetail, vp: ScreenViewport, pickPointWorld: Point3d, pickRadiusView: number, options: LocateOptions): boolean { @@ -227,20 +228,28 @@ export class ElementLocateManager { return preLocated; } - public filterHit(hit: HitDetail, _action: LocateAction, out: LocateResponse): LocateFilterStatus { + public async filterHit(hit: HitDetail, _action: LocateAction, out: LocateResponse): Promise { // Tools must opt-in to locate of transient geometry as it requires special treatment. if (!this.options.allowDecorations && !hit.isElementHit) { out.reason = ElementLocateManager.getFailureMessageKey("Transient"); return LocateFilterStatus.Reject; } + if (undefined !== hit.subCategoryId) { + const appearance = hit.viewport.view.getSubCategoryAppearance(hit.subCategoryId); + if (appearance.dontLocate) { + out.reason = ElementLocateManager.getFailureMessageKey("NotLocatable"); + return LocateFilterStatus.Reject; + } + } + const tool = IModelApp.toolAdmin.activeTool; if (!(tool && tool instanceof InteractiveTool)) return LocateFilterStatus.Accept; - const status = tool.filterHit(hit, out); + const status = await tool.filterHit(hit, out); if (LocateFilterStatus.Reject === status) - out.reason = ElementLocateManager.getFailureMessageKey("ByCommand"); + out.reason = ElementLocateManager.getFailureMessageKey("ByApp"); return status; } @@ -253,7 +262,7 @@ export class ElementLocateManager { IModelApp.tentativePoint.clear(true); } - private _doLocate(response: LocateResponse, newSearch: boolean, testPoint: Point3d, vp: ScreenViewport | undefined, source: InputSource, filterHits: boolean): HitDetail | undefined { + private async _doLocate(response: LocateResponse, newSearch: boolean, testPoint: Point3d, vp: ScreenViewport | undefined, source: InputSource, filterHits: boolean): Promise { if (!vp) return; @@ -263,7 +272,7 @@ export class ElementLocateManager { // if we're snapped to something, that path has the highest priority and becomes the active hit. if (hit) { - if (!filterHits || LocateFilterStatus.Accept === this.filterHit(hit, LocateAction.Identify, response)) + if (!filterHits || LocateFilterStatus.Accept === await this.filterHit(hit, LocateAction.Identify, response)) return hit; response = new LocateResponse(); // we have the reason and explanation we want. @@ -278,7 +287,7 @@ export class ElementLocateManager { let newHit: HitDetail | undefined; while (undefined !== (newHit = this.getNextHit())) { - if (!filterHits || LocateFilterStatus.Accept === this.filterHit(newHit, LocateAction.Identify, response)) + if (!filterHits || LocateFilterStatus.Accept === await this.filterHit(newHit, LocateAction.Identify, response)) return newHit; response = new LocateResponse(); // we have the reason and explanation we want. } @@ -286,11 +295,11 @@ export class ElementLocateManager { return undefined; } - public doLocate(response: LocateResponse, newSearch: boolean, testPoint: Point3d, view: ScreenViewport | undefined, source: InputSource, filterHits = true): HitDetail | undefined { + public async doLocate(response: LocateResponse, newSearch: boolean, testPoint: Point3d, view: ScreenViewport | undefined, source: InputSource, filterHits = true): Promise { response.reason = ElementLocateManager.getFailureMessageKey("NoElements"); response.explanation = ""; - const hit = this._doLocate(response, newSearch, testPoint, view, source, filterHits); + const hit = await this._doLocate(response, newSearch, testPoint, view, source, filterHits); this.setCurrHit(hit); // if we found a hit, remove it from the list of remaining hits near the current search point. diff --git a/core/frontend/src/EntityState.ts b/core/frontend/src/EntityState.ts index 749849f..1417f56 100644 --- a/core/frontend/src/EntityState.ts +++ b/core/frontend/src/EntityState.ts @@ -13,7 +13,7 @@ export class EntityState implements EntityProps { public readonly id: Id64String; public readonly iModel: IModelConnection; public readonly classFullName: string; - public readonly jsonProperties: any; + public readonly jsonProperties: { [key: string]: any }; public static schemaName = "BisCore"; /** diff --git a/core/frontend/src/HitDetail.ts b/core/frontend/src/HitDetail.ts index 5148f26..8b00de1 100644 --- a/core/frontend/src/HitDetail.ts +++ b/core/frontend/src/HitDetail.ts @@ -10,7 +10,7 @@ import { IModelApp } from "./IModelApp"; import { Id64 } from "@bentley/bentleyjs-core"; import { DecorateContext } from "./ViewContext"; import { GraphicType } from "./render/GraphicBuilder"; -import { LinePixels } from "@bentley/imodeljs-common"; +import { LinePixels, GeometryClass } from "@bentley/imodeljs-common"; export const enum SnapMode { Nearest = 1, @@ -98,17 +98,20 @@ export class HitDetail { * @param priority The hit geometry priority/classification. * @param distXY The xy distance to hit in view coordinates. * @param distFraction The near plane distance fraction to hit. + * @param subCategoryId The SubCategory for a persistent element hit. + * @param geometryClass The GeometryClass for a persistent element hit. */ public constructor(public readonly testPoint: Point3d, public readonly viewport: ScreenViewport, public readonly hitSource: HitSource, - public readonly hitPoint: Point3d, public readonly sourceId: string, public readonly priority: HitPriority, public readonly distXY: number, public readonly distFraction: number) { } + public readonly hitPoint: Point3d, public readonly sourceId: string, public readonly priority: HitPriority, public readonly distXY: number, public readonly distFraction: number, + public readonly subCategoryId?: string, public readonly geometryClass?: GeometryClass) { } /** Get the type of HitDetail. * @returns HitDetailType.Hit if this is a HitDetail, HitDetailType.Snap if it is a SnapDetail */ public getHitType(): HitDetailType { return HitDetailType.Hit; } - /** Get the *hit point* for this HitDetail. If this is a HitDetail, it returns the approximate point on the element that cause the hit. - * If this is a SnapDetail, and if the snap is *hot*, returns the *exact* point on the Element for the snap mode. + /** Get the *hit point* for this HitDetail. Returns the approximate point on the element that caused the hit when not a SnapDetail or IntersectDetail. + * For a snap that is *hot*, the *exact* point on the Element for the snap mode is returned, otherwise the close point on the hit geometry is returned. */ public getPoint(): Point3d { return this.hitPoint; } @@ -119,7 +122,7 @@ export class HitDetail { // return whether the sourceId is for a model (reality models etc.) public get isModelHit(): boolean { return this.viewport.iModel.models.getLoaded(this.sourceId) !== undefined; } /** Create a deep copy of this HitDetail */ - public clone(): HitDetail { const val = new HitDetail(this.testPoint, this.viewport, this.hitSource, this.hitPoint, this.sourceId, this.priority, this.distXY, this.distFraction); return val; } + public clone(): HitDetail { const val = new HitDetail(this.testPoint, this.viewport, this.hitSource, this.hitPoint, this.sourceId, this.priority, this.distXY, this.distFraction, this.subCategoryId, this.geometryClass); return val; } /** Draw this HitDetail as a Decoration. Causes the picked element to *flash* */ public draw(_context: DecorateContext) { this.viewport.setFlashed(this.sourceId, 0.25); } @@ -167,7 +170,7 @@ export class SnapDetail extends HitDetail { * @param snapPoint The snapped point in the element */ public constructor(from: HitDetail, public snapMode: SnapMode = SnapMode.Nearest, public heat: SnapHeat = SnapHeat.None, snapPoint?: XYZProps) { - super(from.testPoint, from.viewport, from.hitSource, from.hitPoint, from.sourceId, from.priority, from.distXY, from.distFraction); + super(from.testPoint, from.viewport, from.hitSource, from.hitPoint, from.sourceId, from.priority, from.distXY, from.distFraction, from.subCategoryId, from.geometryClass); this.snapPoint = Point3d.fromJSON(snapPoint ? snapPoint : from.hitPoint); this.adjustedPoint = this.snapPoint.clone(); this.sprite = IconSprites.getSpriteFromUrl(SnapDetail.getSnapSpriteUrl(snapMode)); diff --git a/core/frontend/src/IModelApp.ts b/core/frontend/src/IModelApp.ts index 7bcca37..201b664 100644 --- a/core/frontend/src/IModelApp.ts +++ b/core/frontend/src/IModelApp.ts @@ -94,7 +94,7 @@ export class IModelApp { * MyApp.startup(); * ``` */ - public static startup(imodelClient?: IModelClient) { + public static startup(imodelClient?: IModelClient): void { if (IModelApp._initialized) throw new IModelError(IModelStatus.AlreadyLoaded, "startup may only be called once"); diff --git a/core/frontend/src/IModelConnection.ts b/core/frontend/src/IModelConnection.ts index 2dfe1ea..6bd7edf 100644 --- a/core/frontend/src/IModelConnection.ts +++ b/core/frontend/src/IModelConnection.ts @@ -48,7 +48,7 @@ export class IModelConnection extends IModel { /** A unique Id of this IModelConnection. */ public readonly connectionId = Guid.createValue(); /** The maximum time (in milliseconds) to wait before timing out the request to open a connection to a new iModel */ - private static _connectionTimeout: number = 5 * 60 * 1000; + private static _connectionTimeout: number = 10 * 60 * 1000; private _openAccessToken?: AccessToken; /** Check the [[openMode]] of this IModelConnection to see if it was opened read-only. */ @@ -166,7 +166,7 @@ export class IModelConnection extends IModel { const connectionTimeElapsed = Date.now() - startTime; if (connectionTimeElapsed > IModelConnection._connectionTimeout) { - Logger.logTrace(loggingCategory, `Timed out opening connection in IModelConnection.open (took longer than ${IModelConnection._connectionTimeout} milliseconds)`, () => ({ ...iModelToken, openMode })); + Logger.logError(loggingCategory, `Timed out opening connection in IModelConnection.open (took longer than ${IModelConnection._connectionTimeout} milliseconds)`, () => ({ ...iModelToken, openMode })); throw new IModelError(BentleyStatus.ERROR, "Opening a connection was timed out"); // NEEDS_WORK: More specific error status } @@ -311,7 +311,7 @@ export class IModelConnection extends IModel { * @returns Returns true if the *Change Cache file* is attached to the iModel. false otherwise * @hidden */ - public async changeCacheAttached(): Promise { return await WipRpcInterface.getClient().isChangeCacheAttached(this.iModelToken); } + public async changeCacheAttached(): Promise { return WipRpcInterface.getClient().isChangeCacheAttached(this.iModelToken); } /** * WIP - Attaches the *Change Cache file* to this iModel if it hasn't been attached yet. @@ -320,7 +320,7 @@ export class IModelConnection extends IModel { * @throws [IModelError]($common) if a Change Cache file has already been attached before. * @hidden */ - public async attachChangeCache(): Promise { await WipRpcInterface.getClient().attachChangeCache(this.iModelToken); } + public async attachChangeCache(): Promise { return WipRpcInterface.getClient().attachChangeCache(this.iModelToken); } /** * WIP - Detaches the *Change Cache file* to this iModel if it had been attached before. @@ -329,7 +329,7 @@ export class IModelConnection extends IModel { * See also [Change Summary Overview]($docs/learning/ChangeSummaries) * @hidden */ - public async detachChangeCache(): Promise { await WipRpcInterface.getClient().detachChangeCache(this.iModelToken); } + public async detachChangeCache(): Promise { return WipRpcInterface.getClient().detachChangeCache(this.iModelToken); } /** * Execute a test by name diff --git a/core/frontend/src/Marker.ts b/core/frontend/src/Marker.ts index a744010..8a6a92c 100644 --- a/core/frontend/src/Marker.ts +++ b/core/frontend/src/Marker.ts @@ -48,7 +48,7 @@ export class Marker implements CanvasDecoration { public imageOffset?: XAndY; /** The size of [[image]], in pixels. If undefined, use [[size]]. */ public imageSize?: XAndY; - /** A text Label for this Maker. If undefined, no label is displayed. */ + /** A text Label for this Marker. If undefined, no label is displayed. */ public label?: string; /** The offset for [[label]], in pixels, from the *center* of this Marker. If undefined, (0,0). */ public labelOffset?: XAndY; @@ -172,7 +172,7 @@ export class Marker implements CanvasDecoration { */ public setImage(image: MarkerImage | Promise) { if (image instanceof Promise) - image.then((resolvedImage) => this.image = resolvedImage); + image.then((resolvedImage) => this.image = resolvedImage); // tslint:disable-line:no-floating-promises else this.image = image; } diff --git a/core/frontend/src/ModelSelectorState.ts b/core/frontend/src/ModelSelectorState.ts index b73737e..48ba128 100644 --- a/core/frontend/src/ModelSelectorState.ts +++ b/core/frontend/src/ModelSelectorState.ts @@ -61,5 +61,5 @@ export class ModelSelectorState extends ElementState { public containsModel(modelId: Id64String): boolean { return this.has(modelId.toString()); } /** Make sure all models referenced by this ModelSelectorState are loaded. */ - public load(): Promise { return this.iModel.models.load(this.models); } + public async load(): Promise { return this.iModel.models.load(this.models); } } diff --git a/core/frontend/src/ModelState.ts b/core/frontend/src/ModelState.ts index ff45804..7dddea9 100644 --- a/core/frontend/src/ModelState.ts +++ b/core/frontend/src/ModelState.ts @@ -54,14 +54,19 @@ export class ModelState extends EntityState implements ModelProps { public useRangeForFit(): boolean { return true; } } +export interface TileTreeModelState { + readonly tileTree: TileTree | undefined; + readonly loadStatus: TileTree.LoadStatus; + loadTileTree(asClassifier?: boolean, classifierExpansion?: number): TileTree.LoadStatus; +} /** Represents the front-end state of a [GeometricModel]($backend). * The contents of a GeometricModelState can be rendered inside a [[Viewport]]. */ -export abstract class GeometricModelState extends ModelState { +export abstract class GeometricModelState extends ModelState implements TileTreeModelState { /** @hidden */ - protected _tileTreeState: TileTreeState = new TileTreeState(this); + protected _tileTreeState: TileTreeState = new TileTreeState(this.iModel, !this.is2d, this.id); /** @hidden */ - protected _classifierTileTreeState: TileTreeState = new TileTreeState(this); + protected _classifierTileTreeState: TileTreeState = new TileTreeState(this.iModel, !this.is2d, this.id); /** Returns true if this is a 3d model (a [[GeometricModel3dState]]). */ public abstract get is3d(): boolean; @@ -86,7 +91,7 @@ export abstract class GeometricModelState extends ModelState { } /** @hidden */ - public loadTileTree(asClassifier: boolean = false, classifierExpansion?: number): TileTree.LoadStatus { + public loadTileTree(asClassifier?: boolean, classifierExpansion?: number): TileTree.LoadStatus { const tileTreeState = asClassifier ? this._classifierTileTreeState : this._tileTreeState; if (TileTree.LoadStatus.NotLoaded !== tileTreeState.loadStatus) return tileTreeState.loadStatus; @@ -101,7 +106,7 @@ export abstract class GeometricModelState extends ModelState { return this.loadIModelTileTree(tileTreeState, asClassifier, classifierExpansion); } - private loadIModelTileTree(tileTreeState: TileTreeState, asClassifier: boolean, classifierExpansion?: number): TileTree.LoadStatus { + private loadIModelTileTree(tileTreeState: TileTreeState, asClassifier?: boolean, classifierExpansion?: number): TileTree.LoadStatus { const id = asClassifier ? ("C:" + classifierExpansion as string + "_" + this.id) : this.id; this.iModel.tiles.getTileTreeProps(id).then((result: TileTreeProps) => { diff --git a/core/frontend/src/NoRenderApp.ts b/core/frontend/src/NoRenderApp.ts index 360a236..dacc2cc 100644 --- a/core/frontend/src/NoRenderApp.ts +++ b/core/frontend/src/NoRenderApp.ts @@ -33,7 +33,7 @@ export class NullTarget extends RenderTarget { public onResized(): void { } public dispose(): void { } public updateViewRect(): boolean { return false; } - public readPixels() { return undefined; } + public readPixels(): void { } public readImage() { return undefined; } } diff --git a/core/frontend/src/NotificationManager.ts b/core/frontend/src/NotificationManager.ts index 0b80221..b8d19c3 100644 --- a/core/frontend/src/NotificationManager.ts +++ b/core/frontend/src/NotificationManager.ts @@ -167,7 +167,7 @@ export class NotificationManager { * @param _icon The MessageBox icon type. * @return the response from the user. */ - public openMessageBox(_mbType: MessageBoxType, _message: string, _icon: MessageBoxIconType): Promise { return Promise.resolve(MessageBoxValue.Ok); } + public async openMessageBox(_mbType: MessageBoxType, _message: string, _icon: MessageBoxIconType): Promise { return Promise.resolve(MessageBoxValue.Ok); } /** * Set up for activity messages. diff --git a/core/frontend/src/QuantityFormatter.ts b/core/frontend/src/QuantityFormatter.ts index 2602b9c..cc40c91 100644 --- a/core/frontend/src/QuantityFormatter.ts +++ b/core/frontend/src/QuantityFormatter.ts @@ -408,7 +408,7 @@ export class QuantityFormatter implements UnitsProvider { const activeMap = imperial ? this._imperialFormatSpecsByType : this._metricFormatSpecsByType; if (activeMap.size === 0) { // trigger a load so it will become available - this.loadFormatSpecsForQuantityTypes(imperial); + this.loadFormatSpecsForQuantityTypes(imperial); // tslint:disable-line:no-floating-promises return undefined; } @@ -460,7 +460,7 @@ export class QuantityFormatter implements UnitsProvider { /** Set the flag to return either metric or imperial formats. This call also makes an async request to refresh the cached formats. */ public set useImperialFormats(useImperial: boolean) { this._activeSystemIsImperial = useImperial; - this.loadFormatSpecsForQuantityTypes(useImperial); + this.loadFormatSpecsForQuantityTypes(useImperial); // tslint:disable-line:no-floating-promises } public get useImperialFormats(): boolean { return this._activeSystemIsImperial; } diff --git a/core/frontend/src/SelectionSet.ts b/core/frontend/src/SelectionSet.ts index 0298a63..953dcae 100644 --- a/core/frontend/src/SelectionSet.ts +++ b/core/frontend/src/SelectionSet.ts @@ -131,9 +131,13 @@ export class SelectionSet { * @returns True if any Ids were either added or removed. */ public addAndRemove(adds: Id64Arg, removes: Id64Arg): boolean { - const added = this.add(adds); - const removed = this.remove(removes); - return added || removed; // don't put this on one line. Make sure we call both. + const added = this.add(adds, false); + const removed = this.remove(removes, false); + if (added || removed) { + this.sendChangedEvent(SelectEventType.Replace, Id64.toIdSet(adds)); + return true; + } + return false; } /** Invert the state of a set of Ids in the SelectionSet */ @@ -144,8 +148,24 @@ export class SelectionSet { return this.addAndRemove(elementsToAdd, elementsToRemove); } + private static areEqual(lhs: Set, rhs: Set): boolean { + if (lhs.size !== rhs.size) + return false; + + for (const id of lhs) { + if (!rhs.has(id)) + return false; + } + + return true; + } + /** Change selection set to be the supplied set of Ids. */ public replace(elem: Id64Arg): void { + elem = Id64.toIdSet(elem); + if (SelectionSet.areEqual(elem, this.elements)) + return; + this.elements.clear(); this.add(elem, false); this.sendChangedEvent(SelectEventType.Replace, this.elements); diff --git a/core/frontend/src/Sheet.ts b/core/frontend/src/Sheet.ts index c1c3aae..775a5e6 100644 --- a/core/frontend/src/Sheet.ts +++ b/core/frontend/src/Sheet.ts @@ -1083,7 +1083,7 @@ export class SheetViewState extends ViewState2d { // For each ViewAttachmentProps, load the view that the attachment references. Once the view is loaded, officially construct the attachment & add it to the array. for (const attachmentProps of attachmentPropList) { - this.iModel.views.load(attachmentProps.view.id).then((view: ViewState) => { + this.iModel.views.load(attachmentProps.view.id).then((view: ViewState) => { // tslint:disable-line:no-floating-promises if (view.is3d()) this._attachments.add(new Attachments.Attachment3d(attachmentProps, view as ViewState3d)); else diff --git a/core/frontend/src/Sprites.ts b/core/frontend/src/Sprites.ts index 90839ff..b9527da 100644 --- a/core/frontend/src/Sprites.ts +++ b/core/frontend/src/Sprites.ts @@ -41,12 +41,12 @@ export class Sprite { * @param src The ImageSource holding an image to create the texture for this Sprite. * @note This method creates the image from the ImageSource asynchronously. */ - public fromImageSource(src: ImageSource): void { imageElementFromImageSource(src).then((image) => this.onLoaded(image)); } + public fromImageSource(src: ImageSource): void { imageElementFromImageSource(src).then((image) => this.onLoaded(image)); } // tslint:disable-line:no-floating-promises /** Initialize this Sprite from a URL * @param url The url of an image to load for this Sprite. */ - public fromUrl(url: string): void { imageElementFromUrl(url).then((image) => this.onLoaded(image)); } + public fromUrl(url: string): void { imageElementFromUrl(url).then((image) => this.onLoaded(image)); } // tslint:disable-line:no-floating-promises } /** Icon sprites are loaded from .png files in the assets directory of imodeljs-native. diff --git a/core/frontend/src/TentativePoint.ts b/core/frontend/src/TentativePoint.ts index e8cd140..38cf512 100644 --- a/core/frontend/src/TentativePoint.ts +++ b/core/frontend/src/TentativePoint.ts @@ -77,7 +77,7 @@ export class TentativePoint { public showTentative(): void { if (this.isSnapped) { IModelApp.viewManager.invalidateDecorationsAllViews(); - IModelApp.accuSnap.displayToolTip(this._viewPoint, this.viewport!, undefined); + IModelApp.accuSnap.displayToolTip(this._viewPoint, this.viewport!, undefined); // tslint:disable-line:no-floating-promises } else { this.viewport!.invalidateDecorations(); } diff --git a/core/frontend/src/ViewManager.ts b/core/frontend/src/ViewManager.ts index aa15e13..3af46c3 100644 --- a/core/frontend/src/ViewManager.ts +++ b/core/frontend/src/ViewManager.ts @@ -171,7 +171,8 @@ export class ViewManager { this.notifySelectedViewportChanged(previousVp, vp); - IModelApp.toolAdmin.startDefaultTool(); // ###TODO not in native, where should defaultTool be called? + if (undefined === previousVp) + IModelApp.toolAdmin.startDefaultTool(); return BentleyStatus.SUCCESS; } diff --git a/core/frontend/src/ViewState.ts b/core/frontend/src/ViewState.ts index f494db8..7957f0b 100644 --- a/core/frontend/src/ViewState.ts +++ b/core/frontend/src/ViewState.ts @@ -6,13 +6,13 @@ import { assert, BeTimePoint, Id64, Id64Arg, Id64Set, Id64String, JsonUtils } from "@bentley/bentleyjs-core"; import { - Angle, AxisOrder, ClipVector, Constant, Geometry, LowAndHighXY, LowAndHighXYZ, Map4d, Matrix3d, Plane3dByOriginAndUnitNormal, - Point2d, Point3d, PolyfaceBuilder, Range3d, Ray3d, StrokeOptions, Transform, Vector2d, Vector3d, XAndY, XYAndZ, YawPitchRollAngles, + Angle, AxisOrder, ClipVector, Constant, Geometry, LowAndHighXY, LowAndHighXYZ, Map4d, Matrix3d, Plane3dByOriginAndUnitNormal, + Point2d, Point3d, PolyfaceBuilder, Range3d, Ray3d, StrokeOptions, Transform, Vector2d, Vector3d, XAndY, XYAndZ, YawPitchRollAngles, } from "@bentley/geometry-core"; import { - AxisAlignedBox3d, Camera, ColorDef, Frustum, GraphicParams, Npc, RenderMaterial, SpatialViewDefinitionProps, - SubCategoryAppearance, SubCategoryOverride, TextureMapping, ViewDefinition2dProps, ViewDefinition3dProps, ViewDefinitionProps, - ViewFlags, ViewStateData, AnalysisStyle, + AxisAlignedBox3d, Camera, ColorDef, Frustum, GraphicParams, Npc, RenderMaterial, SpatialViewDefinitionProps, + SubCategoryAppearance, SubCategoryOverride, TextureMapping, ViewDefinition2dProps, ViewDefinition3dProps, ViewDefinitionProps, + ViewFlags, ViewStateData, AnalysisStyle, RenderSchedule, } from "@bentley/imodeljs-common"; import { AuxCoordSystem2dState, AuxCoordSystem3dState, AuxCoordSystemSpatialState, AuxCoordSystemState } from "./AuxCoordSys"; import { CategorySelectorState } from "./CategorySelectorState"; @@ -21,7 +21,7 @@ import { ElementState } from "./EntityState"; import { IModelApp } from "./IModelApp"; import { IModelConnection } from "./IModelConnection"; import { ModelSelectorState } from "./ModelSelectorState"; -import { GeometricModel2dState, GeometricModelState } from "./ModelState"; +import { GeometricModel2dState, GeometricModelState, TileTreeModelState } from "./ModelState"; import { NotifyMessageDetails, OutputMessagePriority } from "./NotificationManager"; import { GraphicType } from "./render/GraphicBuilder"; import { StandardView, StandardViewId } from "./StandardView"; @@ -31,40 +31,40 @@ import { Viewport } from "./Viewport"; /** Describes the orientation of the grid displayed within a [[Viewport]]. */ export const enum GridOrientationType { - /** Oriented with the view. */ - View = 0, - /** Top */ - WorldXY = 1, // Top - /** Right */ - WorldYZ = 2, // Right - /** Front */ - WorldXZ = 3, // Front - /** Oriented by the [[AuxCoordSystem]] */ - AuxCoord = 4, - /** @hidden - * ###TODO not implemented - see ViewState.drawGrid. - */ - GeoCoord = 5, + /** Oriented with the view. */ + View = 0, + /** Top */ + WorldXY = 1, // Top + /** Right */ + WorldYZ = 2, // Right + /** Front */ + WorldXZ = 3, // Front + /** Oriented by the [[AuxCoordSystem]] */ + AuxCoord = 4, + /** @hidden + * ###TODO not implemented - see ViewState.drawGrid. + */ + GeoCoord = 5, } /** Describes the result of a viewing operation such as those exposed by [[ViewState]] and [[Viewport]]. */ export const enum ViewStatus { - Success = 0, - ViewNotInitialized, - AlreadyAttached, - NotAttached, - DrawFailure, - NotResized, - ModelNotFound, - InvalidWindow, - MinWindow, - MaxWindow, - MaxZoom, - MaxDisplayDepth, - InvalidUpVector, - InvalidTargetPoint, - InvalidLens, - InvalidViewport, + Success = 0, + ViewNotInitialized, + AlreadyAttached, + NotAttached, + DrawFailure, + NotResized, + ModelNotFound, + InvalidWindow, + MinWindow, + MaxWindow, + MaxZoom, + MaxDisplayDepth, + InvalidUpVector, + InvalidTargetPoint, + InvalidLens, + InvalidViewport, } /** @@ -72,78 +72,86 @@ export const enum ViewStatus { * Values mean "fraction of view size" and must be between 0 and .25. */ export class MarginPercent { - constructor(public left: number, public top: number, public right: number, public bottom: number) { - const limitMargin = (val: number) => (val < 0.0) ? 0.0 : (val > .25) ? .25 : val; - this.left = limitMargin(left); - this.top = limitMargin(top); - this.right = limitMargin(right); - this.bottom = limitMargin(bottom); - } + constructor(public left: number, public top: number, public right: number, public bottom: number) { + const limitMargin = (val: number) => (val < 0.0) ? 0.0 : (val > .25) ? .25 : val; + this.left = limitMargin(left); + this.top = limitMargin(top); + this.right = limitMargin(right); + this.bottom = limitMargin(bottom); + } } /** * Stores information about sub-categories specific to a ViewState. Functions as a lazily-populated cache. */ export class ViewSubCategories { - private readonly _byCategoryId = new Map(); - private readonly _appearances = new Map(); - - /** Get the Ids of all subcategories belonging to the category with the specified Id, or undefined if no such information is present. */ - public getSubCategories(categoryId: string): Id64Set | undefined { return this._byCategoryId.get(categoryId); } - - /** Get the base appearance of the subcategory with the specified Id, or undefined if no such information is present. */ - public getSubCategoryAppearance(subCategoryId: Id64String): SubCategoryAppearance | undefined { return this._appearances.get(subCategoryId.toString()); } - - /** - * Asynchronously populates this cache with information about subcategories belonging to the specified set of categories. - * This function is invoked on initial loading of a ViewState to obtain subcategory information for the set of - * categories in the ViewState's [[CategorySelector]]. - */ - public async load(categoryIds: Set, iModel: IModelConnection): Promise { - const where = [...categoryIds].join(","); - if (0 === where.length) - return Promise.resolve(); - - const ecsql = "SELECT ECInstanceId as id, Parent.Id as parentId, Properties as appearance FROM BisCore.SubCategory WHERE Parent.Id IN (" + where + ")"; - return iModel.executeQuery(ecsql).then((rows: any[]) => this.loadFromRows(rows)); - } - - /** - * If information for subcategories belonging to the specified categories is not present, enqueues an asynchronous request to load it. - * This function is invoked by ViewState.changeCategoryDisplay() to ensure subcategory information is present for any newly-enabled - * categories. - */ - public update(addedCategoryIds: Set, iModel: IModelConnection): Promise { - let missing: Set | undefined; - for (const catId of addedCategoryIds) { - if (undefined === this._byCategoryId.get(catId)) { - if (undefined === missing) - missing = new Set(); - - missing.add(catId); - } - } - - if (undefined !== missing) - return this.load(missing, iModel); - else - return Promise.resolve(); - } - - private loadFromRows(rows: any[]): void { - for (const row of rows) - this.add(row.parentId as string, row.id as string, new SubCategoryAppearance(JSON.parse(row.appearance))); - } - - private add(categoryId: string, subCategoryId: string, appearance: SubCategoryAppearance) { - let set = this._byCategoryId.get(categoryId); - if (undefined === set) - this._byCategoryId.set(categoryId, set = new Set()); - - set.add(subCategoryId); - - this._appearances.set(subCategoryId, appearance); - } + private readonly _byCategoryId = new Map(); + private readonly _appearances = new Map(); + + /** Get the Ids of all subcategories belonging to the category with the specified Id, or undefined if no such information is present. */ + public getSubCategories(categoryId: string): Id64Set | undefined { return this._byCategoryId.get(categoryId); } + + /** Get the base appearance of the subcategory with the specified Id, or undefined if no such information is present. */ + public getSubCategoryAppearance(subCategoryId: Id64String): SubCategoryAppearance | undefined { return this._appearances.get(subCategoryId.toString()); } + + /** + * Asynchronously populates this cache with information about subcategories belonging to the specified set of categories. + * This function is invoked on initial loading of a ViewState to obtain subcategory information for the set of + * categories in the ViewState's [[CategorySelector]]. + */ + public async load(categoryIds: Set, iModel: IModelConnection): Promise { + const where = [...categoryIds].join(","); + if (0 === where.length) + return Promise.resolve(); + + const ecsql = "SELECT ECInstanceId as id, Parent.Id as parentId, Properties as appearance FROM BisCore.SubCategory WHERE Parent.Id IN (" + where + ")"; + return iModel.executeQuery(ecsql).then((rows: any[]) => this.loadFromRows(rows)); + } + + /** + * If information for subcategories belonging to the specified categories is not present, enqueues an asynchronous request to load it. + * This function is invoked by ViewState.changeCategoryDisplay() to ensure subcategory information is present for any newly-enabled + * categories. + */ + public async update(addedCategoryIds: Set, iModel: IModelConnection): Promise { + let missing: Set | undefined; + for (const catId of addedCategoryIds) { + if (undefined === this._byCategoryId.get(catId)) { + if (undefined === missing) + missing = new Set(); + + missing.add(catId); + } + } + + if (undefined !== missing) + return this.load(missing, iModel); + else + return Promise.resolve(); + } + + private static createSubCategoryAppearance(json?: any) { + let props: SubCategoryAppearance | undefined; + if ("string" === typeof (json) && 0 < json.length) + props = JSON.parse(json); + + return new SubCategoryAppearance(props); + } + + private loadFromRows(rows: any[]): void { + for (const row of rows) + this.add(row.parentId as string, row.id as string, ViewSubCategories.createSubCategoryAppearance(row.appearance)); + } + + private add(categoryId: string, subCategoryId: string, appearance: SubCategoryAppearance) { + let set = this._byCategoryId.get(categoryId); + if (undefined === set) + this._byCategoryId.set(categoryId, set = new Set()); + + set.add(subCategoryId); + + this._appearances.set(subCategoryId, appearance); + } } /** @@ -152,1658 +160,1681 @@ export class ViewSubCategories { * * @see [Views]($docs/learning/frontend/Views.md) */ export abstract class ViewState extends ElementState { - protected _featureOverridesDirty = true; - protected _selectionSetDirty = true; - private _auxCoordSystem?: AuxCoordSystemState; - public description?: string; - public isPrivate?: boolean; - /** Time this ViewState was saved in view undo. */ - public undoTime?: BeTimePoint; - /** A cache of information about subcategories belonging to categories present in this view's [[CategorySelectorState]]. - * It is populated on-demand as new categories are added to the selector. - */ - public readonly subCategories = new ViewSubCategories(); - public static get className() { return "ViewDefinition"; } - - /** @hidden */ - protected constructor(props: ViewDefinitionProps, iModel: IModelConnection, public categorySelector: CategorySelectorState, public displayStyle: DisplayStyleState) { - super(props, iModel); - this.description = props.description; - this.isPrivate = props.isPrivate; - if (categorySelector instanceof ViewState) { // from clone, 3rd argument is source ViewState - this.categorySelector = categorySelector.categorySelector.clone(); - this.displayStyle = categorySelector.displayStyle.clone(); - this.subCategories = categorySelector.subCategories; // NB: This is a cache. No reason to deep-copy. - } - } - - /** @hidden */ - public static createFromStateData(_viewStateData: ViewStateData, _cat: CategorySelectorState, _iModel: IModelConnection): ViewState | undefined { return undefined; } - - /** Get the ViewFlags from the [[DisplayStyleState]] of this ViewState. - * @note Do not modify this object directly. Instead, use the setter as follows: - * - * ```ts - * const flags = viewState.viewFlags.clone(); - * flags.renderMode = RenderMode.SmoothShade; // or whatever alterations are desired - * viewState.viewFlags = flags; - * ```ts - */ - public get viewFlags(): ViewFlags { return this.displayStyle.viewFlags; } - /** Set the ViewFlags and mark them as dirty if they have changed. */ - public set viewFlags(newFlags: ViewFlags) { - if (!this.viewFlags.equals(newFlags)) { - this.setFeatureOverridesDirty(); - this.displayStyle.viewFlags = newFlags; - } - } - /** Get the AnalysisDisplayProperties from the displayStyle of this ViewState. */ - public get AnalysisStyle(): AnalysisStyle | undefined { return this.displayStyle.AnalysisStyle; } - - /** Determine whether this ViewState exactly matches another. - * @see [[ViewState.equalState]] for determining broader equivalence of two ViewStates. - */ - public equals(other: ViewState): boolean { return super.equals(other) && this.categorySelector.equals(other.categorySelector) && this.displayStyle.equals(other.displayStyle); } - - /** Determine whether this ViewState is equivalent to another for the purposes of display. - * @see [[ViewState.equals]] for determining exact equality. - */ - public equalState(other: ViewState): boolean { - return (this.isPrivate === other.isPrivate && - this.categorySelector.id === other.categorySelector.id && - this.displayStyle.id === other.displayStyle.id && - this.categorySelector.equalState(other.categorySelector) && - this.displayStyle.equalState(other.displayStyle) && - JSON.stringify(this.getDetails()) === JSON.stringify(other.getDetails())); - } - - public toJSON(): ViewDefinitionProps { - const json = super.toJSON() as ViewDefinitionProps; - json.categorySelectorId = this.categorySelector.id; - json.displayStyleId = this.displayStyle.id; - json.isPrivate = this.isPrivate; - json.description = this.description; - return json; - } - - /** Asynchronously load any required data for this ViewState from the backend. - * @note callers should await the Promise returned by this method before using this ViewState. - * @see [Views]($docs/learning/frontend/Views.md) - */ - public async load(): Promise { - this._auxCoordSystem = undefined; - const acsId = this.getAuxiliaryCoordinateSystemId(); - if (Id64.isValid(acsId)) { - const props = await this.iModel.elements.getProps(acsId); - this._auxCoordSystem = AuxCoordSystemState.fromProps(props[0], this.iModel); - } - - return this.subCategories.load(this.categorySelector.categories, this.iModel); - } - - /** @hidden */ - public cancelAllTileLoads(): void { - this.forEachModel((model) => { - const tileTree = model.tileTree; - if (tileTree !== undefined) - tileTree.rootTile.cancelAllLoads(); - }); - } - - /** @hidden */ - public get areAllTileTreesLoaded(): boolean { - let allLoaded = true; - this.forEachModel((model: GeometricModelState) => { - const loadStatus = model.loadStatus; - if (loadStatus !== TileTree.LoadStatus.Loaded) - allLoaded = false; - }); - return allLoaded; - } - - /** Get the name of the [[ViewDefinition]] from which this ViewState originated. */ - public get name(): string { return this.code.getValue(); } - - /** Get this view's background color. */ - public get backgroundColor(): ColorDef { return this.displayStyle.backgroundColor; } - - private _neverDrawn?: Id64Set; - private _alwaysDrawn?: Id64Set; - private _alwaysDrawnExclusive: boolean = false; - - /** - * IDs of a set of elements which should not be rendered within this view. - * @note Do not modify this set directly - use [[setNeverDrawn]] or [[clearNeverDrawn]] instead. - * @note This set takes precedence over the [[alwaysDrawn]] set - if an element is present in both sets, it is never drawn. - */ - public get neverDrawn(): Id64Set | undefined { return this._neverDrawn; } - - /** - * IDs of a set of elements which should always be rendered within this view, regardless of category and subcategory visibility. - * If the [[isAlwaysDrawnExclusive]] flag is also set, *only* those elements in this set will be drawn. - * @note Do not modify this set directly - use [[setAlwaysDrawn]] or [[clearAlwaysDrawn]] instead. - * @note The [[neverDrawn]] set takes precedence - if an element is present in both sets, it is never drawn. - */ - public get alwaysDrawn(): Id64Set | undefined { return this._alwaysDrawn; } - - /** Clear the set of always-drawn elements. - * @see [[alwaysDrawn]] - */ - public clearAlwaysDrawn(): void { - if (undefined !== this.alwaysDrawn && 0 < this.alwaysDrawn.size) { - this.alwaysDrawn.clear(); - this._alwaysDrawnExclusive = false; - this.setFeatureOverridesDirty(); - } - } - - /** Clear the set of never-drawn elements. - * @see [[neverDrawn]] - */ - public clearNeverDrawn(): void { - if (undefined !== this.neverDrawn && 0 < this.neverDrawn.size) { - this.neverDrawn.clear(); - this.setFeatureOverridesDirty(); - } - } - - /** Specify the IDs of a set of elements which should never be rendered within this view. - * @see [[neverDrawn]]. - */ - public setNeverDrawn(ids: Id64Set): void { - this._neverDrawn = ids; - this.setFeatureOverridesDirty(); - } - - /** Specify the IDs of a set of elements which should always be rendered within this view, regardless of category and subcategory visibility. - * @param ids The IDs of the elements to always draw. - * @param exclusive If true, *only* the specified elements will be drawn. - * @see [[alwaysDrawn]] - * @see [[isAlwaysDrawnExclusive]] - */ - public setAlwaysDrawn(ids: Id64Set, exclusive: boolean = false): void { - this._alwaysDrawn = ids; - this._alwaysDrawnExclusive = exclusive; - this.setFeatureOverridesDirty(); - } - - /** Remove any [[SubCategoryOverride]] for the specified subcategory. - * @param id The ID of the subcategory. - * @see [[overrideSubCategory]] - */ - public dropSubCategoryOverride(id: Id64String) { - this.displayStyle.dropSubCategoryOverride(id); - this.setFeatureOverridesDirty(); - } - - /** Override the symbology of geometry belonging to a specific subcategory when rendered within this view. - * @param id The ID of the subcategory. - * @param ovr The symbology overrides to apply to all geometry belonging to the specified subcategory. - * @see [[dropSubCategoryOverride]] - */ - public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride) { - this.displayStyle.overrideSubCategory(id, ovr); - this.setFeatureOverridesDirty(); - } - - /** Query the symbology overrides applied to geometry belonging to a specific subcategory when rendered within this view. - * @param id The ID of the subcategory. - * @return The symbology overrides applied to all geometry belonging to the specified subcategory, or undefined if no such overrides exist. - * @see [[overrideSubCategory]] - */ - public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined { return this.displayStyle.getSubCategoryOverride(id); } - - /** Query the symbology with which geometry belonging to a specific subcategory is rendered within this view. - * Every [[SubCategory]] defines a base symbology independent of any [[ViewState]]. - * If a [[SubCategoryOverride]] has been applied to the subcategory within the context of this [[ViewState]], it will be applied to the subcategory's base symbology. - * @param id The ID of the subcategory. - * @return The symbology of the subcategory within this view, including any overrides. - * @see [[overrideSubCategory]] - */ - public getSubCategoryAppearance(id: Id64String): SubCategoryAppearance { - const app = this.subCategories.getSubCategoryAppearance(id); - if (undefined === app) - return SubCategoryAppearance.defaults; - - const ovr = this.getSubCategoryOverride(id); - return undefined !== ovr ? ovr.override(app) : app; - } - - /** @hidden */ - public isSubCategoryVisible(id: Id64String): boolean { - const app = this.subCategories.getSubCategoryAppearance(id.toString()); - if (undefined === app) - return false; - - const ovr = this.getSubCategoryOverride(id); - if (undefined === ovr || undefined === ovr.invisible) - return !app.invisible; - else - return !ovr.invisible; - } - - /** Returns true if the set of elements in the [[alwaysDrawn]] set are the *only* elements rendered within this view. */ - public get isAlwaysDrawnExclusive(): boolean { return this._alwaysDrawnExclusive; } - - /** - * Enable or disable display of elements belonging to a set of categories specified by ID. - * Visibility of individual subcategories belonging to a category can be controlled separately through the use of [[SubCategoryOverride]]s. - * By default, enabling display of a category does not affect display of subcategories thereof which have been overridden to be invisible. - * @param categories The ID(s) of the categories to which the change should be applied. No other categories will be affected. - * @param display Whether or not elements on the specified categories should be displayed in the view. - * @param enableAllSubCategories Specifies that when enabling display for a category, all of its subcategories should also be displayed even if they are overridden to be invisible. - */ - public changeCategoryDisplay(categories: Id64Arg, display: boolean, enableAllSubCategories: boolean = false): void { - if (display) { - this.categorySelector.addCategories(categories); - const categoryIds = Id64.toIdSet(categories); - this.subCategories.update(categoryIds, this.iModel).then(() => { + protected _featureOverridesDirty = true; + protected _selectionSetDirty = true; + private _auxCoordSystem?: AuxCoordSystemState; + private _scheduleTime: number = 0.0; + public description?: string; + public isPrivate?: boolean; + /** Time this ViewState was saved in view undo. */ + public undoTime?: BeTimePoint; + /** A cache of information about subcategories belonging to categories present in this view's [[CategorySelectorState]]. + * It is populated on-demand as new categories are added to the selector. + */ + public readonly subCategories = new ViewSubCategories(); + public static get className() { return "ViewDefinition"; } + + /** @hidden */ + protected constructor(props: ViewDefinitionProps, iModel: IModelConnection, public categorySelector: CategorySelectorState, public displayStyle: DisplayStyleState) { + super(props, iModel); + this.description = props.description; + this.isPrivate = props.isPrivate; + if (categorySelector instanceof ViewState) { // from clone, 3rd argument is source ViewState + this.categorySelector = categorySelector.categorySelector.clone(); + this.displayStyle = categorySelector.displayStyle.clone(); + this.subCategories = categorySelector.subCategories; // NB: This is a cache. No reason to deep-copy. + } + } + + /** @hidden */ + public static createFromStateData(_viewStateData: ViewStateData, _cat: CategorySelectorState, _iModel: IModelConnection): ViewState | undefined { return undefined; } + + /** Get the ViewFlags from the [[DisplayStyleState]] of this ViewState. + * @note Do not modify this object directly. Instead, use the setter as follows: + * + * ```ts + * const flags = viewState.viewFlags.clone(); + * flags.renderMode = RenderMode.SmoothShade; // or whatever alterations are desired + * viewState.viewFlags = flags; + * ```ts + */ + public get viewFlags(): ViewFlags { return this.displayStyle.viewFlags; } + /** Set the ViewFlags and mark them as dirty if they have changed. */ + public set viewFlags(newFlags: ViewFlags) { + if (!this.viewFlags.equals(newFlags)) { + this.setFeatureOverridesDirty(); + this.displayStyle.viewFlags = newFlags; + } + } + /** Get the AnalysisDisplayProperties from the displayStyle of this ViewState. */ + public get AnalysisStyle(): AnalysisStyle | undefined { return this.displayStyle.settings.analysisStyle; } + + /** Get the RenderSchedule.Script from the displayStyle ofthis viewState */ + public get scheduleScript(): RenderSchedule.Script | undefined { return this.displayStyle.settings.scheduleScript; } + public get scheduleTime() { return this._scheduleTime; } + public set scheduleTime(time: number) { + if (this.scheduleTime !== time) { + this._scheduleTime = time; + if (undefined !== this.scheduleScript && this.scheduleScript.containsFeatureOverrides) + this.setFeatureOverridesDirty(); + } + } + + /** Determine whether this ViewState exactly matches another. + * @see [[ViewState.equalState]] for determining broader equivalence of two ViewStates. + */ + public equals(other: ViewState): boolean { return super.equals(other) && this.categorySelector.equals(other.categorySelector) && this.displayStyle.equals(other.displayStyle); } + + /** Determine whether this ViewState is equivalent to another for the purposes of display. + * @see [[ViewState.equals]] for determining exact equality. + */ + public equalState(other: ViewState): boolean { + return (this.isPrivate === other.isPrivate && + this.categorySelector.id === other.categorySelector.id && + this.displayStyle.id === other.displayStyle.id && + this.categorySelector.equalState(other.categorySelector) && + this.displayStyle.equalState(other.displayStyle) && + JSON.stringify(this.getDetails()) === JSON.stringify(other.getDetails())); + } + + public toJSON(): ViewDefinitionProps { + const json = super.toJSON() as ViewDefinitionProps; + json.categorySelectorId = this.categorySelector.id; + json.displayStyleId = this.displayStyle.id; + json.isPrivate = this.isPrivate; + json.description = this.description; + return json; + } + + /** Asynchronously load any required data for this ViewState from the backend. + * @note callers should await the Promise returned by this method before using this ViewState. + * @see [Views]($docs/learning/frontend/Views.md) + */ + public async load(): Promise { + this._auxCoordSystem = undefined; + const acsId = this.getAuxiliaryCoordinateSystemId(); + if (Id64.isValid(acsId)) { + const props = await this.iModel.elements.getProps(acsId); + this._auxCoordSystem = AuxCoordSystemState.fromProps(props[0], this.iModel); + } + + return this.subCategories.load(this.categorySelector.categories, this.iModel); + } + + /** @hidden */ + public cancelAllTileLoads(): void { + this.forEachTileTreeModel((model) => { + const tileTree = model.tileTree; + if (tileTree !== undefined) + tileTree.rootTile.cancelAllLoads(); + }); + } + + /** @hidden */ + public get areAllTileTreesLoaded(): boolean { + let allLoaded = true; + this.forEachTileTreeModel((model) => { + const loadStatus = model.loadStatus; + if (loadStatus !== TileTree.LoadStatus.Loaded) + allLoaded = false; + }); + return allLoaded; + } + + /** Get the name of the [[ViewDefinition]] from which this ViewState originated. */ + public get name(): string { return this.code.getValue(); } + + /** Get this view's background color. */ + public get backgroundColor(): ColorDef { return this.displayStyle.backgroundColor; } + + private _neverDrawn?: Id64Set; + private _alwaysDrawn?: Id64Set; + private _alwaysDrawnExclusive: boolean = false; + + /** + * IDs of a set of elements which should not be rendered within this view. + * @note Do not modify this set directly - use [[setNeverDrawn]] or [[clearNeverDrawn]] instead. + * @note This set takes precedence over the [[alwaysDrawn]] set - if an element is present in both sets, it is never drawn. + */ + public get neverDrawn(): Id64Set | undefined { return this._neverDrawn; } + + /** + * IDs of a set of elements which should always be rendered within this view, regardless of category and subcategory visibility. + * If the [[isAlwaysDrawnExclusive]] flag is also set, *only* those elements in this set will be drawn. + * @note Do not modify this set directly - use [[setAlwaysDrawn]] or [[clearAlwaysDrawn]] instead. + * @note The [[neverDrawn]] set takes precedence - if an element is present in both sets, it is never drawn. + */ + public get alwaysDrawn(): Id64Set | undefined { return this._alwaysDrawn; } + + /** Clear the set of always-drawn elements. + * @see [[alwaysDrawn]] + */ + public clearAlwaysDrawn(): void { + if (undefined !== this.alwaysDrawn && 0 < this.alwaysDrawn.size) { + this.alwaysDrawn.clear(); + this._alwaysDrawnExclusive = false; + this.setFeatureOverridesDirty(); + } + } + + /** Clear the set of never-drawn elements. + * @see [[neverDrawn]] + */ + public clearNeverDrawn(): void { + if (undefined !== this.neverDrawn && 0 < this.neverDrawn.size) { + this.neverDrawn.clear(); + this.setFeatureOverridesDirty(); + } + } + + /** Specify the IDs of a set of elements which should never be rendered within this view. + * @see [[neverDrawn]]. + */ + public setNeverDrawn(ids: Id64Set): void { + this._neverDrawn = ids; + this.setFeatureOverridesDirty(); + } + + /** Specify the IDs of a set of elements which should always be rendered within this view, regardless of category and subcategory visibility. + * @param ids The IDs of the elements to always draw. + * @param exclusive If true, *only* the specified elements will be drawn. + * @see [[alwaysDrawn]] + * @see [[isAlwaysDrawnExclusive]] + */ + public setAlwaysDrawn(ids: Id64Set, exclusive: boolean = false): void { + this._alwaysDrawn = ids; + this._alwaysDrawnExclusive = exclusive; + this.setFeatureOverridesDirty(); + } + + /** Remove any [[SubCategoryOverride]] for the specified subcategory. + * @param id The ID of the subcategory. + * @see [[overrideSubCategory]] + */ + public dropSubCategoryOverride(id: Id64String) { + this.displayStyle.dropSubCategoryOverride(id); this.setFeatureOverridesDirty(); - if (enableAllSubCategories) { - for (const categoryId of categoryIds) { - const subCategoryIds = this.subCategories.getSubCategories(categoryId); - if (undefined !== subCategoryIds) { - for (const subCategoryId of subCategoryIds) - this.changeSubCategoryDisplay(subCategoryId, true); + } + + /** Override the symbology of geometry belonging to a specific subcategory when rendered within this view. + * @param id The ID of the subcategory. + * @param ovr The symbology overrides to apply to all geometry belonging to the specified subcategory. + * @see [[dropSubCategoryOverride]] + */ + public overrideSubCategory(id: Id64String, ovr: SubCategoryOverride) { + this.displayStyle.overrideSubCategory(id, ovr); + this.setFeatureOverridesDirty(); + } + + /** Query the symbology overrides applied to geometry belonging to a specific subcategory when rendered within this view. + * @param id The ID of the subcategory. + * @return The symbology overrides applied to all geometry belonging to the specified subcategory, or undefined if no such overrides exist. + * @see [[overrideSubCategory]] + */ + public getSubCategoryOverride(id: Id64String): SubCategoryOverride | undefined { return this.displayStyle.getSubCategoryOverride(id); } + + /** Query the symbology with which geometry belonging to a specific subcategory is rendered within this view. + * Every [[SubCategory]] defines a base symbology independent of any [[ViewState]]. + * If a [[SubCategoryOverride]] has been applied to the subcategory within the context of this [[ViewState]], it will be applied to the subcategory's base symbology. + * @param id The ID of the subcategory. + * @return The symbology of the subcategory within this view, including any overrides. + * @see [[overrideSubCategory]] + */ + public getSubCategoryAppearance(id: Id64String): SubCategoryAppearance { + const app = this.subCategories.getSubCategoryAppearance(id); + if (undefined === app) + return SubCategoryAppearance.defaults; + + const ovr = this.getSubCategoryOverride(id); + return undefined !== ovr ? ovr.override(app) : app; + } + + /** @hidden */ + public isSubCategoryVisible(id: Id64String): boolean { + const app = this.subCategories.getSubCategoryAppearance(id.toString()); + if (undefined === app) + return false; + + const ovr = this.getSubCategoryOverride(id); + if (undefined === ovr || undefined === ovr.invisible) + return !app.invisible; + else + return !ovr.invisible; + } + + /** Returns true if the set of elements in the [[alwaysDrawn]] set are the *only* elements rendered within this view. */ + public get isAlwaysDrawnExclusive(): boolean { return this._alwaysDrawnExclusive; } + + /** + * Enable or disable display of elements belonging to a set of categories specified by ID. + * Visibility of individual subcategories belonging to a category can be controlled separately through the use of [[SubCategoryOverride]]s. + * By default, enabling display of a category does not affect display of subcategories thereof which have been overridden to be invisible. + * @param categories The ID(s) of the categories to which the change should be applied. No other categories will be affected. + * @param display Whether or not elements on the specified categories should be displayed in the view. + * @param enableAllSubCategories Specifies that when enabling display for a category, all of its subcategories should also be displayed even if they are overridden to be invisible. + */ + public changeCategoryDisplay(categories: Id64Arg, display: boolean, enableAllSubCategories: boolean = false): void { + if (display) { + this.categorySelector.addCategories(categories); + const categoryIds = Id64.toIdSet(categories); + this.subCategories.update(categoryIds, this.iModel).then(() => { // tslint:disable-line:no-floating-promises + this.setFeatureOverridesDirty(); + if (enableAllSubCategories) { + for (const categoryId of categoryIds) { + const subCategoryIds = this.subCategories.getSubCategories(categoryId); + if (undefined !== subCategoryIds) { + for (const subCategoryId of subCategoryIds) + this.changeSubCategoryDisplay(subCategoryId, true); + } + } + } + }); + } else { + this.categorySelector.dropCategories(categories); + } + + this.setFeatureOverridesDirty(); + } + + private changeSubCategoryDisplay(subCategoryId: Id64String, display: boolean): void { + const app = this.subCategories.getSubCategoryAppearance(subCategoryId); + if (undefined === app) + return; // category is not enabled or subcategory does not exist + + const curOvr = this.getSubCategoryOverride(subCategoryId); + const isAlreadyVisible = undefined !== curOvr && undefined !== curOvr.invisible ? !curOvr.invisible : !app.invisible; + if (isAlreadyVisible === display) + return; + + // Preserve existing overrides - just flip the visibility flag. + const json = undefined !== curOvr ? curOvr.toJSON() : {}; + json.invisible = !display; + this.overrideSubCategory(subCategoryId, SubCategoryOverride.fromJSON(json)); + } + + /** Returns true if the set of elements returned by GetAlwaysDrawn() are the *only* elements rendered by this view */ + public get areFeatureOverridesDirty(): boolean { return this._featureOverridesDirty; } + /** @hidden */ + public get isSelectionSetDirty(): boolean { return this._selectionSetDirty; } + + /** Mark the [[FeatureSymbology.Overrides]] associated with this view as "dirty". + * Typically this is handled internally. + * Conditions that may cause the overrides to become dirty include: + * - Toggling the display of a category within the view. + * - Changing the symbology associated with a [[SubCategory]] within the view by adding a [[SubCategoryOverride]] + * - Changes in some application state that affects the [[AddFeatureOverrides]] function registered with [[Viewport]]. + * The next time the [[Viewport]] associated with this [[ViewState]] is rendered, the symbology overrides will be regenerated if they have been marked "dirty". + */ + public setFeatureOverridesDirty(dirty: boolean = true): void { this._featureOverridesDirty = dirty; } + /** @hidden */ + public setSelectionSetDirty(dirty: boolean = true): void { this._selectionSetDirty = dirty; } + public is3d(): this is ViewState3d { return this instanceof ViewState3d; } + public isSpatialView(): this is SpatialViewState { return this instanceof SpatialViewState; } + /** Returns true if [[ViewTool]]s are allowed to operate in three dimensions on this view. */ + public abstract allow3dManipulations(): boolean; + /** @hidden */ + public abstract createAuxCoordSystem(acsName: string): AuxCoordSystemState; + /** Get the extents of this view in [[CoordSystem.World]] coordinates. */ + public abstract getViewedExtents(): AxisAlignedBox3d; + /** Compute a range in [[CoordSystem.World]] coordinates that tightly encloses the contents of this view. + * @see [[FitViewTool]]. + */ + public abstract computeFitRange(): Range3d; + + /** Override this if you want to perform some logic on each iteration of the render loop. + * @hidden + */ + public abstract onRenderFrame(_viewport: Viewport): void; + + /** Returns true if this view displays the contents of a [[Model]] specified by ID. */ + public abstract viewsModel(modelId: Id64String): boolean; + + /** Get the origin of this view in [[CoordSystem.World]] coordinates. */ + public abstract getOrigin(): Point3d; + + /** Get the extents of this view in [[CoordSystem.World]] coordinates. */ + public abstract getExtents(): Vector3d; + + /** Get the 3x3 ortho-normal Matrix3d for this view. */ + public abstract getRotation(): Matrix3d; + + /** Set the origin of this view in [[CoordSystem.World]] coordinates. */ + public abstract setOrigin(viewOrg: Point3d): void; + + /** Set the extents of this view in [[CoordSystem.World]] coordinates. */ + public abstract setExtents(viewDelta: Vector3d): void; + + /** Change the rotation of the view. + * @note viewRot must be ortho-normal. For 2d views, only the rotation angle about the z axis is used. + */ + public abstract setRotation(viewRot: Matrix3d): void; + + /** Execute a function on each viewed model */ + public abstract forEachModel(func: (model: GeometricModelState) => void): void; + + /** Execute a function on each viewed model */ + public forEachTileTreeModel(func: (model: TileTreeModelState) => void): void { this.forEachModel((model: GeometricModelState) => func(model)); } + /** @hidden */ + public createScene(context: SceneContext): void { + this.forEachTileTreeModel((model: TileTreeModelState) => this.addModelToScene(model, context)); + + } + + /** @hidden */ + public createTerrain(context: SceneContext): void { + if (undefined !== this.displayStyle.backgroundMapPlane) + this.displayStyle.backgroundMap.addToScene(context); + } + + /** @hidden */ + public createClassification(context: SceneContext): void { this.forEachModel((model: GeometricModelState) => this.addModelClassifierToScene(model, context)); } + + /** Add view-specific decorations. The base implementation draws the grid. Subclasses must invoke super.decorate() + * @hidden + */ + public decorate(context: DecorateContext): void { + this.drawGrid(context); + if (undefined !== this.displayStyle.backgroundMapPlane) + this.displayStyle.backgroundMap.decorate(context); + } + + /** @hidden */ + public static getStandardViewMatrix(id: StandardViewId): Matrix3d { return StandardView.getStandardRotation(id); } + + /** Orient this view to one of the [[StandardView]] rotations. */ + public setStandardRotation(id: StandardViewId) { this.setRotation(ViewState.getStandardViewMatrix(id)); } + + /** Get the target point of the view. If there is no camera, center is returned. */ + public getTargetPoint(result?: Point3d): Point3d { return this.getCenter(result); } + + /** Get the point at the geometric center of the view. */ + public getCenter(result?: Point3d): Point3d { + const delta = this.getRotation().transpose().multiplyVector(this.getExtents()); + return this.getOrigin().plusScaled(delta, 0.5, result); + } + + /** @hidden */ + public drawGrid(context: DecorateContext): void { + const vp = context.viewport; + if (!vp.isGridOn) + return; + + const orientation = this.getGridOrientation(); + + if (GridOrientationType.AuxCoord === orientation) { + this.auxiliaryCoordinateSystem.drawGrid(context); + return; + } else if (GridOrientationType.GeoCoord === orientation) { + // NEEDSWORK... + } + + const isoGrid = false; + const gridsPerRef = this.getGridsPerRef(); + const spacing = Point2d.createFrom(this.getGridSpacing()); + const origin = Point3d.create(); + const matrix = Matrix3d.createIdentity(); + const fixedRepsAuto = Point2d.create(); + + this.getGridSettings(vp, origin, matrix, orientation); + context.drawStandardGrid(origin, matrix, spacing, gridsPerRef, isoGrid, orientation !== GridOrientationType.View ? fixedRepsAuto : undefined); + } + + /** @hidden */ + public computeWorldToNpc(viewRot?: Matrix3d, inOrigin?: Point3d, delta?: Vector3d): { map: Map4d | undefined, frustFraction: number } { + if (viewRot === undefined) viewRot = this.getRotation(); + const xVector = viewRot.rowX(); + const yVector = viewRot.rowY(); + const zVector = viewRot.rowZ(); + + if (delta === undefined) delta = this.getExtents(); + if (inOrigin === undefined) inOrigin = this.getOrigin(); + + let frustFraction = 1.0; + let xExtent: Vector3d; + let yExtent: Vector3d; + let zExtent: Vector3d; + let origin: Point3d; + + // Compute root vectors along edges of view frustum. + if (this.is3d() && this.isCameraOn) { + const camera = this.camera; + const eyeToOrigin = Vector3d.createStartEnd(camera.eye, inOrigin); // vector from origin on backplane to eye + viewRot.multiplyVectorInPlace(eyeToOrigin); // align with view coordinates. + + const focusDistance = camera.focusDist; + let zDelta = delta.z; + let zBack = eyeToOrigin.z; // Distance from eye to backplane. + let zFront = zBack + zDelta; // Distance from eye to frontplane. + + if (zFront / zBack < Viewport.nearScale24) { + const maximumBackClip = 10000 * Constant.oneKilometer; + if (-zBack > maximumBackClip) { + zBack = -maximumBackClip; + eyeToOrigin.z = zBack; + } + + zFront = zBack * Viewport.nearScale24; + zDelta = zFront - eyeToOrigin.z; + } + + // z out back of eye ===> origin z coordinates are negative. (Back plane more negative than front plane) + const backFraction = -zBack / focusDistance; // Perspective fraction at back clip plane. + const frontFraction = -zFront / focusDistance; // Perspective fraction at front clip plane. + frustFraction = frontFraction / backFraction; + + // delta.x,delta.y are view rectangle sizes at focus distance. Scale to back plane: + xExtent = xVector.scale(delta.x * backFraction); // xExtent at back == delta.x * backFraction. + yExtent = yVector.scale(delta.y * backFraction); // yExtent at back == delta.y * backFraction. + + // Calculate the zExtent in the View coordinate system. + zExtent = new Vector3d(eyeToOrigin.x * (frontFraction - backFraction), eyeToOrigin.y * (frontFraction - backFraction), zDelta); + viewRot.multiplyTransposeVectorInPlace(zExtent); // rotate back to root coordinates. + + origin = new Point3d( + eyeToOrigin.x * backFraction, // Calculate origin in eye coordinates + eyeToOrigin.y * backFraction, + eyeToOrigin.z); + + viewRot.multiplyTransposeVectorInPlace(origin); // Rotate back to root coordinates + origin.plus(camera.eye, origin); // Add the eye point. + } else { + origin = inOrigin; + xExtent = xVector.scale(delta.x); + yExtent = yVector.scale(delta.y); + zExtent = zVector.scale(delta.z); + } + + // calculate the root-to-npc mapping (using expanded frustum) + return { map: Map4d.createVectorFrustum(origin, xExtent, yExtent, zExtent, frustFraction), frustFraction }; + } + + /** + * Calculate the world coordinate Frustum from the parameters of this ViewState. + * @param result Optional Frustum to hold result. If undefined a new Frustum is created. + * @returns The 8-point Frustum with the corners of this ViewState, or undefined if the parameters are invalid. + */ + public calculateFrustum(result?: Frustum): Frustum | undefined { + const val = this.computeWorldToNpc(); + if (undefined === val.map) + return undefined; + + const box = result ? result.initNpc() : new Frustum(); + val.map.transform1.multiplyPoint3dArrayQuietNormalize(box.points); + return box; + } + + /** + * Initialize the origin, extents, and rotation from an existing Frustum + * This function is commonly used in the implementation of [[ViewTool]]s as follows: + * 1. Obtain the ViewState's initial frustum. + * 2. Modify the frustum based on user input. + * 3. Update the ViewState to match the modified frustum. + * @param frustum the input Frustum. + * @return Success if the frustum was successfully updated, or an appropriate error code. + */ + public setupFromFrustum(inFrustum: Frustum): ViewStatus { + const frustum = inFrustum.clone(); // make sure we don't modify input frustum + frustum.fixPointOrder(); + const frustPts = frustum.points; + const viewOrg = frustPts[Npc.LeftBottomRear]; + + // frustumX, frustumY, frustumZ are vectors along edges of the frustum. They are NOT unit vectors. + // X and Y should be perpendicular, and Z should be right handed. + const frustumX = Vector3d.createFrom(frustPts[Npc.RightBottomRear].minus(viewOrg)); + const frustumY = Vector3d.createFrom(frustPts[Npc.LeftTopRear].minus(viewOrg)); + const frustumZ = Vector3d.createFrom(frustPts[Npc.LeftBottomFront].minus(viewOrg)); + + const frustMatrix = Matrix3d.createRigidFromColumns(frustumX, frustumY, AxisOrder.XYZ); + if (!frustMatrix) + return ViewStatus.InvalidWindow; + + // if we're close to one of the standard views, adjust to it to remove any "fuzz" + StandardView.adjustToStandardRotation(frustMatrix); + + const xDir = frustMatrix.getColumn(0); + const yDir = frustMatrix.getColumn(1); + const zDir = frustMatrix.getColumn(2); + + // set up view Rotation matrix as rows of frustum matrix. + const viewRot = frustMatrix.inverse(); + if (!viewRot) + return ViewStatus.InvalidWindow; + + // Left handed frustum? + const zSize = zDir.dotProduct(frustumZ); + if (zSize < 0.0) + return ViewStatus.InvalidWindow; + + const viewDiagRoot = new Vector3d(); + viewDiagRoot.plus2Scaled(xDir, xDir.dotProduct(frustumX), yDir, yDir.dotProduct(frustumY), viewDiagRoot); // vectors on the back plane + viewDiagRoot.plusScaled(zDir, zSize, viewDiagRoot); // add in z vector perpendicular to x,y + + // use center of frustum and view diagonal for origin. Original frustum may not have been orthogonal + frustum.getCenter().plusScaled(viewDiagRoot, -0.5, viewOrg); + + // delta is in view coordinates + const viewDelta = viewRot.multiplyVector(viewDiagRoot); + const validSize = this.validateViewDelta(viewDelta, false); + if (validSize !== ViewStatus.Success) + return validSize; + + this.setOrigin(viewOrg); + this.setExtents(viewDelta); + this.setRotation(viewRot); + return ViewStatus.Success; + } + + /** Get the largest and smallest values allowed for the extents for this ViewState + * @returns an object with members {min, max} + */ + public abstract getExtentLimits(): { min: number, max: number }; + public setDisplayStyle(style: DisplayStyleState) { this.displayStyle = style; } + public getDetails(): any { if (!this.jsonProperties.viewDetails) this.jsonProperties.viewDetails = new Object(); return this.jsonProperties.viewDetails; } + + /** @hidden */ + protected adjustAspectRatio(windowAspect: number): void { + const extents = this.getExtents(); + const viewAspect = extents.x / extents.y; + windowAspect *= this.getAspectRatioSkew(); + + if (Math.abs(1.0 - (viewAspect / windowAspect)) < 1.0e-9) + return; + + const oldDelta = extents.clone(); + if (viewAspect > windowAspect) + extents.y = extents.x / windowAspect; + else + extents.x = extents.y * windowAspect; + + let origin = this.getOrigin(); + const trans = Transform.createOriginAndMatrix(Point3d.createZero(), this.getRotation()); + const newOrigin = trans.multiplyPoint3d(origin); + + newOrigin.x += ((oldDelta.x - extents.x) / 2.0); + newOrigin.y += ((oldDelta.y - extents.y) / 2.0); + + origin = trans.inverse()!.multiplyPoint3d(newOrigin); + this.setOrigin(origin); + this.setExtents(extents); + } + + /** @hidden */ + public showFrustumErrorMessage(status: ViewStatus): void { + let key: string; + switch (status) { + case ViewStatus.InvalidWindow: key = "InvalidWindow"; break; + case ViewStatus.MaxWindow: key = "MaxWindow"; break; + case ViewStatus.MinWindow: key = "MinWindow"; break; + case ViewStatus.MaxZoom: key = "MaxZoom"; break; + default: + return; + } + IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, IModelApp.i18n.translate("Viewing." + key))); + } + + /** @hidden */ + public validateViewDelta(delta: Vector3d, messageNeeded?: boolean): ViewStatus { + const limit = this.getExtentLimits(); + let error = ViewStatus.Success; + + const limitWindowSize = (v: number, ignoreError: boolean) => { + if (v < limit.min) { + v = limit.min; + if (!ignoreError) + error = ViewStatus.MinWindow; + } else if (v > limit.max) { + v = limit.max; + if (!ignoreError) + error = ViewStatus.MaxWindow; + } + return v; + }; + + delta.x = limitWindowSize(delta.x, false); + delta.y = limitWindowSize(delta.y, false); + delta.z = limitWindowSize(delta.z, true); // We ignore z error messages for the sake of 2D views + + if (messageNeeded && error !== ViewStatus.Success) + this.showFrustumErrorMessage(error); + + return error; + } + + /** Returns the view detail associated with the specified name, or undefined if none such exists. + * @hidden + */ + public peekDetail(name: string): any { return this.getDetails()[name]; } + + /** Get the current value of a view detail. If not present, returns an empty object. + * @hidden + */ + public getDetail(name: string): any { const v = this.getDetails()[name]; return v ? v : {}; } + + /** Change the value of a view detail. + * @hidden + */ + public setDetail(name: string, value: any) { this.getDetails()[name] = value; } + + /** Remove a view detail. + * @hidden + */ + public removeDetail(name: string) { delete this.getDetails()[name]; } + + /** Set the CategorySelector for this view. */ + public setCategorySelector(categories: CategorySelectorState) { this.categorySelector = categories; } + + /** get the auxiliary coordinate system state object for this ViewState. */ + public get auxiliaryCoordinateSystem(): AuxCoordSystemState { + if (!this._auxCoordSystem) + this._auxCoordSystem = this.createAuxCoordSystem(""); + return this._auxCoordSystem; + } + + /** Get the ID of the auxiliary coordinate system for this ViewState */ + public getAuxiliaryCoordinateSystemId(): Id64String { return Id64.fromJSON(this.getDetail("acs")); } + + /** Set or clear the AuxiliaryCoordinateSystem for this view. + * @param acs the new AuxiliaryCoordinateSystem for this view. If undefined, no AuxiliaryCoordinateSystem will be used. + */ + public setAuxiliaryCoordinateSystem(acs?: AuxCoordSystemState) { + this._auxCoordSystem = acs; + if (acs) + this.setDetail("acs", acs.id); + else + this.removeDetail("acs"); + } + + /** Determine whether the specified Category is displayed in this view */ + public viewsCategory(id: Id64String): boolean { return this.categorySelector.isCategoryViewed(id); } + + /** Get the aspect ratio (width/height) of this view */ + public getAspectRatio(): number { const extents = this.getExtents(); return extents.x / extents.y; } + + /** Get the aspect ratio skew (x/y, usually 1.0) that is used to exaggerate one axis of the view. */ + public getAspectRatioSkew(): number { return JsonUtils.asDouble(this.getDetail("aspectSkew"), 1.0); } + + /** Set the aspect ratio skew (x/y) for this view. To remove aspect ratio skew, pass 1.0 for val. */ + public setAspectRatioSkew(val: number) { + if (!val || val === 1.0) { + this.removeDetail("aspectSkew"); + } else { + this.setDetail("aspectSkew", val); + } + } + + /** Get the unit vector that points in the view X (left-to-right) direction. + * @param result optional Vector3d to be used for output. If undefined, a new object is created. + */ + public getXVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(0, result); } + + /** Get the unit vector that points in the view Y (bottom-to-top) direction. + * @param result optional Vector3d to be used for output. If undefined, a new object is created. + */ + public getYVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(1, result); } + + /** Get the unit vector that points in the view Z (front-to-back) direction. + * @param result optional Vector3d to be used for output. If undefined, a new object is created. + */ + public getZVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(2, result); } + + /** Set or clear the clipping volume for this view. + * @param clip the new clipping volume. If undefined, clipping is removed from view. + */ + public setViewClip(clip?: ClipVector) { + if (clip && clip.isValid) + this.setDetail("clip", clip.toJSON()); + else + this.removeDetail("clip"); + } + + /** Get the clipping volume for this view, if defined */ + public getViewClip(): ClipVector | undefined { + const clip = this.peekDetail("clip"); + if (clip === undefined) + return undefined; + const clipVector = ClipVector.fromJSON(clip); + return clipVector.isValid ? clipVector : undefined; + } + + /** Set the grid settings for this view */ + public setGridSettings(orientation: GridOrientationType, spacing: Point2d, gridsPerRef: number): void { + switch (orientation) { + case GridOrientationType.WorldYZ: + case GridOrientationType.WorldXZ: + if (!this.is3d()) + return; + break; + + case GridOrientationType.GeoCoord: + if (!this.isSpatialView()) + return; + break; + } + + const details = this.getDetails(); + JsonUtils.setOrRemoveNumber(details, "gridOrient", orientation, GridOrientationType.WorldXY); + JsonUtils.setOrRemoveNumber(details, "gridPerRef", gridsPerRef, 10); + JsonUtils.setOrRemoveNumber(details, "gridSpaceX", spacing.x, 1.0); + JsonUtils.setOrRemoveNumber(details, "gridSpaceY", spacing.y, spacing.x); + } + + /** Populate the given origin and rotation with information from the grid settings from the grid orientation. */ + public getGridSettings(vp: Viewport, origin: Point3d, rMatrix: Matrix3d, orientation: GridOrientationType) { + // start with global origin (for spatial views) and identity matrix + rMatrix.setIdentity(); + origin.setFrom(vp.view.isSpatialView() ? vp.view.iModel.globalOrigin : Point3d.create()); + + switch (orientation) { + case GridOrientationType.View: { + const centerWorld = Point3d.create(0.5, 0.5, 0.5); + vp.npcToWorld(centerWorld, centerWorld); + + rMatrix.setFrom(vp.rotation); + rMatrix.multiplyXYZtoXYZ(origin, origin); + origin.z = centerWorld.z; + rMatrix.multiplyTransposeVectorInPlace(origin); + break; + } + case GridOrientationType.WorldXY: + break; + case GridOrientationType.WorldYZ: { + const rowX = rMatrix.getRow(0); + const rowY = rMatrix.getRow(1); + const rowZ = rMatrix.getRow(2); + rMatrix.setRow(0, rowY); + rMatrix.setRow(1, rowZ); + rMatrix.setRow(2, rowX); + break; + } + case GridOrientationType.WorldXZ: { + const rowX = rMatrix.getRow(0); + const rowY = rMatrix.getRow(1); + const rowZ = rMatrix.getRow(2); + rMatrix.setRow(0, rowX); + rMatrix.setRow(1, rowZ); + rMatrix.setRow(2, rowY); + break; } - } } - }); - } else { - this.categorySelector.dropCategories(categories); - } - - this.setFeatureOverridesDirty(); - } - - private changeSubCategoryDisplay(subCategoryId: Id64String, display: boolean): void { - const app = this.subCategories.getSubCategoryAppearance(subCategoryId); - if (undefined === app) - return; // category is not enabled or subcategory does not exist - - const curOvr = this.getSubCategoryOverride(subCategoryId); - const isAlreadyVisible = undefined !== curOvr && undefined !== curOvr.invisible ? !curOvr.invisible : !app.invisible; - if (isAlreadyVisible === display) - return; - - // Preserve existing overrides - just flip the visibility flag. - const json = undefined !== curOvr ? curOvr.toJSON() : {}; - json.invisible = !display; - this.overrideSubCategory(subCategoryId, SubCategoryOverride.fromJSON(json)); - } - - /** Returns true if the set of elements returned by GetAlwaysDrawn() are the *only* elements rendered by this view */ - public get areFeatureOverridesDirty(): boolean { return this._featureOverridesDirty; } - /** @hidden */ - public get isSelectionSetDirty(): boolean { return this._selectionSetDirty; } - - /** Mark the [[FeatureSymbology.Overrides]] associated with this view as "dirty". - * Typically this is handled internally. - * Conditions that may cause the overrides to become dirty include: - * - Toggling the display of a category within the view. - * - Changing the symbology associated with a [[SubCategory]] within the view by adding a [[SubCategoryOverride]] - * - Changes in some application state that affects the [[AddFeatureOverrides]] function registered with [[Viewport]]. - * The next time the [[Viewport]] associated with this [[ViewState]] is rendered, the symbology overrides will be regenerated if they have been marked "dirty". - */ - public setFeatureOverridesDirty(dirty: boolean = true): void { this._featureOverridesDirty = dirty; } - /** @hidden */ - public setSelectionSetDirty(dirty: boolean = true): void { this._selectionSetDirty = dirty; } - public is3d(): this is ViewState3d { return this instanceof ViewState3d; } - public isSpatialView(): this is SpatialViewState { return this instanceof SpatialViewState; } - /** Returns true if [[ViewTool]]s are allowed to operate in three dimensions on this view. */ - public abstract allow3dManipulations(): boolean; - /** @hidden */ - public abstract createAuxCoordSystem(acsName: string): AuxCoordSystemState; - /** Get the extents of this view in [[CoordSystem.World]] coordinates. */ - public abstract getViewedExtents(): AxisAlignedBox3d; - /** Compute a range in [[CoordSystem.World]] coordinates that tightly encloses the contents of this view. - * @see [[FitViewTool]]. - */ - public abstract computeFitRange(): Range3d; - - /** Override this if you want to perform some logic on each iteration of the render loop. - * @hidden - */ - public abstract onRenderFrame(_viewport: Viewport): void; - - /** Returns true if this view displays the contents of a [[Model]] specified by ID. */ - public abstract viewsModel(modelId: Id64String): boolean; - - /** Get the origin of this view in [[CoordSystem.World]] coordinates. */ - public abstract getOrigin(): Point3d; - - /** Get the extents of this view in [[CoordSystem.World]] coordinates. */ - public abstract getExtents(): Vector3d; - - /** Get the 3x3 ortho-normal Matrix3d for this view. */ - public abstract getRotation(): Matrix3d; - - /** Set the origin of this view in [[CoordSystem.World]] coordinates. */ - public abstract setOrigin(viewOrg: Point3d): void; - - /** Set the extents of this view in [[CoordSystem.World]] coordinates. */ - public abstract setExtents(viewDelta: Vector3d): void; - - /** Change the rotation of the view. - * @note viewRot must be ortho-normal. For 2d views, only the rotation angle about the z axis is used. - */ - public abstract setRotation(viewRot: Matrix3d): void; - - /** Execute a function on each viewed model */ - public abstract forEachModel(func: (model: GeometricModelState) => void): void; - - /** @hidden */ - public createScene(context: SceneContext): void { this.forEachModel((model: GeometricModelState) => this.addModelToScene(model, context)); } - - /** @hidden */ - public createTerrain(context: SceneContext): void { - if (undefined !== this.displayStyle.backgroundMapPlane) - this.displayStyle.backgroundMap.addToScene(context); - } - - /** @hidden */ - public createClassification(context: SceneContext): void { this.forEachModel((model: GeometricModelState) => this.addModelClassifierToScene(model, context)); } - - /** Add view-specific decorations. The base implementation draws the grid. Subclasses must invoke super.decorate() - * @hidden - */ - public decorate(context: DecorateContext): void { - this.drawGrid(context); - if (undefined !== this.displayStyle.backgroundMapPlane) - this.displayStyle.backgroundMap.decorate(context); - } - - /** @hidden */ - public static getStandardViewMatrix(id: StandardViewId): Matrix3d { return StandardView.getStandardRotation(id); } - - /** Orient this view to one of the [[StandardView]] rotations. */ - public setStandardRotation(id: StandardViewId) { this.setRotation(ViewState.getStandardViewMatrix(id)); } - - /** Get the target point of the view. If there is no camera, center is returned. */ - public getTargetPoint(result?: Point3d): Point3d { return this.getCenter(result); } - - /** Get the point at the geometric center of the view. */ - public getCenter(result?: Point3d): Point3d { - const delta = this.getRotation().transpose().multiplyVector(this.getExtents()); - return this.getOrigin().plusScaled(delta, 0.5, result); - } - - /** @hidden */ - public drawGrid(context: DecorateContext): void { - const vp = context.viewport; - if (!vp.isGridOn) - return; - - const orientation = this.getGridOrientation(); - - if (GridOrientationType.AuxCoord === orientation) { - this.auxiliaryCoordinateSystem.drawGrid(context); - return; - } else if (GridOrientationType.GeoCoord === orientation) { - // NEEDSWORK... - } - - const isoGrid = false; - const gridsPerRef = this.getGridsPerRef(); - const spacing = Point2d.createFrom(this.getGridSpacing()); - const origin = Point3d.create(); - const matrix = Matrix3d.createIdentity(); - const fixedRepsAuto = Point2d.create(); - - this.getGridSettings(vp, origin, matrix, orientation); - context.drawStandardGrid(origin, matrix, spacing, gridsPerRef, isoGrid, orientation !== GridOrientationType.View ? fixedRepsAuto : undefined); - } - - /** @hidden */ - public computeWorldToNpc(viewRot?: Matrix3d, inOrigin?: Point3d, delta?: Vector3d): { map: Map4d | undefined, frustFraction: number } { - if (viewRot === undefined) viewRot = this.getRotation(); - const xVector = viewRot.rowX(); - const yVector = viewRot.rowY(); - const zVector = viewRot.rowZ(); - - if (delta === undefined) delta = this.getExtents(); - if (inOrigin === undefined) inOrigin = this.getOrigin(); - - let frustFraction = 1.0; - let xExtent: Vector3d; - let yExtent: Vector3d; - let zExtent: Vector3d; - let origin: Point3d; - - // Compute root vectors along edges of view frustum. - if (this.is3d() && this.isCameraOn) { - const camera = this.camera; - const eyeToOrigin = Vector3d.createStartEnd(camera.eye, inOrigin); // vector from origin on backplane to eye - viewRot.multiplyVectorInPlace(eyeToOrigin); // align with view coordinates. - - const focusDistance = camera.focusDist; - let zDelta = delta.z; - let zBack = eyeToOrigin.z; // Distance from eye to backplane. - let zFront = zBack + zDelta; // Distance from eye to frontplane. - - if (zFront / zBack < Viewport.nearScale24) { - const maximumBackClip = 10000 * Constant.oneKilometer; - if (-zBack > maximumBackClip) { - zBack = -maximumBackClip; - eyeToOrigin.z = zBack; + } + + /** Get the grid settings for this view */ + public getGridOrientation(): GridOrientationType { return JsonUtils.asInt(this.getDetail("gridOrient"), GridOrientationType.WorldXY); } + public getGridsPerRef(): number { return JsonUtils.asInt(this.getDetail("gridPerRef"), 10); } + public getGridSpacing(): XAndY { + const x = JsonUtils.asInt(this.getDetail("gridSpaceX"), 1.0); + return { x, y: JsonUtils.asInt(this.getDetail("gridSpaceY"), x) }; + } + /** + * Change the volume that this view displays, keeping its current rotation. + * @param volume The new volume, in world-coordinates, for the view. The resulting view will show all of worldVolume, by fitting a + * view-axis-aligned bounding box around it. For views that are not aligned with the world coordinate system, this will sometimes + * result in a much larger volume than worldVolume. + * @param aspect The X/Y aspect ratio of the view into which the result will be displayed. If the aspect ratio of the volume does not + * match aspect, the shorter axis is lengthened and the volume is centered. If aspect is undefined, no adjustment is made. + * @param margin The amount of "white space" to leave around the view volume (which essentially increases the volume + * of space shown in the view.) If undefined, no additional white space is added. + * @note for 2d views, only the X and Y values of volume are used. + */ + public lookAtVolume(volume: LowAndHighXYZ | LowAndHighXY, aspect?: number, margin?: MarginPercent) { + const rangeBox = Frustum.fromRange(volume).points; + this.getRotation().multiplyVectorArrayInPlace(rangeBox); + return this.lookAtViewAlignedVolume(Range3d.createArray(rangeBox), aspect, margin); + } + + /** + * look at a volume of space defined by a range in view local coordinates, keeping its current rotation. + * @param volume The new volume, in view-coordinates, for the view. The resulting view will show all of volume. + * @param aspect The X/Y aspect ratio of the view into which the result will be displayed. If the aspect ratio of the volume does not + * match aspect, the shorter axis is lengthened and the volume is centered. If aspect is undefined, no adjustment is made. + * @param margin The amount of "white space" to leave around the view volume (which essentially increases the volume + * of space shown in the view.) If undefined, no additional white space is added. + * @see lookAtVolume + */ + public lookAtViewAlignedVolume(volume: Range3d, aspect?: number, margin?: MarginPercent) { + if (volume.isNull) // make sure volume is valid + return; + + const viewRot = this.getRotation(); + const newOrigin = volume.low.clone(); + let newDelta = Vector3d.createStartEnd(volume.low, volume.high); + + const minimumDepth = Constant.oneMillimeter; + if (newDelta.z < minimumDepth) { + newOrigin.z -= (minimumDepth - newDelta.z) / 2.0; + newDelta.z = minimumDepth; + } + + let origNewDelta = newDelta.clone(); + + const isCameraOn: boolean = this.is3d() && this.isCameraOn; + if (isCameraOn) { + // If the camera is on, the only way to guarantee we can see the entire volume is to set delta at the front plane, not focus plane. + // That generally causes the view to be too large (objects in it are too small), since we can't tell whether the objects are at + // the front or back of the view. For this reason, don't attempt to add any "margin" to camera views. + } else if (margin) { + // compute how much space we'll need for both of X and Y margins in root coordinates + const wPercent = margin.left + margin.right; + const hPercent = margin.top + margin.bottom; + + const marginHorizontal = wPercent / (1 - wPercent) * newDelta.x; + const marginVert = hPercent / (1 - hPercent) * newDelta.y; + + // compute left and bottom margins in root coordinates + const marginLeft = margin.left / (1 - wPercent) * newDelta.x; + const marginBottom = margin.bottom / (1 - hPercent) * newDelta.y; + + // add the margins to the range + newOrigin.x -= marginLeft; + newOrigin.y -= marginBottom; + newDelta.x += marginHorizontal; + newDelta.y += marginVert; + + // don't fix the origin due to changes in delta here + origNewDelta = newDelta.clone(); + } else { + newDelta.scale(1.04, newDelta); // default "dilation" + } + + if (isCameraOn) { + // make sure that the zDelta is large enough so that entire model will be visible from any rotation + const diag = newDelta.magnitudeXY(); + if (diag > newDelta.z) + newDelta.z = diag; } - zFront = zBack * Viewport.nearScale24; - zDelta = zFront - eyeToOrigin.z; - } - - // z out back of eye ===> origin z coordinates are negative. (Back plane more negative than front plane) - const backFraction = -zBack / focusDistance; // Perspective fraction at back clip plane. - const frontFraction = -zFront / focusDistance; // Perspective fraction at front clip plane. - frustFraction = frontFraction / backFraction; - - // delta.x,delta.y are view rectangle sizes at focus distance. Scale to back plane: - xExtent = xVector.scale(delta.x * backFraction); // xExtent at back == delta.x * backFraction. - yExtent = yVector.scale(delta.y * backFraction); // yExtent at back == delta.y * backFraction. - - // Calculate the zExtent in the View coordinate system. - zExtent = new Vector3d(eyeToOrigin.x * (frontFraction - backFraction), eyeToOrigin.y * (frontFraction - backFraction), zDelta); - viewRot.multiplyTransposeVectorInPlace(zExtent); // rotate back to root coordinates. - - origin = new Point3d( - eyeToOrigin.x * backFraction, // Calculate origin in eye coordinates - eyeToOrigin.y * backFraction, - eyeToOrigin.z); - - viewRot.multiplyTransposeVectorInPlace(origin); // Rotate back to root coordinates - origin.plus(camera.eye, origin); // Add the eye point. - } else { - origin = inOrigin; - xExtent = xVector.scale(delta.x); - yExtent = yVector.scale(delta.y); - zExtent = zVector.scale(delta.z); - } - - // calculate the root-to-npc mapping (using expanded frustum) - return { map: Map4d.createVectorFrustum(origin, xExtent, yExtent, zExtent, frustFraction), frustFraction }; - } - - /** - * Calculate the world coordinate Frustum from the parameters of this ViewState. - * @param result Optional Frustum to hold result. If undefined a new Frustum is created. - * @returns The 8-point Frustum with the corners of this ViewState, or undefined if the parameters are invalid. - */ - public calculateFrustum(result?: Frustum): Frustum | undefined { - const val = this.computeWorldToNpc(); - if (undefined === val.map) - return undefined; - - const box = result ? result.initNpc() : new Frustum(); - val.map.transform1.multiplyPoint3dArrayQuietNormalize(box.points); - return box; - } - - /** - * Initialize the origin, extents, and rotation from an existing Frustum - * This function is commonly used in the implementation of [[ViewTool]]s as follows: - * 1. Obtain the ViewState's initial frustum. - * 2. Modify the frustum based on user input. - * 3. Update the ViewState to match the modified frustum. - * @param frustum the input Frustum. - * @return Success if the frustum was successfully updated, or an appropriate error code. - */ - public setupFromFrustum(inFrustum: Frustum): ViewStatus { - const frustum = inFrustum.clone(); // make sure we don't modify input frustum - frustum.fixPointOrder(); - const frustPts = frustum.points; - const viewOrg = frustPts[Npc.LeftBottomRear]; - - // frustumX, frustumY, frustumZ are vectors along edges of the frustum. They are NOT unit vectors. - // X and Y should be perpendicular, and Z should be right handed. - const frustumX = Vector3d.createFrom(frustPts[Npc.RightBottomRear].minus(viewOrg)); - const frustumY = Vector3d.createFrom(frustPts[Npc.LeftTopRear].minus(viewOrg)); - const frustumZ = Vector3d.createFrom(frustPts[Npc.LeftBottomFront].minus(viewOrg)); - - const frustMatrix = Matrix3d.createRigidFromColumns(frustumX, frustumY, AxisOrder.XYZ); - if (!frustMatrix) - return ViewStatus.InvalidWindow; - - // if we're close to one of the standard views, adjust to it to remove any "fuzz" - StandardView.adjustToStandardRotation(frustMatrix); - - const xDir = frustMatrix.getColumn(0); - const yDir = frustMatrix.getColumn(1); - const zDir = frustMatrix.getColumn(2); - - // set up view Rotation matrix as rows of frustum matrix. - const viewRot = frustMatrix.inverse(); - if (!viewRot) - return ViewStatus.InvalidWindow; - - // Left handed frustum? - const zSize = zDir.dotProduct(frustumZ); - if (zSize < 0.0) - return ViewStatus.InvalidWindow; - - const viewDiagRoot = new Vector3d(); - viewDiagRoot.plus2Scaled(xDir, xDir.dotProduct(frustumX), yDir, yDir.dotProduct(frustumY), viewDiagRoot); // vectors on the back plane - viewDiagRoot.plusScaled(zDir, zSize, viewDiagRoot); // add in z vector perpendicular to x,y - - // use center of frustum and view diagonal for origin. Original frustum may not have been orthogonal - frustum.getCenter().plusScaled(viewDiagRoot, -0.5, viewOrg); - - // delta is in view coordinates - const viewDelta = viewRot.multiplyVector(viewDiagRoot); - const validSize = this.validateViewDelta(viewDelta, false); - if (validSize !== ViewStatus.Success) - return validSize; - - this.setOrigin(viewOrg); - this.setExtents(viewDelta); - this.setRotation(viewRot); - return ViewStatus.Success; - } - - /** Get the largest and smallest values allowed for the extents for this ViewState - * @returns an object with members {min, max} - */ - public abstract getExtentLimits(): { min: number, max: number }; - public setDisplayStyle(style: DisplayStyleState) { this.displayStyle = style; } - public getDetails(): any { if (!this.jsonProperties.viewDetails) this.jsonProperties.viewDetails = new Object(); return this.jsonProperties.viewDetails; } - - /** @hidden */ - protected adjustAspectRatio(windowAspect: number): void { - const extents = this.getExtents(); - const viewAspect = extents.x / extents.y; - windowAspect *= this.getAspectRatioSkew(); - - if (Math.abs(1.0 - (viewAspect / windowAspect)) < 1.0e-9) - return; - - const oldDelta = extents.clone(); - if (viewAspect > windowAspect) - extents.y = extents.x / windowAspect; - else - extents.x = extents.y * windowAspect; - - let origin = this.getOrigin(); - const trans = Transform.createOriginAndMatrix(Point3d.createZero(), this.getRotation()); - const newOrigin = trans.multiplyPoint3d(origin); - - newOrigin.x += ((oldDelta.x - extents.x) / 2.0); - newOrigin.y += ((oldDelta.y - extents.y) / 2.0); - - origin = trans.inverse()!.multiplyPoint3d(newOrigin); - this.setOrigin(origin); - this.setExtents(extents); - } - - /** @hidden */ - public showFrustumErrorMessage(status: ViewStatus): void { - let key: string; - switch (status) { - case ViewStatus.InvalidWindow: key = "InvalidWindow"; break; - case ViewStatus.MaxWindow: key = "MaxWindow"; break; - case ViewStatus.MinWindow: key = "MinWindow"; break; - case ViewStatus.MaxZoom: key = "MaxZoom"; break; - default: - return; - } - IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, IModelApp.i18n.translate("Viewing." + key))); - } - - /** @hidden */ - public validateViewDelta(delta: Vector3d, messageNeeded?: boolean): ViewStatus { - const limit = this.getExtentLimits(); - let error = ViewStatus.Success; - - const limitWindowSize = (v: number, ignoreError: boolean) => { - if (v < limit.min) { - v = limit.min; - if (!ignoreError) - error = ViewStatus.MinWindow; - } else if (v > limit.max) { - v = limit.max; - if (!ignoreError) - error = ViewStatus.MaxWindow; - } - return v; - }; - - delta.x = limitWindowSize(delta.x, false); - delta.y = limitWindowSize(delta.y, false); - delta.z = limitWindowSize(delta.z, true); // We ignore z error messages for the sake of 2D views - - if (messageNeeded && error !== ViewStatus.Success) - this.showFrustumErrorMessage(error); - - return error; - } - - /** Returns the view detail associated with the specified name, or undefined if none such exists. - * @hidden - */ - public peekDetail(name: string): any { return this.getDetails()[name]; } - - /** Get the current value of a view detail. If not present, returns an empty object. - * @hidden - */ - public getDetail(name: string): any { const v = this.getDetails()[name]; return v ? v : {}; } - - /** Change the value of a view detail. - * @hidden - */ - public setDetail(name: string, value: any) { this.getDetails()[name] = value; } - - /** Remove a view detail. - * @hidden - */ - public removeDetail(name: string) { delete this.getDetails()[name]; } - - /** Set the CategorySelector for this view. */ - public setCategorySelector(categories: CategorySelectorState) { this.categorySelector = categories; } - - /** get the auxiliary coordinate system state object for this ViewState. */ - public get auxiliaryCoordinateSystem(): AuxCoordSystemState { - if (!this._auxCoordSystem) - this._auxCoordSystem = this.createAuxCoordSystem(""); - return this._auxCoordSystem; - } - - /** Get the ID of the auxiliary coordinate system for this ViewState */ - public getAuxiliaryCoordinateSystemId(): Id64String { return Id64.fromJSON(this.getDetail("acs")); } - - /** Set or clear the AuxiliaryCoordinateSystem for this view. - * @param acs the new AuxiliaryCoordinateSystem for this view. If undefined, no AuxiliaryCoordinateSystem will be used. - */ - public setAuxiliaryCoordinateSystem(acs?: AuxCoordSystemState) { - this._auxCoordSystem = acs; - if (acs) - this.setDetail("acs", acs.id); - else - this.removeDetail("acs"); - } - - /** Determine whether the specified Category is displayed in this view */ - public viewsCategory(id: Id64String): boolean { return this.categorySelector.isCategoryViewed(id); } - - /** Get the aspect ratio (width/height) of this view */ - public getAspectRatio(): number { const extents = this.getExtents(); return extents.x / extents.y; } - - /** Get the aspect ratio skew (x/y, usually 1.0) that is used to exaggerate one axis of the view. */ - public getAspectRatioSkew(): number { return JsonUtils.asDouble(this.getDetail("aspectSkew"), 1.0); } - - /** Set the aspect ratio skew (x/y) for this view. To remove aspect ratio skew, pass 1.0 for val. */ - public setAspectRatioSkew(val: number) { - if (!val || val === 1.0) { - this.removeDetail("aspectSkew"); - } else { - this.setDetail("aspectSkew", val); - } - } - - /** Get the unit vector that points in the view X (left-to-right) direction. - * @param result optional Vector3d to be used for output. If undefined, a new object is created. - */ - public getXVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(0, result); } - - /** Get the unit vector that points in the view Y (bottom-to-top) direction. - * @param result optional Vector3d to be used for output. If undefined, a new object is created. - */ - public getYVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(1, result); } - - /** Get the unit vector that points in the view Z (front-to-back) direction. - * @param result optional Vector3d to be used for output. If undefined, a new object is created. - */ - public getZVector(result?: Vector3d): Vector3d { return this.getRotation().getRow(2, result); } - - /** Set or clear the clipping volume for this view. - * @param clip the new clipping volume. If undefined, clipping is removed from view. - */ - public setViewClip(clip?: ClipVector) { - if (clip && clip.isValid) - this.setDetail("clip", clip.toJSON()); - else - this.removeDetail("clip"); - } - - /** Get the clipping volume for this view, if defined */ - public getViewClip(): ClipVector | undefined { - const clip = this.peekDetail("clip"); - if (clip === undefined) - return undefined; - const clipVector = ClipVector.fromJSON(clip); - return clipVector.isValid ? clipVector : undefined; - } - - /** Set the grid settings for this view */ - public setGridSettings(orientation: GridOrientationType, spacing: Point2d, gridsPerRef: number): void { - switch (orientation) { - case GridOrientationType.WorldYZ: - case GridOrientationType.WorldXZ: + this.validateViewDelta(newDelta, true); + + this.setExtents(newDelta); + if (aspect) + this.adjustAspectRatio(aspect); + + newDelta = this.getExtents(); + + newOrigin.x -= (newDelta.x - origNewDelta.x) / 2.0; + newOrigin.y -= (newDelta.y - origNewDelta.y) / 2.0; + newOrigin.z -= (newDelta.z - origNewDelta.z) / 2.0; + + viewRot.multiplyTransposeVectorInPlace(newOrigin); + this.setOrigin(newOrigin); + if (!this.is3d()) - return; - break; - - case GridOrientationType.GeoCoord: - if (!this.isSpatialView()) - return; - break; - } - - const details = this.getDetails(); - JsonUtils.setOrRemoveNumber(details, "gridOrient", orientation, GridOrientationType.WorldXY); - JsonUtils.setOrRemoveNumber(details, "gridPerRef", gridsPerRef, 10); - JsonUtils.setOrRemoveNumber(details, "gridSpaceX", spacing.x, 1.0); - JsonUtils.setOrRemoveNumber(details, "gridSpaceY", spacing.y, spacing.x); - } - - /** Populate the given origin and rotation with information from the grid settings from the grid orientation. */ - public getGridSettings(vp: Viewport, origin: Point3d, rMatrix: Matrix3d, orientation: GridOrientationType) { - // start with global origin (for spatial views) and identity matrix - rMatrix.setIdentity(); - origin.setFrom(vp.view.isSpatialView() ? vp.view.iModel.globalOrigin : Point3d.create()); - - switch (orientation) { - case GridOrientationType.View: { - const centerWorld = Point3d.create(0.5, 0.5, 0.5); - vp.npcToWorld(centerWorld, centerWorld); - - rMatrix.setFrom(vp.rotation); - rMatrix.multiplyXYZtoXYZ(origin, origin); - origin.z = centerWorld.z; - rMatrix.multiplyTransposeVectorInPlace(origin); - break; - } - case GridOrientationType.WorldXY: - break; - case GridOrientationType.WorldYZ: { - const rowX = rMatrix.getRow(0); - const rowY = rMatrix.getRow(1); - const rowZ = rMatrix.getRow(2); - rMatrix.setRow(0, rowY); - rMatrix.setRow(1, rowZ); - rMatrix.setRow(2, rowX); - break; - } - case GridOrientationType.WorldXZ: { - const rowX = rMatrix.getRow(0); - const rowY = rMatrix.getRow(1); - const rowZ = rMatrix.getRow(2); - rMatrix.setRow(0, rowX); - rMatrix.setRow(1, rowZ); - rMatrix.setRow(2, rowY); - break; - } - } - } - - /** Get the grid settings for this view */ - public getGridOrientation(): GridOrientationType { return JsonUtils.asInt(this.getDetail("gridOrient"), GridOrientationType.WorldXY); } - public getGridsPerRef(): number { return JsonUtils.asInt(this.getDetail("gridPerRef"), 10); } - public getGridSpacing(): XAndY { - const x = JsonUtils.asInt(this.getDetail("gridSpaceX"), 1.0); - return { x, y: JsonUtils.asInt(this.getDetail("gridSpaceY"), x) }; - } - /** - * Change the volume that this view displays, keeping its current rotation. - * @param volume The new volume, in world-coordinates, for the view. The resulting view will show all of worldVolume, by fitting a - * view-axis-aligned bounding box around it. For views that are not aligned with the world coordinate system, this will sometimes - * result in a much larger volume than worldVolume. - * @param aspect The X/Y aspect ratio of the view into which the result will be displayed. If the aspect ratio of the volume does not - * match aspect, the shorter axis is lengthened and the volume is centered. If aspect is undefined, no adjustment is made. - * @param margin The amount of "white space" to leave around the view volume (which essentially increases the volume - * of space shown in the view.) If undefined, no additional white space is added. - * @note for 2d views, only the X and Y values of volume are used. - */ - public lookAtVolume(volume: LowAndHighXYZ | LowAndHighXY, aspect?: number, margin?: MarginPercent) { - const rangeBox = Frustum.fromRange(volume).points; - this.getRotation().multiplyVectorArrayInPlace(rangeBox); - return this.lookAtViewAlignedVolume(Range3d.createArray(rangeBox), aspect, margin); - } - - /** - * look at a volume of space defined by a range in view local coordinates, keeping its current rotation. - * @param volume The new volume, in view-coordinates, for the view. The resulting view will show all of volume. - * @param aspect The X/Y aspect ratio of the view into which the result will be displayed. If the aspect ratio of the volume does not - * match aspect, the shorter axis is lengthened and the volume is centered. If aspect is undefined, no adjustment is made. - * @param margin The amount of "white space" to leave around the view volume (which essentially increases the volume - * of space shown in the view.) If undefined, no additional white space is added. - * @see lookAtVolume - */ - public lookAtViewAlignedVolume(volume: Range3d, aspect?: number, margin?: MarginPercent) { - if (volume.isNull) // make sure volume is valid - return; - - const viewRot = this.getRotation(); - const newOrigin = volume.low.clone(); - let newDelta = Vector3d.createStartEnd(volume.low, volume.high); - - const minimumDepth = Constant.oneMillimeter; - if (newDelta.z < minimumDepth) { - newOrigin.z -= (minimumDepth - newDelta.z) / 2.0; - newDelta.z = minimumDepth; - } - - let origNewDelta = newDelta.clone(); - - const isCameraOn: boolean = this.is3d() && this.isCameraOn; - if (isCameraOn) { - // If the camera is on, the only way to guarantee we can see the entire volume is to set delta at the front plane, not focus plane. - // That generally causes the view to be too large (objects in it are too small), since we can't tell whether the objects are at - // the front or back of the view. For this reason, don't attempt to add any "margin" to camera views. - } else if (margin) { - // compute how much space we'll need for both of X and Y margins in root coordinates - const wPercent = margin.left + margin.right; - const hPercent = margin.top + margin.bottom; - - const marginHorizontal = wPercent / (1 - wPercent) * newDelta.x; - const marginVert = hPercent / (1 - hPercent) * newDelta.y; - - // compute left and bottom margins in root coordinates - const marginLeft = margin.left / (1 - wPercent) * newDelta.x; - const marginBottom = margin.bottom / (1 - hPercent) * newDelta.y; - - // add the margins to the range - newOrigin.x -= marginLeft; - newOrigin.y -= marginBottom; - newDelta.x += marginHorizontal; - newDelta.y += marginVert; - - // don't fix the origin due to changes in delta here - origNewDelta = newDelta.clone(); - } else { - newDelta.scale(1.04, newDelta); // default "dilation" - } - - if (isCameraOn) { - // make sure that the zDelta is large enough so that entire model will be visible from any rotation - const diag = newDelta.magnitudeXY(); - if (diag > newDelta.z) - newDelta.z = diag; - } - - this.validateViewDelta(newDelta, true); - - this.setExtents(newDelta); - if (aspect) - this.adjustAspectRatio(aspect); - - newDelta = this.getExtents(); - - newOrigin.x -= (newDelta.x - origNewDelta.x) / 2.0; - newOrigin.y -= (newDelta.y - origNewDelta.y) / 2.0; - newOrigin.z -= (newDelta.z - origNewDelta.z) / 2.0; - - viewRot.multiplyTransposeVectorInPlace(newOrigin); - this.setOrigin(newOrigin); - - if (!this.is3d()) - return; - - const cameraDef: Camera = this.camera; - cameraDef.validateLens(); - // move the camera back so the entire x,y range is visible at front plane - const frontDist = Math.max(newDelta.x, newDelta.y) / (2.0 * Math.tan(cameraDef.getLensAngle().radians / 2.0)); - const backDist = frontDist + newDelta.z; - - cameraDef.setFocusDistance(frontDist); // do this even if the camera isn't currently on. - this.centerEyePoint(backDist); // do this even if the camera isn't currently on. - this.verifyFocusPlane(); // changes delta/origin - } - - private addModelToScene(model: GeometricModelState, context: SceneContext): void { - model.loadTileTree(); - if (undefined !== model.tileTree) { - model.tileTree.drawScene(context); - } - } - private addModelClassifierToScene(model: GeometricModelState, context: SceneContext): void { - if (model.jsonProperties.classifiers === undefined) - return; - for (const classifier of model.jsonProperties.classifiers) { - if (classifier.isActive) { - const classifierModel = this.iModel.models.getLoaded(classifier.modelId) as GeometricModelState; - if (undefined !== classifierModel) { - classifierModel.loadTileTree(true, classifier.expand); - if (undefined !== classifierModel.classifierTileTree) - classifierModel.classifierTileTree.drawScene(context); + return; + + const cameraDef: Camera = this.camera; + cameraDef.validateLens(); + // move the camera back so the entire x,y range is visible at front plane + const frontDist = Math.max(newDelta.x, newDelta.y) / (2.0 * Math.tan(cameraDef.getLensAngle().radians / 2.0)); + const backDist = frontDist + newDelta.z; + + cameraDef.setFocusDistance(frontDist); // do this even if the camera isn't currently on. + this.centerEyePoint(backDist); // do this even if the camera isn't currently on. + this.verifyFocusPlane(); // changes delta/origin + } + + private addModelToScene(model: TileTreeModelState, context: SceneContext): void { + model.loadTileTree(); + const tileTree = model.tileTree; + if (undefined !== tileTree) { + tileTree.drawScene(context); + } + } + private addModelClassifierToScene(model: GeometricModelState, context: SceneContext): void { + if (model.jsonProperties.classifiers === undefined) + return; + for (const classifier of model.jsonProperties.classifiers) { + if (classifier.isActive) { + const classifierModel = this.iModel.models.getLoaded(classifier.modelId) as GeometricModelState; + if (undefined !== classifierModel) { + classifierModel.loadTileTree(true, classifier.expand); + if (undefined !== classifierModel.classifierTileTree) + classifierModel.classifierTileTree.drawScene(context); + } + } + } + } + + /** + * Set the rotation of this ViewState to the supplied rotation, by rotating it about a point. + * @param rotation The new rotation matrix for this ViewState. + * @param point The point to rotate about. If undefined, use the [[getTargetPoint]]. + */ + public setRotationAboutPoint(rotation: Matrix3d, point?: Point3d): void { + if (undefined === point) + point = this.getTargetPoint(); + + const inverse = rotation.clone().inverse(); + if (undefined === inverse) + return; + + const targetMatrix = inverse.multiplyMatrixMatrix(this.getRotation()); + const worldTransform = Transform.createFixedPointAndMatrix(point, targetMatrix); + const frustum = this.calculateFrustum(); + if (undefined !== frustum) { + frustum.multiply(worldTransform); + this.setupFromFrustum(frustum); } - } - } - } - - /** - * Set the rotation of this ViewState to the supplied rotation, by rotating it about a point. - * @param rotation The new rotation matrix for this ViewState. - * @param point The point to rotate about. If undefined, use the [[getTargetPoint]]. - */ - public setRotationAboutPoint(rotation: Matrix3d, point?: Point3d): void { - if (undefined === point) - point = this.getTargetPoint(); - - const inverse = rotation.clone().inverse(); - if (undefined === inverse) - return; - - const targetMatrix = inverse.multiplyMatrixMatrix(this.getRotation()); - const worldTransform = Transform.createFixedPointAndMatrix(point, targetMatrix); - const frustum = this.calculateFrustum(); - if (undefined !== frustum) { - frustum.multiply(worldTransform); - this.setupFromFrustum(frustum); - } - } + } } /** Defines the state of a view of 3d models. * @see [ViewState Parameters]($docs/learning/frontend/views#viewstate-parameters) */ export abstract class ViewState3d extends ViewState { - /** True if the camera is valid. */ - protected _cameraOn: boolean; - /** The lower left back corner of the view frustum. */ - public readonly origin: Point3d; - /** The extent of the view frustum. */ - public readonly extents: Vector3d; - /** Rotation of the view frustum. */ - public readonly rotation: Matrix3d; - /** The camera used for this view. */ - public readonly camera: Camera; - /** Minimum distance for front plane */ - public forceMinFrontDist = 0.0; - /** @hidden */ - public static get className() { return "ViewDefinition3d"; } - public onRenderFrame(_viewport: Viewport): void { } - public allow3dManipulations(): boolean { return true; } - public constructor(props: ViewDefinition3dProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle3dState) { - super(props, iModel, categories, displayStyle); - this._cameraOn = JsonUtils.asBool(props.cameraOn); - this.origin = Point3d.fromJSON(props.origin); - this.extents = Vector3d.fromJSON(props.extents); - this.rotation = YawPitchRollAngles.fromJSON(props.angles).toMatrix3d(); - assert(this.rotation.isRigid()); - this.camera = new Camera(props.camera); - } - - public toJSON(): ViewDefinition3dProps { - const val = super.toJSON() as ViewDefinition3dProps; - val.cameraOn = this._cameraOn; - val.origin = this.origin; - val.extents = this.extents; - val.angles = YawPitchRollAngles.createFromMatrix3d(this.rotation)!.toJSON(); - assert(undefined !== val.angles, "rotMatrix is illegal"); - val.camera = this.camera; - return val; - } - - public equalState(other: ViewState3d): boolean { - if (!this.origin.isAlmostEqual(other.origin) || !this.extents.isAlmostEqual(other.extents) || !this.rotation.isAlmostEqual(other.rotation)) - return false; - - if (this.isCameraOn !== other.isCameraOn) - return false; - - if (this.isCameraOn && this.camera.equals(other.camera)) // ###TODO: should this be less precise equality? - return false; - - return super.equalState(other); - } - - public get isCameraOn(): boolean { return this._cameraOn; } - public setupFromFrustum(frustum: Frustum): ViewStatus { - const stat = super.setupFromFrustum(frustum); - if (ViewStatus.Success !== stat) - return stat; - - this.turnCameraOff(); - const frustPts = frustum.points; - - // use comparison of back, front plane X sizes to indicate camera or flat view ... - const xBack = frustPts[Npc.LeftBottomRear].distance(frustPts[Npc.RightBottomRear]); - const xFront = frustPts[Npc.LeftBottomFront].distance(frustPts[Npc.RightBottomFront]); - - const flatViewFractionTolerance = 1.0e-6; - if (xFront > xBack * (1.0 + flatViewFractionTolerance)) - return ViewStatus.InvalidWindow; - - // see if the frustum is tapered, and if so, set up camera eyepoint and adjust viewOrg and delta. - const compression = xFront / xBack; - if (compression >= (1.0 - flatViewFractionTolerance)) - return ViewStatus.Success; - - // the frustum has perspective, turn camera on - let viewOrg = frustPts[Npc.LeftBottomRear]; - const viewDelta = this.getExtents().clone(); - const zDir = this.getZVector(); - const frustumZ = viewOrg.vectorTo(frustPts[Npc.LeftBottomFront]); - const frustOrgToEye = frustumZ.scale(1.0 / (1.0 - compression)); - const eyePoint = viewOrg.plus(frustOrgToEye); - - const backDistance = frustOrgToEye.dotProduct(zDir); // distance from eye to back plane of frustum - const focusDistance = backDistance - (viewDelta.z / 2.0); - const focalFraction = focusDistance / backDistance; // ratio of focus plane distance to back plane distance - - viewOrg = eyePoint.plus2Scaled(frustOrgToEye, -focalFraction, zDir, focusDistance - backDistance); // now project that point onto back plane - viewDelta.x *= focalFraction; // adjust view delta for x and y so they are also at focus plane - viewDelta.y *= focalFraction; - - this.setEyePoint(eyePoint); - this.setFocusDistance(focusDistance); - this.setOrigin(viewOrg); - this.setExtents(viewDelta); - this.setLensAngle(this.calcLensAngle()); - this.enableCamera(); - return ViewStatus.Success; - } - - protected static calculateMaxDepth(delta: Vector3d, zVec: Vector3d): number { - const depthRatioLimit = 1.0E8; // Limit for depth Ratio. - const maxTransformRowRatio = 1.0E5; - const minXYComponent = Math.min(Math.abs(zVec.x), Math.abs(zVec.y)); - const maxDepthRatio = (0.0 === minXYComponent) ? depthRatioLimit : Math.min((maxTransformRowRatio / minXYComponent), depthRatioLimit); - return Math.max(delta.x, delta.y) * maxDepthRatio; - } - - public getOrigin(): Point3d { return this.origin; } - public getExtents(): Vector3d { return this.extents; } - public getRotation(): Matrix3d { return this.rotation; } - public setOrigin(origin: XYAndZ) { this.origin.setFrom(origin); } - public setExtents(extents: XYAndZ) { this.extents.setFrom(extents); } - public setRotation(rot: Matrix3d) { this.rotation.setFrom(rot); } - /** @hidden */ - protected enableCamera(): void { if (this.supportsCamera()) this._cameraOn = true; } - public supportsCamera(): boolean { return true; } - public minimumFrontDistance() { return Math.max(15.2 * Constant.oneCentimeter, this.forceMinFrontDist); } - public isEyePointAbove(elevation: number): boolean { return !this._cameraOn ? (this.getZVector().z > 0) : (this.getEyePoint().z > elevation); } - - public getDisplayStyle3d() { return this.displayStyle as DisplayStyle3dState; } - - /** - * Turn the camera off for this view. After this call, the camera parameters in this view definition are ignored and views that use it will - * display with an orthographic (infinite focal length) projection of the view volume from the view direction. - * @note To turn the camera back on, call #lookAt - */ - public turnCameraOff() { this._cameraOn = false; } - - /** Determine whether the camera is valid for this view */ - public get isCameraValid() { return this.camera.isValid; } - - /** Calculate the lens angle formed by the current delta and focus distance */ - public calcLensAngle(): Angle { - const maxDelta = Math.max(this.extents.x, this.extents.y); - return Angle.createRadians(2.0 * Math.atan2(maxDelta * 0.5, this.camera.getFocusDistance())); - } - - /** Get the target point of the view. If there is no camera, view center is returned. */ - public getTargetPoint(result?: Point3d): Point3d { - if (!this._cameraOn) - return super.getTargetPoint(result); - - const viewZ = this.getRotation().getRow(2); - return this.getEyePoint().plusScaled(viewZ, -1.0 * this.getFocusDistance(), result); - } - - /** - * Position the camera for this view and point it at a new target point. - * @param eyePoint The new location of the camera. - * @param targetPoint The new location to which the camera should point. This becomes the center of the view on the focus plane. - * @param upVector A vector that orients the camera's "up" (view y). This vector must not be parallel to the vector from eye to target. - * @param newExtents The new size (width and height) of the view rectangle. The view rectangle is on the focus plane centered on the targetPoint. - * If newExtents is undefined, the existing size is unchanged. - * @param frontDistance The distance from the eyePoint to the front plane. If undefined, the existing front distance is used. - * @param backDistance The distance from the eyePoint to the back plane. If undefined, the existing back distance is used. - * @returns A [[ViewStatus]] indicating whether the camera was successfully positioned. - * @note If the aspect ratio of viewDelta does not match the aspect ratio of a Viewport into which this view is displayed, it will be - * adjusted when the [[Viewport]] is synchronized from this view. - */ - public lookAt(eyePoint: XYAndZ, targetPoint: XYAndZ, upVector: Vector3d, newExtents?: XAndY, frontDistance?: number, backDistance?: number): ViewStatus { - const eye = new Point3d(eyePoint.x, eyePoint.y, eyePoint.z); - const yVec = upVector.normalize(); - if (!yVec) // up vector zero length? - return ViewStatus.InvalidUpVector; - - const zVec = Vector3d.createStartEnd(targetPoint, eye); // z defined by direction from eye to target - const focusDist = zVec.normalizeWithLength(zVec).mag; // set focus at target point - const minFrontDist = this.minimumFrontDistance(); - - if (focusDist <= minFrontDist) // eye and target are too close together - return ViewStatus.InvalidTargetPoint; - - const xVec = new Vector3d(); - if (yVec.crossProduct(zVec).normalizeWithLength(xVec).mag < Geometry.smallMetricDistance) - return ViewStatus.InvalidUpVector; // up is parallel to z - - if (zVec.crossProduct(xVec).normalizeWithLength(yVec).mag < Geometry.smallMetricDistance) - return ViewStatus.InvalidUpVector; - - // we now have rows of the rotation matrix - const rotation = Matrix3d.createRows(xVec, yVec, zVec); - - backDistance = backDistance ? backDistance : this.getBackDistance(); - frontDistance = frontDistance ? frontDistance : this.getFrontDistance(); - - const delta = newExtents ? new Vector3d(Math.abs(newExtents.x), Math.abs(newExtents.y), this.extents.z) : this.extents.clone(); - - frontDistance = Math.max(frontDistance!, (.5 * Constant.oneMeter)); - backDistance = Math.max(backDistance!, focusDist + (.5 * Constant.oneMeter)); - - if (backDistance < focusDist) // make sure focus distance is in front of back distance. - backDistance = focusDist + Constant.oneMillimeter; - - if (frontDistance > focusDist) - frontDistance = focusDist - minFrontDist; - - if (frontDistance < minFrontDist) - frontDistance = minFrontDist; - - delta.z = (backDistance! - frontDistance); - - const frontDelta = delta.scale(frontDistance / focusDist); - const stat = this.validateViewDelta(frontDelta, false); // validate window size on front (smallest) plane - if (ViewStatus.Success !== stat) - return stat; - - if (delta.z > ViewState3d.calculateMaxDepth(delta, zVec)) // make sure we're not zoomed out too far - return ViewStatus.MaxDisplayDepth; - - // The origin is defined as the lower left of the view rectangle on the focus plane, projected to the back plane. - // Start at eye point, and move to center of back plane, then move left half of width. and down half of height - const origin = eye.plus3Scaled(zVec, -backDistance!, xVec, -0.5 * delta.x, yVec, -0.5 * delta.y); - - this.setEyePoint(eyePoint); - this.setRotation(rotation); - this.setFocusDistance(focusDist); - this.setOrigin(origin); - this.setExtents(delta); - this.setLensAngle(this.calcLensAngle()); - this.enableCamera(); - return ViewStatus.Success; - } - - /** - * Position the camera for this view and point it at a new target point, using a specified lens angle. - * @param eyePoint The new location of the camera. - * @param targetPoint The new location to which the camera should point. This becomes the center of the view on the focus plane. - * @param upVector A vector that orients the camera's "up" (view y). This vector must not be parallel to the vector from eye to target. - * @param fov The angle, in radians, that defines the field-of-view for the camera. Must be between .0001 and pi. - * @param frontDistance The distance from the eyePoint to the front plane. If undefined, the existing front distance is used. - * @param backDistance The distance from the eyePoint to the back plane. If undefined, the existing back distance is used. - * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. - * @note The aspect ratio of the view remains unchanged. - */ - public lookAtUsingLensAngle(eyePoint: Point3d, targetPoint: Point3d, upVector: Vector3d, fov: Angle, frontDistance?: number, backDistance?: number): ViewStatus { - const focusDist = eyePoint.vectorTo(targetPoint).magnitude(); // Set focus at target point - - if (focusDist <= Constant.oneMillimeter) // eye and target are too close together - return ViewStatus.InvalidTargetPoint; - - if (fov.radians < .0001 || fov.radians > Math.PI) - return ViewStatus.InvalidLens; - - const extent = 2.0 * Math.tan(fov.radians / 2.0) * focusDist; - const delta = Vector2d.create(this.extents.x, this.extents.y); - const longAxis = Math.max(delta.x, delta.y); - delta.scale(extent / longAxis, delta); - - return this.lookAt(eyePoint, targetPoint, upVector, delta, frontDistance, backDistance); - } - - /** - * Move the camera relative to its current location by a distance in camera coordinates. - * @param distance to move camera. Length is in world units, direction relative to current camera orientation. - * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. - */ - public moveCameraLocal(distance: Vector3d): ViewStatus { - const distWorld = this.getRotation().multiplyTransposeVector(distance); - return this.moveCameraWorld(distWorld); - } - - /** - * Move the camera relative to its current location by a distance in world coordinates. - * @param distance in world units. - * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. - */ - public moveCameraWorld(distance: Vector3d): ViewStatus { - if (!this._cameraOn) { - this.origin.plus(distance, this.origin); - return ViewStatus.Success; - } - - const newTarget = this.getTargetPoint().plus(distance); - const newEyePt = this.getEyePoint().plus(distance); - return this.lookAt(newEyePt, newTarget, this.getYVector()); - } - - /** - * Rotate the camera from its current location about an axis relative to its current orientation. - * @param angle The angle to rotate the camera. - * @param axis The axis about which to rotate the camera. The axis is a direction relative to the current camera orientation. - * @param aboutPt The point, in world coordinates, about which the camera is rotated. If aboutPt is undefined, the camera rotates in place - * (i.e. about the current eyePoint). - * @note Even though the axis is relative to the current camera orientation, the aboutPt is in world coordinates, \b not relative to the camera. - * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. - */ - public rotateCameraLocal(angle: Angle, axis: Vector3d, aboutPt?: Point3d): ViewStatus { - const axisWorld = this.getRotation().multiplyTransposeVector(axis); - return this.rotateCameraWorld(angle, axisWorld, aboutPt); - } - - /** - * Rotate the camera from its current location about an axis in world coordinates. - * @param angle The angle to rotate the camera. - * @param axis The world-based axis (direction) about which to rotate the camera. - * @param aboutPt The point, in world coordinates, about which the camera is rotated. If aboutPt is undefined, the camera rotates in place - * (i.e. about the current eyePoint). - * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. - */ - public rotateCameraWorld(angle: Angle, axis: Vector3d, aboutPt?: Point3d): ViewStatus { - const about = aboutPt ? aboutPt : this.getEyePoint(); - const rotation = Matrix3d.createRotationAroundVector(axis, angle); - if (!rotation) - return ViewStatus.InvalidUpVector; // Invalid axis given - const trans = Transform.createFixedPointAndMatrix(about, rotation); - const newTarget = trans.multiplyPoint3d(this.getTargetPoint()); - const upVec = rotation!.multiplyVector(this.getYVector()); - return this.lookAt(this.getEyePoint(), newTarget, upVec); - } - - /** Get the distance from the eyePoint to the front plane for this view. */ - public getFrontDistance(): number { return this.getBackDistance() - this.extents.z; } - - /** Get the distance from the eyePoint to the back plane for this view. */ - public getBackDistance(): number { - // backDist is the z component of the vector from the origin to the eyePoint . - const eyeOrg = this.origin.vectorTo(this.getEyePoint()); - this.getRotation().multiplyVector(eyeOrg, eyeOrg); - return eyeOrg.z; - } - - /** - * Place the eyepoint of the camera so it is aligned with the center of the view. This removes any 1-point perspective skewing that may be - * present in the current view. - * @param backDistance If defined, the new the distance from the eyepoint to the back plane. Otherwise the distance from the - * current eyepoint is used. - */ - public centerEyePoint(backDistance?: number): void { - const eyePoint = this.getExtents().scale(0.5); - eyePoint.z = backDistance ? backDistance : this.getBackDistance(); - const eye = this.getRotation().multiplyTransposeXYZ(eyePoint.x, eyePoint.y, eyePoint.z); - this.camera.setEyePoint(this.getOrigin().plus(eye)); - } - - /** Center the focus distance of the camera halfway between the front plane and the back plane, keeping the eyepoint, - * lens angle, rotation, back distance, and front distance unchanged. - * @note The focus distance, origin, and delta values are modified, but the view encloses the same volume and appears visually unchanged. - */ - public centerFocusDistance(): void { - const backDist = this.getBackDistance(); - const frontDist = this.getFrontDistance(); - const eye = this.getEyePoint(); - const target = eye.plusScaled(this.getZVector(), frontDist - backDist); - this.lookAtUsingLensAngle(eye, target, this.getYVector(), this.getLensAngle(), frontDist, backDist); - } - - /** Ensure the focus plane lies between the front and back planes. If not, center it. */ - public verifyFocusPlane(): void { - if (!this._cameraOn) - return; - - let backDist = this.getBackDistance(); - const frontDist = backDist - this.extents.z; - const camera = this.camera; - const extents = this.extents; - const rot = this.rotation; - - if (backDist <= 0.0 || frontDist <= 0.0) { - // the camera location is invalid. Set it based on the view range. - const tanAngle = Math.tan(camera.lens.radians / 2.0); - backDist = extents.z / tanAngle; - camera.setFocusDistance(backDist / 2); - this.centerEyePoint(backDist); - return; - } - - const focusDist = camera.focusDist; - if (focusDist > frontDist && focusDist < backDist) - return; - - // put it halfway between front and back planes - camera.setFocusDistance((extents.z / 2.0) + frontDist); - - // moving the focus plane means we have to adjust the origin and delta too (they're on the focus plane, see diagram above) - const ratio = camera.focusDist / focusDist; - extents.x *= ratio; - extents.y *= ratio; - camera.eye.plus3Scaled(rot.rowZ(), -backDist, rot.rowX(), -0.5 * extents.x, rot.rowY(), -0.5 * extents.y, this.origin); // this centers the camera too - } - - /** Get the current location of the eyePoint for camera in this view. */ - public getEyePoint(): Point3d { return this.camera.eye; } - - /** Get the lens angle for this view. */ - public getLensAngle(): Angle { return this.camera.lens; } - - /** Set the lens angle for this view. - * @param angle The new lens angle in radians. Must be greater than 0 and less than pi. - * @note This does not change the view's current field-of-view. Instead, it changes the lens that will be used if the view - * is subsequently modified and the lens angle is used to position the eyepoint. - * @note To change the field-of-view (i.e. "zoom") of a view, pass a new viewDelta to #lookAt - */ - public setLensAngle(angle: Angle): void { this.camera.setLensAngle(angle); } - - /** Change the location of the eyePoint for the camera in this view. - * @param pt The new eyepoint. - * @note This method is generally for internal use only. Moving the eyePoint arbitrarily can result in skewed or illegal perspectives. - * The most common method for user-level camera positioning is #lookAt. - */ - public setEyePoint(pt: XYAndZ): void { this.camera.setEyePoint(pt); } - - /** Set the focus distance for this view. - * @note Changing the focus distance changes the plane on which the delta.x and delta.y values lie. So, changing focus distance - * without making corresponding changes to delta.x and delta.y essentially changes the lens angle, causing a "zoom" effect - */ - public setFocusDistance(dist: number): void { this.camera.setFocusDistance(dist); } - - /** Get the distance from the eyePoint to the focus plane for this view. */ - public getFocusDistance(): number { return this.camera.focusDist; } - public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystem3dState.createNew(acsName, this.iModel); } - - public decorate(context: DecorateContext): void { - super.decorate(context); - this.drawSkyBox(context); - this.drawGroundPlane(context); - } - - /** @hidden */ - protected drawSkyBox(context: DecorateContext): void { - const style3d = this.getDisplayStyle3d(); - if (!style3d.environment.sky.display) - return; - - const vp = context.viewport; - const skyBoxParams = style3d.loadSkyBoxParams(vp.target.renderSystem); - if (undefined !== skyBoxParams) { - const skyBoxGraphic = IModelApp.renderSystem.createSkyBox(skyBoxParams); - context.setSkyBox(skyBoxGraphic!); - } - } - - /** Returns the ground elevation taken from the environment added with the global z position of this imodel. */ - public getGroundElevation(): number { - const env = this.getDisplayStyle3d().environment; - return env.ground.elevation + this.iModel.globalOrigin.z; - } - - /** Return the ground extents, which will originate either from the viewport frustum or the extents of the imodel. */ - public getGroundExtents(vp?: Viewport): AxisAlignedBox3d { - const displayStyle = this.getDisplayStyle3d(); - const extents = new AxisAlignedBox3d(); - if (undefined !== vp && !displayStyle.environment.ground.display) - return extents; // Ground plane is not enabled - - const elevation = this.getGroundElevation(); - - if (undefined !== vp) { - const viewRay = Ray3d.create(Point3d.create(), vp.rotation.rowZ()); - const xyPlane = Plane3dByOriginAndUnitNormal.create(Point3d.create(0, 0, elevation), Vector3d.create(0, 0, 1)); - - // first determine whether the ground plane is displayed in the view - const worldFrust = vp.getFrustum(); - for (const point of worldFrust.points) { - viewRay.origin = point; // We never modify the reference - const xyzPoint = Point3d.create(); - const param = viewRay.intersectionWithPlane(xyPlane!, xyzPoint); - if (param === undefined) - return extents; // View does not show ground plane - } - } - - extents.setFrom(this.iModel.projectExtents); - extents.low.z = extents.high.z = elevation; - - const center = extents.low.interpolate(.5, extents.high); - - const radius = extents.low.distance(extents.high); - extents.setNull(); - extents.extendPoint(center); // Extents now contains single point - extents.low.addScaledInPlace(Vector3d.create(-1, -1, -1), radius); - extents.high.addScaledInPlace(Vector3d.create(1, 1, 1), radius); - extents.low.z = extents.high.z = elevation; - return extents; - } - - /** @hidden */ - protected drawGroundPlane(context: DecorateContext): void { - const extents = this.getGroundExtents(context.viewport); - if (extents.isNull) { - return; - } - - const ground = this.getDisplayStyle3d().environment.ground; - if (!ground.display) - return; - - const points: Point3d[] = [extents.low.clone(), extents.low.clone(), extents.high.clone(), extents.high.clone()]; - points[1].x = extents.high.x; - points[3].x = extents.low.x; - - const aboveGround = this.isEyePointAbove(extents.low.z); - const gradient = ground.getGroundPlaneGradient(aboveGround); - const texture = context.viewport.target.renderSystem.getGradientTexture(gradient, this.iModel); - if (!texture) - return; - - const matParams = new RenderMaterial.Params(); - matParams.diffuseColor = ColorDef.white; - matParams.shadows = false; - matParams.ambient = 1; - matParams.diffuse = 0; - - const mapParams = new TextureMapping.Params(); - const transform = new TextureMapping.Trans2x3(0, 1, 0, 1, 0, 0); - mapParams.textureMatrix = transform; - mapParams.textureMatrix.setTransform(); - matParams.textureMapping = new TextureMapping(texture, mapParams); - const material = context.viewport.target.renderSystem.createMaterial(matParams, this.iModel); - if (!material) - return; - - const params = new GraphicParams(); - params.setLineColor(gradient.keys[0].color); - params.setFillColor(ColorDef.white); // Fill should be set to opaque white for gradient texture... - params.material = material; - - const builder = context.createGraphicBuilder(GraphicType.WorldDecoration); - builder.activateGraphicParams(params); - - /// ### TODO: Until we have more support in geometry package for tracking UV coordinates of higher level geometry - // we will use a PolyfaceBuilder here to add the ground plane as a quad, claim the polyface, and then send that to the GraphicBuilder - const strokeOptions = new StrokeOptions(); - strokeOptions.needParams = true; - const polyfaceBuilder = PolyfaceBuilder.create(strokeOptions); - polyfaceBuilder.toggleReversedFacetFlag(); - const uvParams: Point2d[] = [Point2d.create(0, 0), Point2d.create(1, 0), Point2d.create(1, 1), Point2d.create(0, 1)]; - polyfaceBuilder.addQuadFacet(points, uvParams); - const polyface = polyfaceBuilder.claimPolyface(false); - - builder.addPolyface(polyface, true); - context.addDecorationFromBuilder(builder); - } + /** True if the camera is valid. */ + protected _cameraOn: boolean; + /** The lower left back corner of the view frustum. */ + public readonly origin: Point3d; + /** The extent of the view frustum. */ + public readonly extents: Vector3d; + /** Rotation of the view frustum. */ + public readonly rotation: Matrix3d; + /** The camera used for this view. */ + public readonly camera: Camera; + /** Minimum distance for front plane */ + public forceMinFrontDist = 0.0; + /** @hidden */ + public static get className() { return "ViewDefinition3d"; } + public onRenderFrame(_viewport: Viewport): void { } + public allow3dManipulations(): boolean { return true; } + public constructor(props: ViewDefinition3dProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle3dState) { + super(props, iModel, categories, displayStyle); + this._cameraOn = JsonUtils.asBool(props.cameraOn); + this.origin = Point3d.fromJSON(props.origin); + this.extents = Vector3d.fromJSON(props.extents); + this.rotation = YawPitchRollAngles.fromJSON(props.angles).toMatrix3d(); + assert(this.rotation.isRigid()); + this.camera = new Camera(props.camera); + } + + public toJSON(): ViewDefinition3dProps { + const val = super.toJSON() as ViewDefinition3dProps; + val.cameraOn = this._cameraOn; + val.origin = this.origin; + val.extents = this.extents; + val.angles = YawPitchRollAngles.createFromMatrix3d(this.rotation)!.toJSON(); + assert(undefined !== val.angles, "rotMatrix is illegal"); + val.camera = this.camera; + return val; + } + + public equalState(other: ViewState3d): boolean { + if (!this.origin.isAlmostEqual(other.origin) || !this.extents.isAlmostEqual(other.extents) || !this.rotation.isAlmostEqual(other.rotation)) + return false; + + if (this.isCameraOn !== other.isCameraOn) + return false; + + if (this.isCameraOn && this.camera.equals(other.camera)) // ###TODO: should this be less precise equality? + return false; + + return super.equalState(other); + } + + public get isCameraOn(): boolean { return this._cameraOn; } + public setupFromFrustum(frustum: Frustum): ViewStatus { + const stat = super.setupFromFrustum(frustum); + if (ViewStatus.Success !== stat) + return stat; + + this.turnCameraOff(); + const frustPts = frustum.points; + + // use comparison of back, front plane X sizes to indicate camera or flat view ... + const xBack = frustPts[Npc.LeftBottomRear].distance(frustPts[Npc.RightBottomRear]); + const xFront = frustPts[Npc.LeftBottomFront].distance(frustPts[Npc.RightBottomFront]); + + const flatViewFractionTolerance = 1.0e-6; + if (xFront > xBack * (1.0 + flatViewFractionTolerance)) + return ViewStatus.InvalidWindow; + + // see if the frustum is tapered, and if so, set up camera eyepoint and adjust viewOrg and delta. + const compression = xFront / xBack; + if (compression >= (1.0 - flatViewFractionTolerance)) + return ViewStatus.Success; + + // the frustum has perspective, turn camera on + let viewOrg = frustPts[Npc.LeftBottomRear]; + const viewDelta = this.getExtents().clone(); + const zDir = this.getZVector(); + const frustumZ = viewOrg.vectorTo(frustPts[Npc.LeftBottomFront]); + const frustOrgToEye = frustumZ.scale(1.0 / (1.0 - compression)); + const eyePoint = viewOrg.plus(frustOrgToEye); + + const backDistance = frustOrgToEye.dotProduct(zDir); // distance from eye to back plane of frustum + const focusDistance = backDistance - (viewDelta.z / 2.0); + const focalFraction = focusDistance / backDistance; // ratio of focus plane distance to back plane distance + + viewOrg = eyePoint.plus2Scaled(frustOrgToEye, -focalFraction, zDir, focusDistance - backDistance); // now project that point onto back plane + viewDelta.x *= focalFraction; // adjust view delta for x and y so they are also at focus plane + viewDelta.y *= focalFraction; + + this.setEyePoint(eyePoint); + this.setFocusDistance(focusDistance); + this.setOrigin(viewOrg); + this.setExtents(viewDelta); + this.setLensAngle(this.calcLensAngle()); + this.enableCamera(); + return ViewStatus.Success; + } + + protected static calculateMaxDepth(delta: Vector3d, zVec: Vector3d): number { + const depthRatioLimit = 1.0E8; // Limit for depth Ratio. + const maxTransformRowRatio = 1.0E5; + const minXYComponent = Math.min(Math.abs(zVec.x), Math.abs(zVec.y)); + const maxDepthRatio = (0.0 === minXYComponent) ? depthRatioLimit : Math.min((maxTransformRowRatio / minXYComponent), depthRatioLimit); + return Math.max(delta.x, delta.y) * maxDepthRatio; + } + + public getOrigin(): Point3d { return this.origin; } + public getExtents(): Vector3d { return this.extents; } + public getRotation(): Matrix3d { return this.rotation; } + public setOrigin(origin: XYAndZ) { this.origin.setFrom(origin); } + public setExtents(extents: XYAndZ) { this.extents.setFrom(extents); } + public setRotation(rot: Matrix3d) { this.rotation.setFrom(rot); } + /** @hidden */ + protected enableCamera(): void { if (this.supportsCamera()) this._cameraOn = true; } + public supportsCamera(): boolean { return true; } + public minimumFrontDistance() { return Math.max(15.2 * Constant.oneCentimeter, this.forceMinFrontDist); } + public isEyePointAbove(elevation: number): boolean { return !this._cameraOn ? (this.getZVector().z > 0) : (this.getEyePoint().z > elevation); } + + public getDisplayStyle3d() { return this.displayStyle as DisplayStyle3dState; } + + /** + * Turn the camera off for this view. After this call, the camera parameters in this view definition are ignored and views that use it will + * display with an orthographic (infinite focal length) projection of the view volume from the view direction. + * @note To turn the camera back on, call #lookAt + */ + public turnCameraOff() { this._cameraOn = false; } + + /** Determine whether the camera is valid for this view */ + public get isCameraValid() { return this.camera.isValid; } + + /** Calculate the lens angle formed by the current delta and focus distance */ + public calcLensAngle(): Angle { + const maxDelta = Math.max(this.extents.x, this.extents.y); + return Angle.createRadians(2.0 * Math.atan2(maxDelta * 0.5, this.camera.getFocusDistance())); + } + + /** Get the target point of the view. If there is no camera, view center is returned. */ + public getTargetPoint(result?: Point3d): Point3d { + if (!this._cameraOn) + return super.getTargetPoint(result); + + const viewZ = this.getRotation().getRow(2); + return this.getEyePoint().plusScaled(viewZ, -1.0 * this.getFocusDistance(), result); + } + + /** + * Position the camera for this view and point it at a new target point. + * @param eyePoint The new location of the camera. + * @param targetPoint The new location to which the camera should point. This becomes the center of the view on the focus plane. + * @param upVector A vector that orients the camera's "up" (view y). This vector must not be parallel to the vector from eye to target. + * @param newExtents The new size (width and height) of the view rectangle. The view rectangle is on the focus plane centered on the targetPoint. + * If newExtents is undefined, the existing size is unchanged. + * @param frontDistance The distance from the eyePoint to the front plane. If undefined, the existing front distance is used. + * @param backDistance The distance from the eyePoint to the back plane. If undefined, the existing back distance is used. + * @returns A [[ViewStatus]] indicating whether the camera was successfully positioned. + * @note If the aspect ratio of viewDelta does not match the aspect ratio of a Viewport into which this view is displayed, it will be + * adjusted when the [[Viewport]] is synchronized from this view. + */ + public lookAt(eyePoint: XYAndZ, targetPoint: XYAndZ, upVector: Vector3d, newExtents?: XAndY, frontDistance?: number, backDistance?: number): ViewStatus { + const eye = new Point3d(eyePoint.x, eyePoint.y, eyePoint.z); + const yVec = upVector.normalize(); + if (!yVec) // up vector zero length? + return ViewStatus.InvalidUpVector; + + const zVec = Vector3d.createStartEnd(targetPoint, eye); // z defined by direction from eye to target + const focusDist = zVec.normalizeWithLength(zVec).mag; // set focus at target point + const minFrontDist = this.minimumFrontDistance(); + + if (focusDist <= minFrontDist) // eye and target are too close together + return ViewStatus.InvalidTargetPoint; + + const xVec = new Vector3d(); + if (yVec.crossProduct(zVec).normalizeWithLength(xVec).mag < Geometry.smallMetricDistance) + return ViewStatus.InvalidUpVector; // up is parallel to z + + if (zVec.crossProduct(xVec).normalizeWithLength(yVec).mag < Geometry.smallMetricDistance) + return ViewStatus.InvalidUpVector; + + // we now have rows of the rotation matrix + const rotation = Matrix3d.createRows(xVec, yVec, zVec); + + backDistance = backDistance ? backDistance : this.getBackDistance(); + frontDistance = frontDistance ? frontDistance : this.getFrontDistance(); + + const delta = newExtents ? new Vector3d(Math.abs(newExtents.x), Math.abs(newExtents.y), this.extents.z) : this.extents.clone(); + + frontDistance = Math.max(frontDistance!, (.5 * Constant.oneMeter)); + backDistance = Math.max(backDistance!, focusDist + (.5 * Constant.oneMeter)); + + if (backDistance < focusDist) // make sure focus distance is in front of back distance. + backDistance = focusDist + Constant.oneMillimeter; + + if (frontDistance > focusDist) + frontDistance = focusDist - minFrontDist; + + if (frontDistance < minFrontDist) + frontDistance = minFrontDist; + + delta.z = (backDistance! - frontDistance); + + const frontDelta = delta.scale(frontDistance / focusDist); + const stat = this.validateViewDelta(frontDelta, false); // validate window size on front (smallest) plane + if (ViewStatus.Success !== stat) + return stat; + + if (delta.z > ViewState3d.calculateMaxDepth(delta, zVec)) // make sure we're not zoomed out too far + return ViewStatus.MaxDisplayDepth; + + // The origin is defined as the lower left of the view rectangle on the focus plane, projected to the back plane. + // Start at eye point, and move to center of back plane, then move left half of width. and down half of height + const origin = eye.plus3Scaled(zVec, -backDistance!, xVec, -0.5 * delta.x, yVec, -0.5 * delta.y); + + this.setEyePoint(eyePoint); + this.setRotation(rotation); + this.setFocusDistance(focusDist); + this.setOrigin(origin); + this.setExtents(delta); + this.setLensAngle(this.calcLensAngle()); + this.enableCamera(); + return ViewStatus.Success; + } + + /** + * Position the camera for this view and point it at a new target point, using a specified lens angle. + * @param eyePoint The new location of the camera. + * @param targetPoint The new location to which the camera should point. This becomes the center of the view on the focus plane. + * @param upVector A vector that orients the camera's "up" (view y). This vector must not be parallel to the vector from eye to target. + * @param fov The angle, in radians, that defines the field-of-view for the camera. Must be between .0001 and pi. + * @param frontDistance The distance from the eyePoint to the front plane. If undefined, the existing front distance is used. + * @param backDistance The distance from the eyePoint to the back plane. If undefined, the existing back distance is used. + * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. + * @note The aspect ratio of the view remains unchanged. + */ + public lookAtUsingLensAngle(eyePoint: Point3d, targetPoint: Point3d, upVector: Vector3d, fov: Angle, frontDistance?: number, backDistance?: number): ViewStatus { + const focusDist = eyePoint.vectorTo(targetPoint).magnitude(); // Set focus at target point + + if (focusDist <= Constant.oneMillimeter) // eye and target are too close together + return ViewStatus.InvalidTargetPoint; + + if (fov.radians < .0001 || fov.radians > Math.PI) + return ViewStatus.InvalidLens; + + const extent = 2.0 * Math.tan(fov.radians / 2.0) * focusDist; + const delta = Vector2d.create(this.extents.x, this.extents.y); + const longAxis = Math.max(delta.x, delta.y); + delta.scale(extent / longAxis, delta); + + return this.lookAt(eyePoint, targetPoint, upVector, delta, frontDistance, backDistance); + } + + /** + * Move the camera relative to its current location by a distance in camera coordinates. + * @param distance to move camera. Length is in world units, direction relative to current camera orientation. + * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. + */ + public moveCameraLocal(distance: Vector3d): ViewStatus { + const distWorld = this.getRotation().multiplyTransposeVector(distance); + return this.moveCameraWorld(distWorld); + } + + /** + * Move the camera relative to its current location by a distance in world coordinates. + * @param distance in world units. + * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. + */ + public moveCameraWorld(distance: Vector3d): ViewStatus { + if (!this._cameraOn) { + this.origin.plus(distance, this.origin); + return ViewStatus.Success; + } + + const newTarget = this.getTargetPoint().plus(distance); + const newEyePt = this.getEyePoint().plus(distance); + return this.lookAt(newEyePt, newTarget, this.getYVector()); + } + + /** + * Rotate the camera from its current location about an axis relative to its current orientation. + * @param angle The angle to rotate the camera. + * @param axis The axis about which to rotate the camera. The axis is a direction relative to the current camera orientation. + * @param aboutPt The point, in world coordinates, about which the camera is rotated. If aboutPt is undefined, the camera rotates in place + * (i.e. about the current eyePoint). + * @note Even though the axis is relative to the current camera orientation, the aboutPt is in world coordinates, \b not relative to the camera. + * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. + */ + public rotateCameraLocal(angle: Angle, axis: Vector3d, aboutPt?: Point3d): ViewStatus { + const axisWorld = this.getRotation().multiplyTransposeVector(axis); + return this.rotateCameraWorld(angle, axisWorld, aboutPt); + } + + /** + * Rotate the camera from its current location about an axis in world coordinates. + * @param angle The angle to rotate the camera. + * @param axis The world-based axis (direction) about which to rotate the camera. + * @param aboutPt The point, in world coordinates, about which the camera is rotated. If aboutPt is undefined, the camera rotates in place + * (i.e. about the current eyePoint). + * @returns Status indicating whether the camera was successfully positioned. See values at [[ViewStatus]] for possible errors. + */ + public rotateCameraWorld(angle: Angle, axis: Vector3d, aboutPt?: Point3d): ViewStatus { + const about = aboutPt ? aboutPt : this.getEyePoint(); + const rotation = Matrix3d.createRotationAroundVector(axis, angle); + if (!rotation) + return ViewStatus.InvalidUpVector; // Invalid axis given + const trans = Transform.createFixedPointAndMatrix(about, rotation); + const newTarget = trans.multiplyPoint3d(this.getTargetPoint()); + const upVec = rotation!.multiplyVector(this.getYVector()); + return this.lookAt(this.getEyePoint(), newTarget, upVec); + } + + /** Get the distance from the eyePoint to the front plane for this view. */ + public getFrontDistance(): number { return this.getBackDistance() - this.extents.z; } + + /** Get the distance from the eyePoint to the back plane for this view. */ + public getBackDistance(): number { + // backDist is the z component of the vector from the origin to the eyePoint . + const eyeOrg = this.origin.vectorTo(this.getEyePoint()); + this.getRotation().multiplyVector(eyeOrg, eyeOrg); + return eyeOrg.z; + } + + /** + * Place the eyepoint of the camera so it is aligned with the center of the view. This removes any 1-point perspective skewing that may be + * present in the current view. + * @param backDistance If defined, the new the distance from the eyepoint to the back plane. Otherwise the distance from the + * current eyepoint is used. + */ + public centerEyePoint(backDistance?: number): void { + const eyePoint = this.getExtents().scale(0.5); + eyePoint.z = backDistance ? backDistance : this.getBackDistance(); + const eye = this.getRotation().multiplyTransposeXYZ(eyePoint.x, eyePoint.y, eyePoint.z); + this.camera.setEyePoint(this.getOrigin().plus(eye)); + } + + /** Center the focus distance of the camera halfway between the front plane and the back plane, keeping the eyepoint, + * lens angle, rotation, back distance, and front distance unchanged. + * @note The focus distance, origin, and delta values are modified, but the view encloses the same volume and appears visually unchanged. + */ + public centerFocusDistance(): void { + const backDist = this.getBackDistance(); + const frontDist = this.getFrontDistance(); + const eye = this.getEyePoint(); + const target = eye.plusScaled(this.getZVector(), frontDist - backDist); + this.lookAtUsingLensAngle(eye, target, this.getYVector(), this.getLensAngle(), frontDist, backDist); + } + + /** Ensure the focus plane lies between the front and back planes. If not, center it. */ + public verifyFocusPlane(): void { + if (!this._cameraOn) + return; + + let backDist = this.getBackDistance(); + const frontDist = backDist - this.extents.z; + const camera = this.camera; + const extents = this.extents; + const rot = this.rotation; + + if (backDist <= 0.0 || frontDist <= 0.0) { + // the camera location is invalid. Set it based on the view range. + const tanAngle = Math.tan(camera.lens.radians / 2.0); + backDist = extents.z / tanAngle; + camera.setFocusDistance(backDist / 2); + this.centerEyePoint(backDist); + return; + } + + const focusDist = camera.focusDist; + if (focusDist > frontDist && focusDist < backDist) + return; + + // put it halfway between front and back planes + camera.setFocusDistance((extents.z / 2.0) + frontDist); + + // moving the focus plane means we have to adjust the origin and delta too (they're on the focus plane, see diagram above) + const ratio = camera.focusDist / focusDist; + extents.x *= ratio; + extents.y *= ratio; + camera.eye.plus3Scaled(rot.rowZ(), -backDist, rot.rowX(), -0.5 * extents.x, rot.rowY(), -0.5 * extents.y, this.origin); // this centers the camera too + } + + /** Get the current location of the eyePoint for camera in this view. */ + public getEyePoint(): Point3d { return this.camera.eye; } + + /** Get the lens angle for this view. */ + public getLensAngle(): Angle { return this.camera.lens; } + + /** Set the lens angle for this view. + * @param angle The new lens angle in radians. Must be greater than 0 and less than pi. + * @note This does not change the view's current field-of-view. Instead, it changes the lens that will be used if the view + * is subsequently modified and the lens angle is used to position the eyepoint. + * @note To change the field-of-view (i.e. "zoom") of a view, pass a new viewDelta to #lookAt + */ + public setLensAngle(angle: Angle): void { this.camera.setLensAngle(angle); } + + /** Change the location of the eyePoint for the camera in this view. + * @param pt The new eyepoint. + * @note This method is generally for internal use only. Moving the eyePoint arbitrarily can result in skewed or illegal perspectives. + * The most common method for user-level camera positioning is #lookAt. + */ + public setEyePoint(pt: XYAndZ): void { this.camera.setEyePoint(pt); } + + /** Set the focus distance for this view. + * @note Changing the focus distance changes the plane on which the delta.x and delta.y values lie. So, changing focus distance + * without making corresponding changes to delta.x and delta.y essentially changes the lens angle, causing a "zoom" effect + */ + public setFocusDistance(dist: number): void { this.camera.setFocusDistance(dist); } + + /** Get the distance from the eyePoint to the focus plane for this view. */ + public getFocusDistance(): number { return this.camera.focusDist; } + public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystem3dState.createNew(acsName, this.iModel); } + + public decorate(context: DecorateContext): void { + super.decorate(context); + this.drawSkyBox(context); + this.drawGroundPlane(context); + } + + /** @hidden */ + protected drawSkyBox(context: DecorateContext): void { + const style3d = this.getDisplayStyle3d(); + if (!style3d.environment.sky.display) + return; + + const vp = context.viewport; + const skyBoxParams = style3d.loadSkyBoxParams(vp.target.renderSystem); + if (undefined !== skyBoxParams) { + const skyBoxGraphic = IModelApp.renderSystem.createSkyBox(skyBoxParams); + context.setSkyBox(skyBoxGraphic!); + } + } + + /** Returns the ground elevation taken from the environment added with the global z position of this imodel. */ + public getGroundElevation(): number { + const env = this.getDisplayStyle3d().environment; + return env.ground.elevation + this.iModel.globalOrigin.z; + } + + /** Return the ground extents, which will originate either from the viewport frustum or the extents of the imodel. */ + public getGroundExtents(vp?: Viewport): AxisAlignedBox3d { + const displayStyle = this.getDisplayStyle3d(); + const extents = new AxisAlignedBox3d(); + if (!displayStyle.environment.ground.display) + return extents; // Ground plane is not enabled + + const elevation = this.getGroundElevation(); + + if (undefined !== vp) { + const viewRay = Ray3d.create(Point3d.create(), vp.rotation.rowZ()); + const xyPlane = Plane3dByOriginAndUnitNormal.create(Point3d.create(0, 0, elevation), Vector3d.create(0, 0, 1)); + + // first determine whether the ground plane is displayed in the view + const worldFrust = vp.getFrustum(); + for (const point of worldFrust.points) { + viewRay.origin = point; // We never modify the reference + const xyzPoint = Point3d.create(); + const param = viewRay.intersectionWithPlane(xyPlane!, xyzPoint); + if (param === undefined) + return extents; // View does not show ground plane + } + } + + extents.setFrom(this.iModel.projectExtents); + extents.low.z = extents.high.z = elevation; + + const center = extents.low.interpolate(.5, extents.high); + + const radius = extents.low.distance(extents.high); + extents.setNull(); + extents.extendPoint(center); // Extents now contains single point + extents.low.addScaledInPlace(Vector3d.create(-1, -1, -1), radius); + extents.high.addScaledInPlace(Vector3d.create(1, 1, 1), radius); + extents.low.z = extents.high.z = elevation; + return extents; + } + + /** @hidden */ + protected drawGroundPlane(context: DecorateContext): void { + const extents = this.getGroundExtents(context.viewport); + if (extents.isNull) { + return; + } + + const ground = this.getDisplayStyle3d().environment.ground; + if (!ground.display) + return; + + const points: Point3d[] = [extents.low.clone(), extents.low.clone(), extents.high.clone(), extents.high.clone()]; + points[1].x = extents.high.x; + points[3].x = extents.low.x; + + const aboveGround = this.isEyePointAbove(extents.low.z); + const gradient = ground.getGroundPlaneGradient(aboveGround); + const texture = context.viewport.target.renderSystem.getGradientTexture(gradient, this.iModel); + if (!texture) + return; + + const matParams = new RenderMaterial.Params(); + matParams.diffuseColor = ColorDef.white; + matParams.shadows = false; + matParams.ambient = 1; + matParams.diffuse = 0; + + const mapParams = new TextureMapping.Params(); + const transform = new TextureMapping.Trans2x3(0, 1, 0, 1, 0, 0); + mapParams.textureMatrix = transform; + mapParams.textureMatrix.setTransform(); + matParams.textureMapping = new TextureMapping(texture, mapParams); + const material = context.viewport.target.renderSystem.createMaterial(matParams, this.iModel); + if (!material) + return; + + const params = new GraphicParams(); + params.setLineColor(gradient.keys[0].color); + params.setFillColor(ColorDef.white); // Fill should be set to opaque white for gradient texture... + params.material = material; + + const builder = context.createGraphicBuilder(GraphicType.WorldDecoration); + builder.activateGraphicParams(params); + + /// ### TODO: Until we have more support in geometry package for tracking UV coordinates of higher level geometry + // we will use a PolyfaceBuilder here to add the ground plane as a quad, claim the polyface, and then send that to the GraphicBuilder + const strokeOptions = new StrokeOptions(); + strokeOptions.needParams = true; + const polyfaceBuilder = PolyfaceBuilder.create(strokeOptions); + polyfaceBuilder.toggleReversedFacetFlag(); + const uvParams: Point2d[] = [Point2d.create(0, 0), Point2d.create(1, 0), Point2d.create(1, 1), Point2d.create(0, 1)]; + polyfaceBuilder.addQuadFacet(points, uvParams); + const polyface = polyfaceBuilder.claimPolyface(false); + + builder.addPolyface(polyface, true); + context.addDecorationFromBuilder(builder); + } } /** Defines a view of one or more SpatialModels. * The list of viewed models is stored by the ModelSelector. */ export class SpatialViewState extends ViewState3d { - public modelSelector: ModelSelectorState; - - public static createFromStateData(viewStateData: ViewStateData, categorySelectorState: CategorySelectorState, iModel: IModelConnection): ViewState | undefined { - const displayStyleState = new DisplayStyle3dState(viewStateData.displayStyleProps, iModel); - const modelSelectorState = new ModelSelectorState(viewStateData.modelSelectorProps!, iModel); - - // use "new this" so subclasses are correct. - return new this(viewStateData.viewDefinitionProps as SpatialViewDefinitionProps, iModel, categorySelectorState, displayStyleState, modelSelectorState); - } - - constructor(props: SpatialViewDefinitionProps, iModel: IModelConnection, arg3: CategorySelectorState, displayStyle: DisplayStyle3dState, modelSelector: ModelSelectorState) { - super(props, iModel, arg3, displayStyle); - this.modelSelector = modelSelector; - if (arg3 instanceof SpatialViewState) { // from clone - this.modelSelector = arg3.modelSelector.clone(); - } - } - public equals(other: SpatialViewState): boolean { return super.equals(other) && this.modelSelector.equals(other.modelSelector); } - - public equalState(other: SpatialViewState): boolean { - if (!super.equalState(other)) - return false; - - if (this.modelSelector.id !== other.modelSelector.id) - return false; - - return this.modelSelector.equalState(other.modelSelector); - } - - public static get className() { return "SpatialViewDefinition"; } - public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystemSpatialState.createNew(acsName, this.iModel); } - public getExtentLimits() { return { min: Constant.oneMillimeter, max: Constant.diameterOfEarth }; } - - public computeFitRange(): AxisAlignedBox3d { - // Loop over the current models in the model selector with loaded tile trees and union their ranges - const range = new AxisAlignedBox3d(); - this.forEachModel((model: GeometricModelState) => { - if (model.tileTree !== undefined && model.tileTree.rootTile !== undefined && model.useRangeForFit()) { // can we assume that a loaded model - range.extendRange(model.tileTree.rootTile.computeWorldContentRange()); - } - }); - - if (range.isNull) - range.setFrom(this.getViewedExtents()); - - range.ensureMinLengths(1.0); - - return range; - } - - public getViewedExtents(): AxisAlignedBox3d { - const extents = AxisAlignedBox3d.fromJSON(this.iModel.projectExtents); - extents.scaleAboutCenterInPlace(1.0001); // projectExtents. lying smack up against the extents is not excluded by frustum... - extents.extendRange(this.getGroundExtents()); - return extents; - } - - public toJSON(): SpatialViewDefinitionProps { - const val = super.toJSON() as SpatialViewDefinitionProps; - val.modelSelectorId = this.modelSelector.id; - return val; - } - public async load(): Promise { await super.load(); return this.modelSelector.load(); } - public viewsModel(modelId: Id64String): boolean { return this.modelSelector.containsModel(modelId); } - public clearViewedModels() { this.modelSelector.models.clear(); } - public addViewedModel(id: Id64String) { this.modelSelector.addModels(id); } - public removeViewedModel(id: Id64String) { this.modelSelector.dropModels(id); } - - public forEachModel(func: (model: GeometricModelState) => void) { - for (const modelId of this.modelSelector.models) { - const model = this.iModel.models.getLoaded(modelId); - if (undefined !== model && model.isGeometricModel) - func(model as GeometricModelState); - } - } + public modelSelector: ModelSelectorState; + + public static createFromStateData(viewStateData: ViewStateData, categorySelectorState: CategorySelectorState, iModel: IModelConnection): ViewState | undefined { + const displayStyleState = new DisplayStyle3dState(viewStateData.displayStyleProps, iModel); + const modelSelectorState = new ModelSelectorState(viewStateData.modelSelectorProps!, iModel); + + // use "new this" so subclasses are correct. + return new this(viewStateData.viewDefinitionProps as SpatialViewDefinitionProps, iModel, categorySelectorState, displayStyleState, modelSelectorState); + } + + constructor(props: SpatialViewDefinitionProps, iModel: IModelConnection, arg3: CategorySelectorState, displayStyle: DisplayStyle3dState, modelSelector: ModelSelectorState) { + super(props, iModel, arg3, displayStyle); + this.modelSelector = modelSelector; + if (arg3 instanceof SpatialViewState) { // from clone + this.modelSelector = arg3.modelSelector.clone(); + } + } + public equals(other: SpatialViewState): boolean { return super.equals(other) && this.modelSelector.equals(other.modelSelector); } + + public equalState(other: SpatialViewState): boolean { + if (!super.equalState(other)) + return false; + + if (this.modelSelector.id !== other.modelSelector.id) + return false; + + return this.modelSelector.equalState(other.modelSelector); + } + + public static get className() { return "SpatialViewDefinition"; } + public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystemSpatialState.createNew(acsName, this.iModel); } + public getExtentLimits() { return { min: Constant.oneMillimeter, max: Constant.diameterOfEarth }; } + + public computeFitRange(): AxisAlignedBox3d { + // Loop over the current models in the model selector with loaded tile trees and union their ranges + const range = new AxisAlignedBox3d(); + this.forEachModel((model: GeometricModelState) => { // Only fit real models -- ignore context models for fit. + const tileTree = model.tileTree; + if (tileTree !== undefined && tileTree.rootTile !== undefined && model.useRangeForFit()) { // can we assume that a loaded model + range.extendRange(tileTree.rootTile.computeWorldContentRange()); + } + }); + + if (range.isNull) + range.setFrom(this.getViewedExtents()); + + range.ensureMinLengths(1.0); + + return range; + } + + public getViewedExtents(): AxisAlignedBox3d { + const extents = AxisAlignedBox3d.fromJSON(this.iModel.projectExtents); + extents.scaleAboutCenterInPlace(1.0001); // projectExtents. lying smack up against the extents is not excluded by frustum... + extents.extendRange(this.getGroundExtents()); + return extents; + } + + public toJSON(): SpatialViewDefinitionProps { + const val = super.toJSON() as SpatialViewDefinitionProps; + val.modelSelectorId = this.modelSelector.id; + return val; + } + public async load(): Promise { await super.load(); return this.modelSelector.load(); } + public viewsModel(modelId: Id64String): boolean { return this.modelSelector.containsModel(modelId); } + public clearViewedModels() { this.modelSelector.models.clear(); } + public addViewedModel(id: Id64String) { this.modelSelector.addModels(id); } + public removeViewedModel(id: Id64String) { this.modelSelector.dropModels(id); } + + public forEachModel(func: (model: GeometricModelState) => void) { + for (const modelId of this.modelSelector.models) { + const model = this.iModel.models.getLoaded(modelId); + if (undefined !== model && model.isGeometricModel) + func(model as GeometricModelState); + } + } + public forEachTileTreeModel(func: (model: TileTreeModelState) => void): void { + this.forEachModel((model: GeometricModelState) => func(model)); + this.displayStyle.forEachContextRealityModel((model: TileTreeModelState) => func(model)); + } } /** Defines a spatial view that displays geometry on the image plane using a parallel orthographic projection. */ export class OrthographicViewState extends SpatialViewState { - public static get className() { return "OrthographicViewDefinition"; } - constructor(props: SpatialViewDefinitionProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle3dState, modelSelector: ModelSelectorState) { super(props, iModel, categories, displayStyle, modelSelector); } - public supportsCamera(): boolean { return false; } + public static get className() { return "OrthographicViewDefinition"; } + constructor(props: SpatialViewDefinitionProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle3dState, modelSelector: ModelSelectorState) { super(props, iModel, categories, displayStyle, modelSelector); } + public supportsCamera(): boolean { return false; } } /** Defines the state of a view of a single 2d model. */ export abstract class ViewState2d extends ViewState { - public readonly origin: Point2d; - public readonly delta: Point2d; - public readonly angle: Angle; - public readonly baseModelId: Id64String; - private _viewedExtents?: AxisAlignedBox3d; - - public static get className() { return "ViewDefinition2d"; } - - public constructor(props: ViewDefinition2dProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle2dState) { - super(props, iModel, categories, displayStyle); - this.origin = Point2d.fromJSON(props.origin); - this.delta = Point2d.fromJSON(props.delta); - this.angle = Angle.fromJSON(props.angle); - this.baseModelId = Id64.fromJSON(props.baseModelId); - } - - public toJSON(): ViewDefinition2dProps { - const val = super.toJSON() as ViewDefinition2dProps; - val.origin = this.origin; - val.delta = this.delta; - val.angle = this.angle; - val.baseModelId = this.baseModelId; - return val; - } - - /** Return the model for this 2d view. */ - public getViewedModel(): GeometricModel2dState | undefined { - const model = this.iModel.models.getLoaded(this.baseModelId); - if (model && !(model instanceof GeometricModel2dState)) - return undefined; - return model; - } - - public equalState(other: ViewState2d): boolean { - return this.baseModelId === other.baseModelId && - this.origin.isAlmostEqual(other.origin) && - this.delta.isAlmostEqual(other.delta) && - this.angle.isAlmostEqualNoPeriodShift(other.angle) && - super.equalState(other); - } - - public computeFitRange(): Range3d { return this.getViewedExtents(); } - public getViewedExtents(): AxisAlignedBox3d { - if (undefined === this._viewedExtents) { - const model = this.iModel.models.getLoaded(this.baseModelId); - if (undefined !== model && model.isGeometricModel) { - const tree = (model as GeometricModelState).getOrLoadTileTree(); - if (undefined !== tree) { - this._viewedExtents = new AxisAlignedBox3d(tree.range.low, tree.range.high); - tree.location.multiplyRange(this._viewedExtents, this._viewedExtents); + public readonly origin: Point2d; + public readonly delta: Point2d; + public readonly angle: Angle; + public readonly baseModelId: Id64String; + private _viewedExtents?: AxisAlignedBox3d; + + public static get className() { return "ViewDefinition2d"; } + + public constructor(props: ViewDefinition2dProps, iModel: IModelConnection, categories: CategorySelectorState, displayStyle: DisplayStyle2dState) { + super(props, iModel, categories, displayStyle); + this.origin = Point2d.fromJSON(props.origin); + this.delta = Point2d.fromJSON(props.delta); + this.angle = Angle.fromJSON(props.angle); + this.baseModelId = Id64.fromJSON(props.baseModelId); + } + + public toJSON(): ViewDefinition2dProps { + const val = super.toJSON() as ViewDefinition2dProps; + val.origin = this.origin; + val.delta = this.delta; + val.angle = this.angle; + val.baseModelId = this.baseModelId; + return val; + } + + /** Return the model for this 2d view. */ + public getViewedModel(): GeometricModel2dState | undefined { + const model = this.iModel.models.getLoaded(this.baseModelId); + if (model && !(model instanceof GeometricModel2dState)) + return undefined; + return model; + } + + public equalState(other: ViewState2d): boolean { + return this.baseModelId === other.baseModelId && + this.origin.isAlmostEqual(other.origin) && + this.delta.isAlmostEqual(other.delta) && + this.angle.isAlmostEqualNoPeriodShift(other.angle) && + super.equalState(other); + } + + public computeFitRange(): Range3d { return this.getViewedExtents(); } + public getViewedExtents(): AxisAlignedBox3d { + if (undefined === this._viewedExtents) { + const model = this.iModel.models.getLoaded(this.baseModelId); + if (undefined !== model && model.isGeometricModel) { + const tree = (model as GeometricModelState).getOrLoadTileTree(); + if (undefined !== tree) { + this._viewedExtents = new AxisAlignedBox3d(tree.range.low, tree.range.high); + tree.location.multiplyRange(this._viewedExtents, this._viewedExtents); + } + } } - } - } - - return undefined !== this._viewedExtents ? this._viewedExtents : new AxisAlignedBox3d(); - } - - public onRenderFrame(_viewport: Viewport): void { } - public async load(): Promise { - await super.load(); - return this.iModel.models.load(this.baseModelId); - } - - public allow3dManipulations(): boolean { return false; } - public getOrigin() { return new Point3d(this.origin.x, this.origin.y); } - public getExtents() { return new Vector3d(this.delta.x, this.delta.y); } - public getRotation() { return Matrix3d.createRotationAroundVector(Vector3d.unitZ(), this.angle)!; } - public setExtents(delta: Vector3d) { this.delta.set(delta.x, delta.y); } - public setOrigin(origin: Point3d) { this.origin.set(origin.x, origin.y); } - public setRotation(rot: Matrix3d) { const xColumn = rot.getColumn(0); this.angle.setRadians(Math.atan2(xColumn.y, xColumn.x)); } - public viewsModel(modelId: Id64String) { return this.baseModelId.toString() === modelId.toString(); } - public forEachModel(func: (model: GeometricModelState) => void) { - const model = this.iModel.models.getLoaded(this.baseModelId); - if (undefined !== model && model.isGeometricModel) - func(model as GeometricModelState); - } - public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystem2dState.createNew(acsName, this.iModel); } + + return undefined !== this._viewedExtents ? this._viewedExtents : new AxisAlignedBox3d(); + } + + public onRenderFrame(_viewport: Viewport): void { } + public async load(): Promise { + await super.load(); + return this.iModel.models.load(this.baseModelId); + } + + public allow3dManipulations(): boolean { return false; } + public getOrigin() { return new Point3d(this.origin.x, this.origin.y); } + public getExtents() { return new Vector3d(this.delta.x, this.delta.y); } + public getRotation() { return Matrix3d.createRotationAroundVector(Vector3d.unitZ(), this.angle)!; } + public setExtents(delta: Vector3d) { this.delta.set(delta.x, delta.y); } + public setOrigin(origin: Point3d) { this.origin.set(origin.x, origin.y); } + public setRotation(rot: Matrix3d) { const xColumn = rot.getColumn(0); this.angle.setRadians(Math.atan2(xColumn.y, xColumn.x)); } + public viewsModel(modelId: Id64String) { return this.baseModelId.toString() === modelId.toString(); } + public forEachModel(func: (model: GeometricModelState) => void) { + const model = this.iModel.models.getLoaded(this.baseModelId); + if (undefined !== model && model.isGeometricModel) + func(model as GeometricModelState); + } + public createAuxCoordSystem(acsName: string): AuxCoordSystemState { return AuxCoordSystem2dState.createNew(acsName, this.iModel); } } /** A view of a DrawingModel */ export class DrawingViewState extends ViewState2d { - public static createFromStateData(viewStateData: ViewStateData, cat: CategorySelectorState, iModel: IModelConnection): ViewState | undefined { - const displayStyleState = new DisplayStyle2dState(viewStateData.displayStyleProps, iModel); - // use "new this" so subclasses are correct - return new this(viewStateData.viewDefinitionProps as ViewDefinition2dProps, iModel, cat, displayStyleState); - } - - public static get className() { return "DrawingViewDefinition"; } - public getExtentLimits() { return { min: Constant.oneCentimeter, max: this.iModel.projectExtents.xLength() * 2 }; } + public static createFromStateData(viewStateData: ViewStateData, cat: CategorySelectorState, iModel: IModelConnection): ViewState | undefined { + const displayStyleState = new DisplayStyle2dState(viewStateData.displayStyleProps, iModel); + // use "new this" so subclasses are correct + return new this(viewStateData.viewDefinitionProps as ViewDefinition2dProps, iModel, cat, displayStyleState); + } + + public static get className() { return "DrawingViewDefinition"; } + public getExtentLimits() { return { min: Constant.oneCentimeter, max: this.iModel.projectExtents.xLength() * 2 }; } } diff --git a/core/frontend/src/Viewport.ts b/core/frontend/src/Viewport.ts index b9b66be..205d061 100644 --- a/core/frontend/src/Viewport.ts +++ b/core/frontend/src/Viewport.ts @@ -1070,34 +1070,38 @@ export abstract class Viewport implements IDisposable { if (undefined === readRect) return undefined; - const pixels = this.readPixels(readRect, Pixel.Selector.Distance); - if (!pixels) - return undefined; + let retVal: DepthRangeNpc | undefined; + this.readPixels(readRect, Pixel.Selector.GeometryAndDistance, (pixels) => { + if (!pixels) + return; - let maximum = 0; - let minimum = 1; - const npc = Point3d.create(); - const testPoint = Point2d.create(); - for (testPoint.x = readRect.left; testPoint.x < readRect.right; ++testPoint.x) { - for (testPoint.y = readRect.top; testPoint.y < readRect.bottom; ++testPoint.y) { - if (this.getPixelDataNpcPoint(pixels, testPoint.x, testPoint.y, npc) !== undefined) { - minimum = Math.min(minimum, npc.z); - maximum = Math.max(maximum, npc.z); + let maximum = 0; + let minimum = 1; + const npc = Point3d.create(); + const testPoint = Point2d.create(); + for (testPoint.x = readRect.left; testPoint.x < readRect.right; ++testPoint.x) { + for (testPoint.y = readRect.top; testPoint.y < readRect.bottom; ++testPoint.y) { + if (this.getPixelDataNpcPoint(pixels, testPoint.x, testPoint.y, npc) !== undefined) { + minimum = Math.min(minimum, npc.z); + maximum = Math.max(maximum, npc.z); + } } } - } - if (maximum <= 0) - return undefined; + if (maximum <= 0) + return; - if (undefined === result) { - result = new DepthRangeNpc(minimum, maximum); - } else { - result.minimum = minimum; - result.maximum = maximum; - } + if (undefined === result) { + result = new DepthRangeNpc(minimum, maximum); + } else { + result.minimum = minimum; + result.maximum = maximum; + } - return result; + retVal = result; + }); + + return retVal; } /** Turn the camera on if it is currently off. If the camera is already on, adjust it to use the supplied lens angle. @@ -1687,6 +1691,9 @@ export abstract class Viewport implements IDisposable { target.animationFraction = this.animationFraction; isRedrawNeeded = true; sync.setValidAnimationFraction(); + const scheduleScript = view.displayStyle.settings.scheduleScript; + if (scheduleScript) + view.scheduleTime = scheduleScript.duration.fractionToPoint(target.animationFraction); } if (this.processFlash()) { @@ -1709,24 +1716,27 @@ export abstract class Viewport implements IDisposable { * Read selected data about each pixel within a rectangular region of this Viewport. * @param rect The area of the viewport's contents to read. The origin specifies the upper-left corner. Must lie entirely within the viewport's dimensions. * @param selector Specifies which aspect(s) of data to read. - * @returns a [[Pixel.Buffer]] object from which the selected data can be retrieved, or undefined in the viewport is not active, the rect is out of bounds, or some other error. + * @param receiver A function accepting a [[Pixel.Buffer]] object from which the selected data can be retrieved, or receiving undefined if the viewport is not active, the rect is out of bounds, or some other error. + * @note The [[Pixel.Buffer]] supplied to the `receiver` function becomes invalid once that function exits. Do not store a reference to it. */ - public readPixels(rect: ViewRect, selector: Pixel.Selector): Pixel.Buffer | undefined { + public readPixels(rect: ViewRect, selector: Pixel.Selector, receiver: Pixel.Receiver): void { const viewRect = this.viewRect; if (!rect.isContained(viewRect)) - return undefined; - - return this.target.readPixels(rect, selector); + receiver(undefined); + else + this.target.readPixels(rect, selector, receiver); } /** * Read the current image from this viewport from the rendering system. If a view rectangle outside the actual view is specified, the entire view is captured. * @param rect The area of the view to read. The origin of a viewRect must specify the upper left corner. * @param targetSize The size of the image to be returned. The size can be larger or smaller than the original view. + * @param flipVertically If true, the image is flipped along the x-axis. * @returns The contents of the viewport within the specified rectangle as a bitmap image, or undefined if the image could not be read. + * @note By default the image is returned upside-down. Pass `true` for `flipVertically` to flip it along the x-axis. */ - public readImage(rect: ViewRect = new ViewRect(0, 0, -1, -1), targetSize: Point2d = Point2d.createZero()): ImageBuffer | undefined { - return this.target.readImage(rect, targetSize); + public readImage(rect: ViewRect = new ViewRect(0, 0, -1, -1), targetSize: Point2d = Point2d.createZero(), flipVertically: boolean = false): ImageBuffer | undefined { + return this.target.readImage(rect, targetSize, flipVertically); } /** Get the point at the specified x and y location in the pixel buffer in npc coordinates */ diff --git a/core/frontend/src/frontend.ts b/core/frontend/src/frontend.ts index 27ca451..e8e762d 100644 --- a/core/frontend/src/frontend.ts +++ b/core/frontend/src/frontend.ts @@ -17,6 +17,7 @@ export * from "./AccuDraw"; export * from "./AccuSnap"; export * from "./AuxCoordSys"; export * from "./CategorySelectorState"; +export * from "./ContextRealityModelState"; export * from "./DisplayStyleState"; export * from "./ElementLocateManager"; export * from "./EntityState"; diff --git a/core/frontend/src/public/locales/en/iModelJs.json b/core/frontend/src/public/locales/en/iModelJs.json index 5130e22..6ec7fe5 100644 --- a/core/frontend/src/public/locales/en/iModelJs.json +++ b/core/frontend/src/public/locales/en/iModelJs.json @@ -3,13 +3,6 @@ "NoPreviousViewOps": "No previous viewing operations", "NoPreviousUndos": "Only valid after 'View Previous'" }, - "Manipulator": { - "ModifyHandle": "Handle: %s", - "ModifyHandleAngle": "SHIFT - Tap to change action = ", - "ModifyHandleAction": "SHIFT - Tap for preserve angle on/off = ", - "ProjectExtents": "Project Extents", - "ModifyProjectExtents": "Modify Project Extents" - }, "Viewing": { "InvalidWindow": "Invalid window", "MinWindow": "Minimum window", @@ -31,16 +24,10 @@ }, "LocateFailure": { "NoElements": "No Elements Found", - "LockedModel": "Element is in a model with locate turned off", - "LockedElem": "Element is locked", "ByApp": "Element not valid for tool", - "ByCommand": "Element rejected by tool", - "ByType": "Element type not valid for this tool", - "ByProperties": "Element properties not valid for this tool", - "Transient": "Element is a transient element", - "ModelNotAllowed": "Element is in a model that is not allowed by this tool", - "NotSnappable": "Element is not snappable", - "RejectedByElement": "Element does not allow this operation" + "Transient": "Decoration is not valid for this tool", + "NotSnappable": "SubCategory has snapping turned off", + "NotLocatable": "SubCategory has locate turned off" }, "Element": { "Id": "Id", diff --git a/core/frontend/src/render/FeatureSymbology.ts b/core/frontend/src/render/FeatureSymbology.ts index d82b914..7977477 100644 --- a/core/frontend/src/render/FeatureSymbology.ts +++ b/core/frontend/src/render/FeatureSymbology.ts @@ -64,6 +64,8 @@ export namespace FeatureSymbology { transparency: color.colors.t / 255, }); } + /** Create an Appearance that overrides only the transparency */ + public static fromTransparency(transparencyValue: number) { return this.fromJSON({ transparency: transparencyValue }); } /** Create an Appearance with overrides corresponding to those defined by the supplied SubCategoryOverride. */ public static fromSubCategoryOverride(ovr: SubCategoryOverride): Appearance { @@ -402,6 +404,11 @@ export namespace FeatureSymbology { } } } + if (view.scheduleScript) { + view.scheduleScript.getSymbologyOverrides(view.scheduleTime).forEach((override, elementID) => { + this.overrideElement(elementID, Appearance.fromJSON(override)); + }); + } } } diff --git a/core/frontend/src/render/System.ts b/core/frontend/src/render/System.ts index c21c4d0..931f700 100644 --- a/core/frontend/src/render/System.ts +++ b/core/frontend/src/render/System.ts @@ -9,7 +9,7 @@ import { ClipVector, IndexedPolyface, Plane3dByOriginAndUnitNormal, Point2d, Poi import { AntiAliasPref, BatchType, ColorDef, ElementAlignedBox3d, Feature, FeatureTable, Frustum, Gradient, HiddenLine, Hilite, ImageBuffer, ImageSource, ImageSourceFormat, isValidImageSourceFormat, QParams3d, - QPoint3dList, RenderMaterial, RenderTexture, SceneLights, ViewFlag, ViewFlags, AnalysisStyle, + QPoint3dList, RenderMaterial, RenderTexture, SceneLights, ViewFlag, ViewFlags, AnalysisStyle, GeometryClass, } from "@bentley/imodeljs-common"; import { SkyBox } from "../DisplayStyleState"; import { imageElementFromImageSource } from "../ImageUtil"; @@ -69,12 +69,12 @@ export class RenderPlan { const view = vp.view; const style = view.displayStyle; - const hline = style.is3d() ? style.getHiddenLineParams() : undefined; + const hline = style.is3d() ? style.settings.hiddenLineSettings : undefined; const lights = undefined; // view.is3d() ? view.getLights() : undefined const clipVec = view.getViewClip(); const activeVolume = clipVec !== undefined ? IModelApp.renderSystem.getClipVolume(clipVec, view.iModel) : undefined; const terrainFrustum = (undefined === vp.backgroundMapPlane) ? undefined : ViewFrustum.createFromViewportAndPlane(vp, vp.backgroundMapPlane as Plane3dByOriginAndUnitNormal); - const rp = new RenderPlan(view.is3d(), style.viewFlags, view.backgroundColor, style.monochromeColor, vp.hilite, vp.wantAntiAliasLines, vp.wantAntiAliasText, vp.viewFrustum, terrainFrustum!, activeVolume, hline, lights, view.displayStyle.AnalysisStyle); + const rp = new RenderPlan(view.is3d(), style.viewFlags, view.backgroundColor, style.monochromeColor, vp.hilite, vp.wantAntiAliasLines, vp.wantAntiAliasText, vp.viewFrustum, terrainFrustum!, activeVolume, hline, lights, view.displayStyle.settings.analysisStyle); if (rp.analysisStyle !== undefined && rp.analysisStyle.scalarThematicSettings !== undefined) rp.analysisTexture = vp.target.renderSystem.getGradientTexture(Gradient.Symb.createThematic(rp.analysisStyle.scalarThematicSettings), vp.iModel); @@ -247,10 +247,14 @@ export class GraphicBranch implements IDisposable { export namespace Pixel { /** Describes a single pixel within a [[Pixel.Buffer]]. */ export class Data { - public constructor(public readonly elementId?: Id64String, + public constructor(public readonly feature?: Feature, public readonly distanceFraction: number = -1.0, public readonly type: GeometryType = GeometryType.Unknown, public readonly planarity: Planarity = Planarity.Unknown) { } + + public get elementId(): Id64String | undefined { return undefined !== this.feature ? this.feature.elementId : undefined; } + public get subCategoryId(): Id64String | undefined { return undefined !== this.feature ? this.feature.subCategoryId : undefined; } + public get geometryClass(): GeometryClass | undefined { return undefined !== this.feature ? this.feature.geometryClass : undefined; } } /** Describes the foremost type of geometry which produced the [[Pixel.Data]]. */ @@ -287,16 +291,12 @@ export namespace Pixel { */ export const enum Selector { None = 0, - /** Select the ID of the element which produced each pixel. */ - ElementId = 1 << 0, - /** For each pixel, select the fraction of its distance between the near and far planes. */ - Distance = 1 << 1, - /** Select the type and planarity of geometry which produced each pixel. */ - Geometry = 1 << 2, - /** Select geometry type/planarity and distance fraction associated with each pixel. */ - GeometryAndDistance = Geometry | Distance, + /** Select the [[Feature]] which produced each pixel. */ + Feature = 1 << 0, + /** Select the type and planarity of geometry which produced each pixel as well as the fraction of its distance between the near and far planes. */ + GeometryAndDistance = 1 << 2, /** Select all aspects of each pixel. */ - All = GeometryAndDistance | ElementId, + All = GeometryAndDistance | Feature, } /** A rectangular array of pixels as read from a [[Viewport]]'s frame buffer. Each pixel is represented as a [[Pixel.Data]] object. @@ -306,6 +306,11 @@ export namespace Pixel { /** Retrieve the data associated with the pixel at (x,y) in view coordinates. */ getPixel(x: number, y: number): Data; } + + /** A function which receives the results of a call to [[Viewport.readPixels]]. + * @note The contents of the buffer become invalid once the Receiver function returns. Do not store a reference to it. + */ + export type Receiver = (pixels: Buffer | undefined) => void; } /** @@ -519,9 +524,9 @@ export abstract class RenderTarget implements IDisposable { /** @hidden */ public abstract updateViewRect(): boolean; // force a RenderTarget viewRect to resize if necessary since last draw /** @hidden */ - public abstract readPixels(rect: ViewRect, selector: Pixel.Selector): Pixel.Buffer | undefined; + public abstract readPixels(rect: ViewRect, selector: Pixel.Selector, receiver: Pixel.Receiver): void; /** @hidden */ - public abstract readImage(rect: ViewRect, targetSize: Point2d): ImageBuffer | undefined; + public abstract readImage(rect: ViewRect, targetSize: Point2d, flipVertically: boolean): ImageBuffer | undefined; } /** Describes a texture loaded from an HTMLImageElement */ diff --git a/core/frontend/src/render/primitives/VertexTable.ts b/core/frontend/src/render/primitives/VertexTable.ts index d1ad201..737a893 100644 --- a/core/frontend/src/render/primitives/VertexTable.ts +++ b/core/frontend/src/render/primitives/VertexTable.ts @@ -244,6 +244,26 @@ export class VertexTable implements VertexTableProps { this.auxDisplacements = props.auxDisplacements; this.auxNormals = props.auxNormals; this.auxParams = props.auxParams; + + // ###TODO: Fix add-on to put uniform feature index into vertex table... + if (undefined !== this.uniformFeatureID) + this.fixUpUniformFeatureId(this.uniformFeatureID); + } + + private fixUpUniformFeatureId(id: number): void { + if (0 === id) + return; // already set to zero + + const u32 = new Uint32Array(1); + u32[0] = id; + const u8 = new Uint8Array(u32.buffer); + for (let i = 0; i < this.numVertices; i++) { + const u32Index = (i * this.numRgbaPerVertex) + 2; + const u8Index = u32Index * 4; + for (let j = 0; j < 4; j++) { + this.data[u8Index + j] = u8[j]; + } + } } public static buildFrom(builder: VertexTableBuilder, colorIndex: ColorIndex, featureIndex: FeatureIndex): VertexTable { diff --git a/core/frontend/src/render/webgl/BranchState.ts b/core/frontend/src/render/webgl/BranchState.ts index ef43ae6..2de4118 100644 --- a/core/frontend/src/render/webgl/BranchState.ts +++ b/core/frontend/src/render/webgl/BranchState.ts @@ -5,10 +5,10 @@ /** @module WebGL */ import { Transform } from "@bentley/geometry-core"; -import { ViewFlags, RenderMode } from "@bentley/imodeljs-common"; -import { assert } from "@bentley/bentleyjs-core"; +import { ViewFlags, RenderMode, Feature } from "@bentley/imodeljs-common"; +import { Id64, Id64String, assert, lowerBound } from "@bentley/bentleyjs-core"; import { FeatureSymbology } from "../FeatureSymbology"; -import { Branch } from "./Graphic"; +import { Branch, Batch } from "./Graphic"; import { ClipPlanesVolume, ClipMaskVolume } from "./ClipVolume"; /** @@ -99,3 +99,121 @@ export class BranchStack { this.top.symbologyOverrides = ovrs; } } + +/** + * Assigns a transient, unique 32-bit integer ID to each Batch in a RenderCommands. + * A batch ID of 0 means "no batch". + * The first batch gets batch ID of 1. + * The next batch gets the previous batch's ID plus the number of features in the previous batch's feature table + * (or 1, if empty feature table). + * The IDs are set temporarily as members on the Batch objects and reset to 0 immediately after rendering. + * The currentBatch member identifies the batch containing primitives currently being drawn. + * The combination of the current batch's ID (passed as uniform to shader) and the index of a given Feature within + * its batch's FeatureTable (stored in vertex table) produce a unique ID for every feature rendered during a frame. + * During rendering, the feature IDs are written to the "feature ID" color attachment. + * The batch IDs remain valid during a call to Target.readPixels() so that they can be used to extract + * Features from the Batch's FeatureTables. + */ +export class BatchState { + private _batches: Batch[] = []; // NB: this list is ordered - but *not* indexed - by batch ID. + private _curBatch?: Batch; + + public get currentBatch(): Batch | undefined { return this._curBatch; } + public get currentBatchId(): number { return undefined !== this._curBatch ? this._curBatch.batchId : 0; } + public get isEmpty(): boolean { return 0 === this._batches.length; } + + public push(batch: Batch, allowAdd: boolean): void { + assert(undefined === this.currentBatch, "batches cannot nest"); + this.getBatchId(batch, allowAdd); + this._curBatch = batch; + } + + public pop(): void { + assert(undefined !== this.currentBatch); + this._curBatch = undefined; + } + + public reset(): void { + assert(undefined === this.currentBatch); + for (const batch of this._batches) + batch.batchId = 0; + + this._batches.length = 0; + this._curBatch = undefined; + } + + public getElementId(featureId: number): Id64String { + const batch = this.find(featureId); + if (undefined === batch) + return Id64.invalid; + + const featureIndex = featureId - batch.batchId; + assert(featureIndex >= 0); + + const parts = batch.featureTable.getElementIdParts(featureIndex); + return Id64.fromUint32Pair(parts.low, parts.high); + } + + public getFeature(featureId: number): Feature | undefined { + const batch = this.find(featureId); + if (undefined === batch) + return undefined; + + const featureIndex = featureId - batch.batchId; + assert(featureIndex >= 0); + + return batch.featureTable.findFeature(featureIndex); + } + + public get numFeatureIds() { return this._nextBatchId; } + public get numBatches() { return this._batches.length; } + public findBatchId(featureId: number) { + const batch = this.find(featureId); + return undefined !== batch ? batch.batchId : 0; + } + + private get _nextBatchId(): number { + if (this.isEmpty) + return 1; + + const prev = this._batches[this._batches.length - 1]; + assert(0 !== prev.batchId); + + let prevNumFeatures = prev.featureTable.numFeatures; + if (0 === prevNumFeatures) + prevNumFeatures = 1; + + return prev.batchId + prevNumFeatures; + } + + private getBatchId(batch: Batch, allowAdd: boolean): number { + if (allowAdd && 0 === batch.batchId) { + batch.batchId = this._nextBatchId; + this._batches.push(batch); + } + + return batch.batchId; + } + + private indexOf(featureId: number): number { + if (featureId <= 0) + return -1; + + const found = lowerBound(featureId, this._batches, (lhs: number, rhs: Batch) => { + // Determine if the requested feature ID is within the range of this batch. + if (lhs < rhs.batchId) + return -1; + + const numFeatures = rhs.featureTable.numFeatures; + const nextBatchId = rhs.batchId + (numFeatures > 0 ? numFeatures : 1); + return lhs < nextBatchId ? 0 : 1; + }); + + return found.index < this._batches.length ? found.index : -1; + } + + private find(featureId: number): Batch | undefined { + const index = this.indexOf(featureId); + return -1 !== index ? this._batches[index] : undefined; + } +} diff --git a/core/frontend/src/render/webgl/CachedGeometry.ts b/core/frontend/src/render/webgl/CachedGeometry.ts index 7116808..0a02f60 100644 --- a/core/frontend/src/render/webgl/CachedGeometry.ts +++ b/core/frontend/src/render/webgl/CachedGeometry.ts @@ -548,18 +548,17 @@ export class CompositeGeometry extends TexturedViewportQuadGeometry { // Geometry used to ping-pong the pick buffer data in between opaque passes. export class CopyPickBufferGeometry extends TexturedViewportQuadGeometry { - public static createGeometry(idLow: WebGLTexture, idHigh: WebGLTexture, depthAndOrder: WebGLTexture) { + public static createGeometry(featureId: WebGLTexture, depthAndOrder: WebGLTexture) { const params = ViewportQuad.getInstance().createParams(); if (undefined !== params) { - return new CopyPickBufferGeometry(params, [idLow, idHigh, depthAndOrder]); + return new CopyPickBufferGeometry(params, [featureId, depthAndOrder]); } else { return undefined; } } - public get elemIdLow() { return this._textures[0]; } - public get elemIdHigh() { return this._textures[1]; } - public get depthAndOrder() { return this._textures[2]; } + public get featureId() { return this._textures[0]; } + public get depthAndOrder() { return this._textures[1]; } private constructor(params: IndexedGeometryParams, textures: WebGLTexture[]) { super(params, TechniqueId.CopyPickBuffers, textures); diff --git a/core/frontend/src/render/webgl/DrawCommand.ts b/core/frontend/src/render/webgl/DrawCommand.ts index cb62d7c..736fc49 100644 --- a/core/frontend/src/render/webgl/DrawCommand.ts +++ b/core/frontend/src/render/webgl/DrawCommand.ts @@ -15,7 +15,7 @@ import { Primitive } from "./Primitive"; import { ShaderProgramExecutor } from "./ShaderProgram"; import { RenderPass, RenderOrder, CompositeFlags } from "./RenderFlags"; import { Target } from "./Target"; -import { BranchStack } from "./BranchState"; +import { BranchStack, BatchState } from "./BranchState"; import { GraphicList, Decorations, RenderGraphic } from "../System"; import { TechniqueId } from "./TechniqueId"; import { SurfacePrimitive, SurfaceGeometry } from "./Surface"; @@ -180,13 +180,10 @@ export class BatchPrimitiveCommand extends PrimitiveCommand { } public preExecute(exec: ShaderProgramExecutor): void { - exec.target.currentOverrides = this._batch.getOverrides(exec.target); - assert(undefined === exec.target.currentPickTable); - exec.target.currentPickTable = this._batch.pickTable; + exec.target.pushBatch(this._batch); } public postExecute(exec: ShaderProgramExecutor): void { - exec.target.currentOverrides = undefined; - exec.target.currentPickTable = undefined; + exec.target.popBatch(); } public computeIsFlashed(flashedId: Id64String): boolean { @@ -214,8 +211,8 @@ export class RenderCommands { private readonly _scratchFrustum = new Frustum(); private readonly _scratchRange = new ElementAlignedBox3d(); private readonly _commands: DrawCommands[]; - private readonly _stack: BranchStack; - private _curBatch?: Batch = undefined; + private readonly _stack: BranchStack; // refers to the Target's BranchStack + private readonly _batchState: BatchState; // refers to the Target's BatchState private _forcedRenderPass: RenderPass = RenderPass.None; private _opaqueOverrides: boolean = false; private _translucentOverrides: boolean = false; @@ -245,12 +242,15 @@ export class RenderCommands { return flags; } + private get _curBatch(): Batch | undefined { return this._batchState.currentBatch; } + public hasCommands(pass: RenderPass): boolean { return 0 !== this.getCommands(pass).length; } public isOpaquePass(pass: RenderPass): boolean { return pass >= RenderPass.OpaqueLinear && pass <= RenderPass.OpaqueGeneral; } - constructor(target: Target, stack: BranchStack) { + constructor(target: Target, stack: BranchStack, batchState: BatchState) { this.target = target; this._stack = stack; + this._batchState = batchState; this._commands = Array(RenderPass.COUNT); for (let i = 0; i < RenderPass.COUNT; ++i) this._commands[i] = []; @@ -467,11 +467,15 @@ export class RenderCommands { } public clear(): void { + assert(this._batchState.isEmpty, "BatchState should be cleared at end of frame"); + this._clearCommands(); + } + private _clearCommands(): void { this._commands.forEach((cmds: DrawCommands) => { cmds.splice(0); }); } public initForPickOverlays(overlays: GraphicList): void { - this.clear(); + this._clearCommands(); this._addTranslucentAsOpaque = true; this._stack.pushState(this.target.decorationState); @@ -616,24 +620,13 @@ export class RenderCommands { } } - // Don't bother pushing the batch if no features within it are overridden... - // ^ Actually, we need the pick table... - const pushBatch = /*overrides.AnyOverridden()*/ true; - if (pushBatch) { - this._curBatch = batch; - this._opaqueOverrides = overrides.anyOpaque; - this._translucentOverrides = overrides.anyTranslucent; - } + this._batchState.push(batch, true); + this._opaqueOverrides = overrides.anyOpaque; + this._translucentOverrides = overrides.anyTranslucent; (batch.graphic as Graphic).addCommands(this); - if (!pushBatch) { - assert(!this._opaqueOverrides && !this._translucentOverrides); - assert(undefined === this._curBatch); - return; - } - - this._curBatch = undefined; + this._batchState.pop(); // If the batch contains hilited features, need to render them in the hilite pass const anyHilited = overrides.anyHilited; diff --git a/core/frontend/src/render/webgl/Graphic.ts b/core/frontend/src/render/webgl/Graphic.ts index c55afe6..be7a4d4 100644 --- a/core/frontend/src/render/webgl/Graphic.ts +++ b/core/frontend/src/render/webgl/Graphic.ts @@ -5,110 +5,27 @@ /** @module WebGL */ import { assert, Id64, Id64String, BeTimePoint, IDisposable, dispose } from "@bentley/bentleyjs-core"; -import { ViewFlags, ColorDef, ElementAlignedBox3d } from "@bentley/imodeljs-common"; +import { ViewFlags, ElementAlignedBox3d } from "@bentley/imodeljs-common"; import { Transform } from "@bentley/geometry-core"; import { Primitive } from "./Primitive"; import { RenderGraphic, GraphicBranch, GraphicList, PackedFeatureTable } from "../System"; import { RenderCommands } from "./DrawCommand"; import { FeatureSymbology } from "../FeatureSymbology"; import { TextureHandle, Texture2DHandle, Texture2DDataUpdater } from "./Texture"; -import { LUTDimensions, LUTParams, LUTDimension } from "./FeatureDimensions"; +import { LUTDimensions, LUTParams } from "./FeatureDimensions"; import { Target } from "./Target"; -import { FloatRgba } from "./FloatRGBA"; import { OvrFlags } from "./RenderFlags"; import { LineCode } from "./EdgeOverrides"; import { GL } from "./GL"; import { ClipPlanesVolume, ClipMaskVolume } from "./ClipVolume"; import { RenderPass } from "./RenderFlags"; -class OvrUniform { - public floatFlags: number = 0; - public weight: number = 0; - public lineCode: number = 0; - public unused: number = 0; - public rgba: FloatRgba = FloatRgba.fromColorDef(ColorDef.black, 0); - public flags: OvrFlags = OvrFlags.None; - - public isFlagSet(flag: OvrFlags): boolean { return OvrFlags.None !== (this.flags & flag); } - - public get anyOverridden() { return OvrFlags.None !== this.flags; } - public get allHidden() { return this.isFlagSet(OvrFlags.Visibility); } - public get anyOpaque() { return this.isFlagSet(OvrFlags.Alpha) && 1.0 === this.rgba.alpha; } - public get anyTranslucent() { return this.isFlagSet(OvrFlags.Alpha) && 1.0 > this.rgba.alpha; } - public get anyHilited() { return this.isFlagSet(OvrFlags.Hilited); } - - public initialize(map: PackedFeatureTable, ovrs: FeatureSymbology.Overrides, hilite: Set, flashed: Id64String) { - this.update(map, hilite, flashed, ovrs); - } - - public update(map: PackedFeatureTable, hilites: Set, flashedElemId: Id64String, ovrs?: FeatureSymbology.Overrides) { - assert(map.isUniform); - - // NB: To be consistent with the lookup table approach for non-uniform feature tables and share shader code, we pass - // the override data as two RGBA values - hence all the conversions to floating point range [0.0..1.0] - const feature = map.uniform!; - const isFlashed = Id64.isValid(flashedElemId) && feature.elementId === flashedElemId; - const isHilited = hilites.has(feature.elementId); - - if (undefined === ovrs) { - // We only need to update the 'flashed' and 'hilited' flags - // NB: Don't do so if the feature is invisible - if (OvrFlags.None === (this.flags & OvrFlags.Visibility)) { - this.flags = isFlashed ? (this.flags | OvrFlags.Flashed) : (this.flags & ~OvrFlags.Flashed); - this.flags = isHilited ? (this.flags | OvrFlags.Hilited) : (this.flags & ~OvrFlags.Hilited); - this.floatFlags = this.flags / 256.0; - } - - return; - } - - this.floatFlags = this.weight = this.lineCode = this.unused = 0; - this.rgba = FloatRgba.fromColorDef(ColorDef.black, 0); - this.flags = OvrFlags.None; - - const app = ovrs.getAppearance(feature, map.modelId, map.type); - if (undefined === app) { - // We're invisible. Don't care about any other overrides. - this.flags = OvrFlags.Visibility; - this.floatFlags = this.flags / 256.0; - return; - } - - if (isFlashed) - this.flags |= OvrFlags.Flashed; - - if (isHilited) - this.flags |= OvrFlags.Hilited; - - if (app.overridesRgb && app.rgb) { - this.flags |= OvrFlags.Rgb; - this.rgba = FloatRgba.fromColorDef(ColorDef.from(app.rgb.r, app.rgb.g, app.rgb.b, 1.0)); // NB: Alpha ignored unless OvrFlags.Alpha set... - } - - if (undefined !== app.transparency) { - const alpha = 1.0 - app.transparency; - this.flags |= OvrFlags.Alpha; - this.rgba = new FloatRgba(this.rgba.red, this.rgba.green, this.rgba.blue, alpha); // NB: rgb ignored unless OvrFlags.Rgb set... - } - - if (app.overridesWeight && app.weight) { - this.flags |= OvrFlags.Weight; - this.weight = app.weight / 256.0; - } - - if (app.overridesLinePixels && app.linePixels) { - this.flags |= OvrFlags.LineCode; - this.lineCode = LineCode.valueFromLinePixels(app.linePixels) / 256.0; - } - - if (app.ignoresMaterial) - this.flags |= OvrFlags.IgnoreMaterial; - - this.floatFlags = this.flags / 256.0; - } -} - -class OvrNonUniform { +export class FeatureOverrides implements IDisposable { + public lut?: TextureHandle; + public readonly target: Target; + private _lastOverridesUpdated: BeTimePoint = BeTimePoint.now(); + private _lastFlashUpdated: BeTimePoint = BeTimePoint.now(); + private _lastHiliteUpdated: BeTimePoint = BeTimePoint.now(); public lutParams: LUTParams = new LUTParams(1, 1); public anyOverridden: boolean = true; public allHidden: boolean = true; @@ -116,7 +33,17 @@ class OvrNonUniform { public anyOpaque: boolean = true; public anyHilited: boolean = true; - public initialize(map: PackedFeatureTable, ovrs: FeatureSymbology.Overrides, hilite: Set, flashedElemId: Id64String): TextureHandle | undefined { + public get isUniform() { return 2 === this.lutParams.width && 1 === this.lutParams.height; } + public get isUniformFlashed() { + if (!this.isUniform || undefined === this.lut) + return false; + + const lut = this.lut as Texture2DHandle; + const flags = lut.dataBytes![0]; + return 0 !== (flags & OvrFlags.Flashed); + } + + private _initialize(map: PackedFeatureTable, ovrs: FeatureSymbology.Overrides, hilite: Set, flashedElemId: Id64String): TextureHandle | undefined { const nFeatures = map.numFeatures; const dims: LUTDimensions = LUTDimensions.computeWidthAndHeight(nFeatures, 2); const width = dims.width; @@ -132,7 +59,7 @@ class OvrNonUniform { return TextureHandle.createForData(width, height, data, true, GL.Texture.WrapMode.ClampToEdge); } - public update(map: PackedFeatureTable, lut: TextureHandle, flashedElemId: Id64String, hilites?: Set, ovrs?: FeatureSymbology.Overrides) { + private _update(map: PackedFeatureTable, lut: TextureHandle, flashedElemId: Id64String, hilites?: Set, ovrs?: FeatureSymbology.Overrides) { const updater = new Texture2DDataUpdater(lut.dataBytes!); if (undefined === ovrs) { @@ -268,19 +195,6 @@ class OvrNonUniform { } } } -} - -export class FeatureOverrides implements IDisposable { - public lut?: TextureHandle; - public readonly target: Target; - public dimension: LUTDimension = LUTDimension.Uniform; - - private _uniform?: OvrUniform; - private _nonUniform?: OvrNonUniform; - - private _lastOverridesUpdated: BeTimePoint = BeTimePoint.now(); - private _lastFlashUpdated: BeTimePoint = BeTimePoint.now(); - private _lastHiliteUpdated: BeTimePoint = BeTimePoint.now(); private constructor(target: Target) { this.target = target; @@ -295,49 +209,15 @@ export class FeatureOverrides implements IDisposable { this.lut = undefined; } - public get isNonUniform(): boolean { return LUTDimension.NonUniform === this.dimension; } - public get isUniform(): boolean { return !this.isNonUniform; } - public get anyOverridden(): boolean { return this._uniform ? this._uniform.anyOverridden : this._nonUniform!.anyOverridden; } - public get allHidden(): boolean { return this._uniform ? this._uniform.allHidden : this._nonUniform!.allHidden; } - public get anyOpaque(): boolean { return this._uniform ? this._uniform.anyOpaque : this._nonUniform!.anyOpaque; } - public get anyTranslucent(): boolean { return this._uniform ? this._uniform.anyTranslucent : this._nonUniform!.anyTranslucent; } - public get anyHilited(): boolean { return this._uniform ? this._uniform.anyHilited : this._nonUniform!.anyHilited; } - - public get uniform1(): Float32Array { - if (this.isUniform) { - const uniform = this._uniform!; - return new Float32Array([uniform.floatFlags, uniform.weight, uniform.lineCode, uniform.unused]); - } - return new Float32Array(4); - } - - public get uniform2(): Float32Array { - if (this.isUniform) { - const rgba = this._uniform!.rgba; - return new Float32Array([rgba.red, rgba.green, rgba.blue, rgba.alpha]); - } - return new Float32Array(4); - } - public initFromMap(map: PackedFeatureTable) { const nFeatures = map.numFeatures; assert(0 < nFeatures); - this._uniform = this._nonUniform = undefined; this.lut = undefined; const ovrs: FeatureSymbology.Overrides = this.target.currentFeatureSymbologyOverrides; const hilite: Set = this.target.hilite; - if (1 < nFeatures) { - this._nonUniform = new OvrNonUniform(); - this.lut = this._nonUniform.initialize(map, ovrs, hilite, this.target.flashedElemId); - this.dimension = LUTDimension.NonUniform; - } else { - this._uniform = new OvrUniform(); - this._uniform.initialize(map, ovrs, hilite, this.target.flashedElemId); - this.dimension = LUTDimension.Uniform; - } - + this.lut = this._initialize(map, ovrs, hilite, this.target.flashedElemId); this._lastOverridesUpdated = this._lastFlashUpdated = this._lastHiliteUpdated = BeTimePoint.now(); } @@ -351,10 +231,7 @@ export class FeatureOverrides implements IDisposable { const ovrs = ovrsUpdated ? this.target.currentFeatureSymbologyOverrides : undefined; const hilite = this.target.hilite; if (ovrsUpdated || hiliteUpdated || this._lastFlashUpdated.before(flashLastUpdated)) { - if (this.isUniform) - this._uniform!.update(features, hilite, this.target.flashedElemId, ovrs); - else - this._nonUniform!.update(features, this.lut!, this.target.flashedElemId, undefined !== ovrs || hiliteUpdated ? hilite : undefined, ovrs); + this._update(features, this.lut!, this.target.flashedElemId, undefined !== ovrs || hiliteUpdated ? hilite : undefined, ovrs); this._lastOverridesUpdated = styleLastUpdated; this._lastFlashUpdated = flashLastUpdated; @@ -363,71 +240,10 @@ export class FeatureOverrides implements IDisposable { } } -export interface UniformPickTable { - readonly elemId0: Float32Array; // 4 bytes - readonly elemId1: Float32Array; // 4 bytes -} - -export type NonUniformPickTable = TextureHandle; - -export interface PickTable { - readonly uniform?: UniformPickTable; - readonly nonUniform?: NonUniformPickTable; -} - -const scratchUint32 = new Uint32Array(1); -const scratchBytes = new Uint8Array(scratchUint32.buffer); -function uint32ToFloatArray(value: number): Float32Array { - scratchUint32[0] = value; - const floats = new Float32Array(4); - for (let i = 0; i < 4; i++) - floats[i] = scratchBytes[i] / 255.0; - - return floats; -} - -function createUniformPickTable(elemIdParts: { low: number, high: number }): UniformPickTable { - return { - elemId0: uint32ToFloatArray(elemIdParts.low), - elemId1: uint32ToFloatArray(elemIdParts.high), - }; -} - -function createNonUniformPickTable(features: PackedFeatureTable): NonUniformPickTable | undefined { - const nFeatures = features.numFeatures; - if (nFeatures <= 1) { - assert(false); - return undefined; - } - - const dims = LUTDimensions.computeWidthAndHeight(nFeatures, 2); - assert(dims.width * dims.height >= nFeatures); - - const bytes = new Uint8Array(dims.width * dims.height * 4); - const ids = new Uint32Array(bytes.buffer); - for (let index = 0; index < features.numFeatures; index++) { - const elemIdParts = features.getElementIdParts(index); - ids[index * 2] = elemIdParts.low; - ids[index * 2 + 1] = elemIdParts.high; - } - - return TextureHandle.createForData(dims.width, dims.height, bytes); -} - -function createPickTable(features: PackedFeatureTable): PickTable { - if (!features.anyDefined) - return {}; - else if (features.isUniform) - return { uniform: createUniformPickTable(features.getElementIdParts(0)) }; - else - return { nonUniform: createNonUniformPickTable(features) }; -} - export abstract class Graphic extends RenderGraphic { public abstract addCommands(_commands: RenderCommands): void; public get isPickable(): boolean { return false; } public addHiliteCommands(_commands: RenderCommands, _batch: Batch, _pass: RenderPass): void { assert(false); } - public assignUniformFeatureIndices(_index: number): void { } // ###TODO: Implement for Primitive public toPrimitive(): Primitive | undefined { return undefined; } // public abstract setIsPixelMode(): void; } @@ -436,7 +252,7 @@ export class Batch extends Graphic { public readonly graphic: RenderGraphic; public readonly featureTable: PackedFeatureTable; public readonly range: ElementAlignedBox3d; - private _pickTable?: PickTable; + public batchId: number = 0; // Transient ID assigned while rendering a frame, reset afterward. private _overrides: FeatureOverrides[] = []; public constructor(graphic: RenderGraphic, features: PackedFeatureTable, range: ElementAlignedBox3d) { @@ -456,13 +272,6 @@ export class Batch extends Graphic { this._overrides.length = 0; } - public get pickTable(): PickTable | undefined { - if (undefined === this._pickTable) - this._pickTable = createPickTable(this.featureTable); - - return this._pickTable; - } - public addCommands(commands: RenderCommands): void { commands.addBatch(this); } public get isPickable(): boolean { return true; } @@ -524,14 +333,7 @@ export class Branch extends Graphic { public dispose() { this.branch.dispose(); } public addCommands(commands: RenderCommands): void { commands.addBranch(this); } - public addHiliteCommands(commands: RenderCommands, batch: Batch, pass: RenderPass): void { commands.addHiliteBranch(this, batch, pass); } - - public assignUniformFeatureIndices(index: number): void { - for (const entry of this.branch.entries) { - (entry as Graphic).assignUniformFeatureIndices(index); - } - } } export class WorldDecorations extends Branch { @@ -565,10 +367,4 @@ export class GraphicsArray extends Graphic { (graphic as Graphic).addHiliteCommands(commands, batch, pass); } } - - public assignUniformFeatureIndices(index: number): void { - for (const gf of this.graphics) { - (gf as Graphic).assignUniformFeatureIndices(index); - } - } } diff --git a/core/frontend/src/render/webgl/Handle.ts b/core/frontend/src/render/webgl/Handle.ts index 8d5abc0..2cdc604 100644 --- a/core/frontend/src/render/webgl/Handle.ts +++ b/core/frontend/src/render/webgl/Handle.ts @@ -55,14 +55,14 @@ export class BufferHandle implements IDisposable { public static unbind(target: GL.Buffer.Target): void { System.instance.context.bindBuffer(target, null); } /** Binds this buffer to the specified target and sets the buffer's data store. */ - public bindData(target: GL.Buffer.Target, data: BufferData, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw): void { + public bindData(target: GL.Buffer.Target, data: BufferSource, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw): void { this.bind(target); System.instance.context.bufferData(target, data, usage); BufferHandle.unbind(target); } /** Creates a BufferHandle and binds its data */ - public static createBuffer(target: GL.Buffer.Target, data: BufferData, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw): BufferHandle | undefined { + public static createBuffer(target: GL.Buffer.Target, data: BufferSource, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw): BufferHandle | undefined { const handle = new BufferHandle(); if (handle.isDisposed) { return undefined; @@ -72,7 +72,7 @@ export class BufferHandle implements IDisposable { return handle; } /** Creates a BufferHandle and binds its data */ - public static createArrayBuffer(data: BufferData, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw) { + public static createArrayBuffer(data: BufferSource, usage: GL.Buffer.Usage = GL.Buffer.Usage.StaticDraw) { return BufferHandle.createBuffer(GL.Buffer.Target.ArrayBuffer, data, usage); } @@ -171,7 +171,6 @@ export class QBufferHandle3d extends BufferHandle { /** A handle to the location of an attribute within a shader program */ export class AttributeHandle { private readonly _glId: number; - private static _allocatedIds = new Set(); private constructor(glId: number) { this._glId = glId; } @@ -182,7 +181,6 @@ export class AttributeHandle { return undefined; } - AttributeHandle._allocatedIds.add(glId); return new AttributeHandle(glId); } @@ -190,8 +188,7 @@ export class AttributeHandle { System.instance.context.vertexAttribPointer(this._glId, size, type, normalized, stride, offset); } - public enableVertexAttribArray(): void { System.instance.context.enableVertexAttribArray(this._glId); } - public disableVertexAttribArray(): void { System.instance.context.disableVertexAttribArray(this._glId); } + public enableVertexAttribArray(): void { System.instance.enableVertexAttribArray(this._glId); } public enableArray(buffer: BufferHandle, size: number, type: GL.DataType, normalized: boolean, stride: number, offset: number): void { buffer.bind(GL.Buffer.Target.ArrayBuffer); @@ -199,15 +196,6 @@ export class AttributeHandle { this.enableVertexAttribArray(); BufferHandle.unbind(GL.Buffer.Target.ArrayBuffer); } - - // If we enable a vertex attribute array, assign a buffer to it, and then destroy that buffer (e.g., for decoration graphics), - // on a subsequent draw if a different buffer is not assigned to the still-enabled attribute warnings and display issues will - // occur. So before each render, disable all vertex attribute arrays. - public static disableAll(): void { - const gl = System.instance.context; - for (const id of this._allocatedIds) - gl.disableVertexAttribArray(id); - } } const enum DataType { diff --git a/core/frontend/src/render/webgl/RenderFlags.ts b/core/frontend/src/render/webgl/RenderFlags.ts index ec4c2b3..fafb83e 100644 --- a/core/frontend/src/render/webgl/RenderFlags.ts +++ b/core/frontend/src/render/webgl/RenderFlags.ts @@ -47,20 +47,11 @@ export enum TextureUnit { FeatureSymbology = One, SurfaceTexture = Two, LineCode = Two, - ElementId = Three, - PickElementId0 = Four, - PickElementId1 = Five, - PickDepthAndOrder = Six, + PickFeatureId = Three, + PickDepthAndOrder = Four, - VertexLUT = Seven, - - // WIP - Image lighting (won't work if device limited to 8 textures). - DiffuseMap = WebGLRenderingContext.TEXTURE8, - IBLSpecular = WebGLRenderingContext.TEXTURE9, - BRDFLUT = WebGLRenderingContext.TEXTURE10, - EnvironmentMap = WebGLRenderingContext.TEXTURE11, - AnimationLUT = WebGLRenderingContext.TEXTURE12, + VertexLUT = Five, } /** diff --git a/core/frontend/src/render/webgl/SceneCompositor.ts b/core/frontend/src/render/webgl/SceneCompositor.ts index 063eec4..3a81f44 100644 --- a/core/frontend/src/render/webgl/SceneCompositor.ts +++ b/core/frontend/src/render/webgl/SceneCompositor.ts @@ -13,12 +13,14 @@ import { TechniqueId } from "./TechniqueId"; import { System, RenderType, DepthType } from "./System"; import { Pixel, GraphicList } from "../System"; import { ViewRect } from "../../Viewport"; -import { assert, Id64, Id64String, IDisposable, dispose } from "@bentley/bentleyjs-core"; +import { assert, Id64, IDisposable, dispose } from "@bentley/bentleyjs-core"; import { GL } from "./GL"; import { RenderCommands, ShaderProgramParams, DrawParams, DrawCommands, BatchPrimitiveCommand } from "./DrawCommand"; import { RenderState } from "./RenderState"; import { CompositeFlags, RenderPass, RenderOrder } from "./RenderFlags"; import { FloatRgba } from "./FloatRGBA"; +import { BatchState } from "./BranchState"; +import { Feature } from "@bentley/imodeljs-common"; let progParams: ShaderProgramParams | undefined; let drawParams: DrawParams | undefined; @@ -39,8 +41,7 @@ class Textures implements IDisposable { public accumulation?: TextureHandle; public revealage?: TextureHandle; public color?: TextureHandle; - public idLow?: TextureHandle; - public idHigh?: TextureHandle; + public featureId?: TextureHandle; public depthAndOrder?: TextureHandle; public hilite?: TextureHandle; @@ -48,8 +49,7 @@ class Textures implements IDisposable { this.accumulation = dispose(this.accumulation); this.revealage = dispose(this.revealage); this.color = dispose(this.color); - this.idLow = dispose(this.idLow); - this.idHigh = dispose(this.idHigh); + this.featureId = dispose(this.featureId); this.depthAndOrder = dispose(this.depthAndOrder); this.hilite = dispose(this.hilite); } @@ -76,23 +76,22 @@ class Textures implements IDisposable { } } - // NB: All 3 of these must be of the same type, because they are borrowed by pingpong and bound to the same frame buffer. - // Otherwise there would be no reason to use FLOAT for hilite texture. + // NB: Both of these must be of the same type, because they are borrowed by pingpong and bound to the same frame buffer. this.accumulation = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, pixelDataType); this.revealage = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, pixelDataType); - this.hilite = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, pixelDataType); + + // Hilite texture is a simple on-off, but the smallest texture format WebGL allows us to use as output is RGBA with a byte per component. + this.hilite = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.color = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); - this.idLow = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); - this.idHigh = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); + this.featureId = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.depthAndOrder = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); return undefined !== this.accumulation && undefined !== this.revealage && undefined !== this.color - && undefined !== this.idLow - && undefined !== this.idHigh + && undefined !== this.featureId && undefined !== this.depthAndOrder && undefined !== this.hilite; } @@ -114,7 +113,7 @@ class FrameBuffers implements IDisposable { this.opaqueColor = FrameBuffer.create([boundColor], depth); this.opaqueAndCompositeColor = FrameBuffer.create([textures.color!], depth); - this.depthAndOrder = FrameBuffer.create([textures.depthAndOrder!]); + this.depthAndOrder = FrameBuffer.create([textures.depthAndOrder!], depth); this.hilite = FrameBuffer.create([textures.hilite!]); this.hiliteUsingStencil = FrameBuffer.create([textures.hilite!], depth); if (DepthType.TextureUnsignedInt24Stencil8 === System.instance.capabilities.maxDepthType) { @@ -160,9 +159,9 @@ class Geometry implements IDisposable { class PixelBuffer implements Pixel.Buffer { private readonly _rect: ViewRect; private readonly _selector: Pixel.Selector; - private readonly _elemIdLow?: Uint32Array; - private readonly _elemIdHi?: Uint32Array; + private readonly _featureId?: Uint32Array; private readonly _depthAndOrder?: Uint32Array; + private readonly _batchState: BatchState; private get _numPixels(): number { return this._rect.width * this._rect.height; } @@ -179,17 +178,20 @@ class PixelBuffer implements Pixel.Buffer { y = this._rect.height - 1 - y; return y * this._rect.width + x; } + private getPixel32(data: Uint32Array, pixelIndex: number): number | undefined { return pixelIndex < data.length ? data[pixelIndex] : undefined; } - private getElementId(pixelIndex: number): Id64String | undefined { - if (undefined === this._elemIdLow || undefined === this._elemIdHi) + private getFeature(pixelIndex: number): Feature | undefined { + if (undefined === this._featureId) return undefined; - const lo = this.getPixel32(this._elemIdLow, pixelIndex); - const hi = this.getPixel32(this._elemIdHi, pixelIndex); - return undefined !== lo && undefined !== hi ? Id64.fromUint32Pair(lo, hi) : undefined; + const featureId = this.getPixel32(this._featureId, pixelIndex); + if (undefined === featureId) + return undefined; + + return this._batchState.getFeature(featureId); } private readonly _scratchUint32Array = new Uint32Array(1); @@ -231,56 +233,50 @@ class PixelBuffer implements Pixel.Buffer { let geometryType = px.type; let planarity = px.planarity; - const elemId = Pixel.Selector.None !== (this._selector & Pixel.Selector.ElementId) ? this.getElementId(index) : undefined; - - if (undefined !== this._depthAndOrder) { - const wantDistance = Pixel.Selector.None !== (this._selector & Pixel.Selector.Distance); - const wantGeometry = Pixel.Selector.None !== (this._selector & Pixel.Selector.Geometry); - - const depthAndOrder = wantDistance || wantGeometry ? this.getPixel32(this._depthAndOrder, index) : undefined; + const feature = Pixel.Selector.None !== (this._selector & Pixel.Selector.Feature) ? this.getFeature(index) : undefined; + if (Pixel.Selector.None !== (this._selector & Pixel.Selector.GeometryAndDistance) && undefined !== this._depthAndOrder) { + const depthAndOrder = this.getPixel32(this._depthAndOrder, index); if (undefined !== depthAndOrder) { - if (wantDistance) - distanceFraction = this.decodeDepthRgba(depthAndOrder); - - if (wantGeometry) { - const orderWithPlanarBit = this.decodeRenderOrderRgba(depthAndOrder); - const order = orderWithPlanarBit & ~RenderOrder.PlanarBit; - planarity = (orderWithPlanarBit === order) ? Pixel.Planarity.NonPlanar : Pixel.Planarity.Planar; - switch (order) { - case RenderOrder.None: - geometryType = Pixel.GeometryType.None; - planarity = Pixel.Planarity.None; - break; - case RenderOrder.BlankingRegion: - case RenderOrder.Surface: - geometryType = Pixel.GeometryType.Surface; - break; - case RenderOrder.Linear: - geometryType = Pixel.GeometryType.Linear; - break; - case RenderOrder.Edge: - geometryType = Pixel.GeometryType.Edge; - break; - case RenderOrder.Silhouette: - geometryType = Pixel.GeometryType.Silhouette; - break; - default: - // ###TODO: may run into issues with point clouds - they are not written correctly in C++. - assert(false, "Invalid render order"); - geometryType = Pixel.GeometryType.None; - planarity = Pixel.Planarity.None; - break; - } + distanceFraction = this.decodeDepthRgba(depthAndOrder); + + const orderWithPlanarBit = this.decodeRenderOrderRgba(depthAndOrder); + const order = orderWithPlanarBit & ~RenderOrder.PlanarBit; + planarity = (orderWithPlanarBit === order) ? Pixel.Planarity.NonPlanar : Pixel.Planarity.Planar; + switch (order) { + case RenderOrder.None: + geometryType = Pixel.GeometryType.None; + planarity = Pixel.Planarity.None; + break; + case RenderOrder.BlankingRegion: + case RenderOrder.Surface: + geometryType = Pixel.GeometryType.Surface; + break; + case RenderOrder.Linear: + geometryType = Pixel.GeometryType.Linear; + break; + case RenderOrder.Edge: + geometryType = Pixel.GeometryType.Edge; + break; + case RenderOrder.Silhouette: + geometryType = Pixel.GeometryType.Silhouette; + break; + default: + // ###TODO: may run into issues with point clouds - they are not written correctly in C++. + assert(false, "Invalid render order"); + geometryType = Pixel.GeometryType.None; + planarity = Pixel.Planarity.None; + break; } } } - return new Pixel.Data(elemId, distanceFraction, geometryType, planarity); + return new Pixel.Data(feature, distanceFraction, geometryType, planarity); } private constructor(rect: ViewRect, selector: Pixel.Selector, compositor: SceneCompositor) { this._rect = rect.clone(); this._selector = selector; + this._batchState = compositor.target.batchState; if (Pixel.Selector.None !== (selector & Pixel.Selector.GeometryAndDistance)) { const depthAndOrderBytes = compositor.readDepthAndOrder(rect); @@ -290,15 +286,12 @@ class PixelBuffer implements Pixel.Buffer { this._selector &= ~Pixel.Selector.GeometryAndDistance; } - if (Pixel.Selector.None !== (selector & Pixel.Selector.ElementId)) { - const loBytes = compositor.readElementIds(false, rect); - const hiBytes = undefined !== loBytes ? compositor.readElementIds(true, rect) : undefined; - if (undefined !== loBytes && undefined !== hiBytes) { - this._elemIdLow = new Uint32Array(loBytes.buffer); - this._elemIdHi = new Uint32Array(hiBytes.buffer); - } else { - this._selector &= ~Pixel.Selector.ElementId; - } + if (Pixel.Selector.None !== (selector & Pixel.Selector.Feature)) { + const features = compositor.readFeatureIds(rect); + if (undefined !== features) + this._featureId = new Uint32Array(features.buffer); + else + this._selector &= ~Pixel.Selector.Feature; } } @@ -313,19 +306,20 @@ class PixelBuffer implements Pixel.Buffer { // Orchestrates rendering of the scene on behalf of a Target. // This base class exists only so we don't have to export all the types of the shared Compositor members like Textures, FrameBuffers, etc. export abstract class SceneCompositor implements IDisposable { + public readonly target: Target; + public abstract get currentRenderTargetIndex(): number; public abstract dispose(): void; public abstract draw(_commands: RenderCommands): void; public abstract drawForReadPixels(_commands: RenderCommands, overlays?: GraphicList): void; public abstract readPixels(rect: ViewRect, selector: Pixel.Selector): Pixel.Buffer | undefined; public abstract readDepthAndOrder(rect: ViewRect): Uint8Array | undefined; - public abstract readElementIds(high: boolean, rect: ViewRect): Uint8Array | undefined; + public abstract readFeatureIds(rect: ViewRect): Uint8Array | undefined; - public abstract get elementId0(): TextureHandle; - public abstract get elementId1(): TextureHandle; + public abstract get featureIds(): TextureHandle; public abstract get depthAndOrder(): TextureHandle; - protected constructor() { } + protected constructor(target: Target) { this.target = target; } public static create(target: Target): SceneCompositor { return System.instance.capabilities.supportsDrawBuffers ? new MRTCompositor(target) : new MPCompositor(target); @@ -334,7 +328,6 @@ export abstract class SceneCompositor implements IDisposable { // The actual base class. Specializations are provided based on whether or not multiple render targets are supported. abstract class Compositor extends SceneCompositor { - protected _target: Target; protected _width: number = -1; protected _height: number = -1; protected _textures = new Textures(); @@ -362,9 +355,8 @@ abstract class Compositor extends SceneCompositor { protected abstract pingPong(): void; protected constructor(target: Target, fbos: FrameBuffers, geometry: Geometry) { - super(); + super(target); - this._target = target; this._frameBuffers = fbos; this._geom = geometry; @@ -419,7 +411,7 @@ abstract class Compositor extends SceneCompositor { } public update(): boolean { - const rect = this._target.viewRect; + const rect = this.target.viewRect; const width = rect.width; const height = rect.height; @@ -451,42 +443,42 @@ abstract class Compositor extends SceneCompositor { // Render the background this.renderBackground(commands, needComposite); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Background"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Background"); // Render the sky box this.renderSkyBox(commands, needComposite); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render SkyBox"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render SkyBox"); // Render the terrain this.renderTerrain(commands, needComposite); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Terrain"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Terrain"); // Enable clipping - this._target.pushActiveVolume(); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Enable Clipping"); + this.target.pushActiveVolume(); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Enable Clipping"); // Render opaque geometry this.renderOpaque(commands, needComposite, false); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Opaque"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Opaque"); // Render stencil volumes this.renderClassification(commands, needComposite, false); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Stencils"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Stencils"); if (needComposite) { this._geom.composite!.update(flags); this.clearTranslucent(); this.renderTranslucent(commands); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Translucent"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Translucent"); this.renderHilite(commands); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Render Hilite"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Render Hilite"); this.composite(); - if (this._target.performanceMetrics) this._target.performanceMetrics.recordTime("Composite"); + if (this.target.performanceMetrics) this.target.performanceMetrics.recordTime("Composite"); } - this._target.popActiveVolume(); + this.target.popActiveVolume(); } - public get fullHeight(): number { return this._target.viewRect.height; } + public get fullHeight(): number { return this.target.viewRect.height; } public drawForReadPixels(commands: RenderCommands, overlays?: GraphicList) { if (!this.update()) { @@ -500,10 +492,10 @@ abstract class Compositor extends SceneCompositor { // It's possible we have no pickable scene graphics or decorations, but do have pickable world overlays. const haveRenderCommands = !commands.isEmpty; if (haveRenderCommands) { - this._target.pushActiveVolume(); + this.target.pushActiveVolume(); this.renderOpaque(commands, false, true); this.renderClassification(commands, false, true); - this._target.popActiveVolume(); + this.target.popActiveVolume(); } if (undefined === overlays || 0 === overlays.length) @@ -535,8 +527,8 @@ abstract class Compositor extends SceneCompositor { public readDepthAndOrder(rect: ViewRect): Uint8Array | undefined { return this.readFrameBuffer(rect, this._frameBuffers.depthAndOrder); } - public readElementIds(high: boolean, rect: ViewRect): Uint8Array | undefined { - const tex = high ? this._textures.idHigh : this._textures.idLow; + public readFeatureIds(rect: ViewRect): Uint8Array | undefined { + const tex = this._textures.featureId; if (undefined === tex) return undefined; @@ -590,18 +582,18 @@ abstract class Compositor extends SceneCompositor { return; } - this._target.plan!.selectTerrainFrustum(); - this._target.changeFrustum(this._target.plan!); + this.target.plan!.selectTerrainFrustum(); + this.target.changeFrustum(this.target.plan!); const fbStack = System.instance.frameBufferStack; const fbo = this.getBackgroundFbo(needComposite); fbStack.execute(fbo, true, () => { System.instance.applyRenderState(this.getRenderState(RenderPass.Terrain)); - this._target.techniques.execute(this._target, cmds, RenderPass.Terrain); + this.target.techniques.execute(this.target, cmds, RenderPass.Terrain); }); - this._target.plan!.selectViewFrustum(); - this._target.changeFrustum(this._target.plan!); + this.target.plan!.selectViewFrustum(); + this.target.changeFrustum(this.target.plan!); } private renderSkyBox(commands: RenderCommands, needComposite: boolean) { @@ -613,10 +605,10 @@ abstract class Compositor extends SceneCompositor { const fbStack = System.instance.frameBufferStack; const fbo = this.getBackgroundFbo(needComposite); fbStack.execute(fbo, true, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this.getRenderState(RenderPass.SkyBox)); - this._target.techniques.execute(this._target, cmds, RenderPass.SkyBox); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmds, RenderPass.SkyBox); + this.target.popBranch(); }); } @@ -629,22 +621,22 @@ abstract class Compositor extends SceneCompositor { const fbStack = System.instance.frameBufferStack; const fbo = this.getBackgroundFbo(needComposite); fbStack.execute(fbo, true, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this.getRenderState(RenderPass.Background)); - this._target.techniques.execute(this._target, cmds, RenderPass.Background); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmds, RenderPass.Background); + this.target.popBranch(); }); } private findFlashedClassifier(cmdsByIndex: DrawCommands): number { - if (!Id64.isValid(this._target.flashedElemId)) + if (!Id64.isValid(this.target.flashedElemId)) return -1; // nothing flashed for (let i = 1; i < cmdsByIndex.length; i += 3) { const command = cmdsByIndex[i]; if (command.isPrimitiveCommand) { if (command instanceof BatchPrimitiveCommand) { const batch = command as BatchPrimitiveCommand; - if (batch.computeIsFlashed(this._target.flashedElemId)) { + if (batch.computeIsFlashed(this.target.flashedElemId)) { return (i - 1) / 3; } } @@ -656,10 +648,10 @@ abstract class Compositor extends SceneCompositor { private renderIndexedClassifier(cmdsByIndex: DrawCommands, index: number, needComposite: boolean) { // Set the stencil for the given classifier stencil volume. System.instance.frameBufferStack.execute(this._frameBuffers.stencilSet!, false, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this._stencilSetRenderState); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.Classification, index); - this._target.popBranch(); + this.target.techniques.executeForIndexedClassifier(this.target, cmdsByIndex, RenderPass.Classification, index); + this.target.popBranch(); }); // Process the stencil for the pick data. this.renderIndexedClassifierForReadPixels(cmdsByIndex, index, this._classifyPickDataRenderState, needComposite); @@ -676,12 +668,12 @@ abstract class Compositor extends SceneCompositor { System.instance.frameBufferStack.execute(this.getBackgroundFbo(needComposite), true, () => { if (1 === this._debugStencil) { System.instance.applyRenderState(this.getRenderState(RenderPass.OpaqueGeneral)); - this._target.techniques.execute(this._target, cmds, RenderPass.OpaqueGeneral); + this.target.techniques.execute(this.target, cmds, RenderPass.OpaqueGeneral); } else { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this._debugStencilRenderState); - this._target.techniques.execute(this._target, cmds, RenderPass.Classification); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmds, RenderPass.Classification); + this.target.popBranch(); } }); return; @@ -715,20 +707,20 @@ abstract class Compositor extends SceneCompositor { if (cmds.length > 0) { // Set the stencil for the given classifier stencil volume. fbStack.execute(fboSet, false, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this._stencilSetRenderState); - this._target.techniques.execute(this._target, cmdsH, RenderPass.Hilite); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmdsH, RenderPass.Hilite); + this.target.popBranch(); }); // Process the stencil volumes, blending into the current color buffer. fbStack.execute(fboCopy!, true, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); this._classifyColorRenderState.blend.color = [1.0, 1.0, 1.0, 0.2]; this._classifyColorRenderState.blend.setBlendFunc(GL.BlendFactor.ConstAlpha, GL.BlendFactor.OneMinusConstAlpha); // Mix with select/hilite color System.instance.applyRenderState(this._classifyColorRenderState); - const params = getDrawParams(this._target, this._geom.stencilCopy!); - this._target.techniques.draw(params); - this._target.popBranch(); + const params = getDrawParams(this.target, this._geom.stencilCopy!); + this.target.techniques.draw(params); + this.target.popBranch(); }); } @@ -736,19 +728,19 @@ abstract class Compositor extends SceneCompositor { if (flashedClassifier !== -1) { // Set the stencil for this one classifier. fbStack.execute(this._frameBuffers.stencilSet!, false, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this._stencilSetRenderState); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.Classification, flashedClassifier); - this._target.popBranch(); + this.target.techniques.executeForIndexedClassifier(this.target, cmdsByIndex, RenderPass.Classification, flashedClassifier); + this.target.popBranch(); }); // Process the stencil. fbStack.execute(fboCopy!, true, () => { - this._target.pushState(this._target.decorationState); - this._classifyColorRenderState.blend.color = [1.0, 1.0, 1.0, this._target.flashIntensity * 0.5]; + this.target.pushState(this.target.decorationState); + this._classifyColorRenderState.blend.color = [1.0, 1.0, 1.0, this.target.flashIntensity * 0.5]; this._classifyColorRenderState.blend.setBlendFuncSeparate(GL.BlendFactor.ConstAlpha, GL.BlendFactor.ConstAlpha, GL.BlendFactor.One, GL.BlendFactor.OneMinusConstAlpha); // want to just add flash color System.instance.applyRenderState(this._classifyColorRenderState); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.OpaquePlanar, flashedClassifier); - this._target.popBranch(); + this.target.techniques.executeForIndexedClassifier(this.target, cmdsByIndex, RenderPass.OpaquePlanar, flashedClassifier); + this.target.popBranch(); }); } @@ -756,15 +748,15 @@ abstract class Compositor extends SceneCompositor { // ###TODO: Need a way to only do this to reality mesh and point cloud data // Set the stencil using the stencil command list. fbStack.execute(fboSet!, false, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); System.instance.applyRenderState(this._stencilSetRenderState); - this._target.techniques.execute(this._target, cmds, RenderPass.Classification); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmds, RenderPass.Classification); + this.target.popBranch(); }); // Process the stencil volumes, blending into the current color buffer. fbStack.execute(fboCopy!, true, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); this._classifyColorRenderState.stencil.frontFunction.function = GL.StencilFunction.Equal; this._classifyColorRenderState.stencil.backFunction.function = GL.StencilFunction.Equal; this._classifyColorRenderState.blend.color = [1.0, 1.0, 1.0, 0.4]; @@ -772,9 +764,9 @@ abstract class Compositor extends SceneCompositor { System.instance.applyRenderState(this._classifyColorRenderState); this._classifyColorRenderState.stencil.frontFunction.function = GL.StencilFunction.NotEqual; this._classifyColorRenderState.stencil.backFunction.function = GL.StencilFunction.NotEqual; - const params = getDrawParams(this._target, this._geom.stencilCopy!); - this._target.techniques.draw(params); - this._target.popBranch(); + const params = getDrawParams(this.target, this._geom.stencilCopy!); + this.target.techniques.draw(params); + this.target.popBranch(); }); } @@ -794,26 +786,26 @@ abstract class Compositor extends SceneCompositor { } // Set the stencil for the given classifier stencil volume. system.frameBufferStack.execute(this._frameBuffers.stencilSet!, false, () => { - this._target.pushState(this._target.decorationState); + this.target.pushState(this.target.decorationState); system.applyRenderState(this._stencilSetRenderState); - this._target.techniques.execute(this._target, cmds, RenderPass.Hilite); - this._target.popBranch(); + this.target.techniques.execute(this.target, cmds, RenderPass.Hilite); + this.target.popBranch(); }); // Process the stencil for the hilite data. system.frameBufferStack.execute(this._frameBuffers.hiliteUsingStencil!, true, () => { system.applyRenderState(this._classifyPickDataRenderState); - this._target.techniques.execute(this._target, cmds, RenderPass.Hilite); + this.target.techniques.execute(this.target, cmds, RenderPass.Hilite); }); } private composite() { System.instance.applyRenderState(RenderState.defaults); - const params = getDrawParams(this._target, this._geom.composite!); - this._target.techniques.draw(params); + const params = getDrawParams(this.target, this._geom.composite!); + this.target.techniques.draw(params); } - private getRenderState(pass: RenderPass): RenderState { + protected getRenderState(pass: RenderPass): RenderState { switch (pass) { case RenderPass.OpaqueLinear: case RenderPass.OpaquePlanar: @@ -835,7 +827,7 @@ abstract class Compositor extends SceneCompositor { } System.instance.applyRenderState(this.getRenderState(pass)); - this._target.techniques.execute(this._target, cmds, pass); + this.target.techniques.execute(this.target, cmds, pass); } } @@ -858,7 +850,7 @@ class MRTFrameBuffers extends FrameBuffers { if (undefined === boundColor) return false; - const colorAndPick = [boundColor, textures.idLow!, textures.idHigh!, textures.depthAndOrder!]; + const colorAndPick = [boundColor, textures.featureId!, textures.depthAndOrder!]; this.opaqueAll = FrameBuffer.create(colorAndPick, depth); colorAndPick[0] = textures.color!; @@ -868,14 +860,14 @@ class MRTFrameBuffers extends FrameBuffers { this.translucent = FrameBuffer.create(colors, depth); this.clearTranslucent = FrameBuffer.create(colors); - // We borrow the SceneCompositor's accum, hilite, and revealage textures for the surface pass. + // We borrow the SceneCompositor's accum and revealage textures for the surface pass. // First we render edges, writing to our textures. // Then we copy our textures to borrowed textures. // Finally we render surfaces, writing to our textures and reading from borrowed textures. - const pingPong = [textures.accumulation!, textures.hilite!, textures.revealage!]; + const pingPong = [textures.accumulation!, textures.revealage!]; this.pingPong = FrameBuffer.create(pingPong); - const ids = [boundColor, textures.idLow!, textures.idHigh!]; + const ids = [boundColor, textures.featureId!]; this.idOnly = FrameBuffer.create(ids, depth); colorAndPick[0] = textures.color!; this.idOnlyAndComposite = FrameBuffer.create(ids, depth); @@ -913,7 +905,7 @@ class MRTGeometry extends Geometry { assert(undefined === this.copyPickBuffers); - this.copyPickBuffers = CopyPickBufferGeometry.createGeometry(textures.idLow!.getHandle()!, textures.idHigh!.getHandle()!, textures.depthAndOrder!.getHandle()!); + this.copyPickBuffers = CopyPickBufferGeometry.createGeometry(textures.featureId!.getHandle()!, textures.depthAndOrder!.getHandle()!); this.clearTranslucent = ViewportQuadGeometry.create(TechniqueId.OITClearTranslucent); this.clearPickAndColor = ViewportQuadGeometry.create(TechniqueId.ClearPickAndColor); @@ -940,9 +932,8 @@ class MRTCompositor extends Compositor { return 0; } - public get elementId0(): TextureHandle { return this.getSamplerTexture(this._readPickDataFromPingPong ? 0 : 1); } - public get elementId1(): TextureHandle { return this.getSamplerTexture(this._readPickDataFromPingPong ? 1 : 2); } - public get depthAndOrder(): TextureHandle { return this.getSamplerTexture(this._readPickDataFromPingPong ? 2 : 3); } + public get featureIds(): TextureHandle { return this.getSamplerTexture(this._readPickDataFromPingPong ? 0 : 1); } + public get depthAndOrder(): TextureHandle { return this.getSamplerTexture(this._readPickDataFromPingPong ? 1 : 2); } private get _fbos(): MRTFrameBuffers { return this._frameBuffers as MRTFrameBuffers; } private get _geometry(): MRTGeometry { return this._geom as MRTGeometry; } @@ -955,8 +946,8 @@ class MRTCompositor extends Compositor { // (0,0,0,0) in elementID0 and ElementID1 buffers indicates invalid element id // (0,0,0,0) in DepthAndOrder buffer indicates render order 0 and encoded depth of 0 (= far plane) system.applyRenderState(this._noDepthMaskRenderState); - const params = getDrawParams(this._target, this._geometry.clearPickAndColor!); - this._target.techniques.draw(params); + const params = getDrawParams(this.target, this._geometry.clearPickAndColor!); + this.target.techniques.draw(params); // Clear depth buffer system.applyRenderState(RenderState.defaults); // depthMask == true. @@ -994,7 +985,7 @@ class MRTCompositor extends Compositor { const fbStack = System.instance.frameBufferStack; fbStack.execute(needComposite ? this._fbos.idOnlyAndComposite! : this._fbos.idOnly!, true, () => { System.instance.applyRenderState(state); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.OpaqueGeneral, index); + this.target.techniques.executeForIndexedClassifier(this.target, cmdsByIndex, RenderPass.OpaqueGeneral, index); }); this._readPickDataFromPingPong = false; } @@ -1002,8 +993,8 @@ class MRTCompositor extends Compositor { protected clearTranslucent() { System.instance.applyRenderState(this._noDepthMaskRenderState); System.instance.frameBufferStack.execute(this._fbos.clearTranslucent!, true, () => { - const params = getDrawParams(this._target, this._geometry.clearTranslucent!); - this._target.techniques.draw(params); + const params = getDrawParams(this.target, this._geometry.clearTranslucent!); + this.target.techniques.draw(params); }); } @@ -1016,8 +1007,8 @@ class MRTCompositor extends Compositor { protected pingPong() { System.instance.applyRenderState(this._noDepthMaskRenderState); System.instance.frameBufferStack.execute(this._fbos.pingPong!, true, () => { - const params = getDrawParams(this._target, this._geometry.copyPickBuffers!); - this._target.techniques.draw(params); + const params = getDrawParams(this.target, this._geometry.copyPickBuffers!); + this.target.techniques.draw(params); }); } @@ -1030,10 +1021,8 @@ class MRTCompositor extends Compositor { class MPFrameBuffers extends FrameBuffers { public accumulation?: FrameBuffer; public revealage?: FrameBuffer; - public idLow?: FrameBuffer; - public idHigh?: FrameBuffer; - public idLowWithDepth?: FrameBuffer; - public idHighWithDepth?: FrameBuffer; + public featureId?: FrameBuffer; + public featureIdWithDepth?: FrameBuffer; public init(textures: Textures, depth: DepthBuffer): boolean { if (!super.init(textures, depth)) @@ -1043,13 +1032,10 @@ class MPFrameBuffers extends FrameBuffers { this.accumulation = FrameBuffer.create([textures.accumulation!], depth); this.revealage = FrameBuffer.create([textures.revealage!], depth); - this.idLow = FrameBuffer.create([textures.idLow!]); - this.idHigh = FrameBuffer.create([textures.idHigh!]); - this.idLowWithDepth = FrameBuffer.create([textures.idLow!], depth); - this.idHighWithDepth = FrameBuffer.create([textures.idHigh!], depth); + this.featureId = FrameBuffer.create([textures.featureId!], depth); + this.featureIdWithDepth = FrameBuffer.create([textures.featureId!], depth); - return undefined !== this.accumulation && undefined !== this.revealage && undefined !== this.idLow - && undefined !== this.idHigh && undefined !== this.idLowWithDepth && undefined !== this.idHighWithDepth; + return undefined !== this.accumulation && undefined !== this.revealage && undefined !== this.featureId && undefined !== this.featureIdWithDepth; } public dispose(): void { @@ -1057,10 +1043,8 @@ class MPFrameBuffers extends FrameBuffers { this.accumulation = dispose(this.accumulation); this.revealage = dispose(this.revealage); - this.idLow = dispose(this.idLow); - this.idHigh = dispose(this.idHigh); - this.idLowWithDepth = dispose(this.idLowWithDepth); - this.idHighWithDepth = dispose(this.idHighWithDepth); + this.featureId = dispose(this.featureId); + this.featureIdWithDepth = dispose(this.featureIdWithDepth); } } @@ -1072,7 +1056,7 @@ class MPGeometry extends Geometry { return false; assert(undefined === this.copyColor); - this.copyColor = SingleTexturedViewportQuadGeometry.createGeometry(textures.idLow!.getHandle()!, TechniqueId.CopyColor); + this.copyColor = SingleTexturedViewportQuadGeometry.createGeometry(textures.featureId!.getHandle()!, TechniqueId.CopyColor); return undefined !== this.copyColor; } @@ -1087,27 +1071,42 @@ class MPGeometry extends Geometry { // The chief use case is iOS. class MPCompositor extends Compositor { private _currentRenderTargetIndex: number = 0; + private readonly _opaqueRenderStateWithEqualDepthFunc = new RenderState(); public constructor(target: Target) { super(target, new MPFrameBuffers(), new MPGeometry()); + + this._opaqueRenderStateWithEqualDepthFunc.flags.depthTest = true; + this._opaqueRenderStateWithEqualDepthFunc.depthFunc = GL.DepthFunc.LessOrEqual; + } + + protected getRenderState(pass: RenderPass): RenderState { + if (this._currentRenderTargetIndex > 0) { + switch (pass) { + case RenderPass.OpaqueLinear: + case RenderPass.OpaquePlanar: + case RenderPass.OpaqueGeneral: + return this._opaqueRenderStateWithEqualDepthFunc; + } + } + + return super.getRenderState(pass); } private get _fbos(): MPFrameBuffers { return this._frameBuffers as MPFrameBuffers; } private get _geometry(): MPGeometry { return this._geom as MPGeometry; } public get currentRenderTargetIndex(): number { return this._currentRenderTargetIndex; } - public get elementId0(): TextureHandle { return this._readPickDataFromPingPong ? this._textures.accumulation! : this._textures.idLow!; } - public get elementId1(): TextureHandle { return this._readPickDataFromPingPong ? this._textures.hilite! : this._textures.idHigh!; } + public get featureIds(): TextureHandle { return this._readPickDataFromPingPong ? this._textures.accumulation! : this._textures.featureId!; } public get depthAndOrder(): TextureHandle { return this._readPickDataFromPingPong ? this._textures.revealage! : this._textures.depthAndOrder!; } protected getBackgroundFbo(needComposite: boolean): FrameBuffer { return needComposite ? this._fbos.opaqueAndCompositeColor! : this._fbos.opaqueColor!; } protected clearOpaque(needComposite: boolean): void { - const bg = FloatRgba.fromColorDef(this._target.bgColor); + const bg = FloatRgba.fromColorDef(this.target.bgColor); this.clearFbo(needComposite ? this._fbos.opaqueAndCompositeColor! : this._fbos.opaqueColor!, bg.red, bg.green, bg.blue, bg.alpha, true); this.clearFbo(this._fbos.depthAndOrder!, 0, 0, 0, 0, true); - this.clearFbo(this._fbos.idLow!, 0, 0, 0, 0, true); - this.clearFbo(this._fbos.idHigh!, 0, 0, 0, 0, true); + this.clearFbo(this._fbos.featureId!, 0, 0, 0, 0, true); } protected renderOpaque(commands: RenderCommands, needComposite: boolean, renderForReadPixels: boolean): void { @@ -1135,16 +1134,10 @@ class MPCompositor extends Compositor { this._readPickDataFromPingPong = true; const stack = System.instance.frameBufferStack; this._currentRenderTargetIndex = 1; - stack.execute(this._fbos.idLowWithDepth!, true, () => { - state.stencil.backOperation.zPass = GL.StencilOperation.Keep; // don't clear the stencil yet so we can do another pass. - System.instance.applyRenderState(state); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.OpaqueGeneral, index); - }); - this._currentRenderTargetIndex = 2; - stack.execute(this._fbos.idHighWithDepth!, true, () => { - state.stencil.backOperation.zPass = GL.StencilOperation.Zero; // clear the stencil this time before the next classifier is drawn. + stack.execute(this._fbos.featureIdWithDepth!, true, () => { + state.stencil.backOperation.zPass = GL.StencilOperation.Zero; System.instance.applyRenderState(state); - this._target.techniques.executeForIndexedClassifier(this._target, cmdsByIndex, RenderPass.OpaqueGeneral, index); + this.target.techniques.executeForIndexedClassifier(this.target, cmdsByIndex, RenderPass.OpaqueGeneral, index); }); this._currentRenderTargetIndex = 0; this._readPickDataFromPingPong = false; @@ -1155,9 +1148,7 @@ class MPCompositor extends Compositor { const stack = System.instance.frameBufferStack; stack.execute(colorFbo, true, () => this.drawPass(commands, pass, pingPong)); this._currentRenderTargetIndex++; - stack.execute(this._fbos.idLow!, true, () => this.drawPass(commands, pass, false)); - this._currentRenderTargetIndex++; - stack.execute(this._fbos.idHigh!, true, () => this.drawPass(commands, pass, false)); + stack.execute(this._fbos.featureId!, true, () => this.drawPass(commands, pass, false)); this._currentRenderTargetIndex++; stack.execute(this._fbos.depthAndOrder!, true, () => this.drawPass(commands, pass, false)); this._currentRenderTargetIndex = 0; @@ -1184,17 +1175,16 @@ class MPCompositor extends Compositor { protected pingPong() { System.instance.applyRenderState(this._noDepthMaskRenderState); - this.copyFbo(this._textures.idLow!, this._fbos.accumulation!); - this.copyFbo(this._textures.idHigh!, this._fbos.revealage!); - this.copyFbo(this._textures.depthAndOrder!, this._fbos.hilite!); + this.copyFbo(this._textures.featureId!, this._fbos.accumulation!); + this.copyFbo(this._textures.depthAndOrder!, this._fbos.revealage!); } private copyFbo(src: TextureHandle, dst: FrameBuffer): void { const geom = this._geometry.copyColor!; geom.texture = src.getHandle()!; System.instance.frameBufferStack.execute(dst, true, () => { - const params = getDrawParams(this._target, geom); - this._target.techniques.draw(params); + const params = getDrawParams(this.target, geom); + this.target.techniques.draw(params); }); } diff --git a/core/frontend/src/render/webgl/ShaderBuilder.ts b/core/frontend/src/render/webgl/ShaderBuilder.ts index 03dfb86..285c0d1 100644 --- a/core/frontend/src/render/webgl/ShaderBuilder.ts +++ b/core/frontend/src/render/webgl/ShaderBuilder.ts @@ -420,7 +420,6 @@ export class ShaderBuilder extends ShaderVariables { src.addline("#define FragColor0 gl_FragData[0]"); src.addline("#define FragColor1 gl_FragData[1]"); src.addline("#define FragColor2 gl_FragData[2]"); - src.addline("#define FragColor3 gl_FragData[3]"); } if (isLit) { @@ -464,9 +463,6 @@ export const enum VertexShaderComponent { // (Required) Return this vertex's position in clip space. // vec4 computePosition(vec4 rawPos) ComputePosition, - // (Optional) Add the element id to the vertex shader. - // void computeElementId() - AddComputeElementId, // (Optional) After all output (varying) values have been computed, return true if this vertex should be discarded. // bool checkForLateDiscard() CheckForLateDiscard, @@ -518,7 +514,7 @@ export class VertexShaderBuilder extends ShaderBuilder { main.addline(" vec4 rawPosition = unquantizeVertexPosition(a_pos, u_qOrigin, u_qScale);"); if (this._isAnimated) - main.addline(" rawPosition.xyz += computeAnimationDisplacement(u_animDispParams.x, u_animDispParams.y, u_animDispParams.z, u_qAnimDispOrigin, u_qAnimDispScale);"); + main.addline(" rawPosition.xyz += computeAnimationDisplacement(g_vertexLUTIndex, u_animDispParams.x, u_animDispParams.y, u_animDispParams.z, u_qAnimDispOrigin, u_qAnimDispScale);"); const checkForEarlyDiscard = this.get(VertexShaderComponent.CheckForEarlyDiscard); if (undefined !== checkForEarlyDiscard) { @@ -538,11 +534,6 @@ export class VertexShaderBuilder extends ShaderBuilder { main.add(GLSLVertex.discard); } - const compElemId = this.get(VertexShaderComponent.AddComputeElementId); - if (undefined !== compElemId) { - main.addline(" computeElementId();"); - } - main.addline(" gl_Position = computePosition(rawPosition);"); for (const comp of this._computedVarying) { diff --git a/core/frontend/src/render/webgl/ShaderProgram.ts b/core/frontend/src/render/webgl/ShaderProgram.ts index ee8971e..aac2151 100644 --- a/core/frontend/src/render/webgl/ShaderProgram.ts +++ b/core/frontend/src/render/webgl/ShaderProgram.ts @@ -12,7 +12,7 @@ import { Target } from "./Target"; import { RenderPass } from "./RenderFlags"; import { TechniqueFlags } from "./TechniqueFlags"; import { System } from "./System"; -import { Branch } from "./Graphic"; +import { Branch, Batch } from "./Graphic"; /** Flags which control some conditional branches in shader code */ export const enum ShaderFlags { @@ -266,9 +266,10 @@ export class ShaderProgram implements IDisposable { uniform.bind(params); } - for (const attribute of this._attributes) { + for (const attribute of this._attributes) attribute.bind(params); - } + + System.instance.updateVertexAttribArrays(); params.geometry.draw(); } @@ -349,6 +350,8 @@ export class ShaderProgramExecutor { public pushBranch(branch: Branch): void { this.target.pushBranch(this, branch); } public popBranch(): void { this.target.popBranch(); } + public pushBatch(batch: Batch): void { this.target.pushBatch(batch); } + public popBatch(): void { this.target.popBatch(); } private changeProgram(program?: ShaderProgram): boolean { if (this._program === program) { diff --git a/core/frontend/src/render/webgl/System.ts b/core/frontend/src/render/webgl/System.ts index 2de1023..3823554 100644 --- a/core/frontend/src/render/webgl/System.ts +++ b/core/frontend/src/render/webgl/System.ts @@ -428,6 +428,8 @@ export class System extends RenderSystem { private readonly _drawBuffersExtension?: WEBGL_draw_buffers; private readonly _textureStats?: TextureStats; private readonly _textureBindings: TextureBinding[] = []; + private readonly _curVertexAttribStates: boolean[] = [ false, false, false, false ]; + private readonly _nextVertexAttribStates: boolean[] = [ false, false, false, false ]; // The following are initialized immediately after the System is constructed. private _lineCodeTexture?: TextureHandle; @@ -789,4 +791,28 @@ export class System extends RenderSystem { } } } + + // System keeps track of current enabled state of vertex attribute arrays. + // This prevents errors caused by leaving a vertex attrib array enabled after disposing of the buffer bound to it; + // also prevents unnecessarily 'updating' the enabled state of a vertex attrib array when it hasn't actually changed. + public enableVertexAttribArray(id: number): void { this._nextVertexAttribStates[id] = true; } + public updateVertexAttribArrays(): void { + const cur = this._curVertexAttribStates; + const next = this._nextVertexAttribStates; + const context = this.context; + for (let i = 0; i < next.length; i++) { + const wasEnabled = cur[i]; + const nowEnabled = next[i]; + if (wasEnabled !== nowEnabled) { + if (wasEnabled) + context.disableVertexAttribArray(i); + else + context.enableVertexAttribArray(i); + + cur[i] = nowEnabled; + } + + next[i] = false; + } + } } diff --git a/core/frontend/src/render/webgl/Target.ts b/core/frontend/src/render/webgl/Target.ts index e3438bb..9a35888 100644 --- a/core/frontend/src/render/webgl/Target.ts +++ b/core/frontend/src/render/webgl/Target.ts @@ -12,9 +12,9 @@ import { FeatureSymbology } from "../FeatureSymbology"; import { Techniques } from "./Technique"; import { TechniqueId } from "./TechniqueId"; import { System } from "./System"; -import { BranchStack, BranchState } from "./BranchState"; +import { BranchStack, BranchState, BatchState } from "./BranchState"; import { ShaderFlags, ShaderProgramExecutor } from "./ShaderProgram"; -import { Branch, WorldDecorations, FeatureOverrides, PickTable, Batch } from "./Graphic"; +import { Branch, WorldDecorations, FeatureOverrides, Batch } from "./Graphic"; import { EdgeOverrides } from "./EdgeOverrides"; import { ViewRect } from "../../Viewport"; import { RenderCommands, DrawParams, ShaderProgramParams } from "./DrawCommand"; @@ -30,7 +30,6 @@ import { ShaderLights } from "./Lighting"; import { Pixel } from "../System"; import { ClipDef } from "./TechniqueFlags"; import { ClipMaskVolume, ClipPlanesVolume } from "./ClipVolume"; -import { AttributeHandle } from "./Handle"; export const enum FrustumUniformType { TwoDee, @@ -165,9 +164,16 @@ export class PerformanceMetrics { } } +function swapImageByte(image: ImageBuffer, i0: number, i1: number) { + const tmp = image.data[i0]; + image.data[i0] = image.data[i1]; + image.data[i1] = tmp; +} + export abstract class Target extends RenderTarget { protected _decorations?: Decorations; private _stack = new BranchStack(); + private _batchState = new BatchState(); private _scene: GraphicList = []; private _terrain: GraphicList = []; private _dynamics?: GraphicList; @@ -210,13 +216,12 @@ export abstract class Target extends RenderTarget { public analysisStyle?: AnalysisStyle; public analysisTexture?: RenderTexture; private _currentOverrides?: FeatureOverrides; - public currentPickTable?: PickTable; private _batches: Batch[] = []; public plan?: RenderPlan; protected constructor(rect?: ViewRect) { super(); - this._renderCommands = new RenderCommands(this, this._stack); + this._renderCommands = new RenderCommands(this, this._stack, this._batchState); this._overlayRenderState = new RenderState(); this._overlayRenderState.flags.depthMask = false; this._overlayRenderState.flags.blend = true; @@ -243,7 +248,6 @@ export abstract class Target extends RenderTarget { public get flashIntensity(): number { return this._flashIntensity; } public get overridesUpdateTime(): BeTimePoint { return this._overridesUpdateTime; } - public get areDecorationOverridesActive(): boolean { return false; } // ###TODO public get fStop(): number { return this._fStop; } public get ambientLight(): Float32Array { return this._ambientLight; } @@ -340,6 +344,17 @@ export abstract class Target extends RenderTarget { this._activeClipVolume.pop(this); } + public get batchState(): BatchState { return this._batchState; } + public get currentBatchId(): number { return this._batchState.currentBatchId; } + public pushBatch(batch: Batch) { + this._batchState.push(batch, false); + this.currentOverrides = batch.getOverrides(this); + } + public popBatch() { + this.currentOverrides = undefined; + this._batchState.pop(); + } + public addBatch(batch: Batch) { assert(this._batches.indexOf(batch) < 0); this._batches.push(batch); @@ -362,7 +377,6 @@ export abstract class Target extends RenderTarget { public changeDecorations(decs: Decorations): void { this._decorations = dispose(this._decorations); this._decorations = decs; - AttributeHandle.disableAll(); } public changeScene(scene: GraphicList, _activeVolume: ClipPlanesVolume | ClipMaskVolume) { this._scene = scene; @@ -712,6 +726,9 @@ export abstract class Target extends RenderTarget { if (this.performanceMetrics) this.performanceMetrics.recordTime("Overlay Draws"); } + // Reset the batch IDs in all batches drawn for this call. + this._batchState.reset(); + this._endPaint(); if (this.performanceMetrics) this.performanceMetrics.recordTime("End Paint"); @@ -771,13 +788,15 @@ export abstract class Target extends RenderTarget { return this._dcAssigned; } - public readPixels(rect: ViewRect, selector: Pixel.Selector): Pixel.Buffer | undefined { + public readPixels(rect: ViewRect, selector: Pixel.Selector, receiver: Pixel.Receiver): void { // We can't reuse the previous frame's data for a variety of reasons, chief among them that some types of geometry (surfaces, translucent stuff) don't write // to the pick buffers and others we don't want - such as non-pickable decorations - do. // Render to an offscreen buffer so that we don't destroy the current color buffer. const texture = TextureHandle.createForAttachment(rect.width, rect.height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); - if (undefined === texture) - return undefined; + if (undefined === texture) { + receiver(undefined); + return; + } let result: Pixel.Buffer | undefined; const fbo = FrameBuffer.create([texture]); @@ -790,7 +809,10 @@ export abstract class Target extends RenderTarget { } dispose(texture); - return result; + receiver(result); + + // Reset the batch IDs in all batches drawn for this call. + this._batchState.reset(); } private readonly _scratchTmpFrustum = new Frustum(); @@ -816,7 +838,6 @@ export abstract class Target extends RenderTarget { this.pushState(state); // Create a culling frustum based on the input rect. - // NB: C++ BSIRect => TypeScript ViewRect... const viewRect = this.viewRect; const leftScale = (rect.left - viewRect.left) / (viewRect.right - viewRect.left); const rightScale = (viewRect.right - rect.right) / (viewRect.right - viewRect.left); @@ -845,7 +866,6 @@ export abstract class Target extends RenderTarget { interpolateFrustumPoint(rectFrust, tmpFrust, Npc._111, topScale, Npc._101); // Repopulate the command list, omitting non-pickable decorations and putting transparent stuff into the opaque passes. - // ###TODO: Handle pickable decorations. this._renderCommands.clear(); this._renderCommands.setCheckRange(rectFrust); this._renderCommands.init(this._scene, this._terrain, this._decorations, this._dynamics, true); @@ -896,7 +916,7 @@ export abstract class Target extends RenderTarget { return true; } - public readImage(wantRectIn: ViewRect, targetSizeIn: Point2d): ImageBuffer | undefined { + public readImage(wantRectIn: ViewRect, targetSizeIn: Point2d, flipVertically: boolean): ImageBuffer | undefined { // Determine capture rect and validate const actualViewRect = this.renderRect; @@ -957,6 +977,24 @@ export abstract class Target extends RenderTarget { } if (isEmptyImage) return undefined; + + if (flipVertically) { + const halfHeight = Math.floor(image.height / 2); + const numBytesPerRow = image.width * 4; + for (let loY = 0; loY < halfHeight; loY++) { + for (let x = 0; x < image.width; x++) { + const hiY = (image.height - 1) - loY; + const loIdx = loY * numBytesPerRow + x * 4; + const hiIdx = hiY * numBytesPerRow + x * 4; + + swapImageByte(image, loIdx, hiIdx); + swapImageByte(image, loIdx + 1, hiIdx + 1); + swapImageByte(image, loIdx + 2, hiIdx + 2); + swapImageByte(image, loIdx + 3, hiIdx + 3); + } + } + } + return image; } diff --git a/core/frontend/src/render/webgl/Technique.ts b/core/frontend/src/render/webgl/Technique.ts index 235179e..75ca95e 100644 --- a/core/frontend/src/render/webgl/Technique.ts +++ b/core/frontend/src/render/webgl/Technique.ts @@ -8,7 +8,7 @@ import { assert, using, IDisposable, dispose } from "@bentley/bentleyjs-core"; import { ShaderProgram, ShaderProgramExecutor } from "./ShaderProgram"; import { TechniqueId } from "./TechniqueId"; import { TechniqueFlags, FeatureMode, ClipDef } from "./TechniqueFlags"; -import { ProgramBuilder, VertexShaderComponent, FragmentShaderComponent, ClippingShaders } from "./ShaderBuilder"; +import { ProgramBuilder, FragmentShaderComponent, ClippingShaders } from "./ShaderBuilder"; import { DrawParams, DrawCommands } from "./DrawCommand"; import { Target } from "./Target"; import { RenderPass, CompositeFlags } from "./RenderFlags"; @@ -24,7 +24,7 @@ import { addMonochrome } from "./glsl/Monochrome"; import { createSurfaceBuilder, createSurfaceHiliter, addMaterial, addSurfaceDiscardByAlpha } from "./glsl/Surface"; import { createPointStringBuilder, createPointStringHiliter } from "./glsl/PointString"; import { createPointCloudBuilder, createPointCloudHiliter } from "./glsl/PointCloud"; -import { addElementId, addFeatureSymbology, addRenderOrder, computeElementId, computeUniformElementId, FeatureSymbologyOptions } from "./glsl/FeatureSymbology"; +import { addFeatureId, addFeatureSymbology, addUniformFeatureSymbology, addRenderOrder, FeatureSymbologyOptions } from "./glsl/FeatureSymbology"; import { GLSLFragment, addPickBufferOutputs } from "./glsl/Fragment"; import { addFrustum, addEyeSpace } from "./glsl/Common"; import { addModelViewMatrix } from "./glsl/Vertex"; @@ -127,18 +127,17 @@ export abstract class VariedTechnique implements Technique { this.addShader(builder, flags, gl); } - protected addElementId(builder: ProgramBuilder, feat: FeatureMode, alwaysUniform: boolean = false) { + protected addFeatureId(builder: ProgramBuilder, feat: FeatureMode) { const frag = builder.frag; if (FeatureMode.None === feat) frag.set(FragmentShaderComponent.AssignFragData, GLSLFragment.assignFragColor); else { const vert = builder.vert; - vert.set(VertexShaderComponent.AddComputeElementId, alwaysUniform ? computeUniformElementId : computeElementId); addFrustum(builder); addEyeSpace(builder); addModelViewMatrix(vert); addRenderOrder(frag); - addElementId(builder, alwaysUniform); + addFeatureId(builder); addPickBufferOutputs(frag); } } @@ -239,7 +238,7 @@ class PolylineTechnique extends VariedTechnique { this.addTranslucentShader(builderTrans, flags, gl); addFeatureSymbology(builder, featureMode, FeatureSymbologyOptions.None); } - this.addElementId(builder, featureMode); + this.addFeatureId(builder, featureMode); flags.reset(featureMode); this.addShader(builder, flags, gl); } @@ -289,7 +288,7 @@ class EdgeTechnique extends VariedTechnique { this.addTranslucentShader(builderTrans, flags, gl); addFeatureSymbology(builder, featureMode, FeatureSymbologyOptions.None); } - this.addElementId(builder, featureMode); + this.addFeatureId(builder, featureMode); flags.reset(featureMode); this.addShader(builder, flags, gl); } @@ -335,7 +334,7 @@ class PointStringTechnique extends VariedTechnique { this.addTranslucentShader(builderTrans, flags, gl); addFeatureSymbology(builder, featureMode, FeatureSymbologyOptions.None); } - this.addElementId(builder, featureMode); + this.addFeatureId(builder, featureMode); flags.reset(featureMode); this.addShader(builder, flags, gl); } @@ -356,19 +355,23 @@ class PointStringTechnique extends VariedTechnique { } class PointCloudTechnique extends VariedTechnique { - private static readonly _kHilite = numFeatureVariants(1); + private static readonly _kHilite = 1; + private static readonly _kFeature = 2; public constructor(gl: WebGLRenderingContext) { - super(numFeatureVariants(1) + numHiliteVariants); + super(3); - const flags = scratchTechniqueFlags; this.addHiliteShader(gl, createPointCloudHiliter); - for (const feature of featureModes) { - flags.reset(feature); + + const flags = scratchTechniqueFlags; + const pointCloudFeatureModes = [ FeatureMode.None, FeatureMode.Overrides ]; + for (const featureMode of pointCloudFeatureModes) { + flags.reset(featureMode); const builder = createPointCloudBuilder(); - const opts = FeatureMode.Overrides === feature ? FeatureSymbologyOptions.PointCloud : FeatureSymbologyOptions.None; - addFeatureSymbology(builder, feature, opts, true); - this.addElementId(builder, feature, true); + if (FeatureMode.Overrides === featureMode) + addUniformFeatureSymbology(builder); + + this.addFeatureId(builder, featureMode); this.addShader(builder, flags, gl); } } @@ -376,13 +379,12 @@ class PointCloudTechnique extends VariedTechnique { protected get _debugDescription() { return "PointCloud"; } public computeShaderIndex(flags: TechniqueFlags): number { - let index: number; if (flags.isHilite) - index = PointCloudTechnique._kHilite; + return PointCloudTechnique._kHilite; + else if (FeatureMode.None !== flags.featureMode) + return PointCloudTechnique._kFeature; else - index = flags.featureMode; - - return index; + return 0; } } diff --git a/core/frontend/src/render/webgl/TechniqueFlags.ts b/core/frontend/src/render/webgl/TechniqueFlags.ts index e27d105..58e9f97 100644 --- a/core/frontend/src/render/webgl/TechniqueFlags.ts +++ b/core/frontend/src/render/webgl/TechniqueFlags.ts @@ -54,7 +54,7 @@ export class TechniqueFlags { if (undefined !== target.currentOverrides) { this.featureMode = FeatureMode.Overrides; - } else if (undefined !== target.currentPickTable) { + } else if (0 !== target.currentBatchId) { this.featureMode = FeatureMode.Pick; } else { this.featureMode = FeatureMode.None; diff --git a/core/frontend/src/render/webgl/glsl/ClearPickAndColor.ts b/core/frontend/src/render/webgl/glsl/ClearPickAndColor.ts index c16e2be..6fc1e2c 100644 --- a/core/frontend/src/render/webgl/glsl/ClearPickAndColor.ts +++ b/core/frontend/src/render/webgl/glsl/ClearPickAndColor.ts @@ -16,7 +16,6 @@ const assignFragData = ` FragColor0 = baseColor; FragColor1 = vec4(0.0); FragColor2 = vec4(0.0); - FragColor3 = vec4(0.0); `; export function createClearPickAndColorProgram(context: WebGLRenderingContext): ShaderProgram { diff --git a/core/frontend/src/render/webgl/glsl/Clipping.ts b/core/frontend/src/render/webgl/glsl/Clipping.ts index 1823830..3190976 100644 --- a/core/frontend/src/render/webgl/glsl/Clipping.ts +++ b/core/frontend/src/render/webgl/glsl/Clipping.ts @@ -90,18 +90,14 @@ const applyClipPlanes = ` if (plane.xyz == vec3(0.0)) // indicates start of new clip plane set { numPlaneSets = numPlaneSets + 1; - if (clippedByCurrentPlaneSet) - numSetsClippedBy = numSetsClippedBy + 1; - + numSetsClippedBy += int(clippedByCurrentPlaneSet); clippedByCurrentPlaneSet = false; } else if (!clippedByCurrentPlaneSet && calcClipPlaneDist(v_eyeSpace, plane, u_viewMatrix) < 0.0) clippedByCurrentPlaneSet = true; } - if (clippedByCurrentPlaneSet) - numSetsClippedBy = numSetsClippedBy + 1; - + numSetsClippedBy += int(clippedByCurrentPlaneSet); if (numSetsClippedBy == numPlaneSets) discard; `; diff --git a/core/frontend/src/render/webgl/glsl/Color.ts b/core/frontend/src/render/webgl/glsl/Color.ts index d67b12a..db5ad0f 100644 --- a/core/frontend/src/render/webgl/glsl/Color.ts +++ b/core/frontend/src/render/webgl/glsl/Color.ts @@ -15,22 +15,19 @@ import { addRenderPass } from "./RenderPass"; // Vertex const computeElementColor = ` - vec4 color = u_color; - if (isShaderBitSet(kShaderBit_NonUniformColor)) { - // Color table is appended to vertex data. Compute the index of the vertex one-past-the-end of the vertex data - float colorTableStart = u_vertParams.z * u_vertParams.w; // num rgba per-vertex times num vertices - float colorIndex = decodeUInt16(g_vertexData2); - vec2 tc = computeLUTCoords(colorTableStart+colorIndex, u_vertParams.xy, g_vert_center, 1.0); - color = TEXTURE(u_vertLUT, tc); - } + // Color table is appended to vertex data. Compute the index of the vertex one-past-the-end of the vertex data + float colorTableStart = u_vertParams.z * u_vertParams.w; // num rgba per-vertex times num vertices + float colorIndex = decodeUInt16(g_vertexData2); + vec2 tc = computeLUTCoords(colorTableStart+colorIndex, u_vertParams.xy, g_vert_center, 1.0); + vec4 color = mix(u_color, TEXTURE(u_vertLUT, tc), extractShaderBit(kShaderBit_NonUniformColor)); `; const computeBaseAlpha = ` g_baseAlpha = color.a; `; const adjustAndReturnColor = ` - if (kRenderPass_OpaqueLinear <= u_renderPass && kRenderPass_OpaqueGeneral >= u_renderPass) - color = adjustPreMultipliedAlpha(color, 1.0); - + // If in opaque pass, un-premultiply any alpha + float inOpaquePass = float(kRenderPass_OpaqueLinear <= u_renderPass && kRenderPass_OpaqueGeneral >= u_renderPass); + color = mix(color, adjustPreMultipliedAlpha(color, 1.0), inOpaquePass); return color; `; diff --git a/core/frontend/src/render/webgl/glsl/Common.ts b/core/frontend/src/render/webgl/glsl/Common.ts index f445d57..1246255 100644 --- a/core/frontend/src/render/webgl/glsl/Common.ts +++ b/core/frontend/src/render/webgl/glsl/Common.ts @@ -13,8 +13,11 @@ import { System, RenderType } from "../System"; import { assert } from "@bentley/bentleyjs-core"; import { SurfaceGeometry } from "../Surface"; +const extractShaderBit = ` + float extractShaderBit(float flag) { return extractNthBit(floor(u_shaderFlags + 0.5), flag); } +`; const isShaderBitSet = ` -bool isShaderBitSet(float flag) { return 0.0 != extractNthBit(floor(u_shaderFlags + 0.5), flag); } +bool isShaderBitSet(float flag) { return 0.0 != extractShaderBit(flag); } `; function addShaderFlagsLookup(shader: ShaderBuilder) { @@ -24,6 +27,7 @@ function addShaderFlagsLookup(shader: ShaderBuilder) { shader.addConstant("kShaderBit_OITScaleOutput", VariableType.Float, "3.0"); shader.addFunction(GLSLCommon.extractNthBit); + shader.addFunction(extractShaderBit); shader.addFunction(isShaderBitSet); } @@ -92,6 +96,17 @@ export function addEyeSpace(builder: ProgramBuilder) { builder.addInlineComputedVarying("v_eyeSpace", VariableType.Vec4, computeEyeSpace); } +export const addUInt32s = ` + vec4 addUInt32s(vec4 a, vec4 b) + { + vec4 c = a + b; + if (c.x > 255.0) { c.x -= 256.0; c.y += 1.0; } + if (c.y > 255.0) { c.y -= 256.0; c.z += 1.0; } + if (c.z > 255.0) { c.z -= 256.0; c.w += 1.0; } + return c; + } +`; + export namespace GLSLCommon { // Expects flags in range [0...256] with no fraction; and bit is [0..31] with no fraction. // Returns 1.0 if the nth bit is set, 0.0 otherwise. @@ -104,9 +119,4 @@ float extractNthBit(float flags, float n) { return floor(fract(flags/denom)*2.0); } `; - - // TFS#794899 and related...float values of 0.0 or 1.0 used to indicate false/true apparently do not - // get precisely preserved when interpolated to fragment shader despite all triangles producing - // same values. Use this to work around it and make it clear what the code is actually doing. - export const floatToBool = "\nbool floatToBool(float f) { return floor(f + 0.5) > 0.0; }\n"; } diff --git a/core/frontend/src/render/webgl/glsl/Composite.ts b/core/frontend/src/render/webgl/glsl/Composite.ts index 727c869..00493c4 100644 --- a/core/frontend/src/render/webgl/glsl/Composite.ts +++ b/core/frontend/src/render/webgl/glsl/Composite.ts @@ -45,20 +45,15 @@ bool isOutlined() { } `; -const isInHiliteRegion = "\nbool isInHiliteRegion() { return 0.0 != TEXTURE(u_hilite, v_texCoord).r; }\n"; - const computeHiliteColor = "\nvec4 computeColor() { return TEXTURE(u_opaque, v_texCoord); }\n"; const computeHiliteBaseColor = ` - bool isHilite = isInHiliteRegion(); - if (isHilite || !isOutlined()) { - float ratio = isHilite ? u_hilite_settings.y : 0.0; - vec4 baseColor = computeColor(); - baseColor.rgb = mix(baseColor.rgb, u_hilite_color.rgb, ratio); - return baseColor; - } else { - return vec4(u_hilite_color.rgb, 1.0); - } + float isHilite = floor(TEXTURE(u_hilite, v_texCoord).r + 0.5); + float ratio = u_hilite_settings.y * isHilite; + vec4 baseColor = computeColor(); + baseColor.rgb = mix(baseColor.rgb, u_hilite_color.rgb, ratio); + // If outlined, use hilite color for boundaries, else use base color, mixed with hilite color if inside hilite region. + return mix(vec4(u_hilite_color.rgb, 1.0), baseColor, max(isHilite, 1.0 - float(isOutlined()))); `; const computeTranslucentColor = ` @@ -95,7 +90,6 @@ export function createCompositeProgram(flags: CompositeFlags, context: WebGLRend addWindowToTexCoords(frag); frag.addFunction(isEdgePixel); frag.addFunction(isOutlined); - frag.addFunction(isInHiliteRegion); frag.addUniform("u_hilite", VariableType.Sampler2D, (prog) => { prog.addGraphicUniform("u_hilite", (uniform, params) => { diff --git a/core/frontend/src/render/webgl/glsl/CopyPickBuffers.ts b/core/frontend/src/render/webgl/glsl/CopyPickBuffers.ts index f622566..b5172d5 100644 --- a/core/frontend/src/render/webgl/glsl/CopyPickBuffers.ts +++ b/core/frontend/src/render/webgl/glsl/CopyPickBuffers.ts @@ -15,9 +15,8 @@ import { System } from "../System"; const computeBaseColor = "return vec4(1.0);"; const assignFragData = ` - FragColor0 = TEXTURE(u_pickElementId0, v_texCoord); - FragColor1 = TEXTURE(u_pickElementId1, v_texCoord); - FragColor2 = TEXTURE(u_pickDepthAndOrder, v_texCoord); + FragColor0 = TEXTURE(u_pickFeatureId, v_texCoord); + FragColor1 = TEXTURE(u_pickDepthAndOrder, v_texCoord); `; export function createCopyPickBuffersProgram(context: WebGLRenderingContext): ShaderProgram { @@ -30,21 +29,15 @@ export function createCopyPickBuffersProgram(context: WebGLRenderingContext): Sh // NB: Never used - we gl.clear() each attachment directly. frag.set(FragmentShaderComponent.AssignFragData, "FragColor = vec4(0.0);"); } else { - frag.addUniform("u_pickElementId0", VariableType.Sampler2D, (prog) => { - prog.addGraphicUniform("u_pickElementId0", (uniform, params) => { - Texture2DHandle.bindSampler(uniform, (params.geometry as CopyPickBufferGeometry).elemIdLow, TextureUnit.Zero); - }); - }, VariablePrecision.High); - - frag.addUniform("u_pickElementId1", VariableType.Sampler2D, (prog) => { - prog.addGraphicUniform("u_pickElementId1", (uniform, params) => { - Texture2DHandle.bindSampler(uniform, (params.geometry as CopyPickBufferGeometry).elemIdHigh, TextureUnit.One); + frag.addUniform("u_pickFeatureId", VariableType.Sampler2D, (prog) => { + prog.addGraphicUniform("u_pickFeatureId", (uniform, params) => { + Texture2DHandle.bindSampler(uniform, (params.geometry as CopyPickBufferGeometry).featureId, TextureUnit.Zero); }); }, VariablePrecision.High); frag.addUniform("u_pickDepthAndOrder", VariableType.Sampler2D, (prog) => { prog.addGraphicUniform("u_pickDepthAndOrder", (uniform, params) => { - Texture2DHandle.bindSampler(uniform, (params.geometry as CopyPickBufferGeometry).depthAndOrder, TextureUnit.Two); + Texture2DHandle.bindSampler(uniform, (params.geometry as CopyPickBufferGeometry).depthAndOrder, TextureUnit.One); }); }, VariablePrecision.High); diff --git a/core/frontend/src/render/webgl/glsl/Edge.ts b/core/frontend/src/render/webgl/glsl/Edge.ts index fa6b1d0..4dba161 100644 --- a/core/frontend/src/render/webgl/glsl/Edge.ts +++ b/core/frontend/src/render/webgl/glsl/Edge.ts @@ -22,8 +22,8 @@ import { addNormalMatrix } from "./Vertex"; import { octDecodeNormal } from "./Surface"; const decodeEndPointAndQuadIndices = ` - float index = decodeUInt32(a_endPointAndQuadIndices.xyz); - vec2 tc = computeLUTCoords(index, u_vertParams.xy, g_vert_center, u_vertParams.z); + g_otherIndex = decodeUInt32(a_endPointAndQuadIndices.xyz); + vec2 tc = computeLUTCoords(g_otherIndex, u_vertParams.xy, g_vert_center, u_vertParams.z); vec4 enc1 = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); tc.x += g_vert_stepX; vec4 enc2 = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); @@ -31,6 +31,8 @@ const decodeEndPointAndQuadIndices = ` g_otherPos = unquantizePosition(qpos, u_qOrigin, u_qScale); g_quadIndex = a_endPointAndQuadIndices.w; `; +const animateEndPoint = `g_otherPos.xyz += computeAnimationDisplacement(g_otherIndex, u_animDispParams.x, u_animDispParams.y, u_animDispParams.z, u_qAnimDispOrigin, u_qAnimDispScale); +`; const checkForSilhouetteDiscard = ` vec3 n0 = u_nmx * octDecodeNormal(a_normals.xy); @@ -82,14 +84,8 @@ const computePosition = ` float perpDist = weight / 2.0; float alongDist = 0.0; - if (g_quadIndex == 1.0) { - perpDist = -perpDist; - } else if (g_quadIndex == 2.0) { - perpDist = -perpDist; - alongDist = distance(rawPos, other); - } else if (g_quadIndex == 3.0) { - alongDist = distance(rawPos, other); - } + perpDist *= sign(0.5 - float(g_quadIndex == 1.0 || g_quadIndex == 2.0)); // negate for index 1 and 2 + alongDist += distance(rawPos, other) * float(g_quadIndex >= 2.0); // index 2 and 3 correspond to 'far' endpoint of segment pos.x += perp.x * perpDist * 2.0 * pos.w / u_viewport.z; pos.y += perp.y * perpDist * 2.0 * pos.w / u_viewport.w; @@ -109,8 +105,11 @@ function createBase(isSilhouette: boolean, isAnimated: boolean): ProgramBuilder vert.addGlobal("g_quadIndex", VariableType.Float); vert.addGlobal("g_windowPos", VariableType.Vec4); vert.addGlobal("g_windowDir", VariableType.Vec2); + vert.addGlobal("g_otherIndex", VariableType.Float); vert.addInitializer(decodeEndPointAndQuadIndices); + if (isAnimated) + vert.addInitializer(animateEndPoint); vert.addGlobal("lineCodeEyePos", VariableType.Vec4); vert.addGlobal("lineCodeDist", VariableType.Float, "0.0"); diff --git a/core/frontend/src/render/webgl/glsl/FeatureSymbology.ts b/core/frontend/src/render/webgl/glsl/FeatureSymbology.ts index af3c417..5fc4b9e 100644 --- a/core/frontend/src/render/webgl/glsl/FeatureSymbology.ts +++ b/core/frontend/src/render/webgl/glsl/FeatureSymbology.ts @@ -14,22 +14,20 @@ import { VariablePrecision, FragmentShaderComponent, } from "../ShaderBuilder"; -import { Hilite, FeatureIndexType } from "@bentley/imodeljs-common"; +import { Hilite } from "@bentley/imodeljs-common"; import { TextureUnit } from "../RenderFlags"; import { FloatRgba } from "../FloatRGBA"; import { FeatureMode } from "../TechniqueFlags"; import { GLSLVertex, addAlpha } from "./Vertex"; import { GLSLFragment, addWindowToTexCoords } from "./Fragment"; -import { GLSLCommon, addEyeSpace } from "./Common"; +import { GLSLCommon, addEyeSpace, addUInt32s } from "./Common"; import { GLSLDecode } from "./Decode"; import { addLookupTable } from "./LookupTable"; -import { LUTDimension, FeatureDimension, computeFeatureDimension } from "../FeatureDimensions"; import { assert } from "@bentley/bentleyjs-core"; import { addRenderPass } from "./RenderPass"; import { SurfaceGeometry } from "../Surface"; import { UniformHandle } from "../Handle"; import { DrawParams } from "../DrawCommand"; -import { System } from "../System"; export const enum FeatureSymbologyOptions { None = 0, @@ -42,7 +40,6 @@ export const enum FeatureSymbologyOptions { Surface = HasOverrides | Color | Alpha, Point = HasOverrides | Color | Weight | Alpha, Linear = HasOverrides | Color | Weight | LineCode | Alpha, - PointCloud = HasOverrides | Color, } function addFlagConstants(builder: ShaderBuilder): void { @@ -57,28 +54,21 @@ function addFlagConstants(builder: ShaderBuilder): void { builder.addConstant("kOvrBit_IgnoreMaterial", VariableType.Float, "7.0"); } -function addDimensionConstants(shader: ShaderBuilder): void { - shader.addConstant("kFeatureDimension_Empty", VariableType.Float, "0.0"); - shader.addConstant("kFeatureDimension_SingleUniform", VariableType.Float, "1.0"); - shader.addConstant("kFeatureDimension_SingleNonUniform", VariableType.Float, "2.0"); - shader.addConstant("kFeatureDimension_Multiple", VariableType.Float, "3.0"); -} +const computeFeatureIndex = ` + g_featureIndex = floor(TEXTURE(u_vertLUT, g_featureIndexCoords) * 255.0 + 0.5); +`; const getFeatureIndex = ` float getFeatureIndex() { - if (u_featureInfo.x <= kFeatureDimension_SingleNonUniform) - return u_featureInfo.y; - - vec2 tc = g_featureIndexCoords; - vec4 enc = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); - return decodeUInt32(enc.xyz); +` + computeFeatureIndex + ` + return decodeUInt32(g_featureIndex.xyz); } `; // Returns 1.0 if the specified flag is not globally overridden and is set in flags const extractNthLinearFeatureBit = ` float extractNthFeatureBit(float flags, float n) { - return 0.0 == extractNthBit(u_globalOvrFlags, n) ? extractNthBit(flags, n) : 0.0; + return (1.0 - extractNthBit(u_globalOvrFlags, n)) * extractNthBit(flags, n); } `; @@ -92,33 +82,15 @@ const computeFeatureTextureCoords = ` vec2 computeFeatureTextureCoords() { return compute_feature_coords(getFeatureIndex()); } `; -const getFirstUniformFeatureRgba = ` -vec4 getFirstFeatureRgba() { - return u_featureOverrides1; -} -`; - const getFirstFeatureRgba = ` vec4 getFirstFeatureRgba() { - if (u_featureInfo.x <= kFeatureDimension_SingleUniform) - return u_featureOverrides1; - feature_texCoord = computeFeatureTextureCoords(); return TEXTURE(u_featureLUT, feature_texCoord); } `; -const getSecondUniformFeatureRgba = ` -vec4 getSecondFeatureRgba() { - return u_featureOverrides2; -} -`; - const getSecondFeatureRgba = ` vec4 getSecondFeatureRgba() { - if (u_featureInfo.x <= kFeatureDimension_SingleUniform) - return u_featureOverrides2; - vec2 coord = feature_texCoord; coord.x += g_feature_stepX; return TEXTURE(u_featureLUT, coord); @@ -127,48 +99,19 @@ vec4 getSecondFeatureRgba() { const computeLineWeight = ` float ComputeLineWeight() { - return 1.0 == linear_feature_overrides.x ? linear_feature_overrides.y : u_lineWeight; + return mix(u_lineWeight, linear_feature_overrides.y, linear_feature_overrides.x); } `; const computeLineCode = ` float ComputeLineCode() { - return 1.0 == linear_feature_overrides.z ? linear_feature_overrides.w : u_lineCode; + return mix(u_lineCode, linear_feature_overrides.w, linear_feature_overrides.z); } `; -function addFeatureIndex(vert: VertexShaderBuilder, alwaysUniform: boolean = false): void { - addDimensionConstants(vert); - - if (!alwaysUniform) { - vert.addUniform("u_featureInfo", VariableType.Vec2, (prog) => { - prog.addGraphicUniform("u_featureInfo", (uniform, params) => { - let dims = FeatureDimension.Empty; - const value = [0, 0]; - const features = params.geometry.featuresInfo; - const featureIndexType = undefined !== features ? features.type : FeatureIndexType.Empty; - if (FeatureIndexType.Uniform === featureIndexType) - value[1] = features!.uniform!; - - const ovrs = params.target.currentOverrides; - if (undefined !== ovrs) { - if (params.target.areDecorationOverridesActive) - dims = computeFeatureDimension(LUTDimension.Uniform, FeatureIndexType.Uniform); - else - dims = computeFeatureDimension(ovrs.dimension, featureIndexType); - } else { - const pickTable = params.target.currentPickTable; - if (undefined !== pickTable) - dims = computeFeatureDimension(undefined !== pickTable.nonUniform ? LUTDimension.NonUniform : LUTDimension.Uniform, featureIndexType); - } - - value[0] = dims; - uniform.setUniform2fv(value); - }); - }); - - vert.addFunction(getFeatureIndex); - } +function addFeatureIndex(vert: VertexShaderBuilder): void { + vert.addGlobal("g_featureIndex", VariableType.Vec4); + vert.addFunction(getFeatureIndex); } // Discards vertex if feature is invisible; or rendering opaque during translucent pass or vice-versa @@ -189,16 +132,21 @@ const checkVertexDiscard = ` return (isOpaquePass && hasAlpha) || (isTranslucentPass && !hasAlpha); `; -function addCommon(builder: ProgramBuilder, mode: FeatureMode, opts: FeatureSymbologyOptions, alwaysUniform: boolean): boolean { +function addCommon(builder: ProgramBuilder, mode: FeatureMode, opts: FeatureSymbologyOptions): boolean { if (FeatureMode.None === mode) return false; const vert = builder.vert; - addFeatureIndex(vert, alwaysUniform); + addFeatureIndex(vert); const haveOverrides = FeatureSymbologyOptions.None !== (opts & FeatureSymbologyOptions.HasOverrides); - if (!haveOverrides) + if (!haveOverrides) { + // For pick output we must compute g_featureIndex... + if (FeatureMode.Pick === mode) + vert.set(VertexShaderComponent.ComputeFeatureOverrides, computeFeatureIndex); + return true; + } const wantWeight = FeatureSymbologyOptions.None !== (opts & FeatureSymbologyOptions.Weight); const wantLineCode = FeatureSymbologyOptions.None !== (opts & FeatureSymbologyOptions.LineCode); @@ -237,56 +185,31 @@ function addCommon(builder: ProgramBuilder, mode: FeatureMode, opts: FeatureSymb vert.addFunction(extractNthSurfaceFeatureBit); } - if (alwaysUniform) { - vert.addFunction(getFirstUniformFeatureRgba); - } else { - addLookupTable(vert, "feature", "2.0"); - vert.addGlobal("feature_texCoord", VariableType.Vec2); - vert.addFunction(computeFeatureTextureCoords); - vert.addFunction(getFirstFeatureRgba); - - vert.addUniform("u_featureLUT", VariableType.Sampler2D, (prog) => { - prog.addGraphicUniform("u_featureLUT", (uniform, params) => { - const ovr = params.target.currentOverrides; - assert(undefined !== ovr); - if (ovr!.isNonUniform) - ovr!.lut!.bindSampler(uniform, TextureUnit.FeatureSymbology); - else - System.instance.ensureSamplerBound(uniform, TextureUnit.FeatureSymbology); - }); - }); - vert.addUniform("u_featureParams", VariableType.Vec2, (prog) => { - prog.addGraphicUniform("u_featureParams", (uniform, params) => { - const ovr = params.target.currentOverrides!; - if (ovr.isNonUniform) - uniform.setUniform2fv([ovr.lut!.width, ovr.lut!.height]); - }); - }); - } + addLookupTable(vert, "feature", "2.0"); + vert.addGlobal("feature_texCoord", VariableType.Vec2); + vert.addFunction(computeFeatureTextureCoords); + vert.addFunction(getFirstFeatureRgba); - vert.addUniform("u_featureOverrides1", VariableType.Vec4, (prog) => { - prog.addGraphicUniform("u_featureOverrides1", (uniform, params) => { + vert.addUniform("u_featureLUT", VariableType.Sampler2D, (prog) => { + prog.addGraphicUniform("u_featureLUT", (uniform, params) => { + const ovr = params.target.currentOverrides; + assert(undefined !== ovr); + ovr!.lut!.bindSampler(uniform, TextureUnit.FeatureSymbology); + }); + }); + vert.addUniform("u_featureParams", VariableType.Vec2, (prog) => { + prog.addGraphicUniform("u_featureParams", (uniform, params) => { const ovr = params.target.currentOverrides!; - if (ovr.isUniform) - uniform.setUniform4fv(ovr.uniform1); + uniform.setUniform2fv([ovr.lut!.width, ovr.lut!.height]); }); }); if (wantColor) { - vert.addFunction(alwaysUniform ? getSecondUniformFeatureRgba : getSecondFeatureRgba); - if (wantAlpha) + vert.addFunction(getSecondFeatureRgba); + if (wantAlpha) { addAlpha(vert); - - vert.addUniform("u_featureOverrides2", VariableType.Vec4, (prog) => { - prog.addGraphicUniform("u_featureOverrides2", (uniform, params) => { - const ovr = params.target.currentOverrides!; - if (ovr.isUniform) - uniform.setUniform4fv(ovr.uniform2); - }); - }); - - if (wantAlpha) vert.set(VertexShaderComponent.CheckForDiscard, checkVertexDiscard); + } } return true; @@ -348,17 +271,17 @@ const computeHiliteOverridesWithWeight = computeHiliteOverrides + ` value.b * 256.0); `; -export function addSurfaceHiliter(builder: ProgramBuilder, wantWeight: boolean = false, alwaysUniform: boolean = false): void { - addHiliter(builder, wantWeight, alwaysUniform); +export function addSurfaceHiliter(builder: ProgramBuilder, wantWeight: boolean = false): void { + addHiliter(builder, wantWeight); builder.frag.set(FragmentShaderComponent.ComputeBaseColor, computeSurfaceHiliteColor); } -export function addHiliter(builder: ProgramBuilder, wantWeight: boolean = false, alwaysUniform: boolean = false): void { +export function addHiliter(builder: ProgramBuilder, wantWeight: boolean = false): void { let opts = FeatureSymbologyOptions.HasOverrides; if (wantWeight) opts |= FeatureSymbologyOptions.Weight; // hiliter never needs line code or color... - if (!addCommon(builder, FeatureMode.Overrides, opts, alwaysUniform)) + if (!addCommon(builder, FeatureMode.Overrides, opts)) return; builder.addVarying("v_feature_hilited", VariableType.Float); @@ -370,17 +293,11 @@ export function addHiliter(builder: ProgramBuilder, wantWeight: boolean = false, builder.frag.set(FragmentShaderComponent.AssignFragData, GLSLFragment.assignFragColor); } -function addSamplers(frag: FragmentShaderBuilder, testElementId: boolean) { - if (testElementId) { - frag.addUniform("u_pickElementId0", VariableType.Sampler2D, (prog) => { - prog.addProgramUniform("u_pickElementId0", (uniform, params) => { - params.target.compositor.elementId0.bindSampler(uniform, TextureUnit.PickElementId0); - }); - }, VariablePrecision.High); - - frag.addUniform("u_pickElementId1", VariableType.Sampler2D, (prog) => { - prog.addProgramUniform("u_pickElementId1", (uniform, params) => { - params.target.compositor.elementId1.bindSampler(uniform, TextureUnit.PickElementId1); +function addSamplers(frag: FragmentShaderBuilder, testFeatureId: boolean) { + if (testFeatureId) { + frag.addUniform("u_pickFeatureId", VariableType.Sampler2D, (prog) => { + prog.addProgramUniform("u_pickFeatureId", (uniform, params) => { + params.target.compositor.featureIds.bindSampler(uniform, TextureUnit.PickFeatureId); }); }, VariablePrecision.High); } @@ -400,52 +317,44 @@ vec2 readDepthAndOrder(vec2 tc) { } `; +// ####TODO vertex shader already tests transparency threshold...native renderer tests here as well? const checkForEarlySurfaceDiscard = ` - if (u_renderPass > kRenderPass_Translucent || u_renderPass <= kRenderPass_Background) - return false; + float factor = float(u_renderPass <= kRenderPass_Translucent); // never discard during specific passes + float term = 0.0; // float(isBelowTransparencyThreshold()); // else always discard if alpha < transparency threshold vec2 tc = windowCoordsToTexCoords(gl_FragCoord.xy); vec2 depthAndOrder = readDepthAndOrder(tc); float surfaceDepth = computeLinearDepth(v_eyeSpace.z); - return depthAndOrder.x > u_renderOrder && abs(depthAndOrder.y - surfaceDepth) < 4.0e-5; + term += float(depthAndOrder.x > u_renderOrder && abs(depthAndOrder.y - surfaceDepth) < 4.0e-5); + return factor * term > 0.0; `; -const checkForEarlySurfaceDiscardWithElemID = ` - if (u_renderPass > kRenderPass_Translucent || u_renderPass <= kRenderPass_Background) - return false; - else if (!isSurfaceBitSet(kSurfaceBit_HasNormals)) - return false; // no normal == never-lit geometry == never rendered with edges == don't have to test further +// ####TODO vertex shader already tests transparency threshold...native renderer tests here as well? +const checkForEarlySurfaceDiscardWithFeatureID = ` + // No normals => unlt => reality model => no edges. + bool neverDiscard = u_renderPass > kRenderPass_Translucent || !isSurfaceBitSet(kSurfaceBit_HasNormals); + bool alwaysDiscard = false; // !neverDiscard && isBelowTransparencyThreshold(); vec2 tc = windowCoordsToTexCoords(gl_FragCoord.xy); vec2 depthAndOrder = readDepthAndOrder(tc); - if (depthAndOrder.x <= u_renderOrder) - return false; // just do normal z-testing. + bool discardByOrder = depthAndOrder.x > u_renderOrder; // Calculate depthTolerance for letting edges show through their own surfaces - vec3 eyeDir; - float dtWidthFactor; - if (u_frustum.z == kFrustumType_Perspective) { - eyeDir = normalize(-v_eyeSpace.xyz); - dtWidthFactor = -v_eyeSpace.z * u_pixelWidthFactor; - } else { - eyeDir = vec3(0.0, 0.0, 1.0); - dtWidthFactor = u_pixelWidthFactor; - } + float perspectiveFrustum = step(kFrustumType_Perspective, u_frustum.z); + vec4 eyeDirAndWidthFactor = mix(vec4(0.0, 0.0, 1.0, u_pixelWidthFactor), vec4(normalize(-v_eyeSpace.xyz), -v_eyeSpace.z * u_pixelWidthFactor), perspectiveFrustum); + vec3 eyeDir = eyeDirAndWidthFactor.xyz; + float dtWidthFactor = eyeDirAndWidthFactor.w; // Compute depth tolerance based on angle of triangle to screen + float isSilhouette = float(depthAndOrder.x == kRenderOrder_Silhouette); float dSq = dot(eyeDir, v_n); - if (depthAndOrder.x == kRenderOrder_Silhouette) // curved surface - dSq *= 0.5; - else - dSq *= 0.9; - + dSq *= 0.5 + 0.4 * (1.0 - isSilhouette); dSq = dSq * dSq; dSq = max(dSq, 0.0001); dSq = min(dSq, 0.999); float depthTolerance = dtWidthFactor * v_lineWeight * sqrt((1.0 - dSq) / dSq); - if (depthAndOrder.x == kRenderOrder_Silhouette) // curved surface - depthTolerance = depthTolerance * 1.333; + depthTolerance *= 1.0 + .333 * isSilhouette; // Make sure stuff behind camera doesn't get pushed in front of it depthTolerance = max(depthTolerance, 0.0); @@ -455,29 +364,19 @@ const checkForEarlySurfaceDiscardWithElemID = ` float surfaceDepth = computeLinearDepth(v_eyeSpace.z); float depthDelta = abs(depthAndOrder.y - surfaceDepth); - if (depthDelta > depthTolerance) - return false; // don't discard and let normal z-testing happen + bool withinDepthTolerance = depthDelta <= depthTolerance; - // Does pick buffer contain same element? - vec4 elemId0 = TEXTURE(u_pickElementId0, tc); + // Does pick buffer contain same feature? + vec4 featId = TEXTURE(u_pickFeatureId, tc); // Converting to ints to test since varying floats can be interpolated incorrectly - ivec4 elemId0_i = ivec4(elemId0 * 255.0 + 0.5); - ivec4 v_element_id0_i = ivec4(v_element_id0 * 255.0 + 0.5); - bool isSameElement = elemId0_i == v_element_id0_i; - if (isSameElement) { - vec4 elemId1 = TEXTURE(u_pickElementId1, tc); - ivec4 elemId1_i = ivec4(elemId1 * 255.0 + 0.5); - ivec4 v_element_id1_i = ivec4(v_element_id1 * 255.0 + 0.5); - isSameElement = elemId1_i == v_element_id1_i; - } - if (!isSameElement) { - // If what was in the pick buffer is a planar line/edge/silhouette then we've already tested the depth so return true to discard. - // If it was a planar surface then use a tighter and constant tolerance to see if we want to let it show through since we're only fighting roundoff error. - return (depthAndOrder.x > kRenderOrder_PlanarSurface) || ((depthAndOrder.x == kRenderOrder_PlanarSurface) && (depthDelta <= 4.0e-5)); - } + ivec4 featId_i = ivec4(featId * 255.0 + 0.5); + ivec4 v_feature_id_i = ivec4(v_feature_id * 255.0 + 0.5); + bool isSameFeature = featId_i == v_feature_id_i; - return true; // discard surface in favor of pick buffer contents. + // If what was in the pick buffer is a planar line/edge/silhouette then we've already tested the depth so return true to discard. + // If it was a planar surface then use a tighter and constant tolerance to see if we want to let it show through since we're only fighting roundoff error. + return alwaysDiscard || (!neverDiscard && discardByOrder && withinDepthTolerance && (isSameFeature || ((depthAndOrder.x > kRenderOrder_PlanarSurface) || ((depthAndOrder.x == kRenderOrder_PlanarSurface) && (depthDelta <= 4.0e-5))))); `; function addEdgeWidth(builder: ShaderBuilder) { @@ -490,22 +389,7 @@ function addEdgeWidth(builder: ShaderBuilder) { }); } -export const computeUniformElementId = ` - v_element_id0 = u_element_id0; - v_element_id1 = u_element_id1; -`; - -export const computeElementId = ` - if (u_featureInfo.x <= kFeatureDimension_SingleUniform) { - v_element_id0 = u_element_id0; - v_element_id1 = u_element_id1; - } else { - vec2 texc = computeElementIdTextureCoords(); - v_element_id0 = TEXTURE(u_elementIdLUT, texc); - texc.x += g_elementId_stepX; - v_element_id1 = TEXTURE(u_elementIdLUT, texc); - } -`; +export const computeFeatureId = `v_feature_id = addUInt32s(u_batch_id, g_featureIndex) / 255.0;`; function addRenderOrderConstants(builder: ShaderBuilder) { builder.addConstant("kRenderOrder_None", VariableType.Float, "0.0"); @@ -563,56 +447,29 @@ function addPixelWidthFactor(builder: ShaderBuilder) { }); } -const computeElementIdTextureCoords = ` -vec2 computeElementIdTextureCoords() { - return compute_elementId_coords(getFeatureIndex()); +const scratchBytes = new Uint8Array(4); +const scratchBatchId = new Uint32Array(scratchBytes.buffer); +const scratchBatchComponents = [0, 0, 0, 0]; + +function addBatchId(vert: VertexShaderBuilder) { + vert.addUniform("u_batch_id", VariableType.Vec4, (prog) => { + prog.addGraphicUniform("u_batch_id", (uniform, params) => { + const batchId = params.target.currentBatchId; + scratchBatchId[0] = batchId; + scratchBatchComponents[0] = scratchBytes[0]; + scratchBatchComponents[1] = scratchBytes[1]; + scratchBatchComponents[2] = scratchBytes[2]; + scratchBatchComponents[3] = scratchBytes[3]; + uniform.setUniform4fv(scratchBatchComponents); + }); + }, VariablePrecision.High); } -`; - -export function addElementId(builder: ProgramBuilder, alwaysUniform: boolean = false) { - builder.addVarying("v_element_id0", VariableType.Vec4); - builder.addVarying("v_element_id1", VariableType.Vec4); +export function addFeatureId(builder: ProgramBuilder) { const vert = builder.vert; - if (alwaysUniform) { - vert.addFunction("void computeElementId()", computeUniformElementId); - } else { - addLookupTable(vert, "elementId", "2.0"); - vert.addFunction(computeElementIdTextureCoords); - vert.addFunction("void computeElementId()", computeElementId); - vert.addUniform("u_elementIdLUT", VariableType.Sampler2D, (prog) => { - prog.addGraphicUniform("u_elementIdLUT", (uniform, params) => { - assert(undefined !== params.target.currentPickTable); - const table = params.target.currentPickTable!; - if (undefined !== table.nonUniform) - table.nonUniform.bindSampler(uniform, TextureUnit.ElementId); - else - System.instance.ensureSamplerBound(uniform, TextureUnit.ElementId); - }); - }); - vert.addUniform("u_elementIdParams", VariableType.Vec2, (prog) => { - prog.addGraphicUniform("u_elementIdParams", (uniform, params) => { - const table = params.target.currentPickTable!; - if (undefined !== table.nonUniform) - uniform.setUniform2fv([table.nonUniform.width, table.nonUniform.height]); - }); - }); - } - - vert.addUniform("u_element_id0", VariableType.Vec4, (prog) => { - prog.addGraphicUniform("u_element_id0", (uniform, params) => { - const table = params.target.currentPickTable!; - if (undefined !== table.uniform) - uniform.setUniform4fv(table.uniform.elemId0); - }); - }); - vert.addUniform("u_element_id1", VariableType.Vec4, (prog) => { - prog.addGraphicUniform("u_element_id1", (uniform, params) => { - const table = params.target.currentPickTable!; - if (undefined !== table.uniform) - uniform.setUniform4fv(table.uniform.elemId1); - }); - }); + vert.addFunction(addUInt32s); + builder.addInlineComputedVarying("v_feature_id", VariableType.Vec4, computeFeatureId); + addBatchId(vert); } // For hidden line + solid fill modes...translucent + opaque passes only. @@ -645,7 +502,6 @@ export function addSurfaceDiscard(builder: ProgramBuilder, feat: FeatureMode) { addFeatureIndex(vert); addEdgeWidth(vert); vert.addFunction(GLSLVertex.computeLineWeight); - vert.set(VertexShaderComponent.AddComputeElementId, computeElementId); addSamplers(frag, true); addRenderOrderConstants(frag); @@ -653,11 +509,11 @@ export function addSurfaceDiscard(builder: ProgramBuilder, feat: FeatureMode) { frag.addFunction(GLSLFragment.computeLinearDepth); frag.addFunction(GLSLDecode.depthRgb); frag.addFunction(readDepthAndOrder); - frag.set(FragmentShaderComponent.CheckForEarlyDiscard, checkForEarlySurfaceDiscardWithElemID); + frag.set(FragmentShaderComponent.CheckForEarlyDiscard, checkForEarlySurfaceDiscardWithFeatureID); addEyeSpace(builder); builder.addInlineComputedVarying("v_lineWeight", VariableType.Float, "v_lineWeight = ComputeLineWeight();"); - addElementId(builder); + addFeatureId(builder); } addRenderOrder(frag); @@ -705,18 +561,21 @@ const computeFeatureOverrides = ` v_feature_alpha_flashed.y += 2.0 * extractNthFeatureBit(flags, kOvrBit_Hilited); `; +// v_feature_rgb.r = -1.0 if rgb color not overridden for feature. +// v_feature_alpha_flashed.x = -1.0 if alpha not overridden for feature. const applyFeatureColor = ` - if (v_feature_rgb.r >= 0.0) - baseColor.rgb = v_feature_rgb.rgb * baseColor.a; - - if (v_feature_alpha_flashed.x >= 0.0) - baseColor = adjustPreMultipliedAlpha(baseColor, v_feature_alpha_flashed.x); - - return baseColor; + vec4 color = mix(baseColor, vec4(v_feature_rgb.rgb * baseColor.a, baseColor.a), step(0.0, v_feature_rgb.r)); + return mix(color, adjustPreMultipliedAlpha(color, v_feature_alpha_flashed.x), step(0.0, v_feature_alpha_flashed.x)); `; const applyFlash = ` float flashHilite = floor(v_feature_alpha_flashed.y + 0.5); + return doApplyFlash(flashHilite, baseColor); +`; + +// u_hilite_color.a is 1.0 for lit geometry, 0.0 for unlit. Lit gets brightened; unlit gets tweened. +const doApplyFlash = ` +vec4 doApplyFlash(float flashHilite, vec4 baseColor) { float isFlashed = (flashHilite == 1.0 || flashHilite == 3.0) ? 1.0 : 0.0; float isHilited = (flashHilite >= 2.0) ? 1.0 : 0.0; @@ -724,22 +583,38 @@ const applyFlash = ` baseColor = revertPreMultipliedAlpha(baseColor); baseColor.rgb = mix(baseColor.rgb, u_hilite_color.rgb, hiliteRatio); - if (u_hilite_color.a == 1.0) { // .a indicates lit geometry - brighten it - const float maxBrighten = 0.2; - float brighten = u_flash_intensity * maxBrighten; - baseColor.rgb += isFlashed * brighten; - } else { // unlit geometry - tween it toward flash color - float maxTween = 0.75; - float hiliteFraction = u_flash_intensity * isFlashed * maxTween; - baseColor.rgb *= (1.0 - hiliteFraction); - baseColor.rgb += u_hilite_color.rgb * hiliteFraction; - } + const float maxBrighten = 0.2; + float brighten = u_flash_intensity * maxBrighten; + vec3 brightRgb = baseColor.rgb + isFlashed * brighten; - return applyPreMultipliedAlpha(baseColor); + const float maxTween = 0.75; + float hiliteFraction = u_flash_intensity * isFlashed * maxTween; + vec3 tweenRgb = baseColor.rgb * (1.0 - hiliteFraction); + tweenRgb += u_hilite_color.rgb * hiliteFraction; + + vec4 color = vec4(mix(tweenRgb, brightRgb, u_hilite_color.a), baseColor.a); + return applyPreMultipliedAlpha(color); +} `; -export function addFeatureSymbology(builder: ProgramBuilder, feat: FeatureMode, opts: FeatureSymbologyOptions, alwaysUniform: boolean = false): void { - if (!addCommon(builder, feat, opts, alwaysUniform) || FeatureSymbologyOptions.None === opts) +function addApplyFlash(frag: FragmentShaderBuilder) { + addHiliteSettings(frag); + + frag.addFunction(GLSLFragment.revertPreMultipliedAlpha); + frag.addFunction(GLSLFragment.applyPreMultipliedAlpha); + frag.addFunction(GLSLFragment.adjustPreMultipliedAlpha); + frag.addFunction(doApplyFlash); + frag.set(FragmentShaderComponent.ApplyFlash, applyFlash); + + frag.addUniform("u_flash_intensity", VariableType.Float, (prog) => { + prog.addProgramUniform("u_flash_intensity", (uniform, params) => { + uniform.setUniform1f(params.target.flashIntensity); + }); + }); +} + +export function addFeatureSymbology(builder: ProgramBuilder, feat: FeatureMode, opts: FeatureSymbologyOptions): void { + if (!addCommon(builder, feat, opts) || FeatureSymbologyOptions.None === opts) return; assert((FeatureSymbologyOptions.HasOverrides | FeatureSymbologyOptions.Color) === (opts & (FeatureSymbologyOptions.HasOverrides | FeatureSymbologyOptions.Color))); @@ -751,18 +626,45 @@ export function addFeatureSymbology(builder: ProgramBuilder, feat: FeatureMode, vert.set(VertexShaderComponent.ComputeFeatureOverrides, computeFeatureOverrides); const frag = builder.frag; - addHiliteSettings(frag); - frag.addFunction(GLSLCommon.floatToBool); + addApplyFlash(frag); frag.set(FragmentShaderComponent.ApplyFeatureColor, applyFeatureColor); +} - frag.addFunction(GLSLFragment.revertPreMultipliedAlpha); - frag.addFunction(GLSLFragment.applyPreMultipliedAlpha); - frag.addFunction(GLSLFragment.adjustPreMultipliedAlpha); - frag.set(FragmentShaderComponent.ApplyFlash, applyFlash); +// If we're running the hilite shader for a uniform feature, it follows that the feature must be hilited. +// So the hilite shader simply needs to output '1' for every fragment. +export function addUniformHiliter(builder: ProgramBuilder): void { + builder.frag.set(FragmentShaderComponent.ComputeBaseColor, `return vec4(1.0);`); + builder.frag.set(FragmentShaderComponent.AssignFragData, GLSLFragment.assignFragColor); +} - frag.addUniform("u_flash_intensity", VariableType.Float, (prog) => { - prog.addProgramUniform("u_flash_intensity", (uniform, params) => { - uniform.setUniform1f(params.target.flashIntensity); +// For a uniform feature table, the feature ID output to pick buffers is equal to the batch ID. +// The following symbology overrides are supported: +// - Visibility - implcitly, because if the feature is invisible its geometry will never be drawn. +// - Flash +// - Hilite +// In future we may find a reason to support color and/or transparency. +// This shader could be simplified, but want to share code with the non-uniform versions...hence uniforms/globals with "v_" prefix typically used for varyings... +export function addUniformFeatureSymbology(builder: ProgramBuilder): void { + // addFeatureIndex() + builder.vert.addGlobal("g_featureIndex", VariableType.Vec4, "vec4(0.0)", true); + + // addFeatureSymbology() + builder.frag.addUniform("v_feature_alpha_flashed", VariableType.Vec2, (prog) => { + prog.addGraphicUniform("v_feature_alpha_flashed", (uniform, params) => { + // only the 'y' component is used. first bit = flashed, second = hilited. + let value = 0; + const ovr = params.target.currentOverrides; + if (undefined !== ovr) { + if (ovr.anyHilited) // any hilited implies all hilited. + value = 2; + + if (ovr.isUniformFlashed) + value += 1; + } + + uniform.setUniform2fv([0.0, value]); }); }); + + addApplyFlash(builder.frag); } diff --git a/core/frontend/src/render/webgl/glsl/Fragment.ts b/core/frontend/src/render/webgl/glsl/Fragment.ts index b7bb984..10550bc 100644 --- a/core/frontend/src/render/webgl/glsl/Fragment.ts +++ b/core/frontend/src/render/webgl/glsl/Fragment.ts @@ -62,31 +62,29 @@ export function addNormalMatrixF(frag: FragmentShaderBuilder) { */ const reverseWhiteOnWhite = ` - if (u_reverseWhiteOnWhite > 0.5) { - // Account for erroneous interpolation from varying vec3(1.0)... - const vec3 white = vec3(1.0); - const vec3 epsilon = vec3(0.0001); - vec3 color = baseColor.a > 0.0 ? baseColor.rgb / baseColor.a : baseColor.rgb; // revert premultiplied alpha - vec3 delta = (color + epsilon) - white; - if (delta.x > 0.0 && delta.y > 0.0 && delta.z > 0.0) - baseColor.rgb = vec3(0.0); - } - return baseColor; + const vec3 white = vec3(1.0); + const vec3 epsilon = vec3(0.0001); + vec3 color = baseColor.rgb / max(0.0001, baseColor.a); // revert premultiplied alpha + vec3 delta = (color + epsilon) - white; + vec4 wowColor = vec4(baseColor.rgb * vec3(float(delta.x <= 0.0 || delta.y <= 0.0 || delta.z <= 0.0)), baseColor.a); // set to black if almost white + wowColor.rgb *= wowColor.a; // reapply premultiplied alpha + return mix(baseColor, wowColor, floor(u_reverseWhiteOnWhite + 0.5)); `; const computePickBufferOutputs = ` - float linearDepth = computeLinearDepth(v_eyeSpace.z); vec4 output0 = baseColor; - vec4 output1 = v_element_id0; - vec4 output2 = v_element_id1; - vec4 output3 = vec4(u_renderOrder * 0.0625, encodeDepthRgb(linearDepth)); // near=1, far=0 + + // Fix interpolation errors despite all vertices sending exact same v_feature_id... + ivec4 v_feature_id_i = ivec4(v_feature_id * 255.0 + 0.5); + vec4 output1 = vec4(v_feature_id_i) / 255.0; + float linearDepth = computeLinearDepth(v_eyeSpace.z); + vec4 output2 = vec4(u_renderOrder * 0.0625, encodeDepthRgb(linearDepth)); // near=1, far=0 `; const assignPickBufferOutputsMRT = computePickBufferOutputs + ` FragColor0 = output0; FragColor1 = output1; FragColor2 = output2; - FragColor3 = output3; `; const assignPickBufferOutputsMP = computePickBufferOutputs + ` @@ -94,10 +92,8 @@ const assignPickBufferOutputsMP = computePickBufferOutputs + ` FragColor = output0; else if (1 == u_renderTargetIndex) FragColor = output1; - else if (2 == u_renderTargetIndex) - FragColor = output2; else - FragColor = output3; + FragColor = output2; `; export function addPickBufferOutputs(frag: FragmentShaderBuilder): void { @@ -119,8 +115,7 @@ export namespace GLSLFragment { export const revertPreMultipliedAlpha = ` vec4 revertPreMultipliedAlpha(vec4 rgba) { - if (0.0 < rgba.a) - rgba.rgb /= rgba.a; + rgba.rgb /= max(0.0001, rgba.a); return rgba; } `; @@ -135,9 +130,7 @@ vec4 applyPreMultipliedAlpha(vec4 rgba) { export const adjustPreMultipliedAlpha = ` vec4 adjustPreMultipliedAlpha(vec4 rgba, float newAlpha) { float oldAlpha = rgba.a; - if (0.0 < oldAlpha) - rgba.rgb /= oldAlpha; - + rgba.rgb /= max(0.0001, oldAlpha); rgba.rgb *= newAlpha; rgba.a = newAlpha; return rgba; diff --git a/core/frontend/src/render/webgl/glsl/Lighting.ts b/core/frontend/src/render/webgl/glsl/Lighting.ts index 788b646..ab22543 100644 --- a/core/frontend/src/render/webgl/glsl/Lighting.ts +++ b/core/frontend/src/render/webgl/glsl/Lighting.ts @@ -15,137 +15,6 @@ import { import { addFrustum } from "./Common"; import { Material } from "../Material"; -/* ###TODO: IBL -import { addNormalMatrixF } from "./Fragment"; -import { TextureUnit } from "../RenderFlags"; -*/ - -/* ###TODO: Source Lighting -const maxShaderLights = 64; -*/ - -/** - * Lights - stored in a u_lightData array (3 entries per light) - * Type is determined from the sign of cosHPhi and cosHTheta. - * Point: cosHPhi < 0 - * Spot: cosHTheta >= 0 && cosHPhi >= 0 - * Directional: cosHTheta < 0 - * Attenuation 0, 1, and 2 are calculated here based on using no attenuation, - * using linear attenuation, or using distance squared. - * If linear, the equation we use is: 1 / (0.5 + 1.5 / halfBrightDist * d) - * where atten1 = -1.5 / halfBrightDist (linear not used here) - * If distance squared, the equation is: realBright / (1 + d * d) - * where atten1 = realBright - * For no attenuation (atten = 1), atten1 is 0 - * cosHTheta (cos(Theta / 2)) is passed for spots - * cosHPhi (cos(Phi / 2)) is passed for spots - */ -/* ###TODO: Source Lighting -const computeSourceLighting = ` -void computeSingleSourceLight (inout vec3 diffuse, inout vec3 specular, vec3 camPos, vec3 norm, vec3 vPntToView, float specPow, - vec3 lightCol, vec3 lightPos, vec3 lightDir, float lCosHTheta, float lCosHPhi, float atten1) { - vec3 curDiff = vec3(0.0, 0.0, 0.0); - vec3 curSpec = vec3(0.0, 0.0, 0.0); - - // vPosToLt is the normalized vector from the vertex pos to the light pos - vec3 vPosToLt = lightPos - camPos; - float lpDist = length (vPosToLt); // calc lpDist separately for later - if (lCosHTheta < 0.0) // if directional - vPosToLt = lightDir; - else - vPosToLt *= 1.0 / lpDist; // normalize - - // Calculate spot factor - float spotf = 1.0; // default value for not spot or if (rho > lCosHTheta) - if ((lCosHTheta >= 0.0) && (lCosHPhi >= 0.0)) { // if spotlight - float rho = dot (vPosToLt, lightDir); - if (rho <= lCosHPhi) // outside of spot - spotf = 0.0; - else if (rho <= lCosHTheta) { // between inner and outer cones - float t1 = lCosHTheta - lCosHPhi; - // prevent divisor from being less than 0 (which still could happen here) - if (t1 > 0.0) { - spotf = (rho - lCosHPhi) / t1; - // do hermite blending on spotf - spotf = (3.0 - 2.0 * spotf) * spotf * spotf; - } - } - } - - // Calculate attenuation - float atten = 1.0; // for dir light or no atten - if (atten1 > 0.0) // D2 (only distance squared currently used if attenuation) - atten = atten1 / (1.0 + lpDist * lpDist); - - atten *= spotf; // at this point atten and spotf are 1.0 if not applicable - - float NdotL = dot (norm, vPosToLt); - float attenNdotL = atten * NdotL; - - if (attenNdotL > 0.0) { - curDiff = attenNdotL * lightCol; - // use the Phong reflection V dot R where R = 2N * (N dot L) - L - vec3 R = 2.0 * NdotL * norm - vPosToLt; - float rDotV = dot (vPntToView, R); - if (rDotV > 0.0) - curSpec = atten * pow (abs(rDotV), specPow) * lightCol; //TODO: abs should not be necessary here - } - diffuse += curDiff; specular += curSpec; -} - -void computeAllSourceLights (inout vec3 diffuse, inout vec3 specular, vec3 position, vec3 normal, vec3 vPntToView, float specularExp) { - for (int i = 0; i < kMaxShaderLights; ++i) - computeSingleSourceLight (diffuse, specular, position, normal, vPntToView, specularExp, LightColor(i), LightPos(i), LightDir(i), cosHTheta(i), cosHPhi(i), LightAtten1(i)); -} - -vec3 computeSourceLighting (vec3 normal, vec3 toEye, vec3 position, float specularExp, vec3 specularColor, vec3 inputColor) { - vec3 diffuse = vec3(0.0, 0.0, 0.0); - vec3 specular = vec3(0.0, 0.0, 0.0); - - if (u_lightCount > 0) - computeAllSourceLights (diffuse, specular, position, normal, -toEye, specularExp); - - // diffuse = diffuse * inputColor.rgb + u_ambientLight * ambientCol + specular * specularColor + emissiveCol; - vec3 total = diffuse * inputColor + specular * specularColor; - - // since not HDR, just scale by u_fstop value - if (u_fstop >= 0.0) - total *= (u_fstop + 1.0); - else - total /= (-u_fstop + 1.0); - - return total; -} -`; -*/ - -/* ###TODO: IBL -const sampleRGBM = ` -vec3 decodeRGBM(in vec4 rgbm) { - return 5.0 * rgbm.rgb * rgbm.a; -} - -vec3 sampleRGBM (in sampler2D map, in vec3 dir) { // Normalized direction. - vec2 uv; - uv.x = .25 - atan(dir.y, dir.x) / (2.0 * 3.141592); // To match crazy skybox code. - uv.y = .5 - asin(dir.z) / 3.141592; - return decodeRGBM(TEXTURE(map, uv)); -} -`; - -const applyReflection = ` - vec3 toScreen(in vec3 linear) { return pow (linear, vec3(1.0/2.2)); } - - vec3 applyReflection(in vec3 inColor, in vec3 normal, in vec4 reflectivity) { - if (!isSurfaceBitSet(kSurfaceBit_EnvironmentMap) || reflectivity.a <= 0.0) - return inColor; - vec3 reflWorld = normalize(reflect(v_pos.xyz, normal.xyz)) * u_nmx; - vec3 environmentMapColor = reflectivity.rgb * toScreen(sampleRGBM(u_environmentMap, reflWorld)); - return mix (inColor, environmentMapColor, reflectivity.a); - } -`; -*/ - const computeSimpleLighting = ` void computeSimpleLight (inout float diffuse, inout float specular, vec3 normal, vec3 toEye, vec3 lightDir, float lightIntensity, float specularExponent) { diffuse += lightIntensity * max(dot(normal, lightDir), 0.0); @@ -155,82 +24,46 @@ void computeSimpleLight (inout float diffuse, inout float specular, vec3 normal, } `; -/* ###TODO -#ifdef PBR_WIP - const toLinear = `{ return "vec3 toLinear(in vec3 gamma) { return pow(gamma, vec3(1.8)); }`; -#endif -*/ - -/* ###TODO: IBL & PBR -const toLuminance = `{ return "float toLuminance(in vec3 rgb) { return 0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b; }`; -*/ - const applyLighting = ` if (isSurfaceBitSet(kSurfaceBit_ApplyLighting) && baseColor.a > 0.0) { // Lighting algorithms written in terms of non-pre-multiplied alpha... float alpha = baseColor.a; baseColor.rgb /= alpha; + // negate normal if not front-facing vec3 normal = normalize(v_n.xyz); - vec3 toEye; - - if (!gl_FrontFacing) - normal = -normal; - - if (kFrustumType_Perspective == u_frustum.z) // perspective - toEye = normalize (v_pos.xyz); - else - toEye = vec3(0.0, 0.0, -1.0); - - bool useDefaults = isSurfaceBitSet(kSurfaceBit_IgnoreMaterial); - float specularExp = useDefaults ? 43.2 : u_specular.a; - vec3 specularColor = useDefaults ? vec3(1.0) : u_specular.rgb; - float diffuseWeight = useDefaults ? .6 : u_material.r; - float specularWeight = useDefaults ? .4 : u_material.g; - vec3 litColor = vec3(0.0); - - /* ##TODO: IBL - baseColor.rgb = applyReflection(baseColor.rgb, normal, u_reflect); - - if (u_lightMix.x > 0.0001) { - vec3 normalWorld = normal * u_nmx; - vec3 normalWorld = normal * u_nmx; - vec3 imageDiffuse = sampleRGBM(u_diffuseMap, normalWorld); - - litColor += u_lightMix.x * toLuminance (imageDiffuse) * baseColor.rgb; - - if (0.0 != u_imageSolar.w) { - float diffuseIntensity = 0.0, specularIntensity = 0.0; + normal *= 2.0 * float(gl_FrontFacing) - 1.0; + vec3 toEye = mix(vec3(0.0, 0.0, -1.0), normalize(v_pos.xyz), float(kFrustumType_Perspective == u_frustum.z)); - computeSimpleLight(diffuseIntensity, specularIntensity, normalWorld, toEye, u_imageSolar.xyz, 1.0, specularExp); - litColor += u_lightMix.x * (u_imageSolar.w * diffuseWeight * diffuseIntensity * baseColor.rgb + specularIntensity * specularWeight * specularColor); - } - } - */ + float useDefaults = extractSurfaceBit(kSurfaceBit_IgnoreMaterial); + const vec4 defaultSpecular = vec4(1.0, 1.0, 1.0, 43.2); // rgb, exponent + vec4 specular = mix(u_specular, defaultSpecular, useDefaults); + vec3 specularColor = specular.rgb; + float specularExp = specular.a; - if (u_lightMix.y > 0.0001) { // Default. - float diffuseIntensity = 0.0, specularIntensity = 0.0; + const vec2 defaultWeights = vec2(.6, .4); // diffuse, specular + vec2 weights = mix(u_material.rg, defaultWeights, useDefaults); + float diffuseWeight = weights.r; + float specularWeight = weights.g; - // Use a pair of lights that is something in-between portrait lighting & something more out-doorsy with a slightly more overhead main light. - // This will make more sense in a wider variety of scenes since this is the only lighting currently supported. - computeSimpleLight (diffuseIntensity, specularIntensity, normal, toEye, normalize(vec3(0.2, 0.5, 0.5)), 1.0, specularExp); - computeSimpleLight (diffuseIntensity, specularIntensity, normal, toEye, normalize(vec3(-0.3, 0.0, 0.3)), .30, specularExp); + vec3 litColor = vec3(0.0); - litColor += u_lightMix.y * diffuseWeight * diffuseIntensity * baseColor.rgb + specularIntensity * specularWeight * specularColor; - } + float diffuseIntensity = 0.0, specularIntensity = 0.0; - /* ###TODO: Source Lighting - if (u_lightMix.z > 0.0) // Source. - litColor += u_lightMix.z * computeSourceLighting(normal, toEye, v_pos, specularExp, specularColor, baseColor.rgb); - */ + // Use a pair of lights that is something in-between portrait lighting & something more out-doorsy with a slightly more overhead main light. + // This will make more sense in a wider variety of scenes since this is the only lighting currently supported. + computeSimpleLight (diffuseIntensity, specularIntensity, normal, toEye, normalize(vec3(0.2, 0.5, 0.5)), 1.0, specularExp); + computeSimpleLight (diffuseIntensity, specularIntensity, normal, toEye, normalize(vec3(-0.3, 0.0, 0.3)), .30, specularExp); - if (u_lightMix.a > 0.0) - litColor.rgb += u_lightMix.a * baseColor.rgb; + const float directionalIntensity = 0.92; + const float ambientIntensity = 0.08; + litColor += directionalIntensity * diffuseWeight * diffuseIntensity * baseColor.rgb + specularIntensity * specularWeight * specularColor; + litColor.rgb += ambientIntensity * baseColor.rgb; // Clamp while preserving hue. - float maxIntensity = max(litColor.r, max(litColor.g, litColor.b)); + float maxIntensity = max(litColor.r, max(litColor.g, litColor.b)); - baseColor.rgb = (maxIntensity > 1.0) ? (litColor / maxIntensity) : litColor; + baseColor.rgb = litColor / max(1.0, maxIntensity); // Restore pre-multiplied alpha... baseColor.rgb *= alpha; @@ -239,75 +72,10 @@ const applyLighting = ` return baseColor; `; -const scratchLighting = { - lightMix: new Float32Array(4), -}; - export function addLighting(builder: ProgramBuilder) { addFrustum(builder); const frag = builder.frag; - frag.addUniform("u_lightMix", VariableType.Vec4, (shaderProg) => { - shaderProg.addGraphicUniform("u_lightMix", (uniform, params) => { - // const viewFlags = params.m_target.currentViewFlags(); // TODO set lighting based on these - always default for now. - const data = scratchLighting.lightMix; - data[0] = 0.0; // set to 1.0 for IBL - data[1] = 0.0; // set to 1.0 for default portrait lighting - data[2] = 0.0; // set to 1.0 for using scene lights - data[3] = 0.0; // set > 0.0 for constant lighting - // ###TODO: IBL - use the following commented out line instead of the one after it which is there just to use params. - // const doDiffuseImageLighting: boolean = (undefined !== params.target.diffuseMap); - const doDiffuseImageLighting = (undefined === params.target); - if (doDiffuseImageLighting) - data[0] = 1.0; - else { - // Use default lighting, a pair of directional lights + a little ambient. - data[1] = 0.92; - data[3] = 0.08; - } - uniform.setUniform4fv(data); - }); - }); - - // #if !defined(GLES3_CONFORMANT) - // frag.AddExtension("GL_EXT_shader_texture_lod"); - // #endif - - /* ###TODO: IBL - addNormalMatrixF(frag); - addEnvironmentMap(frag); - addDiffuseMap(frag); - frag.addFunction(sampleRGBM); - frag.addFunction(toLuminance); - frag.addFunction(applyReflection); - frag.addUniform("u_imageSolar", VariableType.Vec4, (shader) => { - shader.addGraphicUniform("u_imageSolar", (uniform, params) => { - const imageSolar = params.target.imageSolar; - const data = new Float32Array(4); - if (undefined !== imageSolar) { - data[0] = imageSolar.direction.x; - data[1] = imageSolar.direction.y; - data[2] = imageSolar.direction.z; - data[3] = imageSolar.intensity; - } else { - data[0] = 0.0; - data[1] = -1.0; - data[2] = 0.0; - data[3] = 1.0; - } - uniform.setUniform4fv(data); - }); - }); - */ - - /* ###TODO BPR - frag.addFunction(ToLinear()); - frag.addFunction(ToLuminance()); - addIBLDiffuse(frag); - addBRDFTexture(frag); - frag.addFunction(IBLSpecular()); - */ - frag.addUniform("u_material", VariableType.Vec3, (shader) => { shader.addGraphicUniform("u_material", (uniform, params) => { const material = params.target.currentViewFlags.materials ? params.geometry.material : undefined; @@ -326,93 +94,6 @@ export function addLighting(builder: ProgramBuilder) { }); }); - /* ###TODO BPR - frag.addUniform("u_reflect", VariableType.Vec4, (shader) => { - shader.addGraphicUniform("u_reflect", (uniform, params) => { - const data = new Float32Array(4); - if (params.target.currentViewFlags.showMaterials()) { - // const mat = params.geometry.material; - // data[0] = mat.reflectColor.red; - // data[1] = mat.reflectColor.green; - // data[2] = mat.reflectColor.blue; - // data[3] = mat.reflect; - data[0] = 1.0; - data[1] = 1.0; - data[2] = 1.0; - data[3] = 0.0; - } else { - data[0] = 1.0; - data[1] = 1.0; - data[2] = 1.0; - data[3] = 0.0; - } - uniform.setUniform4fv(data); - }); - }); - */ - frag.addFunction(computeSimpleLighting); - - /* ###TODO: Source Lighting - frag.addFunction(computeSourceLighting); - - frag.addUniform("u_fstop", VariableType.Float, (shader) => { - shader.addGraphicUniform("u_fstop", (uniform, params) => { - uniform.setUniform1f(params.target.fStop); - }); - }); - - // frag.addUniform("u_ambientLight", VariableType.Vec3, (shader) => { - // shader.addGraphicUniform("u_ambientLight", (uniform, params) => { - // uniform.setUniform3fv(params.target.ambientLight); - // }); - // } - - frag.addUniform("u_lightCount", VariableType.Int, (shader) => { - shader.addGraphicUniform("u_lightCount", (uniform, params) => { - if (undefined === params.target.shaderLights) - uniform.setUniform1i(0); - else - uniform.setUniform1i(params.target.shaderLights.numLights); - }); - }); - - const name = "u_lightData[" + maxShaderLights + "*3]"; - frag.addUniform(name, VariableType.Vec4, (shader) => { - shader.addGraphicUniform("u_lightData[0]", (uniform, params) => { - if (undefined !== params.target.shaderLights) { - let numLights = params.target.shaderLights.numLights; - if (numLights > 0) { - if (numLights > maxShaderLights) - numLights = maxShaderLights; - uniform.setUniform4fv(params.target.shaderLights.data); - } - } - }); - }); - */ - frag.set(FragmentShaderComponent.ApplyLighting, applyLighting); } - -/* ###TODO: IBL -function addEnvironmentMap(frag: FragmentShaderBuilder) { - frag.addUniform("u_environmentMap", VariableType.Sampler2D, (shader) => { - shader.addGraphicUniform("u_environmentMap", (uniform, params) => { - if (undefined !== params.target.environmentMap) - params.target.environmentMap!.bindSampler (uniform, TextureUnit.EnvironmentMap); - return true; - }); - }); -} - -function addDiffuseMap(frag: FragmentShaderBuilder) { - frag.addUniform("u_diffuseMap", VariableType.Sampler2D, (shader) => { - shader.addGraphicUniform("u_diffuseMap", (uniform, params) => { - if (undefined !== params.target.diffuseMap) - params.target.diffuseMap.bindSampler (uniform, TextureUnit.DiffuseMap); - return true; - }); - }); -} -*/ diff --git a/core/frontend/src/render/webgl/glsl/Monochrome.ts b/core/frontend/src/render/webgl/glsl/Monochrome.ts index ef7dc9f..e857443 100644 --- a/core/frontend/src/render/webgl/glsl/Monochrome.ts +++ b/core/frontend/src/render/webgl/glsl/Monochrome.ts @@ -7,26 +7,21 @@ import { FragmentShaderBuilder, FragmentShaderComponent, VariableType } from "../ShaderBuilder"; import { FloatRgba } from "../FloatRGBA"; +// The alpha component of the mono color is 1.0 if lit, 0.0 if unlit. +// Unlit stuff (edges, polylines) uses the mono color directly in order for white-on-white reversal to work correctly. const applyMonochromeColor = ` - if (!isShaderBitSet(kShaderBit_Monochrome)) - return baseColor; + // Compute lit monochrome color + vec3 litRgb = baseColor.rgb; + litRgb /= max(0.0001, baseColor.a); // un-premultiply alpha + litRgb = vec3(dot(litRgb, vec3(.222, .707, .071))); + litRgb *= u_monoRgb.rgb; - // The alpha component of the mono color = 1.0 if lit, 0.0 if unlit - // We need to use the mono color directly for unlit stuff (i.e. edges, polylines) in order for white-on-white reversal to work correctly. - // But it looks terrible for lit surfaces. - if (u_monoRgb.a > 0.5) { - // Preserve translucency... - if (baseColor.a > 0.0) - baseColor.rgb /= baseColor.a; + // Select lit or unlit based on u_monoColor.a + vec4 monoColor = vec4(mix(u_monoRgb.rgb, litRgb, u_monoRgb.a), baseColor.a); + monoColor.rgb *= monoColor.a; - baseColor.rgb = vec3(dot(baseColor.rgb, vec3(.222, .707, .071))); - baseColor.rgb *= u_monoRgb.rgb; - } else { - baseColor.rgb = u_monoRgb.rgb; - } - - baseColor.rgb *= baseColor.a; - return baseColor; + // Select monochrome or element color based on shader flag + return mix(baseColor, monoColor, extractShaderBit(kShaderBit_Monochrome)); `; export function addMonochrome(frag: FragmentShaderBuilder): void { diff --git a/core/frontend/src/render/webgl/glsl/PointCloud.ts b/core/frontend/src/render/webgl/glsl/PointCloud.ts index 60c70db..1b2aecb 100644 --- a/core/frontend/src/render/webgl/glsl/PointCloud.ts +++ b/core/frontend/src/render/webgl/glsl/PointCloud.ts @@ -6,7 +6,7 @@ import { assert } from "@bentley/bentleyjs-core"; import { addModelViewProjectionMatrix } from "./Vertex"; -import { addHiliter } from "./FeatureSymbology"; +import { addUniformHiliter } from "./FeatureSymbology"; import { ProgramBuilder, VertexShaderComponent, FragmentShaderComponent, VariableType } from "../ShaderBuilder"; import { PointCloudGeometry } from "../PointCloud"; import { GL } from "../GL"; @@ -44,6 +44,6 @@ export function createPointCloudBuilder(): ProgramBuilder { export function createPointCloudHiliter(): ProgramBuilder { const builder = createBuilder(); - addHiliter(builder, false, true); + addUniformHiliter(builder); return builder; } diff --git a/core/frontend/src/render/webgl/glsl/PointString.ts b/core/frontend/src/render/webgl/glsl/PointString.ts index 70f47ba..cc3ef64 100644 --- a/core/frontend/src/render/webgl/glsl/PointString.ts +++ b/core/frontend/src/render/webgl/glsl/PointString.ts @@ -13,9 +13,7 @@ import { ProgramBuilder, VertexShaderComponent, VariableType, FragmentShaderComp const computePosition = ` float lineWeight = ComputeLineWeight(); - if (lineWeight > 4.0) - lineWeight += 0.5; // ###TODO: Fudge factor for rounding fat points... - + lineWeight += 0.5 * float(lineWeight > 4.0); // fudge factor for rounding fat points... gl_PointSize = lineWeight; return u_mvp * rawPos; `; diff --git a/core/frontend/src/render/webgl/glsl/Surface.ts b/core/frontend/src/render/webgl/glsl/Surface.ts index 2899c21..7e7c19e 100644 --- a/core/frontend/src/render/webgl/glsl/Surface.ts +++ b/core/frontend/src/render/webgl/glsl/Surface.ts @@ -37,27 +37,15 @@ const sampleSurfaceTexture = ` } `; +// u_matRgb.a = 1.0 if color overridden by material, 0.0 otherwise. +// u_matAlpha.y = 1.0 if alpha overridden by material. +// if this is a raster glyph, the sampled color has already been modified - do not modify further. const applyMaterialOverrides = ` - bool isTextured = isSurfaceBitSet(kSurfaceBit_HasTexture); - bool useTextureWeight = isTextured && u_textureWeight < 1.0; - bool useMatColor = !isSurfaceBitSet(kSurfaceBit_IgnoreMaterial) && (!isTextured || useTextureWeight); - - if (useMatColor) { - // u_matRgb.a = 1.0 if color overridden by material, 0.0 otherwise. - if (u_matRgb.a > 0.5) - baseColor.rgb = u_matRgb.rgb * baseColor.a; - - // u_matAlpha.y = 1.0 if alpha overridden by material. - if (u_matAlpha.y > 0.5) - baseColor = adjustPreMultipliedAlpha(baseColor, u_matAlpha.x); - } - - if (useTextureWeight) { - vec4 texColor = sampleSurfaceTexture(); - baseColor = mix(baseColor, texColor, u_textureWeight); - } - - return baseColor; + float useMatColor = 1.0 - extractSurfaceBit(kSurfaceBit_IgnoreMaterial); + vec4 matColor = mix(baseColor, vec4(u_matRgb.rgb * baseColor.a, baseColor.a), useMatColor * u_matRgb.a); + matColor = mix(matColor, adjustPreMultipliedAlpha(matColor, u_matAlpha.x), useMatColor * u_matAlpha.y); + float textureWeight = u_textureWeight * extractSurfaceBit(kSurfaceBit_HasTexture) * (1.0 - u_applyGlyphTex); + return mix(matColor, g_surfaceTexel, textureWeight); `; export function addMaterial(frag: FragmentShaderBuilder): void { @@ -117,8 +105,12 @@ export function createSurfaceHiliter(): ProgramBuilder { } // nvidia hardware incorrectly interpolates varying floats when we send the same exact value for every vertex... +const extractSurfaceBit = ` +float extractSurfaceBit(float flag) { return extractNthBit(floor(v_surfaceFlags + 0.5), flag); } +`; + const isSurfaceBitSet = ` -bool isSurfaceBitSet(float flag) { return 0.0 != extractNthBit(floor(v_surfaceFlags + 0.5), flag); } +bool isSurfaceBitSet(float flag) { return 0.0 != extractSurfaceBit(flag); } `; function addSurfaceFlagsLookup(builder: ShaderBuilder) { @@ -142,6 +134,7 @@ function addSurfaceFlagsLookup(builder: ShaderBuilder) { builder.addConstant("kSurfaceMask_EnvironmentMap", VariableType.Float, "128.0"); builder.addFunction(GLSLCommon.extractNthBit); + builder.addFunction(extractSurfaceBit); builder.addFunction(isSurfaceBitSet); } @@ -174,18 +167,11 @@ vec3 octDecodeNormal(vec2 e) { `; const computeNormal = ` - if (!isSurfaceBitSet(kSurfaceBit_HasNormals)) - return vec3(0.0); - - vec2 normal = g_vertexData2; - if (isSurfaceBitSet(kSurfaceBit_HasColorAndNormal)) { - vec2 tc = g_vertexBaseCoords; - tc.x += 3.0 * g_vert_stepX; - vec4 enc = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); - normal = enc.xy; - } - - return normalize(u_nmx * octDecodeNormal(normal)); + vec2 tc = g_vertexBaseCoords; + tc.x += 3.0 * g_vert_stepX; + vec4 enc = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); + vec2 normal = mix(g_vertexData2, enc.xy, extractSurfaceBit(kSurfaceBit_HasColorAndNormal)); + return mix(vec3(0.0), normalize(u_nmx * octDecodeNormal(normal)), extractSurfaceBit(kSurfaceBit_HasNormals)); `; const computeAnimatedNormal = ` @@ -194,19 +180,14 @@ const computeAnimatedNormal = ` ` + computeNormal; const applyBackgroundColor = ` - if (isSurfaceBitSet(kSurfaceBit_BackgroundFill)) - baseColor.rgb = u_bgColor.rgb; - - return baseColor; + return mix(baseColor, vec4(u_bgColor.rgb, 1.0), extractSurfaceBit(kSurfaceBit_BackgroundFill)); `; const computeTexCoord = ` - if (!isSurfaceBitSet(kSurfaceBit_HasTexture)) - return vec2(0.0); vec2 tc = g_vertexBaseCoords; tc.x += 3.0 * g_vert_stepX; vec4 rgba = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); vec2 qcoords = vec2(decodeUInt16(rgba.xy), decodeUInt16(rgba.zw)); - return unquantize2d(qcoords, u_qTexCoordParams); + return mix(vec2(0.0), unquantize2d(qcoords, u_qTexCoordParams), extractSurfaceBit(kSurfaceBit_HasTexture)); `; const computeAnimatedTexCoord = ` if (u_animScalarQParams.x >= 0.0) @@ -216,27 +197,30 @@ const getSurfaceColor = ` vec4 getSurfaceColor() { return v_color; } `; +// If we have texture weight < 1.0 we must compute the element/material color first then mix with texture color +// in ApplyMaterialOverrides(). Do the sample once, here, and store in a global variable for possible later use. +// If a glyph texture, must mix getSurfaceColor() with texture color so texture color alpha is applied 100% and +// surface color rgb is scaled by texture color rgb (latter is full white originally but stretched via mipmapping). const computeBaseColor = ` - if (isSurfaceBitSet(kSurfaceBit_HasTexture) && u_textureWeight >= 1.0) { - // if a glyph texture, must mix getSurfaceColor() with texCol so texCol.a is applied 100% and - // surfCol.rgb is scaled by texCol.rgb (texCol.rgb = full white originally but stretched via mipMapping) - if (u_applyGlyphTex > 0) { - vec4 surfCol = getSurfaceColor(); - const vec3 white = vec3(1.0); - const vec3 epsilon = vec3(0.0001); - vec3 color = surfCol.a > 0.0 ? surfCol.rgb / surfCol.a : surfCol.rgb; // revert premultiplied alpha - vec3 delta = (color + epsilon) - white; - if (u_reverseWhiteOnWhite > 0.5 && delta.x > 0.0 && delta.y > 0.0 && delta.z > 0.0) - surfCol.rgb = vec3(0.0); - - vec4 texCol = sampleSurfaceTexture(); - return vec4(surfCol.rgb * texCol.rgb, texCol.a); - } else { - return sampleSurfaceTexture(); - } - } else { - return getSurfaceColor(); // if textured, compute surface/material color first then mix with texture in applyMaterialOverrides... - } + g_surfaceTexel = sampleSurfaceTexture(); + vec4 surfaceColor = getSurfaceColor(); + + // Compute color for raster glyph. + vec4 glyphColor = surfaceColor; + const vec3 white = vec3(1.0); + const vec3 epsilon = vec3(0.0001); + vec3 color = glyphColor.rgb / max(0.0001, glyphColor.a); // revert premultiplied alpha + vec3 delta = (color + epsilon) - white; + + // set to black if almost white + glyphColor.rgb *= float(u_reverseWhiteOnWhite <= 0.5 || delta.x <= 0.0 || delta.y <= 0.0 || delta.z <= 0.0); + glyphColor = vec4(glyphColor.rgb * g_surfaceTexel.rgb, g_surfaceTexel.a); + + // Choose glyph color or unmodified texture sample + vec4 texColor = mix(g_surfaceTexel, glyphColor, u_applyGlyphTex); + + // If untextured, or textureWeight < 1.0, choose surface color. + return mix(surfaceColor, texColor, extractSurfaceBit(kSurfaceBit_HasTexture) * floor(u_textureWeight)); `; function addSurfaceFlags(builder: ProgramBuilder, withFeatureOverrides: boolean) { @@ -315,13 +299,15 @@ export function createSurfaceBuilder(feat: FeatureMode, animated: boolean): Prog addTexture(builder, animated); - builder.frag.addUniform("u_applyGlyphTex", VariableType.Int, (prog) => { + builder.frag.addUniform("u_applyGlyphTex", VariableType.Float, (prog) => { prog.addGraphicUniform("u_applyGlyphTex", (uniform, params) => { const surfGeom = params.geometry as SurfaceGeometry; const surfFlags: SurfaceFlags = surfGeom.computeSurfaceFlags(params.programParams); - if (SurfaceFlags.None !== (SurfaceFlags.HasTexture & surfFlags)) { - uniform.setUniform1i(surfGeom.isGlyph ? 1 : 0); - } + let isGlyph = false; + if (SurfaceFlags.None !== (SurfaceFlags.HasTexture & surfFlags)) + isGlyph = surfGeom.isGlyph; + + uniform.setUniform1f(isGlyph ? 1 : 0); }); }); @@ -340,6 +326,7 @@ export function createSurfaceBuilder(feat: FeatureMode, animated: boolean): Prog addPickBufferOutputs(builder.frag); } + builder.frag.addGlobal("g_surfaceTexel", VariableType.Vec4); builder.frag.set(FragmentShaderComponent.ComputeBaseColor, computeBaseColor); return builder; diff --git a/core/frontend/src/render/webgl/glsl/Translucency.ts b/core/frontend/src/render/webgl/glsl/Translucency.ts index 4a27d44..fbd68c5 100644 --- a/core/frontend/src/render/webgl/glsl/Translucency.ts +++ b/core/frontend/src/render/webgl/glsl/Translucency.ts @@ -11,26 +11,27 @@ import { addFrustum, addEyeSpace } from "./Common"; import { System } from "../System"; const computeAlphaWeight = ` -float computeAlphaWeight(float a, bool flatAlpha) { +float computeAlphaWeight(float a, float flatAlpha) { // See Weighted Blended Order-Independent Transparency for examples of different weighting functions: // http://jcgt.org/published/0002/02/09/ // We are using Equation 10 from the above paper. Equation 10 directly uses screen-space gl_FragCoord.z. // flatAlphaWeight bit is set if we want to apply OIT transparency using a constant Z value of 1. // computeLinearDepth() removes the perspective and puts z in linear [0..1] - float z = flatAlpha ? 1.0 : computeLinearDepth(v_eyeSpace.z); + float z = mix(computeLinearDepth(v_eyeSpace.z), 1.0, flatAlpha); return pow(a + 0.01, 4.0) + max(1e-2, 3.0 * 1e3 * pow(z, 3.0)); } `; const computeOutputs = ` - bool flatAlpha = isShaderBitSet(kShaderBit_OITScaleOutput); + float flatAlpha = extractShaderBit(kShaderBit_OITFlatAlphaWeight); + float scaleOutput = extractShaderBit(kShaderBit_OITScaleOutput); vec3 Ci = baseColor.rgb; float ai = min(0.99, baseColor.a); // OIT algorithm does not nicely handle a=1 float wzi = computeAlphaWeight(ai, flatAlpha); // If we are scaling output into the 0 to 1 range, we use the maximum output of the alpha weight function. - float outputScale = flatAlpha ? 1.0 / 3001.040604 : 1.0; + float outputScale = mix(1.0, 1.0 / 3001.040604, scaleOutput); vec4 output0 = vec4(Ci * wzi * outputScale, ai); vec4 output1 = vec4(ai * wzi * outputScale); diff --git a/core/frontend/src/render/webgl/glsl/Vertex.ts b/core/frontend/src/render/webgl/glsl/Vertex.ts index a3e7198..2429ba3 100644 --- a/core/frontend/src/render/webgl/glsl/Vertex.ts +++ b/core/frontend/src/render/webgl/glsl/Vertex.ts @@ -48,7 +48,7 @@ vec4 unquantizeVertexPosition(vec3 encodedIndex, vec3 origin, vec3 scale) { `; const computeAnimationFrameDisplacement = ` -vec3 computeAnimationFrameDisplacement(float frameIndex, vec3 origin, vec3 scale) { +vec3 computeAnimationFrameDisplacement(float vertexLUTIndex, float frameIndex, vec3 origin, vec3 scale) { vec2 tc = computeLUTCoords(frameIndex + g_vertexLUTIndex * 2.0, u_vertParams.xy, g_vert_center, 1.0); vec4 enc1 = floor(TEXTURE(u_vertLUT, tc) * 255.0 + 0.5); tc.x += g_vert_stepX; @@ -58,12 +58,12 @@ vec3 computeAnimationFrameDisplacement(float frameIndex, vec3 origin, vec3 scale }`; const computeAnimationDisplacement = ` -vec3 computeAnimationDisplacement(float frameIndex0, float frameIndex1, float fraction, vec3 origin, vec3 scale) { +vec3 computeAnimationDisplacement(float vertexLUTIndex, float frameIndex0, float frameIndex1, float fraction, vec3 origin, vec3 scale) { if (frameIndex0 < 0.0) return vec3(0.0, 0.0, 0.0); -vec3 displacement = computeAnimationFrameDisplacement(frameIndex0, origin, scale); +vec3 displacement = computeAnimationFrameDisplacement(vertexLUTIndex, frameIndex0, origin, scale); if (fraction > 0.0) { - vec3 displacement1 = computeAnimationFrameDisplacement(frameIndex1, origin, scale); + vec3 displacement1 = computeAnimationFrameDisplacement(vertexLUTIndex, frameIndex1, origin, scale); displacement += fraction * (displacement1 - displacement); } return displacement; @@ -176,7 +176,7 @@ export function addAnimation(vert: VertexShaderBuilder, includeTexture: boolean, vert.addUniform("u_animDispParams", VariableType.Vec3, (prog) => { prog.addGraphicUniform("u_animDispParams", (uniform, params) => { - scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = .0; + scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = 0.0; const lutGeom: LUTGeometry = params.geometry as LUTGeometry; const analysisStyle = params.target.analysisStyle; let channel: any; @@ -191,25 +191,27 @@ export function addAnimation(vert: VertexShaderBuilder, includeTexture: boolean, const lutGeom: LUTGeometry = params.geometry as LUTGeometry; const analysisStyle = params.target.analysisStyle; let channel: any; - if (lutGeom.lut.auxDisplacements && analysisStyle && analysisStyle.displacementChannelName && undefined !== (channel = lutGeom.lut.auxDisplacements.get(analysisStyle.displacementChannelName))) - uniform.setUniform3fv(channel.qScale); - else { - scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = .0; - uniform.setUniform3fv(scratchAnimDisplacementParams); + scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = 0.0; + if (lutGeom.lut.auxDisplacements && analysisStyle && analysisStyle.displacementChannelName && undefined !== (channel = lutGeom.lut.auxDisplacements.get(analysisStyle.displacementChannelName))) { + const displacementScale = analysisStyle.displacementScale ? analysisStyle.displacementScale : 1.0; + for (let i = 0; i < 3; i++) + scratchAnimDisplacementParams[i] = channel.qScale[i] * displacementScale; // Apply displacement scale. } + uniform.setUniform3fv(scratchAnimDisplacementParams); }); }); vert.addUniform("u_qAnimDispOrigin", VariableType.Vec3, (prog) => { prog.addGraphicUniform("u_qAnimDispOrigin", (uniform, params) => { const lutGeom: LUTGeometry = params.geometry as LUTGeometry; const analysisStyle = params.target.analysisStyle; + scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = 0.0; let channel: any; - if (lutGeom.lut.auxDisplacements && analysisStyle && analysisStyle.displacementChannelName && undefined !== (channel = lutGeom.lut.auxDisplacements.get(analysisStyle.displacementChannelName))) - uniform.setUniform3fv(channel.qOrigin); - else { - scratchAnimDisplacementParams[0] = scratchAnimDisplacementParams[1] = scratchAnimDisplacementParams[2] = .0; - uniform.setUniform3fv(scratchAnimDisplacementParams); + if (lutGeom.lut.auxDisplacements && analysisStyle && analysisStyle.displacementChannelName && undefined !== (channel = lutGeom.lut.auxDisplacements.get(analysisStyle.displacementChannelName))) { + const displacementScale = analysisStyle.displacementScale ? analysisStyle.displacementScale : 1.0; + for (let i = 0; i < 3; i++) + scratchAnimDisplacementParams[i] = channel.qOrigin[i] * displacementScale; // Apply displacement scale } + uniform.setUniform3fv(scratchAnimDisplacementParams); }); }); if (includeNormal) { diff --git a/core/frontend/src/tile/RealityModelTileTree.ts b/core/frontend/src/tile/RealityModelTileTree.ts index 98928a1..7561116 100644 --- a/core/frontend/src/tile/RealityModelTileTree.ts +++ b/core/frontend/src/tile/RealityModelTileTree.ts @@ -13,8 +13,10 @@ import { TileTree, TileTreeState, Tile, TileLoader } from "./TileTree"; import { IModelApp } from "../IModelApp"; /** @hidden */ -class CesiumUtils { - public static rangeFromBoundingVolume(boundingVolume: any): Range3d { +export class RealityModelTileUtils { + public static rangeFromBoundingVolume(boundingVolume: any): Range3d | undefined { + if (undefined === boundingVolume || !Array.isArray(boundingVolume.box)) + return undefined; const box: number[] = boundingVolume.box; const center = Point3d.create(box[0], box[1], box[2]); const ux = Vector3d.create(box[3], box[4], box[5]); @@ -66,12 +68,12 @@ class RealityModelTileProps implements TileProps { public hasContents: boolean; constructor(json: any, thisId: string) { this.contentId = thisId; - this.range = CesiumUtils.rangeFromBoundingVolume(json.boundingVolume); + this.range = RealityModelTileUtils.rangeFromBoundingVolume(json.boundingVolume)!; this.isLeaf = !Array.isArray(json.children) || 0 === json.children.length; this.hasContents = undefined !== json.content && undefined !== json.content.url; if (this.hasContents) { - this.contentRange = json.content.boundingVolume && CesiumUtils.rangeFromBoundingVolume(json.content.boundingVolume); - this.maximumSize = CesiumUtils.maximumSizeFromGeometricTolerance(Range3d.fromJSON(this.range), json.geometricError); + this.contentRange = json.content.boundingVolume && RealityModelTileUtils.rangeFromBoundingVolume(json.content.boundingVolume); + this.maximumSize = RealityModelTileUtils.maximumSizeFromGeometricTolerance(Range3d.fromJSON(this.range), json.geometricError); } else { this.maximumSize = 0.0; } @@ -135,7 +137,7 @@ class RealityModelTileLoader extends TileLoader { let foundChild = tilesetJson.children[childIndex]; const thisParentId = parentId.length ? (parentId + "_" + childId) : childId; - if (separatorIndex >= 0) { return await this.findTileInJson(foundChild, id.substring(separatorIndex + 1), thisParentId); } + if (separatorIndex >= 0) { return this.findTileInJson(foundChild, id.substring(separatorIndex + 1), thisParentId); } if (undefined !== foundChild.content && foundChild.content.url.endsWith("json")) { // A child may contain a subTree... const subTree = await this._tree.client.getTileJson(foundChild.content.url); foundChild = subTree.root; @@ -161,7 +163,7 @@ export class RealityModelTileTree { const tileClient = new RealityModelTileClient(url); const json = await tileClient.getRootDocument(url); const ecefLocation = iModel.ecefLocation; - const rootTransform: Transform = CesiumUtils.transformFromJson(json.root.transform); + const rootTransform: Transform = RealityModelTileUtils.transformFromJson(json.root.transform); let tilesetToDb = Transform.createIdentity(); if (undefined !== tilesetToDbJson) { diff --git a/core/frontend/src/tile/TileIO.ts b/core/frontend/src/tile/TileIO.ts index 8d099c2..6969770 100644 --- a/core/frontend/src/tile/TileIO.ts +++ b/core/frontend/src/tile/TileIO.ts @@ -106,7 +106,7 @@ export namespace TileIO { public nextUint32s(numUint32s: number): Uint32Array { const numBytes = numUint32s * 4; - const uint32s = new Uint32Array(this.arrayBuffer, this.curPos, numBytes); + const uint32s = new Uint32Array(this.arrayBuffer, this.curPos, numUint32s); this.advance(numBytes); return uint32s; } diff --git a/core/frontend/src/tile/TileTree.ts b/core/frontend/src/tile/TileTree.ts index 6f8d3d2..6a2b5fe 100644 --- a/core/frontend/src/tile/TileTree.ts +++ b/core/frontend/src/tile/TileTree.ts @@ -43,7 +43,6 @@ import { PntsTileIO } from "./PntsTileIO"; import { DgnTileIO } from "./DgnTileIO"; import { IModelTileIO } from "./IModelTileIO"; import { ViewFrustum } from "../Viewport"; -import { SpatialModelState } from "../ModelState"; function compareMissingTiles(lhs: Tile, rhs: Tile): number { const diff = compareNumbers(lhs.depth, rhs.depth); @@ -303,7 +302,7 @@ export class Tile implements IDisposable { } if (Tile.Visibility.Visible === vis) { // This tile is of appropriate resolution to draw. If need loading or refinement, enqueue. - if (!this.isReady && !this.isQueued) { + if (!this.isReady) { args.insertMissing(this); } @@ -670,7 +669,7 @@ export class TileTree implements IDisposable { public requestTiles(missing: Tile[]): void { // TBD - cancel any loaded/queued tiles which are no longer needed. - this.loader.loadTileContents(missing); + this.loader.loadTileContents(missing); // tslint:disable-line:no-floating-promises } public createDrawArgs(context: SceneContext): Tile.DrawArgs { @@ -744,7 +743,7 @@ export abstract class TileLoader { const read = reader.read(); read.catch((_err) => tile.setNotFound()); - read.then((result) => { + read.then((result) => { // tslint:disable-line:no-floating-promises // Make sure we still want this tile - may been unloaded, imodel may have been closed, IModelApp may have shut down taking render system with it, etc. if (tile.isLoading) { tile.setGraphic(result.renderGraphic, result.isLeaf, result.contentRange, result.sizeMultiplier); @@ -898,11 +897,11 @@ export namespace TileTree { export class TileTreeState { public tileTree?: TileTree; public loadStatus: TileTree.LoadStatus = TileTree.LoadStatus.NotLoaded; - public get iModel() { return this._modelState.iModel; } + public get iModel() { return this._iModel; } - constructor(private _modelState: SpatialModelState) { } + constructor(private _iModel: IModelConnection, private _is3d: boolean, private _modelId: Id64String) { } public setTileTree(props: TileTreeProps, loader: TileLoader) { - this.tileTree = new TileTree(TileTree.Params.fromJSON(props, this._modelState.iModel, this._modelState.is3d, loader, this._modelState.id)); + this.tileTree = new TileTree(TileTree.Params.fromJSON(props, this._iModel, this._is3d, loader, this._modelId)); this.loadStatus = TileTree.LoadStatus.Loaded; } } diff --git a/core/frontend/src/tile/WebMercatorTileTree.ts b/core/frontend/src/tile/WebMercatorTileTree.ts index 2d56455..d9aade7 100644 --- a/core/frontend/src/tile/WebMercatorTileTree.ts +++ b/core/frontend/src/tile/WebMercatorTileTree.ts @@ -5,7 +5,7 @@ /** @module Tile */ import { assert, ActivityLoggingContext, Guid } from "@bentley/bentleyjs-core"; -import { TileTreeProps, TileProps, Cartographic, ImageSource, ImageSourceFormat, RenderTexture, EcefLocation } from "@bentley/imodeljs-common"; +import { TileTreeProps, TileProps, Cartographic, ImageSource, ImageSourceFormat, RenderTexture, EcefLocation, BackgroundMapType, BackgroundMapProps } from "@bentley/imodeljs-common"; import { JsonUtils } from "@bentley/bentleyjs-core"; import { Range3dProps, Range3d, TransformProps, Transform, Point3d, Point2d, Range2d, Vector3d, Angle } from "@bentley/geometry-core"; import { TileLoader, TileTree, Tile, TileRequests } from "./TileTree"; @@ -188,7 +188,7 @@ class WebMercatorTileLoader extends TileLoader { } else { const textureLoad = this.loadTextureImage(imageSource as ImageSource, this._iModel, IModelApp.renderSystem); textureLoad.catch((_err) => missingTile.setNotFound()); - textureLoad.then((result) => { + textureLoad.then((result) => { // tslint:disable-line:no-floating-promises missingTile.setGraphic(IModelApp.renderSystem.createTile(result as RenderTexture, corners as Point3d[])); }); } @@ -210,14 +210,11 @@ class WebMercatorTileLoader extends TileLoader { public get maxDepth(): number { return this._providerInitialized ? this._imageryProvider.maximumZoomLevel : 32; } } -// The type of background map -enum MapType { Street = 1, Aerial = 2, Hybrid = 3 } // These are aligned with BackgroundMapType in MicroStation. - // Represents the service that is providing map tiles for Web Mercator models (background maps). abstract class ImageryProvider { - public mapType: MapType; + public mapType: BackgroundMapType; - constructor(mapType: MapType) { + constructor(mapType: BackgroundMapType) { this.mapType = mapType; } @@ -324,7 +321,7 @@ class BingMapProvider extends ImageryProvider { private _missingTileData?: Uint8Array; private _logoImage?: HTMLImageElement; - constructor(mapType: MapType) { + constructor(mapType: BackgroundMapType) { super(mapType); this._zoomMin = this._zoomMax = 0; this._tileHeight = this._tileWidth = 0; @@ -398,7 +395,7 @@ class BingMapProvider extends ImageryProvider { for (const match of matchingAttributions) { dataString = dataString.concat("
  • ", match.copyrightMessage, "
  • "); } - IModelApp.notifications.openMessageBox(MessageBoxType.LargeOk, dataString, MessageBoxIconType.Information); + IModelApp.notifications.openMessageBox(MessageBoxType.LargeOk, dataString, MessageBoxIconType.Information); // tslint:disable-line:no-floating-promises } public getCopyrightImage(_bgMapState: BackgroundMapState): HTMLImageElement | undefined { return this._logoImage; } @@ -434,9 +431,9 @@ class BingMapProvider extends ImageryProvider { const alctx = new ActivityLoggingContext(Guid.createValue()); let imagerySet = "Road"; - if (MapType.Aerial === this.mapType) + if (BackgroundMapType.Aerial === this.mapType) imagerySet = "Aerial"; - else if (MapType.Hybrid === this.mapType) + else if (BackgroundMapType.Hybrid === this.mapType) imagerySet = "AerialWithLabels"; let bingRequestUrl: string = "http://dev.virtualearth.net/REST/v1/Imagery/Metadata/{imagerySet}?o=json&incl=ImageryProviders&key={bingKey}"; @@ -462,7 +459,7 @@ class BingMapProvider extends ImageryProvider { this.readAttributions(thisResourceProps.imageryProviders); // read the Bing logo data, used in getCopyrightImage - this.readLogo().then((logoByteArray) => { + this.readLogo().then((logoByteArray) => { // tslint:disable-line:no-floating-promises this._logoImage = new Image(); const base64Data = Base64.btoa(String.fromCharCode.apply(null, logoByteArray)); this._logoImage.src = "data:image/png;base64," + base64Data; @@ -472,7 +469,7 @@ class BingMapProvider extends ImageryProvider { // for tiles at zoom levels where they don't have data. Their application stops you from zooming in when that's the // case, but we can't stop - the user might want to look at design data a closer zoom. So we intentionally load such // a tile, and then compare other tiles to it, rejecting them if they match. - this.loadTile(0, 0, this._zoomMax - 1).then((tileData: ImageSource | undefined) => { + this.loadTile(0, 0, this._zoomMax - 1).then((tileData: ImageSource | undefined) => { // tslint:disable-line:no-floating-promises if (tileData !== undefined) this._missingTileData = tileData.data; }); } catch (error) { @@ -481,7 +478,7 @@ class BingMapProvider extends ImageryProvider { } // reads the Bing logo from the url returned as part of the first response. - private readLogo(): Promise { + private async readLogo(): Promise { const alctx = new ActivityLoggingContext(Guid.createValue()); if (!this._logoUrl || (this._logoUrl.length === 0)) return Promise.resolve(undefined); @@ -520,19 +517,19 @@ class MapBoxProvider extends ImageryProvider { private _zoomMax: number; private _baseUrl: string; - constructor(mapType: MapType) { + constructor(mapType: BackgroundMapType) { super(mapType); this._zoomMin = 1; this._zoomMax = 20; switch (mapType) { - case MapType.Street: + case BackgroundMapType.Street: this._baseUrl = "http://api.mapbox.com/v4/mapbox.streets/"; break; - case MapType.Aerial: + case BackgroundMapType.Aerial: this._baseUrl = "http://api.mapbox.com/v4/mapbox.satellite/"; break; - case MapType.Hybrid: + case BackgroundMapType.Hybrid: this._baseUrl = "http://api.mapbox.com/v4/mapbox.streets-satellite/"; break; @@ -575,9 +572,9 @@ export class BackgroundMapState { private _tileTree?: TileTree; private _loadStatus: TileTree.LoadStatus = TileTree.LoadStatus.NotLoaded; private _provider?: ImageryProvider; - private _providerName: string; + public providerName: string; private _groundBias: number; - private _mapType: MapType; + public mapType: BackgroundMapType; public setTileTree(props: TileTreeProps, loader: TileLoader) { this._tileTree = new TileTree(TileTree.Params.fromJSON(props, this._iModel, true, loader, "")); @@ -597,11 +594,11 @@ export class BackgroundMapState { return displayTiles; } - public constructor(json: any, private _iModel: IModelConnection) { - this._providerName = JsonUtils.asString(json.providerName, "BingProvider"); + public constructor(json: BackgroundMapProps, private _iModel: IModelConnection) { + this.providerName = JsonUtils.asString(json.providerName, "BingProvider"); // this.providerData = JsonUtils.asString(json.providerData, "aerial"); this._groundBias = JsonUtils.asDouble(json.groundBias, 0.0); - this._mapType = json.providerData ? JsonUtils.asInt(json.providerData.mapType, MapType.Hybrid) : MapType.Hybrid; + this.mapType = json.providerData ? JsonUtils.asInt(json.providerData.mapType, BackgroundMapType.Hybrid) : BackgroundMapType.Hybrid; } private loadTileTree(): TileTree.LoadStatus { @@ -612,10 +609,10 @@ export class BackgroundMapState { return this._loadStatus; } - if ("BingProvider" === this._providerName) { - this._provider = new BingMapProvider(this._mapType); - } else if ("MapBoxProvider" === this._providerName) { - this._provider = new MapBoxProvider(this._mapType); + if ("BingProvider" === this.providerName) { + this._provider = new BingMapProvider(this.mapType); + } else if ("MapBoxProvider" === this.providerName) { + this._provider = new MapBoxProvider(this.mapType); } if (this._provider === undefined) throw new BentleyError(IModelStatus.BadModel, "WebMercator provider invalid"); @@ -667,4 +664,12 @@ export class BackgroundMapState { style.pointerEvents = "initial"; } } + + public equalsProps(props: BackgroundMapProps): boolean { + const providerName = JsonUtils.asString(props.providerName, "BingProvider"); + const groundBias = JsonUtils.asDouble(props.groundBias, 0.0); + const mapType = undefined !== props.providerData ? JsonUtils.asInt(props.providerData.mapType, BackgroundMapType.Hybrid) : BackgroundMapType.Hybrid; + + return providerName === this.providerName && groundBias === this._groundBias && mapType === this.mapType; + } } diff --git a/core/frontend/src/tools/AccuDrawTool.ts b/core/frontend/src/tools/AccuDrawTool.ts index edb9d3e..2ff3a74 100644 --- a/core/frontend/src/tools/AccuDrawTool.ts +++ b/core/frontend/src/tools/AccuDrawTool.ts @@ -990,7 +990,7 @@ class AccuDrawShortcutsTool extends InputCollector { public onPostInstall(): void { super.onPostInstall(); this.initLocateElements(false, true, undefined, CoordinateLockOverrides.None); this._shortcut.doManipulationStart(); } // NOTE: InputCollector inherits suspended primitive's state, set everything... public onCleanup(): void { this._shortcut.doManipulationStop(this._cancel); } public async onDataButtonDown(ev: BeButtonEvent): Promise { if (await this._shortcut.doManipulation(ev, false)) { this._cancel = false; this.exitTool(); } return EventHandled.No; } - public async onMouseMotion(ev: BeButtonEvent): Promise { this._shortcut.doManipulation(ev, true); } + public async onMouseMotion(ev: BeButtonEvent): Promise { this._shortcut.doManipulation(ev, true); } // tslint:disable-line:no-floating-promises public decorate(context: DecorateContext) { this._shortcut.onDecorate(context); } public exitTool() { super.exitTool(); AccuDrawShortcuts.requestInputFocus(); } // re-grab focus when auto-focus tool setting set... } @@ -1000,7 +1000,7 @@ export abstract class AccuDrawTool { if (this.activateAccuDrawOnStart()) IModelApp.accuDraw.activate(); - this.doManipulation(undefined, true); + this.doManipulation(undefined, true); // tslint:disable-line:no-floating-promises } public doManipulationStop(cancel: boolean) { diff --git a/core/frontend/src/tools/EditManipulator.ts b/core/frontend/src/tools/EditManipulator.ts index 9eb7a6a..4676aaa 100644 --- a/core/frontend/src/tools/EditManipulator.ts +++ b/core/frontend/src/tools/EditManipulator.ts @@ -42,9 +42,9 @@ export namespace EditManipulator { public onPostInstall(): void { super.onPostInstall(); this.init(); } public async onDataButtonDown(ev: BeButtonEvent): Promise { if (!this.accept(ev)) return EventHandled.No; this.exitTool(); this.manipulator.onManipulatorEvent(EventType.Accept); return EventHandled.Yes; } public async onResetButtonUp(ev: BeButtonEvent): Promise { if (!this.cancel(ev)) return EventHandled.No; this.exitTool(); this.manipulator.onManipulatorEvent(EventType.Cancel); return EventHandled.Yes; } - public async onTouchMove(ev: BeTouchEvent): Promise { IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } - public async onTouchComplete(ev: BeTouchEvent): Promise { IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } - public async onTouchCancel(ev: BeTouchEvent): Promise { IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } + public async onTouchMove(ev: BeTouchEvent): Promise { return IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } + public async onTouchComplete(ev: BeTouchEvent): Promise { return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } + public async onTouchCancel(ev: BeTouchEvent): Promise { return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } } export abstract class HandleProvider { @@ -125,7 +125,7 @@ export namespace EditManipulator { /** run tool to handle interactive drag/click modification. */ protected abstract modifyControls(_hit: HitDetail, _ev: BeButtonEvent): boolean; - public onManipulatorEvent(_eventType: EventType): void { this.updateControls(); } + public onManipulatorEvent(_eventType: EventType): void { this.updateControls(); } // tslint:disable-line:no-floating-promises protected async onDoubleClick(_hit: HitDetail, _ev: BeButtonEvent): Promise { return EventHandled.No; } protected async onRightClick(_hit: HitDetail, _ev: BeButtonEvent): Promise { return EventHandled.No; } diff --git a/core/frontend/src/tools/EventController.ts b/core/frontend/src/tools/EventController.ts index 2885e10..ff2a9ef 100644 --- a/core/frontend/src/tools/EventController.ts +++ b/core/frontend/src/tools/EventController.ts @@ -25,7 +25,7 @@ export class EventController { this.addDomListeners(["mousedown", "mouseup", "mousemove", "mouseenter", "mouseleave", "wheel", "touchstart", "touchend", "touchcancel", "touchmove"], element); element.oncontextmenu = () => false; - element.onselectstart = () => false; + (element as any).onselectstart = () => false; // TODO: onselectstart is experimental. This cast should be removed once it becomes official. } public destroy() { diff --git a/core/frontend/src/tools/IdleTool.ts b/core/frontend/src/tools/IdleTool.ts index ea54548..f6de182 100644 --- a/core/frontend/src/tools/IdleTool.ts +++ b/core/frontend/src/tools/IdleTool.ts @@ -148,11 +148,11 @@ export class IdleTool extends InteractiveTool { public async onTouchTap(ev: BeTouchEvent): Promise { if (ev.isSingleTap) { // Send data down/up for single finger tap. - IModelApp.toolAdmin.convertTouchTapToButtonDownAndUp(ev, BeButton.Data); + IModelApp.toolAdmin.convertTouchTapToButtonDownAndUp(ev, BeButton.Data); // tslint:disable-line:no-floating-promises return EventHandled.Yes; } else if (ev.isTwoFingerTap) { // Send reset down/up for two finger tap. - IModelApp.toolAdmin.convertTouchTapToButtonDownAndUp(ev, BeButton.Reset); + IModelApp.toolAdmin.convertTouchTapToButtonDownAndUp(ev, BeButton.Reset); // tslint:disable-line:no-floating-promises return EventHandled.Yes; } else if (ev.isDoubleTap) { // Fit view on single finger double tap. diff --git a/core/frontend/src/tools/PrimitiveTool.ts b/core/frontend/src/tools/PrimitiveTool.ts index 33b5620..efd5ae1 100644 --- a/core/frontend/src/tools/PrimitiveTool.ts +++ b/core/frontend/src/tools/PrimitiveTool.ts @@ -28,8 +28,8 @@ export const enum ModifyElementSource { } /** - * The PrimitiveTool class can be used to implement a primitive command. Placement - * tools that don't need to locate or modify elements are good candidates for a PrimitiveTool. + * The PrimitiveTool class can be used to implement tools to create or modify geometric elements. + * @see [Writing a PrimitiveTool]($docs/learning/frontend/primitivetools.md) */ export abstract class PrimitiveTool extends InteractiveTool { public targetView?: Viewport; @@ -71,7 +71,7 @@ export abstract class PrimitiveTool extends InteractiveTool { // ###TODO lock specific code // IBriefcaseManager:: Request req; - // req.Locks().Insert(db, LockLevel:: Shared); + // req.locks.Insert(db, LockLevel:: Shared); // if (!db.BriefcaseManager().AreResourcesAvailable(req, nullptr, IBriefcaseManager:: FastQuery:: Yes)) // return false; // another briefcase has locked the db for editing } @@ -94,7 +94,7 @@ export abstract class PrimitiveTool extends InteractiveTool { if (this.requireWriteableTarget()) { // ###TODO lock specific code // IBriefcaseManager:: Request req; - // req.Locks().Insert(* targetModel, LockLevel:: Shared); + // req.locks.Insert(* targetModel, LockLevel:: Shared); // if (!db.BriefcaseManager().AreResourcesAvailable(req, nullptr, IBriefcaseManager:: FastQuery:: Yes)) // return false; // another briefcase has locked the model for editing } @@ -110,7 +110,6 @@ export abstract class PrimitiveTool extends InteractiveTool { */ public isValidLocation(ev: BeButtonEvent, isButtonEvent: boolean): boolean { const vp = ev.viewport; - if (undefined === vp) return false; @@ -118,8 +117,7 @@ export abstract class PrimitiveTool extends InteractiveTool { return true; const view = vp.view; - const iModel = view.iModel; - if (!view.isSpatialView() || iModel.isReadonly || !this.requireWriteableTarget()) + if (!view.isSpatialView()) return true; // NOTE: If points aren't being adjusted then the tool shouldn't be creating geometry currently (ex. locating elements) and we shouldn't filter point... @@ -130,13 +128,12 @@ export abstract class PrimitiveTool extends InteractiveTool { if (!IModelApp.accuSnap.isSnapEnabled) return true; - const extents = iModel.projectExtents; + const extents = view.iModel.projectExtents; if (extents.containsPoint(ev.point)) return true; - if (isButtonEvent && ev.isDown) { + if (isButtonEvent && ev.isDown) IModelApp.notifications.outputMessage(new NotifyMessageDetails(OutputMessagePriority.Error, IModelApp.i18n.translate("CoreTools:tools.ElementSet.Error.ProjectExtents"))); - } return false; } @@ -169,8 +166,7 @@ export abstract class PrimitiveTool extends InteractiveTool { public abstract onRestartTool(): void; /** - * Called to reset tool to initial state. This method is provided here for convenience; the only - * external caller is ElementSetTool. PrimitiveTool implements this method to call onRestartTool. + * Called to reset tool to initial state. PrimitiveTool implements this method to call onRestartTool. */ public onReinitialize(): void { this.onRestartTool(); } diff --git a/core/frontend/src/tools/SelectTool.ts b/core/frontend/src/tools/SelectTool.ts index e12e2be..8260884 100644 --- a/core/frontend/src/tools/SelectTool.ts +++ b/core/frontend/src/tools/SelectTool.ts @@ -1,5 +1,4 @@ /*--------------------------------------------------------------------------------------------- -/*--------------------------------------------------------------------------------------------- * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ @@ -193,71 +192,72 @@ export class SelectionTool extends PrimitiveTool { const rect = new ViewRect(); rect.initFromRange(range); - const pixels = vp.readPixels(rect, Pixel.Selector.ElementId); - if (undefined === pixels) - return; - - let contents = new Set(); - const testPoint = Point2d.createZero(); - - if (SelectionMethod.Box === method) { - const outline = overlap ? undefined : new Set(); - const offset = range.clone(); - offset.expandInPlace(-2); - for (testPoint.x = range.low.x; testPoint.x <= range.high.x; ++testPoint.x) { - for (testPoint.y = range.low.y; testPoint.y <= range.high.y; ++testPoint.y) { - const pixel = pixels.getPixel(testPoint.x, testPoint.y); - if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) - continue; // no geometry at this location... - if (undefined !== outline && !offset.containsPoint(testPoint)) - outline.add(pixel.elementId.toString()); - else - contents.add(pixel.elementId.toString()); + vp.readPixels(rect, Pixel.Selector.Feature, (pixels) => { + if (undefined === pixels) + return; + + let contents = new Set(); + const testPoint = Point2d.createZero(); + + if (SelectionMethod.Box === method) { + const outline = overlap ? undefined : new Set(); + const offset = range.clone(); + offset.expandInPlace(-2); + for (testPoint.x = range.low.x; testPoint.x <= range.high.x; ++testPoint.x) { + for (testPoint.y = range.low.y; testPoint.y <= range.high.y; ++testPoint.y) { + const pixel = pixels.getPixel(testPoint.x, testPoint.y); + if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) + continue; // no geometry at this location... + if (undefined !== outline && !offset.containsPoint(testPoint)) + outline.add(pixel.elementId.toString()); + else + contents.add(pixel.elementId.toString()); + } } - } - if (undefined !== outline && 0 !== outline.size) { - const inside = new Set(); - contents.forEach((id) => { if (!outline.has(id)) inside.add(id); }); - contents = inside; - } - } else { - const closePoint = Point2d.createZero(); - for (testPoint.x = range.low.x; testPoint.x <= range.high.x; ++testPoint.x) { - for (testPoint.y = range.low.y; testPoint.y <= range.high.y; ++testPoint.y) { - const pixel = pixels.getPixel(testPoint.x, testPoint.y); - if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) - continue; // no geometry at this location... - const fraction = testPoint.fractionOfProjectionToLine(pts[0], pts[1], 0.0); - pts[0].interpolate(fraction, pts[1], closePoint); - if (closePoint.distance(testPoint) < 1.5) - contents.add(pixel.elementId.toString()); + if (undefined !== outline && 0 !== outline.size) { + const inside = new Set(); + contents.forEach((id) => { if (!outline.has(id)) inside.add(id); }); + contents = inside; + } + } else { + const closePoint = Point2d.createZero(); + for (testPoint.x = range.low.x; testPoint.x <= range.high.x; ++testPoint.x) { + for (testPoint.y = range.low.y; testPoint.y <= range.high.y; ++testPoint.y) { + const pixel = pixels.getPixel(testPoint.x, testPoint.y); + if (undefined === pixel || undefined === pixel.elementId || Id64.isInvalid(pixel.elementId)) + continue; // no geometry at this location... + const fraction = testPoint.fractionOfProjectionToLine(pts[0], pts[1], 0.0); + pts[0].interpolate(fraction, pts[1], closePoint); + if (closePoint.distance(testPoint) < 1.5) + contents.add(pixel.elementId.toString()); + } } } - } - if (!this.wantPickableDecorations()) - contents.forEach((id) => { if (Id64.isTransient(id)) contents.delete(id); }); + if (!this.wantPickableDecorations()) + contents.forEach((id) => { if (Id64.isTransient(id)) contents.delete(id); }); - if (0 === contents.size) { - if (!ev.isControlKey && this.wantSelectionClearOnMiss(ev)) - this.iModel.selectionSet.emptyAll(); - return; - } + if (0 === contents.size) { + if (!ev.isControlKey && this.wantSelectionClearOnMiss(ev)) + this.iModel.selectionSet.emptyAll(); + return; + } - switch (this.getSelectionMode()) { - case SelectionMode.Replace: - if (!ev.isControlKey) - this.processSelection(contents, SelectionProcessing.ReplaceSelectionWithElement); - else - this.processSelection(contents, SelectionProcessing.InvertElementInSelection); - break; - case SelectionMode.Add: - this.processSelection(contents, SelectionProcessing.AddElementToSelection); - break; - case SelectionMode.Remove: - this.processSelection(contents, SelectionProcessing.RemoveElementFromSelection); - break; - } + switch (this.getSelectionMode()) { + case SelectionMode.Replace: + if (!ev.isControlKey) + this.processSelection(contents, SelectionProcessing.ReplaceSelectionWithElement); + else + this.processSelection(contents, SelectionProcessing.InvertElementInSelection); + break; + case SelectionMode.Add: + this.processSelection(contents, SelectionProcessing.AddElementToSelection); + break; + case SelectionMode.Remove: + this.processSelection(contents, SelectionProcessing.RemoveElementFromSelection); + break; + } + }); } protected selectByPointsStart(ev: BeButtonEvent): boolean { @@ -300,7 +300,7 @@ export class SelectionTool extends PrimitiveTool { public async selectDecoration(ev: BeButtonEvent, currHit?: HitDetail): Promise { if (undefined === currHit) - currHit = IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource); + currHit = await IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource); if (undefined !== currHit && !currHit.isElementHit) return IModelApp.viewManager.onDecorationButtonEvent(currHit, ev); @@ -335,7 +335,7 @@ export class SelectionTool extends PrimitiveTool { return EventHandled.Yes; } - const hit = IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource); + const hit = await IModelApp.locateManager.doLocate(new LocateResponse(), true, ev.point, ev.viewport, ev.inputSource); if (hit !== undefined) { if (EventHandled.Yes === await this.selectDecoration(ev, hit)) return EventHandled.Yes; @@ -378,7 +378,7 @@ export class SelectionTool extends PrimitiveTool { // Play nice w/auto-locate, only remove previous hit if not currently auto-locating or over previous hit if (undefined === autoHit || autoHit.isSameHit(lastHit)) { const response = new LocateResponse(); - const nextHit = IModelApp.locateManager.doLocate(response, false, ev.point, ev.viewport, ev.inputSource); + const nextHit = await IModelApp.locateManager.doLocate(response, false, ev.point, ev.viewport, ev.inputSource); // remove element(s) previously selected if in replace mode, or if we have a next element in add mode if (SelectionMode.Replace === this.getSelectionMode() || undefined !== nextHit) @@ -394,7 +394,7 @@ export class SelectionTool extends PrimitiveTool { if (EventHandled.Yes === await this.selectDecoration(ev, IModelApp.accuSnap.currHit)) return EventHandled.Yes; - IModelApp.accuSnap.resetButton(); + IModelApp.accuSnap.resetButton(); // tslint:disable-line:no-floating-promises return EventHandled.Yes; } @@ -407,9 +407,9 @@ export class SelectionTool extends PrimitiveTool { return (this.isSuspended || this.isSelectByPoints) ? EventHandled.Yes : EventHandled.No; } - public async onTouchMove(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } - public async onTouchComplete(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } - public async onTouchCancel(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } + public async onTouchMove(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) return IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } + public async onTouchComplete(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } + public async onTouchCancel(ev: BeTouchEvent): Promise { if (this.isSelectByPoints) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } public decorate(context: DecorateContext): void { this.selectByPointsDecorate(context); } @@ -417,7 +417,7 @@ export class SelectionTool extends PrimitiveTool { return (modifier === BeModifierKeys.Shift && this.isSelectByPoints) ? EventHandled.Yes : EventHandled.No; } - public filterHit(hit: HitDetail, _out?: LocateResponse): LocateFilterStatus { + public async filterHit(hit: HitDetail, _out?: LocateResponse): Promise { if (!this.wantPickableDecorations() && !hit.isElementHit) return LocateFilterStatus.Reject; diff --git a/core/frontend/src/tools/Tool.ts b/core/frontend/src/tools/Tool.ts index 546b42b..6dd74a6 100644 --- a/core/frontend/src/tools/Tool.ts +++ b/core/frontend/src/tools/Tool.ts @@ -172,7 +172,7 @@ export class BeTouchEvent extends BeButtonEvent { points.push(this.getTouchPosition(list[i], vp)); } const centroid = Point2d.createZero(); - PolygonOps.centroidAndArea(points, centroid); + PolygonOps.centroidAndAreaXY(points, centroid); return centroid; } } @@ -457,16 +457,16 @@ export abstract class InteractiveTool extends Tool { /** Invoked to allow tools to filter which elements can be located. * @return Reject if hit is unacceptable for this tool (fill out response with explanation, if it is defined) */ - public filterHit(_hit: HitDetail, _out?: LocateResponse): LocateFilterStatus { return LocateFilterStatus.Accept; } + public async filterHit(_hit: HitDetail, _out?: LocateResponse): Promise { return LocateFilterStatus.Accept; } /** Helper method to keep the view cursor, display of locate circle, and coordinate lock overrides consistent with [[AccuSnap.isLocateEnabled]] and [[AccuSnap.isSnapEnabled]]. - * @param enableLocate Value to pass to [[IModelApp.accuSnap.enableLocate]]. Tools that locate elements should always pass true to give the user feedback regarding the element at the current cursor location. - * @param enableSnap Optional value to pass to [[IModelApp.accuSnap.enableSnap]]. Tools that don't care about the element pick location should not pass true. Default is false. + * @param enableLocate Value to pass to [[AccuSnap.enableLocate]]. Tools that locate elements should always pass true to give the user feedback regarding the element at the current cursor location. + * @param enableSnap Optional value to pass to [[AccuSnap.enableSnap]]. Tools that don't care about the element pick location should not pass true. Default is false. * @note User must also have snapping enabled [[AccuSnap.isSnapEnabledByUser]], otherwise [[TentativePoint]] is used to snap. * @param cursor Optional tool specific cursor override. Default is either cross or dynamics cursor depending on whether dynamics are currently active. - * @param coordLockOvr Optional tool specific coordinate lock overrides. A tool that only identifies elements and does not use [[BeButtonEvent.point]] can set [[ToolState.coordLockOvr]] to CoordinateLockOverrides.ACS - * or CoordinateLockOverrides.ACS, otherwise locate is affected by the input point being first projected to the ACS plane. A tool that will use [[BeButtonEvent.point]], especially those that call [[AccuSnap.enableSnap]] - * should honor all locks and leave [[ToolState.coordLockOvr]] set to CoordinateLockOverrides.None, the default for ViewTool and PrimitiveTool. + * @param coordLockOvr Optional tool specific coordinate lock overrides. A tool that only identifies elements and does not use [[BeButtonEvent.point]] can set ToolState.coordLockOvr to CoordinateLockOverrides.ACS + * or CoordinateLockOverrides.All, otherwise locate is affected by the input point being first projected to the ACS plane. A tool that will use [[BeButtonEvent.point]], especially those that call [[AccuSnap.enableSnap]] + * should honor all locks and leave ToolState.coordLockOvr set to CoordinateLockOverrides.None, the default for ViewTool and PrimitiveTool. */ public changeLocateState(enableLocate: boolean, enableSnap?: boolean, cursor?: string, coordLockOvr?: CoordinateLockOverrides): void { if (undefined !== cursor) { diff --git a/core/frontend/src/tools/ToolAdmin.ts b/core/frontend/src/tools/ToolAdmin.ts index f0775d3..ec483bc 100644 --- a/core/frontend/src/tools/ToolAdmin.ts +++ b/core/frontend/src/tools/ToolAdmin.ts @@ -75,7 +75,7 @@ export class ToolSettings { /** @hidden */ export class ToolState { public coordLockOvr = CoordinateLockOverrides.None; - public locateCircleOn = true; + public locateCircleOn = false; public setFrom(other: ToolState) { this.coordLockOvr = other.coordLockOvr; this.locateCircleOn = other.locateCircleOn; } public clone(): ToolState { const val = new ToolState(); val.setFrom(this); return val; } } @@ -497,7 +497,7 @@ export class ToolAdmin { case "touchstart": { current.lastTouchStart = ev; if (undefined !== tool) - tool.onTouchStart(ev); + tool.onTouchStart(ev); // tslint:disable-line:no-floating-promises return; } @@ -544,13 +544,13 @@ export class ToolAdmin { case "touchcancel": { current.lastTouchStart = undefined; if (undefined !== tool) - tool.onTouchCancel(ev); + tool.onTouchCancel(ev); // tslint:disable-line:no-floating-promises return; } case "touchmove": { if (undefined !== tool) - tool.onTouchMove(ev); + tool.onTouchMove(ev); // tslint:disable-line:no-floating-promises if (undefined === current.lastTouchStart) return; @@ -576,7 +576,7 @@ export class ToolAdmin { current.lastTouchStart = undefined; if (undefined === tool || EventHandled.Yes !== await tool.onTouchMoveStart(ev, touchStart)) - this.idleTool.onTouchMoveStart(ev, touchStart); + this.idleTool.onTouchMoveStart(ev, touchStart); // tslint:disable-line:no-floating-promises return; } return; @@ -630,7 +630,7 @@ export class ToolAdmin { if (!ToolAdmin._wantEventLoop) // flag turned on at startup return; - IModelApp.toolAdmin.processEvent(); + IModelApp.toolAdmin.processEvent(); // tslint:disable-line:no-floating-promises IModelApp.viewManager.renderLoop(); requestAnimationFrame(ToolAdmin.eventLoop); } @@ -847,7 +847,7 @@ export class ToolAdmin { } if (tool) { - tool.onMouseMotion(ev); + tool.onMouseMotion(ev); // tslint:disable-line:no-floating-promises this.updateDynamics(ev); } diff --git a/core/frontend/src/tools/ViewTool.ts b/core/frontend/src/tools/ViewTool.ts index d228d08..3bcd2fd 100644 --- a/core/frontend/src/tools/ViewTool.ts +++ b/core/frontend/src/tools/ViewTool.ts @@ -278,7 +278,7 @@ export abstract class ViewManip extends ViewTool { ev.coordsFrom = CoordSource.Precision; // don't want raw point used... } - IModelApp.toolAdmin.processWheelEvent(ev, false); + IModelApp.toolAdmin.processWheelEvent(ev, false); // tslint:disable-line:no-floating-promises return EventHandled.Yes; } @@ -296,7 +296,7 @@ export abstract class ViewManip extends ViewTool { this.isDragging = true; if (0 === this.nPts) - this.onDataButtonDown(ev); + this.onDataButtonDown(ev); // tslint:disable-line:no-floating-promises return EventHandled.Yes; } @@ -362,9 +362,9 @@ export abstract class ViewManip extends ViewTool { public async onTouchTap(ev: BeTouchEvent): Promise { return ev.isSingleTap ? EventHandled.Yes : EventHandled.No; } // Prevent IdleTool from converting single tap into data button down/up... public async onTouchMoveStart(ev: BeTouchEvent, startEv: BeTouchEvent): Promise { if (!this.inHandleModify && startEv.isSingleTouch) await IModelApp.toolAdmin.convertTouchMoveStartToButtonDownAndMotion(startEv, ev); return this.inHandleModify ? EventHandled.Yes : EventHandled.No; } - public async onTouchMove(ev: BeTouchEvent): Promise { if (this.inHandleModify) IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } - public async onTouchComplete(ev: BeTouchEvent): Promise { if (this.inHandleModify) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } - public async onTouchCancel(ev: BeTouchEvent): Promise { if (this.inHandleModify) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } + public async onTouchMove(ev: BeTouchEvent): Promise { if (this.inHandleModify) return IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } + public async onTouchComplete(ev: BeTouchEvent): Promise { if (this.inHandleModify) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } + public async onTouchCancel(ev: BeTouchEvent): Promise { if (this.inHandleModify) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } public onPostInstall(): void { super.onPostInstall(); @@ -1739,9 +1739,9 @@ export class WindowAreaTool extends ViewTool { public async onMouseMotion(ev: BeButtonEvent) { this.doManipulation(ev, true); } public async onTouchTap(ev: BeTouchEvent): Promise { return ev.isSingleTap ? EventHandled.Yes : EventHandled.No; } // Prevent IdleTool from converting single tap into data button down/up... public async onTouchMoveStart(ev: BeTouchEvent, startEv: BeTouchEvent): Promise { if (!this._haveFirstPoint && startEv.isSingleTouch) await IModelApp.toolAdmin.convertTouchMoveStartToButtonDownAndMotion(startEv, ev); return this._haveFirstPoint ? EventHandled.Yes : EventHandled.No; } - public async onTouchMove(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } - public async onTouchComplete(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } - public async onTouchCancel(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } + public async onTouchMove(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) return IModelApp.toolAdmin.convertTouchMoveToMotion(ev); } + public async onTouchComplete(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev); } + public async onTouchCancel(ev: BeTouchEvent): Promise { if (this._haveFirstPoint) return IModelApp.toolAdmin.convertTouchEndToButtonUp(ev, BeButton.Reset); } private computeWindowCorners(): Point3d[] | undefined { const vp = this._viewport; diff --git a/core/frontend/tslint.json b/core/frontend/tslint.json index 66909b8..52f6c8f 100644 --- a/core/frontend/tslint.json +++ b/core/frontend/tslint.json @@ -1,3 +1,6 @@ { - "extends": "@bentley/build-tools/tslint.json" + "extends": "@bentley/build-tools/tslint.json", + "rules": { + "promise-function-async": false // Problem processing src/tools/ToolAdmin.ts? + } } \ No newline at end of file diff --git a/core/geometry/CHANGELOG.json b/core/geometry/CHANGELOG.json index 5c1d542..0aabea1 100644 --- a/core/geometry/CHANGELOG.json +++ b/core/geometry/CHANGELOG.json @@ -1,6 +1,102 @@ { "name": "@bentley/geometry-core", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/geometry-core_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "Geometry Coverage" + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/geometry-core_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": {} + }, + { + "version": "0.169.0", + "tag": "@bentley/geometry-core_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/geometry-core_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/geometry-core_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Coverage" + }, + { + "comment": "AnalyticRoots and Polynomial coverage" + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/geometry-core_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/geometry-core_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": { + "none": [ + { + "comment": "bspline docs. Add bezier curve left and right subdivision methods\"" + }, + { + "comment": "Correct return value (undefined is right!) for LineString3d.pointAt (index)" + } + ] + } + }, + { + "version": "0.164.0", + "tag": "@bentley/geometry-core_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Complete analysis import test application" + }, + { + "comment": "Add support for PolyfaceAuxData to PolyfaceVisitor" + }, + { + "comment": "implement curve method \"moveSignedDistanceFromFraction\"" + }, + { + "comment": "polyface.compress performance problem -- extraneous reallocations" + }, + { + "comment": "CurveChainWithDistanceIndex derivative and distance methods" + }, + { + "comment": "PolyfaceAuxData documentation" + }, + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/geometry-core_v0.163.0", diff --git a/core/geometry/CHANGELOG.md b/core/geometry/CHANGELOG.md index 197ecb5..d68fcfb 100644 --- a/core/geometry/CHANGELOG.md +++ b/core/geometry/CHANGELOG.md @@ -1,6 +1,62 @@ # Change Log - @bentley/geometry-core -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- Geometry Coverage + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +*Version update only* + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Coverage +- AnalyticRoots and Polynomial coverage + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +### Updates + +- bspline docs. Add bezier curve left and right subdivision methods" +- Correct return value (undefined is right!) for LineString3d.pointAt (index) + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Complete analysis import test application +- Add support for PolyfaceAuxData to PolyfaceVisitor +- implement curve method "moveSignedDistanceFromFraction" +- polyface.compress performance problem -- extraneous reallocations +- CurveChainWithDistanceIndex derivative and distance methods +- PolyfaceAuxData documentation +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/geometry/internaldocs/ConstructingRotationMatricesFromAngles.md b/core/geometry/internaldocs/ConstructingRotationMatricesFromAngles.md index ce740a3..b490d58 100644 --- a/core/geometry/internaldocs/ConstructingRotationMatricesFromAngles.md +++ b/core/geometry/internaldocs/ConstructingRotationMatricesFromAngles.md @@ -11,7 +11,7 @@ A thorough discussion is of pure rotations is found at * https://en.wikipedia.org/wiki/Euler_angles * https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions -## In IModelJS, the prefered "angle to matrix" construction is the YawPitchRollAngles class. +## In iModel.js, the prefered "angle to matrix" construction is the YawPitchRollAngles class. The YawPitchRollAngles class implements the "ship" or "airplane" view of rotations. The coordinate system is: @@ -54,10 +54,10 @@ If you settle on a particular order of application, both directions of conversio For even more confusion, some software packages treat points and vectors as "row" data and other software packages treat them as "column" data. This affects matrix construction -- the matrix required for some effect on row data is the _transpose_ of the matrix required for the same effect on column data. -## In IModelJS, points and vectors are viewed as _columns_ +## In iModel.js, points and vectors are viewed as _columns_ -## In IModelJS, you can work with (a) other rotation orders and (b) "vectors as rows" via OrderedRotationAngles class. +## In iModel.js, you can work with (a) other rotation orders and (b) "vectors as rows" via OrderedRotationAngles class. diff --git a/core/geometry/package.json b/core/geometry/package.json index 7b5502d..cd5eebe 100644 --- a/core/geometry/package.json +++ b/core/geometry/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/geometry-core", - "version": "0.163.0", + "version": "0.171.0", "license": "MIT", "description": "Bentley Core Geometry library", "main": "lib/geometry-core.js", @@ -10,7 +10,7 @@ "url": "https://github.com/imodeljs/imodeljs" }, "devDependencies": { - "@bentley/build-tools": "0.163.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/mocha": "^5.2.5", "@types/node": "10.10.3", @@ -26,7 +26,7 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0" }, "scripts": { "build": "tsc 1>&2", diff --git a/core/geometry/src/Geometry.ts b/core/geometry/src/Geometry.ts index fcd5518..e0aa763 100644 --- a/core/geometry/src/Geometry.ts +++ b/core/geometry/src/Geometry.ts @@ -566,5 +566,22 @@ export class Geometry { * @param apply01 if false, accept all x. */ public static isIn01WithTolerance(x: number, tolerance: number): boolean { return x + tolerance >= 0.0 && x - tolerance <= 1.0; } + /** + * restrict x so it is in the interval `[a,b]`, allowing a,b to be in either order. + * @param x + * @param a (usually the lower) interval limit + * @param b (usually the upper) interval limit + */ + public static restrictToInterval(x: number, a: number, b: number): number { + if (a <= b) { + if (x < a) return a; + if (x > b) return b; + return x; + } + // reversed interval .... + if (x < b) return b; + if (x > a) return a; + return x; + } } diff --git a/core/geometry/src/bspline/BSpline1dNd.ts b/core/geometry/src/bspline/BSpline1dNd.ts index 0bd05ea..9124f49 100644 --- a/core/geometry/src/bspline/BSpline1dNd.ts +++ b/core/geometry/src/bspline/BSpline1dNd.ts @@ -9,6 +9,7 @@ /* tslint:disable:variable-name jsdoc-format no-empty no-console*/ import { Point3d } from "../geometry3d/Point3dVector3d"; import { KnotVector } from "./KnotVector"; +import { Geometry } from "../Geometry"; /** Bspline knots and poles for 1d-to-Nd. */ export class BSpline1dNd { @@ -133,4 +134,21 @@ export class BSpline1dNd { } this.knots.reflectKnots(); } + /** + * Test if the leading and trailing polygon coordinates are replicated in the manner of a "closed" bspline polygon which has been expanded + * to act as a normal bspline. + * @returns true if `degree` leading and trailing polygon blocks match + */ + public testCloseablePolygon(): boolean { + const degree = this.degree; + const blockSize = this.poleLength; + const indexDelta = (this.numPoles - this.degree) * blockSize; + const data = this.packedData; + const numValuesToTest = degree * blockSize; + for (let i0 = 0; i0 < numValuesToTest; i0++) { + if (!Geometry.isSameCoordinate(data[i0], data[i0 + indexDelta])) + return false; + } + return true; + } } diff --git a/core/geometry/src/bspline/BSplineCurve.ts b/core/geometry/src/bspline/BSplineCurve.ts index 13461a2..d061fcd 100644 --- a/core/geometry/src/bspline/BSplineCurve.ts +++ b/core/geometry/src/bspline/BSplineCurve.ts @@ -329,14 +329,19 @@ export class BSplineCurve3d extends BSplineCurve3dBase { public copyKnots(includeExtraEndKnot: boolean): number[] { return this._bcurve.knots.copyKnots(includeExtraEndKnot); } /** Create a bspline with uniform knots. */ - public static createUniformKnots(poles: Point3d[], order: number): BSplineCurve3d | undefined { - const numPoles = poles.length; + public static createUniformKnots(poles: Point3d[] | Float64Array, order: number): BSplineCurve3d | undefined { + const numPoles = poles instanceof Float64Array ? poles.length / 3 : poles.length; if (order < 1 || numPoles < order) return undefined; const knots = KnotVector.createUniformClamped(poles.length, order - 1, 0.0, 1.0); - const curve = new BSplineCurve3d(poles.length, order, knots); - let i = 0; - for (const p of poles) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; } + const curve = new BSplineCurve3d(numPoles, order, knots); + if (poles instanceof Float64Array) { + for (let i = 0; i < 3 * numPoles; i++) + curve._bcurve.packedData[i] = poles[i]; + } else { + let i = 0; + for (const p of poles) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; } + } return curve; } /** Create a bspline with given knots. @@ -454,7 +459,7 @@ export class BSplineCurve3d extends BSplineCurve3dBase { return Point3dArray.isCloseToPlane(this._bcurve.packedData, plane); } - public quickLength(): number { return Point3dArray.sumLengths(this._bcurve.packedData); } + public quickLength(): number { return Point3dArray.sumEdgeLengths(this._bcurve.packedData); } public emitStrokableParts(handler: IStrokeHandler, options?: StrokeOptions): void { const needBeziers = handler.announceBezierCurve !== undefined; const workBezier = this.initializeWorkBezier(); @@ -497,22 +502,10 @@ export class BSplineCurve3d extends BSplineCurve3dBase { public get isClosable(): boolean { if (!this._bcurve.knots.wrappable) return false; - const degree = this.degree; - const leftKnotIndex = this._bcurve.knots.leftKnotIndex; - const rightKnotIndex = this._bcurve.knots.rightKnotIndex; - const period = this._bcurve.knots.rightKnot - this._bcurve.knots.leftKnot; - const indexDelta = rightKnotIndex - leftKnotIndex; - for (let k0 = leftKnotIndex - degree + 1; k0 < leftKnotIndex + degree - 1; k0++) { - const k1 = k0 + indexDelta; - if (!Geometry.isSameCoordinate(this._bcurve.knots.knots[k0] + period, this._bcurve.knots.knots[k1])) - return false; - } - const poleIndexDelta = this.numPoles - this.degree; - for (let p0 = 0; p0 + 1 < degree; p0++) { - const p1 = p0 + poleIndexDelta; - if (!Geometry.isSamePoint3d(this.getPolePoint3d(p0) as Point3d, this.getPolePoint3d(p1) as Point3d)) - return false; - } + if (!this._bcurve.knots.testClosable()) + return false; + if (!this._bcurve.testCloseablePolygon()) + return false; return true; } /** diff --git a/core/geometry/src/bspline/BSplineCurve3dH.ts b/core/geometry/src/bspline/BSplineCurve3dH.ts index 1dd9a8c..19e472e 100644 --- a/core/geometry/src/bspline/BSplineCurve3dH.ts +++ b/core/geometry/src/bspline/BSplineCurve3dH.ts @@ -67,20 +67,34 @@ export class BSplineCurve3dH extends BSplineCurve3dBase { /** Return a simple array of the control points coordinates */ public copyPointsFloat64Array(): Float64Array { return this._bcurve.packedData.slice(); } - /** Create a bspline with uniform knots. */ - public static createUniformKnots(poles: Point3d[] | Point4d[], order: number): BSplineCurve3dH | undefined { - const numPoles = poles.length; + /** Create a bspline with uniform knots. + * * Control points may be supplied as: + * * array of Point4d, with weight already multiplied into the `[wx,wy,wz,w]` + * * array of Point3d, with implied weight 1. + * * Float64Array, blocked as xyzw, i.e. 4 doubles per control point. + * @param controlPoints pole data in array form as noted above. + * @param order curve order (1 more than degree) + */ + public static createUniformKnots(controlPoints: Point3d[] | Point4d[] | Float64Array, order: number): BSplineCurve3dH | undefined { + const numPoles = (controlPoints instanceof Float64Array) ? controlPoints.length / 4 : controlPoints.length; if (order < 1 || numPoles < order) return undefined; - const knots = KnotVector.createUniformClamped(poles.length, order - 1, 0.0, 1.0); - const curve = new BSplineCurve3dH(poles.length, order, knots); + const knots = KnotVector.createUniformClamped(controlPoints.length, order - 1, 0.0, 1.0); + const curve = new BSplineCurve3dH(numPoles, order, knots); let i = 0; - if (poles[0] instanceof Point3d) - for (const p of poles) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = 1.0; } - else if (poles[0] instanceof Point4d) - for (const p of (poles as Point4d[])) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = p.w; } - else + if (controlPoints[0] instanceof Point3d) { + for (const p of (controlPoints as Point3d[])) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = 1.0; } + } else if (controlPoints[0] instanceof Point4d) { + for (const p of (controlPoints as Point4d[])) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = p.w; } + } else if (controlPoints instanceof Float64Array) { + const qPoles = controlPoints as Float64Array; + const numQ = qPoles.length; + for (let k = 0; k < numQ; k++) { + curve._bcurve.packedData[k] = qPoles[k]; + } + } else { return undefined; + } return curve; } /** Create a bspline with given knots. @@ -92,9 +106,9 @@ export class BSplineCurve3dH extends BSplineCurve3dBase { * ** If poleArray.length + order == knotArray.length + 2, the knots are in modern form. * */ - public static create(poleArray: Float64Array | Point4d[], knotArray: Float64Array | number[], order: number): BSplineCurve3dH | undefined { - let numPoles = poleArray.length; - if (poleArray instanceof Float64Array) { + public static create(controlPoints: Float64Array | Point4d[] | Point3d[], knotArray: Float64Array | number[], order: number): BSplineCurve3dH | undefined { + let numPoles = controlPoints.length; + if (controlPoints instanceof Float64Array) { numPoles /= 4; // blocked as xyz } const numKnots = knotArray.length; @@ -104,15 +118,15 @@ export class BSplineCurve3dH extends BSplineCurve3dBase { return undefined; const knots = KnotVector.create(knotArray, order - 1, skipFirstAndLast); const curve = new BSplineCurve3dH(numPoles, order, knots); - if (poleArray instanceof Float64Array) { + if (controlPoints instanceof Float64Array) { let i = 0; - for (const coordinate of poleArray) { curve._bcurve.packedData[i++] = coordinate; } - } else if (poleArray[0] instanceof Point4d) { + for (const coordinate of controlPoints) { curve._bcurve.packedData[i++] = coordinate; } + } else if (controlPoints[0] instanceof Point4d) { let i = 0; - for (const p of poleArray) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = p.w; } - } else if (poleArray[0] instanceof Point3d) { + for (const p of (controlPoints as Point4d[])) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = p.w; } + } else if (controlPoints[0] instanceof Point3d) { let i = 0; - for (const p of poleArray) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = 1.0; } + for (const p of controlPoints) { curve._bcurve.packedData[i++] = p.x; curve._bcurve.packedData[i++] = p.y; curve._bcurve.packedData[i++] = p.z; curve._bcurve.packedData[i++] = 1.0; } } return curve; } @@ -184,7 +198,7 @@ export class BSplineCurve3dH extends BSplineCurve3dBase { return Point4dArray.isCloseToPlane(this._bcurve.packedData, plane); } - public quickLength(): number { return Point3dArray.sumLengths(this._bcurve.packedData); } + public quickLength(): number { return Point3dArray.sumEdgeLengths(this._bcurve.packedData); } public emitStrokableParts(handler: IStrokeHandler, options?: StrokeOptions): void { const needBeziers = (handler as any).announceBezierCurve; @@ -239,7 +253,7 @@ export class BSplineCurve3dH extends BSplineCurve3dBase { return false; } const poleIndexDelta = this.numPoles - this.degree; - for (let p0 = 0; p0 + 1 < degree; p0++) { + for (let p0 = 0; p0 < degree; p0++) { const p1 = p0 + poleIndexDelta; if (!Geometry.isSamePoint3d(this.getPolePoint3d(p0) as Point3d, this.getPolePoint3d(p1) as Point3d)) return false; diff --git a/core/geometry/src/bspline/BSplineSurface.ts b/core/geometry/src/bspline/BSplineSurface.ts index 107f47e..f8323e7 100644 --- a/core/geometry/src/bspline/BSplineSurface.ts +++ b/core/geometry/src/bspline/BSplineSurface.ts @@ -138,6 +138,17 @@ export abstract class BSpline2dNd extends GeometryQuery { public numPolesTotal(): number { return this.coffs.length / this.poleDimension; } public numPolesUV(select: UVSelect): number { return this._numPoles[select]; } public poleStepUV(select: UVSelect): number { return select === 0 ? 1 : this._numPoles[0]; } + + public static validOrderAndPoleCounts(orderU: number, numPolesU: number, orderV: number, numPolesV: number, numUV: number): boolean { + if (orderU < 2 || numPolesU < orderU) + return false; + if (orderV < 2 || numPolesV < orderV) + return false; + if (numPolesU * numPolesV !== numUV) + return false; + return true; + } + public getPoint3dPole(i: number, j: number, result?: Point3d): Point3d | undefined { return Point3d.createFromPacked(this.coffs, i + j * this._numPoles[0], result); } @@ -378,6 +389,45 @@ export abstract class BSpline2dNd extends GeometryQuery { public setWrappable(select: UVSelect, value: boolean) { this.knots[select].wrappable = value; } + /** + * Test if `degree` leading and trailing (one of U or V) blocks match, as if the data is an unwrapped closed spline in the slected direction. + * @param select select U or V direction + * @returns true if coordinates matched. + */ + public isClosable(select: UVSelect): boolean { + if (!this.knots[select].wrappable) + return false; + if (!this.knots[select].testClosable()) + return false; + + const numU = this.numPolesUV(0); + const numV = this.numPolesUV(1); + const blockSize = this.poleDimension; + const rowToRowStep = numU * blockSize; + const degreeU = this.degreeUV(0); + const degreeV = this.degreeUV(1); + const data = this.coffs; + if (select === 0) { + const numTest = blockSize * degreeU; // degreeU contiguous poles. + for (let row = 0; row < numV; row++) { + const i0 = row * rowToRowStep; + const i1 = i0 + rowToRowStep - numTest; + for (let i = 0; i < numTest; i++) { + if (!Geometry.isSameCoordinate(data[i0 + i], data[i1 + i])) + return false; + } + } + } else { + // Test the entire multi-row contiguous block in one loop . .. + const numTest = degreeV * rowToRowStep; + const i1 = blockSize * numU * numV - numTest; + for (let i = 0; i < numTest; i++) { + if (!Geometry.isSameCoordinate(data[i], data[i1 + i])) + return false; + } + } + return true; + } } /** BSplineSurface3d is a parametric surface in xyz space. @@ -460,14 +510,13 @@ export class BSplineSurface3d extends BSpline2dNd implements BSplineSurface3dQue let numPoles = controlPointArray.length; if (controlPointArray instanceof Float64Array) numPoles /= 3; - if (numPolesU * numPolesV !== numPoles) return undefined; + if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles)) + return undefined; // shift knots-of-interest limits for overclampled case ... const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2; const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2; const skipFirstAndLastU = (numPolesU + orderU === numKnotsU); const skipFirstAndLastV = (numPolesV + orderV === numKnotsV); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; const knotsU = knotArrayU ? KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) : @@ -511,14 +560,14 @@ export class BSplineSurface3d extends BSpline2dNd implements BSplineSurface3dQue knotArrayV: number[] | Float64Array | undefined): BSplineSurface3d | undefined { const numPolesV = points.length; const numPolesU = points[0].length; - + const numPoles = numPolesU * numPolesV; // shift knots-of-interest limits for overclampled case ... const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2; const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2; const skipFirstAndLastU = (numPolesU + orderU === numKnotsU); const skipFirstAndLastV = (numPolesV + orderV === numKnotsV); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; + if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles)) + return undefined; const knotsU = knotArrayU ? KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) : @@ -527,9 +576,6 @@ export class BSplineSurface3d extends BSpline2dNd implements BSplineSurface3dQue KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) : KnotVector.createUniformClamped(numPolesU, orderU - 1, 0.0, 1.0); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; - const surface = new BSplineSurface3d(numPolesU, numPolesV, knotsU, knotsV); let i = 0; for (const row of points) { @@ -610,42 +656,6 @@ export class BSplineSurface3d extends BSpline2dNd implements BSplineSurface3dQue public isInPlane(plane: Plane3dByOriginAndUnitNormal): boolean { return Point3dArray.isCloseToPlane(this.coffs, plane); } - /** - * return true if the spline is (a) unclamped with (degree-1) matching knot intervals, - * (b) (degree-1) wrapped points, - * (c) marked wrappable from construction time. - */ - public isClosable(select: UVSelect): boolean { - if (!this.knots[select].wrappable) - return false; - const degree = this.degreeUV(select); - const knots = this.knots[select]; - const leftKnotIndex = knots.leftKnotIndex; - const rightKnotIndex = knots.rightKnotIndex; - const period = knots.rightKnot - knots.leftKnot; - const indexDelta = rightKnotIndex - leftKnotIndex; - for (let k0 = leftKnotIndex - degree + 1; k0 < leftKnotIndex + degree - 1; k0++) { - const k1 = k0 + indexDelta; - if (!Geometry.isSameCoordinate(knots.knots[k0] + period, knots.knots[k1])) - return false; - } - const poleIndexDelta = this.numPolesUV(select) - this.degreeUV(select); // index jump between equal wrapped poles. - const numStringer = select === 0 ? this.numPolesUV(1) : this.numPolesUV(0); - const i0Step = select === 0 ? 0 : 1; // to advance stringer - const j0Step = select === 0 ? 1 : 0; // to advance stringer - const iStep = 1 - i0Step; // to advance within stringer - const jStep = 1 - j0Step; // to advance within stringer - for (let stringer = 0, i0 = 0, j0 = 0; stringer < numStringer; stringer++ , i0 += i0Step, j0 += j0Step) { - for (let p0 = 0; p0 < degree; p0++) { - const p1 = p0 + poleIndexDelta; - if (!Geometry.isSamePoint3d( - this.getPole(i0 + p0 * iStep, j0 + p0 * jStep) as Point3d, - this.getPole(i0 + p1 * iStep, j0 + p1 * jStep) as Point3d)) - return false; - } - } - return true; - } public dispatchToGeometryHandler(handler: GeometryHandler): any { return handler.handleBSplineSurface3d(this); @@ -716,13 +726,13 @@ export class BSplineSurface3dH extends BSpline2dNd implements BSplineSurface3dQu knotArrayV: number[] | undefined): BSplineSurface3dH | undefined { const numPoles = controlPointArray.length; if (numPolesU * numPolesV !== numPoles) return undefined; + if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles)) + return undefined; const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2; const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2; const skipFirstAndLastU = (numPolesU + orderU === numKnotsU); const skipFirstAndLastV = (numPolesV + orderV === numKnotsV); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; const knotsU = knotArrayU ? KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) : @@ -731,9 +741,6 @@ export class BSplineSurface3dH extends BSpline2dNd implements BSplineSurface3dQu KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) : KnotVector.createUniformClamped(numPolesV, orderV - 1, 0.0, 1.0); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; - const surface = new BSplineSurface3dH(numPolesU, numPolesV, knotsU, knotsV); Point4dArray.packPointsAndWeightsToFloat64Array(controlPointArray, weightArray, surface.coffs); return surface; @@ -757,14 +764,16 @@ export class BSplineSurface3dH extends BSpline2dNd implements BSplineSurface3dQu knotArrayV: number[]): BSplineSurface3dH | undefined { const numPolesV = xyzwGrid.length; const numPolesU = xyzwGrid[0].length; + const numPoles = numPolesU * numPolesV; + if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles)) + return undefined; + // const numPoles = numPolesU * numPolesV; // shift knots-of-interest limits for overclampled case ... const numKnotsU = knotArrayU.length; const numKnotsV = knotArrayV.length; const skipFirstAndLastU = (numPolesU + orderU === numKnotsU); const skipFirstAndLastV = (numPolesV + orderV === numKnotsV); - if (orderU < 1 || numPolesU < orderU) return undefined; - if (orderV < 1 || numPolesV < orderV) return undefined; const knotsU = KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU); const knotsV = KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV); @@ -883,42 +892,6 @@ export class BSplineSurface3dH extends BSpline2dNd implements BSplineSurface3dQu public isInPlane(plane: Plane3dByOriginAndUnitNormal): boolean { return Point4dArray.isCloseToPlane(this.coffs, plane); } - /** - * return true if the spline is (a) unclamped with (degree-1) matching knot intervals, - * (b) (degree-1) wrapped points, - * (c) marked wrappable from construction time. - */ - public isClosable(select: UVSelect): boolean { - if (!this.knots[select].wrappable) - return false; - const degree = this.degreeUV(select); - const knots = this.knots[select]; - const leftKnotIndex = knots.leftKnotIndex; - const rightKnotIndex = knots.rightKnotIndex; - const period = knots.rightKnot - knots.leftKnot; - const indexDelta = rightKnotIndex - leftKnotIndex; - for (let k0 = leftKnotIndex - degree + 1; k0 < leftKnotIndex + degree - 1; k0++) { - const k1 = k0 + indexDelta; - if (!Geometry.isSameCoordinate(knots.knots[k0] + period, knots.knots[k1])) - return false; - } - const poleIndexDelta = this.numPolesUV(select) - this.degreeUV(select); // index jump between equal wrapped poles. - const numStringer = select === 0 ? this.numPolesUV(1) : this.numPolesUV(0); - const i0Step = select === 0 ? 0 : 1; // to advance stringer - const j0Step = select === 0 ? 0 : 1; // to advance stringer - const iStep = 1 - i0Step; // to advance within stringer - const jStep = 1 - j0Step; // to advance within stringer - for (let stringer = 0, i0 = 0, j0 = 0; stringer < numStringer; stringer++ , i0 += i0Step, j0 += j0Step) { - for (let p0 = 0; p0 + 1 < degree; p0++) { - const p1 = p0 + poleIndexDelta; - if (!Geometry.isSamePoint3d( - this.getPole(i0 + p0 * iStep, j0 + p0 * jStep) as Point3d, - this.getPole(i0 + p1 * iStep, j0 + p1 * jStep) as Point3d)) - return false; - } - } - return true; - } /** * Pass `this` (strongly typed) to `handler.handleBsplineSurface3dH(this)`. * @param handler double dispatch handler. diff --git a/core/geometry/src/bspline/Bezier1dNd.ts b/core/geometry/src/bspline/Bezier1dNd.ts index 4c31b45..4a0acb9 100644 --- a/core/geometry/src/bspline/Bezier1dNd.ts +++ b/core/geometry/src/bspline/Bezier1dNd.ts @@ -171,6 +171,7 @@ export class Bezier1dNd { // /** * interpolate at `fraction` between poleA and poleB. + * * Data is left "in place" in poleIndexA * @param poleIndexA first pole index * @param fraction fractional position * @param poleIndexB second pole index @@ -271,6 +272,62 @@ export class Bezier1dNd { } return true; } + /** + * Apply deCasteljou interpolations to isolate a smaller bezier polygon, representing interval 0..fraction of the original + * @param fracton "end" fraction for split. + * @returns false if fraction is 0 -- no changes applied. + */ + public subdivideInPlaceKeepLeft(fraction: number): boolean { + if (Geometry.isAlmostEqualNumber(fraction, 1.0)) + return true; + if (Geometry.isAlmostEqualNumber(fraction, 0.0)) + return false; + const g = 1.0 - fraction; // interpolations will pull towards right indices + const order = this.order; + for (let level = 1; level < order; level++) { + for (let i1 = order - 1; i1 >= level; i1--) { + this.interpolatePoleInPlace(i1, g, i1 - 1); // leave updates to right + } + } + return true; + } + + /** + * Apply deCasteljou interpolations to isolate a smaller bezier polygon, representing interval 0..fraction of the original + * @param fracton "end" fraction for split. + * @returns false if fraction is 0 -- no changes applied. + */ + public subdivideInPlaceKeepRight(fraction: number): boolean { + if (Geometry.isAlmostEqualNumber(fraction, 0.0)) + return true; + if (Geometry.isAlmostEqualNumber(fraction, 1.0)) + return false; + const order = this.order; + for (let level = 1; level < order; level++) { + for (let i0 = 0; i0 + level < order; i0++) + this.interpolatePoleInPlace(i0, fraction, i0 + 1); // leave updates to left. + } + return true; + } + + /** + * Saturate a univaraite bspline coefficient array in place + * @param fracton0 fracton for first split. This is the start of the output polygon + * @param fracton1 fracton for first split. This is the start of the output polygon + * @return false if fractions are (almost) identical. + */ + public subdivideToIntervalInPlace(fraction0: number, fraction1: number): boolean { + if (Geometry.isAlmostEqualNumber(fraction0, fraction1)) + return false; + if (fraction1 < fraction0) { + this.subdivideToIntervalInPlace(fraction0, fraction1); + this.reverseInPlace(); + return true; + } + this.subdivideInPlaceKeepLeft(fraction1); + this.subdivideInPlaceKeepRight(fraction0 / fraction1); + return true; + } /** optional interval for mapping to a parent object */ public interval?: Segment1d; diff --git a/core/geometry/src/bspline/BezierCurve3d.ts b/core/geometry/src/bspline/BezierCurve3d.ts index 67f0869..1220694 100644 --- a/core/geometry/src/bspline/BezierCurve3d.ts +++ b/core/geometry/src/bspline/BezierCurve3d.ts @@ -108,6 +108,12 @@ export class BezierCurve3d extends BezierCurveBase { public clone(): BezierCurve3d { return new BezierCurve3d(this._polygon.clonePolygon()); } + public clonePartialCurve(f0: number, f1: number): BezierCurve3d | undefined { + const partialCurve = new BezierCurve3d(this._polygon.clonePolygon()); + partialCurve._polygon.subdivideToIntervalInPlace(f0, f1); + return partialCurve; + } + /** * Return a curve after transform. */ diff --git a/core/geometry/src/bspline/BezierCurveBase.ts b/core/geometry/src/bspline/BezierCurveBase.ts index eb9b1c0..bb9d968 100644 --- a/core/geometry/src/bspline/BezierCurveBase.ts +++ b/core/geometry/src/bspline/BezierCurveBase.ts @@ -52,8 +52,19 @@ export abstract class BezierCurveBase extends CurvePrimitive { /** reverse the poles in place */ public reverseInPlace(): void { this._polygon.reverseInPlace(); } /** saturate the pole in place, using knot intervals from `spanIndex` of the `knotVector` */ - public saturateInPlace(knotVector: KnotVector, spanIndex: number): boolean { return this._polygon.saturateInPlace(knotVector, spanIndex); } - public get degree(): number { return this._polygon.order - 1; } + public saturateInPlace(knotVector: KnotVector, spanIndex: number): boolean { + const boolstat = this._polygon.saturateInPlace(knotVector, spanIndex); + if (boolstat) { + this.setInterval( + knotVector.spanFractionToFraction(spanIndex, 0.0), + knotVector.spanFractionToFraction(spanIndex, 1.0)); + } + return boolstat; + } + + public get degree(): number { + return this._polygon.order - 1; + } public get order(): number { return this._polygon.order; } public get numPoles(): number { return this._polygon.order; } /** Get pole `i` as a Point3d. @@ -99,7 +110,7 @@ export abstract class BezierCurveBase extends CurvePrimitive { if (!point) return true; if (!plane.isPointInPlane(point)) - return false; + break; // which gets to return false, which isotherwise unreachable . . . } return false; } @@ -116,30 +127,17 @@ export abstract class BezierCurveBase extends CurvePrimitive { } public startPoint(): Point3d { - const result = this.getPolePoint3d(0); - if (!result) return Point3d.createZero(); + const result = this.getPolePoint3d(0)!; // ASSUME non-trivial pole set -- if null comes back, it bubbles out return result; } public endPoint(): Point3d { - const result = this.getPolePoint3d(this.order - 1); - if (!result) return Point3d.createZero(); + const result = this.getPolePoint3d(this.order - 1)!; // ASSUME non-trivial pole set return result; } public quickLength(): number { return this.polygonLength(); } - /** Extend range by all poles. */ - public extendRange(rangeToExtend: Range3d, transform?: Transform): void { - let i = 0; - if (transform) { - while (this.getPolePoint3d(i++, this._workPoint0)) { - rangeToExtend.extendTransformedPoint(transform, this._workPoint0); - } - } else { - while (this.getPolePoint3d(i++, this._workPoint0)) { - rangeToExtend.extend(this._workPoint0); - } - } - } + /** Concrete classes must implement extendRange . . . */ + public abstract extendRange(rangeToExtend: Range3d, transform?: Transform): void; protected _workBezier?: UnivariateBezier; // available for bezier logic within a method protected _workCoffsA?: Float64Array; protected _workCoffsB?: Float64Array; diff --git a/core/geometry/src/bspline/KnotVector.ts b/core/geometry/src/bspline/KnotVector.ts index 1b78727..1617b50 100644 --- a/core/geometry/src/bspline/KnotVector.ts +++ b/core/geometry/src/bspline/KnotVector.ts @@ -71,7 +71,20 @@ export class KnotVector { } /** @returns Return the total knot distance from beginning to end. */ public get knotLength01(): number { return this._knot1 - this._knot0; } - + /** @returns true if all numeric values have wraparound conditions for "closed" knotVector. */ + public testClosable(): boolean { + const leftKnotIndex = this.leftKnotIndex; + const rightKnotIndex = this.rightKnotIndex; + const period = this.rightKnot - this.leftKnot; + const degree = this.degree; + const indexDelta = rightKnotIndex - leftKnotIndex; + for (let k0 = leftKnotIndex - degree + 1; k0 < leftKnotIndex + degree - 1; k0++) { + const k1 = k0 + indexDelta; + if (!Geometry.isSameCoordinate(this.knots[k0] + period, this.knots[k1])) + return false; + } + return true; + } public isAlmostEqual(other: KnotVector): boolean { if (this.degree !== other.degree) return false; return NumberArray.isAlmostEqual(this.knots, other.knots, KnotVector.knotTolerance); @@ -108,6 +121,23 @@ export class KnotVector { knots.setupFixedValues(); return knots; } + /** + * Create knot vector with {degree-1} replicated knots at start and end, and uniform knots between. + * @param numInterval number of intervals in knot space. (NOT POLE COUNT) + * @param degree degree of polynomial + * @param a0 left knot value for active interval + * @param a1 right knot value for active interval + */ + public static createUniformWrapped(numInterval: number, degree: number, a0: number, a1: number): KnotVector { + const knots = new KnotVector(numInterval + 2 * degree - 1, degree); + const du = 1.0 / numInterval; + for (let i = 1 - degree, k = 0; i < numInterval + degree; i++ , k++) { + knots.knots[k] = Geometry.interpolate(a0, i * du, a1); + } + knots.setupFixedValues(); + return knots; + } + /** * Create knot vector with given knot values and degree. * @param knotArray knot values @@ -276,11 +306,11 @@ export class KnotVector { const k = this.spanIndexToLeftKnotIndex(spanIndex); return this.knots[k + 1] - this.knots[k]; } -/** - * Given a span index, test if it is withn range and has nonzero length. - * * note that a false return does not imply there are no more spans. This may be a double knot (zero length span) followed by more real spans - * @param spanIndex index of span to test. - */ + /** + * Given a span index, test if it is withn range and has nonzero length. + * * note that a false return does not imply there are no more spans. This may be a double knot (zero length span) followed by more real spans + * @param spanIndex index of span to test. + */ public isIndexOfRealSpan(spanIndex: number): boolean { if (spanIndex >= 0 && spanIndex < this.knots.length - this.degree) return !Geometry.isSmallMetricDistance(this.spanIndexToSpanLength(spanIndex)); @@ -300,12 +330,28 @@ export class KnotVector { * in classic over-clamped manner */ public copyKnots(includeExtraEndKnot: boolean): number[] { + const wrap = this.wrappable && this.testClosable(); + const leftIndex = this.leftKnotIndex; + const rightIndex = this.rightKnotIndex; + const a0 = this.leftKnot; + const a1 = this.rightKnot; + const delta = a1 - a0; + const degree = this.degree; const values: number[] = []; - if (includeExtraEndKnot) - values.push(this.knots[0]); + if (includeExtraEndKnot) { + if (wrap) { + values.push(this.knots[rightIndex - degree] - delta); + } else { + values.push(this.knots[0]); + } + } for (const u of this.knots) values.push(u); - if (includeExtraEndKnot) - values.push(values[values.length - 1]); + if (includeExtraEndKnot) { + if (wrap) { + values.push(this.knots[leftIndex + degree] + delta); + } else + values.push(values[values.length - 1]); + } return values; } } diff --git a/core/geometry/src/clipping/ClipPlane.ts b/core/geometry/src/clipping/ClipPlane.ts index 2f47c7d..d42b3ab 100644 --- a/core/geometry/src/clipping/ClipPlane.ts +++ b/core/geometry/src/clipping/ClipPlane.ts @@ -14,7 +14,7 @@ import { Point4d } from "../geometry4d/Point4d"; import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; import { Geometry } from "../Geometry"; import { Angle } from "../geometry3d/Angle"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { AnalyticRoots } from "../numerics/Polynomials"; import { Arc3d } from "../curve/Arc3d"; import { Clipper, ClipUtilities } from "./ClipUtils"; diff --git a/core/geometry/src/clipping/ClipUtils.ts b/core/geometry/src/clipping/ClipUtils.ts index e738793..676efbd 100644 --- a/core/geometry/src/clipping/ClipUtils.ts +++ b/core/geometry/src/clipping/ClipUtils.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Range1d } from "../geometry3d/Range"; -import { GrowableFloat64Array, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { Arc3d } from "../curve/Arc3d"; import { UnionOfConvexClipPlaneSets } from "./UnionOfConvexClipPlaneSets"; import { CurvePrimitive, AnnounceNumberNumber, AnnounceNumberNumberCurvePrimitive } from "../curve/CurvePrimitive"; diff --git a/core/geometry/src/clipping/ConvexClipPlaneSet.ts b/core/geometry/src/clipping/ConvexClipPlaneSet.ts index 695c46e..9405c6b 100644 --- a/core/geometry/src/clipping/ConvexClipPlaneSet.ts +++ b/core/geometry/src/clipping/ConvexClipPlaneSet.ts @@ -14,7 +14,7 @@ import { Matrix4d } from "../geometry4d/Matrix4d"; import { Geometry } from "../Geometry"; import { Angle } from "../geometry3d/Angle"; import { PolygonOps } from "../geometry3d/PointHelpers"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Arc3d } from "../curve/Arc3d"; import { ClipPlane } from "./ClipPlane"; import { ClipPlaneContainment, Clipper, ClipUtilities } from "./ClipUtils"; diff --git a/core/geometry/src/clipping/UnionOfConvexClipPlaneSets.ts b/core/geometry/src/clipping/UnionOfConvexClipPlaneSets.ts index 15844ee..f0e855f 100644 --- a/core/geometry/src/clipping/UnionOfConvexClipPlaneSets.ts +++ b/core/geometry/src/clipping/UnionOfConvexClipPlaneSets.ts @@ -8,7 +8,7 @@ import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Segment1d } from "../geometry3d/Segment1d"; import { Range3d } from "../geometry3d/Range"; import { Transform } from "../geometry3d/Transform"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Matrix4d } from "../geometry4d/Matrix4d"; import { LineSegment3d } from "../curve/LineSegment3d"; import { Arc3d } from "../curve/Arc3d"; diff --git a/core/geometry/src/curve/Arc3d.ts b/core/geometry/src/curve/Arc3d.ts index e43ee4f..63a8670 100644 --- a/core/geometry/src/curve/Arc3d.ts +++ b/core/geometry/src/curve/Arc3d.ts @@ -20,7 +20,7 @@ import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVecto import { GeometryHandler, IStrokeHandler } from "../geometry3d/GeometryHandler"; import { CurvePrimitive } from "./CurvePrimitive"; import { GeometryQuery } from "./GeometryQuery"; -import { CurveLocationDetail } from "./CurveLocationDetail"; +import { CurveLocationDetail, CurveSearchStatus } from "./CurveLocationDetail"; import { AnnounceNumberNumberCurvePrimitive } from "./CurvePrimitive"; import { StrokeOptions } from "./StrokeOptions"; import { Clipper } from "../clipping/ClipUtils"; @@ -70,6 +70,11 @@ export class Arc3d extends CurvePrimitive implements BeJSONFunctions { public get matrix(): Matrix3d { return this._matrix.clone(); } public get sweep(): AngleSweep { return this._sweep; } public set sweep(value: AngleSweep) { this._sweep.setFrom(value); } + /** + * An Arc3d extends along its complete elliptic arc + */ + public get isExtensibleFractionSpace(): boolean { return true; } + // constructor copies the pointers !!! private constructor(center: Point3d, matrix: Matrix3d, sweep: AngleSweep) { super(); @@ -123,6 +128,19 @@ export class Arc3d extends CurvePrimitive implements BeJSONFunctions { } return new Arc3d(center.clone(), matrix, sweep ? sweep.clone() : AngleSweep.create360()); } + /** + * Return a quick estimate of the eccentricity of the ellipse. + * * The estimator is the cross magnitude of the product of vectors U and V, divided by square of the larger magnitude + * * for typical Arc3d with perpendicular UV, this is exactly the small axis divided by large. + * * note that the eccentricity is AT MOST ONE. + */ + public quickEccentricity(): number { + const magX = this._matrix.columnXMagnitude(); + const magY = this._matrix.columnYMagnitude(); + const jacobian = this._matrix.columnXYCrossProductMagnitude(); + const largeAxis = Geometry.maxXY(magX, magY); + return jacobian / (largeAxis * largeAxis); + } /** Create a circular arc defined by start point, any intermediate point, and end point. * If the points are colinear, assemble them into a linestring. */ @@ -219,21 +237,46 @@ export class Arc3d extends CurvePrimitive implements BeJSONFunctions { * Uses quadrature. */ public curveLength(): number { + return this.curveLengthBetweenFractions(0, 1); + } + public static readonly quadratureGuassCount = 5; + /** In quadrature for arc length, use this interval (divided by quickEccentricity) */ + public static readonly quadratureIntervalAngleDegrees = 10.0; + /** * If this is a circular arc, return the simple length derived from radius and sweep. + * * Otherwise (i.e. if this elliptical) fall through CurvePrimitive integrator. + */ + public curveLengthBetweenFractions(fraction0: number, fraction1: number): number { const simpleLength = this.getFractionToDistanceScale(); if (simpleLength !== undefined) - return simpleLength; - // fall through for true ellipse . .. stroke and accumulate quadrature ... - return super.curveLength(); + return simpleLength * Math.abs(fraction1 - fraction0); + // fall through for true ellipse . .. stroke and accumulate quadrature with typical count . .. + let f0 = fraction0; + let f1 = fraction1; + if (fraction0 > fraction1) { + f0 = fraction1; + f1 = fraction0; + } + const sweepDegrees = (f1 - f0) * this._sweep.sweepDegrees; + let eccentricity = this.quickEccentricity(); + if (eccentricity < 0.00001) + eccentricity = 0.00001; + let numInterval = Math.ceil(sweepDegrees / (eccentricity * Arc3d.quadratureIntervalAngleDegrees)); + if (numInterval > 400) + numInterval = 400; + if (numInterval < 1) + numInterval = 1; + return super.curveLengthWithFixedIntervalCountQuadrature(f0, f1, numInterval, Arc3d.quadratureGuassCount); } -/** - * Return an approximate (but easy to compute) arc length. - * The estimate is: - * * Form 8 chords on full circle, proportionally fewer for partials. (But 2 extras if less than half circle.) - * * sum the chord lengths - * * For a circle, we know this crude approximation has to be increased by a factor (theta/(2 sin (theta/2))) - * * Apply that factor. - * * Experiments confirm that this is within 3 percent for a variety of eccentricities and arc sweeps. - */ + + /** + * Return an approximate (but easy to compute) arc length. + * The estimate is: + * * Form 8 chords on full circle, proportionally fewer for partials. (But 2 extras if less than half circle.) + * * sum the chord lengths + * * For a circle, we know this crude approximation has to be increased by a factor (theta/(2 sin (theta/2))) + * * Apply that factor. + * * Experiments confirm that this is within 3 percent for a variety of eccentricities and arc sweeps. + */ public quickLength(): number { const totalSweep = Math.abs(this._sweep.sweepRadians); let numInterval = Math.ceil(4 * totalSweep / Math.PI); @@ -258,6 +301,28 @@ export class Arc3d extends CurvePrimitive implements BeJSONFunctions { const factor = dTheta / (2.0 * Math.sin(0.5 * dTheta)); return chordSum * factor; } + /** + * * See extended comments on `CurvePrimitive.moveSignedDistanceFromFraction` + * * A zero length line generates `CurveSearchStatus.error` + * * Nonzero length line generates `CurveSearchStatus.success` or `CurveSearchStatus.stoppedAtBoundary` + */ + public moveSignedDistanceFromFraction(startFraction: number, signedDistance: number, allowExtension: false, result?: CurveLocationDetail): CurveLocationDetail { + if (!this.isCircular) // suppress extension !!! + return super.moveSignedDistanceFromFractionGeneric(startFraction, signedDistance, allowExtension, result); + const totalLength = this.curveLength(); + const signedFractionMove = Geometry.conditionalDivideFraction(signedDistance, totalLength); + if (signedFractionMove === undefined) { + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus( + this, startFraction, this.fractionToPoint(startFraction), 0.0, CurveSearchStatus.error); + } + return CurveLocationDetail.createConditionalMoveSignedDistance( + allowExtension, + this, + startFraction, + startFraction + signedFractionMove, + signedDistance, + result); + } public allPerpendicularAngles(spacePoint: Point3d, _extend: boolean = false, _endpoints: boolean = false): number[] { const radians: number[] = []; diff --git a/core/geometry/src/curve/PathWithDistanceIndex.ts b/core/geometry/src/curve/CurveChainWithDistanceIndex.ts similarity index 57% rename from core/geometry/src/curve/PathWithDistanceIndex.ts rename to core/geometry/src/curve/CurveChainWithDistanceIndex.ts index 8b91273..118b560 100644 --- a/core/geometry/src/curve/PathWithDistanceIndex.ts +++ b/core/geometry/src/curve/CurveChainWithDistanceIndex.ts @@ -1,363 +1,462 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ - -/** @module Curve */ - -import { Geometry } from "../Geometry"; -import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; -import { Transform } from "../geometry3d/Transform"; -import { IStrokeHandler, GeometryHandler } from "../geometry3d/GeometryHandler"; -import { StrokeOptions } from "./StrokeOptions"; -import { CurvePrimitive } from "./CurvePrimitive"; -import { GeometryQuery } from "./GeometryQuery"; -import { CurveChain } from "./CurveCollection"; -import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; -import { LineString3d } from "./LineString3d"; -import { Range3d } from "../geometry3d/Range"; -import { Ray3d } from "../geometry3d/Ray3d"; -import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors"; -/** - * * Annotation of an interval of a curve. - * * The interval is marked with two pairs of numbers: - * * * fraction0, fraction1 = fraction parameters along the parent curve - * * * distance0,distance1 = distances within containing PathWithDistanceIndex - */ -class PathFragment { - public distance0: number; - public distance1: number; - public fraction0: number; - public fraction1: number; - public curve: CurvePrimitive; - public constructor(fraction0: number, fraction1: number, distance0: number, distance1: number, curve: CurvePrimitive) { - this.fraction0 = fraction0; - this.fraction1 = fraction1; - this.distance0 = distance0; - this.distance1 = distance1; - this.curve = curve; - } - /** - * @returns true if the distance is within the distance limits of this fragment. - * @param distance - */ - public containsDistance(distance: number): boolean { - return distance >= this.distance0 && distance <= this.distance1; - } - /** Convert distance to local fraction, and apply that to interpolate between the stored curve fractions */ - public distanceToCurveFraction(distance: number): number { - return Geometry.inverseInterpolate( - this.fraction0, this.distance0, - this.fraction1, this.distance1, - distance, this.fraction0)!; // the interval "must" have nonzero length, division should be safe . .. - } - /** Return the scale factor to map curve fraction derivatives to parent fraction derivatives - * @param globalDistance total length of the global curve. - */ - public fractionScaleFactor(globalDistance: number): number { - return globalDistance * (this.fraction1 - this.fraction0) / (this.distance1 - this.distance0); - } - public reverseFractionsAndDistances(totalDistance: number) { - const f0 = this.fraction0; - const f1 = this.fraction1; - const d0 = this.distance0; - const d1 = this.distance1; - this.fraction0 = 1.0 - f1; - this.fraction1 = 1.0 - f0; - this.distance0 = totalDistance - d1; - this.distance1 = totalDistance - d0; - } -} -/** Non-instantiable class to build a distance index for a path. */ -class PathIndexConstructionContext implements IStrokeHandler { - private _fragments: PathFragment[]; - private _accumulatedDistance: number; - private constructor() { - this._accumulatedDistance = 0; - this._fragments = []; - } - // ignore curve announcements -- they are repeated in stroke announcements - public startParentCurvePrimitive(_cp: CurvePrimitive) { } - public startCurvePrimitive(_cp: CurvePrimitive) { } - public endParentCurvePrimitive(_cp: CurvePrimitive) { } - public endCurvePrimitive(_cp: CurvePrimitive) { } - // um .. we need to see curves? how to reject? - public announcePointTangent(_xyz: Point3d, _fraction: number, _tangent: Vector3d) { } - /** Announce numPoints interpolated between point0 and point1, with associated fractions */ - public announceSegmentInterval( - cp: CurvePrimitive, - point0: Point3d, - point1: Point3d, - numStrokes: number, - fraction0: number, - fraction1: number): void { - let d0 = this._accumulatedDistance; - if (numStrokes <= 1) { - this._accumulatedDistance += point0.distance(point1); - this._fragments.push(new PathFragment(fraction0, fraction1, d0, this._accumulatedDistance, cp)); - } else { - let f1; - for (let i = 1, f0 = 0.0; i <= numStrokes; i++ , f0 = f1) { - f1 = Geometry.interpolate(fraction0, i / numStrokes, fraction1); - d0 = this._accumulatedDistance; - this._accumulatedDistance += (Math.abs(f1 - f0) * point0.distance(point1)); - this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp)); - } - } - } - public announceIntervalForUniformStepStrokes( - cp: CurvePrimitive, - numStrokes: number, - fraction0: number, - fraction1: number): void { - let f1, d, d0; - for (let i = 1, f0 = fraction0; i <= numStrokes; i++ , f0 = f1) { - f1 = Geometry.interpolate(fraction0, i / numStrokes, fraction1); - d = cp.curveLengthBetweenFractions(f0, f1); - d0 = this._accumulatedDistance; - this._accumulatedDistance += d; - this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp)); - } - } - public static createPathFragmentIndex(path: CurveChain, options?: StrokeOptions): PathFragment[] { - const handler = new PathIndexConstructionContext(); - for (const curve of path.children) { - curve.emitStrokableParts(handler, options); - } - const fragments = handler._fragments; - return fragments; - } -} -/** - * `CurveChainWithDistanceIndex` is a CurvePrimitive whose fractional parameterization is proportional to true - * distance along a CurveChain. - * * The curve chain can be any type derived from CurveChain. - * * * i.e. either a `Path` or a `Loop` - */ -export class CurveChainWithDistanceIndex extends CurvePrimitive { - private _path: CurveChain; - private _fragments: PathFragment[]; - private _totalLength: number; // matches final fragment distance1. - public isSameGeometryClass(other: GeometryQuery): boolean { return other instanceof CurveChainWithDistanceIndex; } - // finall assembly of PathWithDistanceIndex -- caller must create valid fragment index. - private constructor(path: CurveChain, fragments: PathFragment[]) { - super(); - this._path = path; - this._fragments = fragments; - this._totalLength = fragments[fragments.length - 1].distance1; - } - /** - * Create a clone, transformed and with its own distance index. - * @param transform transform to apply in the clone. - */ - public cloneTransformed(transform: Transform): CurvePrimitive | undefined { - const c = this._path.clone(); - if (c !== undefined && c instanceof CurveChain && c.tryTransformInPlace(transform)) - return CurveChainWithDistanceIndex.createCapture(c as CurveChain); - return undefined; - } - public clone(): CurvePrimitive | undefined { - const c = this._path.clone(); - if (c !== undefined && c instanceof CurveChain) - return CurveChainWithDistanceIndex.createCapture(c as CurveChain); - return undefined; - } - /** Ask if the curve is within tolerance of a plane. - * @returns Returns true if the curve is completely within tolerance of the plane. - */ - public isInPlane(plane: Plane3dByOriginAndUnitNormal): boolean { - for (const c of this._path.children) { - if (!c.isInPlane(plane)) - return false; - } - return true; - } - - /** return the start point of the primitive. The default implementation returns fractionToPoint (0.0) */ - public startPoint(result?: Point3d): Point3d { - const c = this._path.cyclicCurvePrimitive(0); - if (c) - return c.startPoint(result); - return Point3d.createZero(result); - } - /** @returns return the end point of the primitive. The default implementation returns fractionToPoint(1.0) */ - public endPoint(result?: Point3d): Point3d { - const c = this._path.cyclicCurvePrimitive(-1); - if (c) - return c.startPoint(result); - return Point3d.createZero(result); - } - /** Add strokes to caller-supplied linestring */ - public emitStrokes(dest: LineString3d, options?: StrokeOptions): void { - for (const c of this._path.children) { - c.emitStrokes(dest, options); - } - } - /** Ask the curve to announce points and simple subcurve fragments for stroking. - * See IStrokeHandler for description of the sequence of the method calls. - */ - public emitStrokableParts(dest: IStrokeHandler, options?: StrokeOptions): void { - for (const c of this._path.children) { - c.emitStrokableParts(dest, options); - } - } - /** dispatch the path to the handler */ - public dispatchToGeometryHandler(handler: GeometryHandler): any { - this._path.dispatchToGeometryHandler(handler); - } - /** Extend (increase) `rangeToExtend` as needed to include these curves (optionally transformed) - */ - public extendRange(rangeToExtend: Range3d, transform?: Transform): void { - this._path.extendRange(rangeToExtend, transform); - } - /** - * - * * Curve length is always positive. - * @returns Returns a (high accuracy) length of the curve between fractional positions - * @returns Returns the length of the curve. - */ - public curveLengthBetweenFractions(fraction0: number, fraction1: number): number { - return Math.abs(fraction1 - fraction0) * this._totalLength; - } - /** - * - * @param primitives primitive array to be CAPTURED (not cloned) - */ - public static createCapture(path: CurveChain, options?: StrokeOptions): CurveChainWithDistanceIndex { - const fragments = PathIndexConstructionContext.createPathFragmentIndex(path, options); - const result = new CurveChainWithDistanceIndex(path, fragments); - return result; - } - /** - * Resolve a fraction to a PathFragment - * @param distance - * @param allowExtrapolation - */ - protected distanceToFragment(distance: number, allowExtrapolation: boolean = false): PathFragment | undefined { - const numFragments = this._fragments.length; - const fragments = this._fragments!; - if (numFragments > 0) { - if (distance < 0.0) - return allowExtrapolation ? fragments[0] : undefined; - if (distance >= this._totalLength) - return allowExtrapolation ? fragments[numFragments - 1] : undefined; - // humbug, linear search - for (const fragment of fragments) { - if (fragment.containsDistance(distance)) return fragment; - } - } - return undefined; - } - /** - * @returns the total length of curves. - */ - public curveLength(): number { - return this._totalLength; - } - /** - * @returns the total length of curves. - */ - public quickLength(): number { - return this._totalLength; - } - - /** Return the point (x,y,z) on the curve at fractional position. - * @param fraction fractional position along the geometry. - * @returns Returns a point on the curve. - */ - public fractionToPoint(fraction: number, result?: Point3d): Point3d { - const distanceAlongPath = fraction * this._totalLength; - let fragment = this.distanceToFragment(distanceAlongPath, true); - if (fragment) { - const curveFraction = fragment.distanceToCurveFraction(distanceAlongPath); - return fragment.curve.fractionToPoint(curveFraction, result); - } else { - fragment = this.distanceToFragment(distanceAlongPath, true); - return this._fragments[0].curve.fractionToPoint(0.0); - } - } - - /** Return the point (x,y,z) and derivative on the curve at fractional position. - * - * * Note that this derivative is "derivative of xyz with respect to fraction." - * * this derivative shows the speed of the "fractional point" moving along the curve. - * * this is not generally a unit vector. use fractionToPointAndUnitTangent for a unit vector. - * @param fraction fractional position along the geometry. - * @returns Returns a ray whose origin is the curve point and direction is the derivative with respect to the fraction. - */ - public fractionToPointAndDerivative(fraction: number, result?: Ray3d): Ray3d { - const distanceAlongPath = fraction * this._totalLength; - const fragment = this.distanceToFragment(distanceAlongPath, true)!; - const curveFraction = fragment.distanceToCurveFraction(distanceAlongPath); - result = fragment.curve.fractionToPointAndDerivative(curveFraction, result); - result.direction.scaleInPlace(fragment.fractionScaleFactor(this._totalLength)); - return result; - } - - /** - * - * @param fraction fractional position on the curve - * @param result optional receiver for the result. - * @returns Returns a ray whose origin is the curve point and direction is the unit tangent. - */ - public fractionToPointAndUnitTangent(fraction: number, result?: Ray3d): Ray3d { - const distanceAlongPath = fraction * this._totalLength; - const fragment = this.distanceToFragment(distanceAlongPath, true)!; - const curveFraction = fragment.distanceToCurveFraction(distanceAlongPath); - return fragment.curve.fractionToPointAndDerivative(curveFraction, result); - } - /** Return a plane with - * - * * origin at fractional position along the curve - * * vectorU is the first derivative, i.e. tangent vector with length equal to the rate of change with respect to the fraction. - * * vectorV is the second derivative, i.e.derivative of vectorU. - */ - public fractionToPointAnd2Derivatives(fraction: number, result?: Plane3dByOriginAndVectors): Plane3dByOriginAndVectors | undefined { - const distanceAlongPath = fraction * this._totalLength; - const fragment = this.distanceToFragment(distanceAlongPath, true)!; - const curveFraction = fragment.distanceToCurveFraction(distanceAlongPath); - result = fragment.curve.fractionToPointAnd2Derivatives(curveFraction, result); - if (result) { - const derivativeScale = fragment.fractionScaleFactor(this._totalLength); - result.vectorU.scaleInPlace(derivativeScale); - result.vectorV.scaleInPlace(derivativeScale * derivativeScale); - } - return result; - } - /** Attempt to transform in place. - * * Warning: If any child fails, this object becomes invalid. But that should never happen. - */ - public tryTransformInPlace(transform: Transform): boolean { - let numFail = 0; - for (const c of this._path.children) { - if (!c.tryTransformInPlace(transform)) - numFail++; - } - return numFail === 0; - } - /** Reverse the curve's data so that its fractional stroking moves in the opposite direction. */ - public reverseInPlace(): void { - this._path.reverseChildrenInPlace(); - const totalLength = this._totalLength; - for (const fragment of this._fragments) - fragment.reverseFractionsAndDistances(totalLength); - for (let i = 0, j = this._fragments.length - 1; i < j; i++ , j--) { - const fragment = this._fragments[i]; - this._fragments[i] = this._fragments[j]; - this._fragments[j] = fragment; - } - } - /** - * Test for equality conditions: - * * Mismatched totalLength is a quick exit condition - * * If totalLength matches, recurse to the path for matching primitives. - * @param other - */ - public isAlmostEqual(other: GeometryQuery): boolean { - if (other instanceof CurveChainWithDistanceIndex) { - return Geometry.isSameCoordinate(this._totalLength, other._totalLength) - && this._path.isAlmostEqual(other._path); - } - return false; - } -} +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +/** @module Curve */ + +import { Geometry } from "../Geometry"; +import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; +import { Transform } from "../geometry3d/Transform"; +import { IStrokeHandler, GeometryHandler } from "../geometry3d/GeometryHandler"; +import { StrokeOptions } from "./StrokeOptions"; +import { CurvePrimitive } from "./CurvePrimitive"; +import { GeometryQuery } from "./GeometryQuery"; +import { CurveChain } from "./CurveCollection"; +import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; +import { LineString3d } from "./LineString3d"; +import { Range3d } from "../geometry3d/Range"; +import { Ray3d } from "../geometry3d/Ray3d"; +import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors"; +import { CurveLocationDetail } from "./CurveLocationDetail"; +/** + * * Annotation of an interval of a curve. + * * The interval is marked with two pairs of numbers: + * * * fraction0, fraction1 = fraction parameters along the child curve + * * * distance0,distance1 = distances within containing CurveChainWithDistanceIndex + */ +class PathFragment { + public chainDistance0: number; + public chainDistance1: number; + public childFraction0: number; + public childFraction1: number; + public childCurve: CurvePrimitive; + public constructor(childFraction0: number, childFraction1: number, distance0: number, distance1: number, childCurve: CurvePrimitive) { + this.childFraction0 = childFraction0; + this.childFraction1 = childFraction1; + this.chainDistance0 = distance0; + this.chainDistance1 = distance1; + this.childCurve = childCurve; + } + /** + * @returns true if the distance is within the distance limits of this fragment. + * @param distance + */ + public containsChainDistance(distance: number): boolean { + return distance >= this.chainDistance0 && distance <= this.chainDistance1; + } + + /** + * @returns true if this fragment addresses `curve` and brackets `fraction` + * @param distance + */ + public containsChildCurveAndChildFraction(curve: CurvePrimitive, fraction: number): boolean { + return this.childCurve === curve && fraction >= this.childFraction0 && fraction <= this.childFraction1; + } + + /** Convert distance to local fraction, and apply that to interpolate between the stored curve fractions. + * Note that proportional calculation does NOT account for nonuniform parameterization in the child curve. + */ + public chainDistanceToInterpolatedChildFraction(distance: number): number { + return Geometry.inverseInterpolate( + this.childFraction0, this.chainDistance0, + this.childFraction1, this.chainDistance1, + distance, this.childFraction0)!; // the interval "must" have nonzero length, division should be safe . .. + } + /** Convert chainDistance to true chidFraction, using detailed moveSignedDistanceFromFraction + */ + public chainDistanceToAccurateChildFraction(chainDistance: number): number { + // The fragments are really expected to do good mappings in their distance range ... + const childDetail = this.childCurve.moveSignedDistanceFromFraction( + this.childFraction0, chainDistance - this.chainDistance0, false); + return childDetail.fraction; + } + /** Return the scale factor to map childCurve fraction derivatives to chain fraction derivatives + * @param globalDistance total length of the global curve. + */ + public fractionScaleFactor(globalDistance: number): number { + return globalDistance * (this.childFraction1 - this.childFraction0) / (this.chainDistance1 - this.chainDistance0); + } + public reverseFractionsAndDistances(totalDistance: number) { + const f0 = this.childFraction0; + const f1 = this.childFraction1; + const d0 = this.chainDistance0; + const d1 = this.chainDistance1; + this.childFraction0 = 1.0 - f1; + this.childFraction1 = 1.0 - f0; + this.chainDistance0 = totalDistance - d1; + this.chainDistance1 = totalDistance - d0; + } + /** + * convert a fractional position on the childCurve to distance in the chain space. + * @param fraction fraction along the curve within this fragment + */ + public childFractionTChainDistance(fraction: number): number { + return this.chainDistance0 + this.childCurve.curveLengthBetweenFractions(this.childFraction0, fraction); + } +} +/** Non-instantiable class to build a distance index for a path. */ +class DistanceIndexConstructionContext implements IStrokeHandler { + private _fragments: PathFragment[]; + private _accumulatedDistance: number; + private constructor() { + this._accumulatedDistance = 0; + this._fragments = []; + } + // ignore curve announcements -- they are repeated in stroke announcements + public startParentCurvePrimitive(_cp: CurvePrimitive) { } + public startCurvePrimitive(_cp: CurvePrimitive) { } + public endParentCurvePrimitive(_cp: CurvePrimitive) { } + public endCurvePrimitive(_cp: CurvePrimitive) { } + // um .. we need to see curves? how to reject? + public announcePointTangent(_xyz: Point3d, _fraction: number, _tangent: Vector3d) { } + /** Announce numPoints interpolated between point0 and point1, with associated fractions */ + public announceSegmentInterval( + cp: CurvePrimitive, + point0: Point3d, + point1: Point3d, + numStrokes: number, + fraction0: number, + fraction1: number): void { + let d0 = this._accumulatedDistance; + if (numStrokes <= 1) { + this._accumulatedDistance += point0.distance(point1); + this._fragments.push(new PathFragment(fraction0, fraction1, d0, this._accumulatedDistance, cp)); + } else { + let f1; + for (let i = 1, f0 = 0.0; i <= numStrokes; i++ , f0 = f1) { + f1 = Geometry.interpolate(fraction0, i / numStrokes, fraction1); + d0 = this._accumulatedDistance; + this._accumulatedDistance += (Math.abs(f1 - f0) * point0.distance(point1)); + this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp)); + } + } + } + public announceIntervalForUniformStepStrokes( + cp: CurvePrimitive, + numStrokes: number, + fraction0: number, + fraction1: number): void { + let f1, d, d0; + for (let i = 1, f0 = fraction0; i <= numStrokes; i++ , f0 = f1) { + f1 = Geometry.interpolate(fraction0, i / numStrokes, fraction1); + d = cp.curveLengthBetweenFractions(f0, f1); + d0 = this._accumulatedDistance; + this._accumulatedDistance += d; + this._fragments.push(new PathFragment(f0, f1, d0, this._accumulatedDistance, cp)); + } + } + public static createPathFragmentIndex(path: CurveChain, options?: StrokeOptions): PathFragment[] { + const handler = new DistanceIndexConstructionContext(); + for (const curve of path.children) { + curve.emitStrokableParts(handler, options); + } + const fragments = handler._fragments; + return fragments; + } +} +/** + * `CurveChainWithDistanceIndex` is a CurvePrimitive whose fractional parameterization is proportional to true + * distance along a CurveChain. + * * The curve chain can be any type derived from CurveChain. + * * * i.e. either a `Path` or a `Loop` + */ +export class CurveChainWithDistanceIndex extends CurvePrimitive { + private _path: CurveChain; + private _fragments: PathFragment[]; + private _totalLength: number; // matches final fragment distance1. + public isSameGeometryClass(other: GeometryQuery): boolean { return other instanceof CurveChainWithDistanceIndex; } + // finall assembly of CurveChainWithDistanceIndex -- caller must create valid fragment index. + private constructor(path: CurveChain, fragments: PathFragment[]) { + super(); + this._path = path; + this._fragments = fragments; + this._totalLength = fragments[fragments.length - 1].chainDistance1; + } + /** + * Create a clone, transformed and with its own distance index. + * @param transform transform to apply in the clone. + */ + public cloneTransformed(transform: Transform): CurvePrimitive | undefined { + const c = this._path.clone(); + if (c !== undefined && c instanceof CurveChain && c.tryTransformInPlace(transform)) + return CurveChainWithDistanceIndex.createCapture(c as CurveChain); + return undefined; + } + public clone(): CurvePrimitive | undefined { + const c = this._path.clone(); + if (c !== undefined && c instanceof CurveChain) + return CurveChainWithDistanceIndex.createCapture(c as CurveChain); + return undefined; + } + /** Ask if the curve is within tolerance of a plane. + * @returns Returns true if the curve is completely within tolerance of the plane. + */ + public isInPlane(plane: Plane3dByOriginAndUnitNormal): boolean { + for (const c of this._path.children) { + if (!c.isInPlane(plane)) + return false; + } + return true; + } + + /** return the start point of the primitive. The default implementation returns fractionToPoint (0.0) */ + public startPoint(result?: Point3d): Point3d { + const c = this._path.cyclicCurvePrimitive(0); + if (c) + return c.startPoint(result); + return Point3d.createZero(result); + } + /** @returns return the end point of the primitive. The default implementation returns fractionToPoint(1.0) */ + public endPoint(result?: Point3d): Point3d { + const c = this._path.cyclicCurvePrimitive(-1); + if (c) + return c.startPoint(result); + return Point3d.createZero(result); + } + /** Add strokes to caller-supplied linestring */ + public emitStrokes(dest: LineString3d, options?: StrokeOptions): void { + for (const c of this._path.children) { + c.emitStrokes(dest, options); + } + } + /** Ask the curve to announce points and simple subcurve fragments for stroking. + * See IStrokeHandler for description of the sequence of the method calls. + */ + public emitStrokableParts(dest: IStrokeHandler, options?: StrokeOptions): void { + for (const c of this._path.children) { + c.emitStrokableParts(dest, options); + } + } + /** dispatch the path to the handler */ + public dispatchToGeometryHandler(handler: GeometryHandler): any { + this._path.dispatchToGeometryHandler(handler); + } + /** Extend (increase) `rangeToExtend` as needed to include these curves (optionally transformed) + */ + public extendRange(rangeToExtend: Range3d, transform?: Transform): void { + this._path.extendRange(rangeToExtend, transform); + } + /** + * + * * Curve length is always positive. + * @returns Returns a (high accuracy) length of the curve between fractional positions + * @returns Returns the length of the curve. + */ + public curveLengthBetweenFractions(fraction0: number, fraction1: number): number { + return Math.abs(fraction1 - fraction0) * this._totalLength; + } + /** + * + * @param primitives primitive array to be CAPTURED (not cloned) + */ + public static createCapture(path: CurveChain, options?: StrokeOptions): CurveChainWithDistanceIndex { + const fragments = DistanceIndexConstructionContext.createPathFragmentIndex(path, options); + const result = new CurveChainWithDistanceIndex(path, fragments); + return result; + } + + /** + * Resolve a fraction of the CurveChain to a PathFragment + * @param distance + * @param allowExtrapolation + */ + protected chainDistanceToFragment(distance: number, allowExtrapolation: boolean = false): PathFragment | undefined { + const numFragments = this._fragments.length; + const fragments = this._fragments!; + if (numFragments > 0) { + if (distance < 0.0) + return allowExtrapolation ? fragments[0] : undefined; + if (distance >= this._totalLength) + return allowExtrapolation ? fragments[numFragments - 1] : undefined; + // humbug, linear search + for (const fragment of fragments) { + if (fragment.containsChainDistance(distance)) return fragment; + } + } + return undefined; + } + /** + * Convert distance along the chain to fraction along the chain. + * @param distance distance along the chain + */ + public chainDistanceToChainFraction(distance: number): number { return distance / this._totalLength; } + /** + * Resolve a fraction within a specific curve to a fragment. + * @param curve + * @param fraction + */ + protected curveAndChildFractionToFragment(curve: CurvePrimitive, fraction: number): PathFragment | undefined { + const numFragments = this._fragments.length; + const fragments = this._fragments!; + if (numFragments > 0) { + // humbug, linear search + for (const fragment of fragments) { + if (fragment.containsChildCurveAndChildFraction(curve, fraction)) return fragment; + } + } + return undefined; + } + + /** + * @returns the total length of curves. + */ + public curveLength(): number { + return this._totalLength; + } + /** + * @returns the total length of curves. + */ + public quickLength(): number { + return this._totalLength; + } + + /** Return the point (x,y,z) on the curve at fractional position along the chain. + * @param fraction fractional position along the geometry. + * @returns Returns a point on the curve. + */ + public fractionToPoint(fraction: number, result?: Point3d): Point3d { + const chainDistance = fraction * this._totalLength; + let fragment = this.chainDistanceToFragment(chainDistance, true); + if (fragment) { + const childFraction = fragment.chainDistanceToAccurateChildFraction(chainDistance); + return fragment.childCurve.fractionToPoint(childFraction, result); + } + fragment = this.chainDistanceToFragment(chainDistance, true); + return this._fragments[0].childCurve.fractionToPoint(0.0, result); + } + + /** Return the point (x,y,z) and derivative on the curve at fractional position. + * + * * Note that this derivative is "derivative of xyz with respect to fraction." + * * this derivative shows the speed of the "fractional point" moving along the curve. + * * this is not generally a unit vector. use fractionToPointAndUnitTangent for a unit vector. + * @param fraction fractional position along the geometry. + * @returns Returns a ray whose origin is the curve point and direction is the derivative with respect to the fraction. + */ + public fractionToPointAndDerivative(fraction: number, result?: Ray3d): Ray3d { + const distanceAlongPath = fraction * this._totalLength; + const fragment = this.chainDistanceToFragment(distanceAlongPath, true)!; + const curveFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath); + result = fragment.childCurve.fractionToPointAndDerivative(curveFraction, result); + const a = this._totalLength / result.direction.magnitude(); + result.direction.scaleInPlace(a); + return result; + } + + /** + * + * @param fraction fractional position on the curve + * @param result optional receiver for the result. + * @returns Returns a ray whose origin is the curve point and direction is the unit tangent. + */ + public fractionToPointAndUnitTangent(fraction: number, result?: Ray3d): Ray3d { + const distanceAlongPath = fraction * this._totalLength; + const fragment = this.chainDistanceToFragment(distanceAlongPath, true)!; + const curveFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath); + result = fragment.childCurve.fractionToPointAndDerivative(curveFraction, result); + result.direction.normalizeInPlace(); + return result; + } + /** Return a plane with + * + * * origin at fractional position along the curve + * * vectorU is the first derivative, i.e. tangent vector with length equal to the rate of change with respect to the fraction. + * * vectorV is the second derivative, i.e.derivative of vectorU. + */ + public fractionToPointAnd2Derivatives(fraction: number, result?: Plane3dByOriginAndVectors): Plane3dByOriginAndVectors | undefined { + const totalLength = this._totalLength; + const distanceAlongPath = fraction * totalLength; + const fragment = this.chainDistanceToFragment(distanceAlongPath, true)!; + const curveFraction = fragment.chainDistanceToAccurateChildFraction(distanceAlongPath); + result = fragment.childCurve.fractionToPointAnd2Derivatives(curveFraction, result); + if (!result) + return undefined; + const dotUU = result.vectorU.magnitudeSquared(); + const magU = Math.sqrt(dotUU); + const dotUV = result.vectorU.dotProduct(result.vectorV); + const duds = 1.0 / magU; + const a = duds * duds; + Vector3d.createAdd2Scaled(result.vectorV, a, result.vectorU, -a * dotUV / dotUU, result.vectorV); // IN PLACE update to vectorV. + result.vectorU.scale(duds); + // scale for 0..1 parameterization .... + result.vectorU.scaleInPlace(totalLength); + result.vectorV.scaleInPlace(totalLength * totalLength); + return result; + } + /** Attempt to transform in place. + * * Warning: If any child fails, this object becomes invalid. But that should never happen. + */ + public tryTransformInPlace(transform: Transform): boolean { + let numFail = 0; + for (const c of this._path.children) { + if (!c.tryTransformInPlace(transform)) + numFail++; + } + return numFail === 0; + } + /** Reverse the curve's data so that its fractional stroking moves in the opposite direction. */ + public reverseInPlace(): void { + this._path.reverseChildrenInPlace(); + const totalLength = this._totalLength; + for (const fragment of this._fragments) + fragment.reverseFractionsAndDistances(totalLength); + for (let i = 0, j = this._fragments.length - 1; i < j; i++ , j--) { + const fragment = this._fragments[i]; + this._fragments[i] = this._fragments[j]; + this._fragments[j] = fragment; + } + } + /** + * Test for equality conditions: + * * Mismatched totalLength is a quick exit condition + * * If totalLength matches, recurse to the path for matching primitives. + * @param other + */ + public isAlmostEqual(other: GeometryQuery): boolean { + if (other instanceof CurveChainWithDistanceIndex) { + return Geometry.isSameCoordinate(this._totalLength, other._totalLength) + && this._path.isAlmostEqual(other._path); + } + return false; + } + + /** Implement moveSignedDistanceFromFraction. + * * See `CurvePrimitive` for parameter details. + * * The returned location directly identifies fractional position along the CurveChainWithDistanceIndex, and has pointer to an additional detail for the child curve. + */ + public moveSignedDistanceFromFraction(startFraction: number, signedDistance: number, allowExtension: boolean, result?: CurveLocationDetail): CurveLocationDetail { + const distanceA = startFraction * this._totalLength; + const distanceB = distanceA + signedDistance; + const fragmentB = this.chainDistanceToFragment(distanceB, true)!; + const childDetail = fragmentB.childCurve.moveSignedDistanceFromFraction(fragmentB.childFraction0, distanceB - fragmentB.chainDistance0, allowExtension, result); + const endFraction = startFraction + (signedDistance / this._totalLength); + const chainDetail = CurveLocationDetail.createConditionalMoveSignedDistance(allowExtension, this, startFraction, endFraction, signedDistance, result); + chainDetail.childDetail = childDetail; + return chainDetail; + } + + /** Search for the curve point that is closest to the spacePoint. + * * The CurveChainWithDistanceIndex invokes the base class CurvePrimitive method, which + * (via a handler) determines a CurveLocation detail among the children. + * * The returned detail directly identifies fractional position along the CurveChainWithDistanceIndex, and has pointer to an additional detail for the child curve. + * @param spacePoint point in space + * @param extend true to extend the curve (NOT USED) + * @returns Returns a CurveLocationDetail structure that holds the details of the close point. + */ + public closestPoint(spacePoint: Point3d, _extend: boolean): CurveLocationDetail | undefined { + // umm... to "extend", would require selective extension of first, last + const childDetail = super.closestPoint(spacePoint, false); + if (!childDetail) + return undefined; + const fragment = this.curveAndChildFractionToFragment(childDetail.curve!, childDetail.fraction); + if (fragment) { + const chainDistance = fragment.childFractionTChainDistance(childDetail.fraction); + const chainFraction = this.chainDistanceToChainFraction(chainDistance); + const chainDetail = CurveLocationDetail.createCurveFractionPoint(this, chainFraction, childDetail.point); + chainDetail.childDetail = childDetail; + return chainDetail; + } + return undefined; + } +} diff --git a/core/geometry/src/curve/CurveCollection.ts b/core/geometry/src/curve/CurveCollection.ts index 7ac98f3..104ce24 100644 --- a/core/geometry/src/curve/CurveCollection.ts +++ b/core/geometry/src/curve/CurveCollection.ts @@ -13,7 +13,7 @@ import { AnyCurve } from "./CurveChain"; import { CurvePrimitive } from "./CurvePrimitive"; import { LineSegment3d } from "./LineSegment3d"; import { LineString3d } from "./LineString3d"; -import { GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { GeometryHandler } from "../geometry3d/GeometryHandler"; // import { SumLengthsContext, GapSearchContext, CountLinearPartsSearchContext, CloneCurvesContext, TransformInPlaceContext } from "./CurveSearches"; diff --git a/core/geometry/src/curve/CurveCurveIntersectXY.ts b/core/geometry/src/curve/CurveCurveIntersectXY.ts index 6beb09f..9f68b67 100644 --- a/core/geometry/src/curve/CurveCurveIntersectXY.ts +++ b/core/geometry/src/curve/CurveCurveIntersectXY.ts @@ -24,7 +24,7 @@ import { Point4d } from "../geometry4d/Point4d"; import { Transform } from "../geometry3d/Transform"; import { Matrix3d } from "../geometry3d/Matrix3d"; import { Arc3d } from "./Arc3d"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { BSplineCurve3d, BSplineCurve3dBase } from "../bspline/BSplineCurve"; import { BezierCurveBase } from "../bspline/BezierCurveBase"; import { BezierCurve3dH } from "../bspline/BezierCurve3dH"; diff --git a/core/geometry/src/curve/CurveLocationDetail.ts b/core/geometry/src/curve/CurveLocationDetail.ts index 2c2d27f..e8e9c4f 100644 --- a/core/geometry/src/curve/CurveLocationDetail.ts +++ b/core/geometry/src/curve/CurveLocationDetail.ts @@ -6,6 +6,7 @@ import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Ray3d } from "../geometry3d/Ray3d"; import { CurvePrimitive } from "./CurvePrimitive"; +import { Geometry } from "../Geometry"; /** * An enumeration of special conditions being described by a CurveLocationDetail. */ @@ -21,6 +22,31 @@ export enum CurveIntervalRole { /** This is the end of an interval */ intervalEnd = 12, } +/** + * Return code for CurvePrimitive method `moveSignedDistanceFromFraction` + */ +export enum CurveSearchStatus { + /** unimplemented or zero length curve */ + error, + /** complete success of search */ + success = 1, + /** search ended prematurely (e.g. at incomplete distance moved) at start or end of curve */ + stoppedAtBoundary = 2, +} + +/** + * use to update a vector in case where source and prior result are both possibly undefined. + * * Any undefined source returns undefined. + * * For defined source, reuse optional result if available. + * @param source optional source + * @param result optional result + */ +function optionalVectorUpdate(source: Vector3d | undefined, result: Vector3d | undefined): Vector3d | undefined { + if (source) { + return source.clone(result); + } + return undefined; +} /** * CurveLocationDetail carries point and paramter data about a point evaluated on a curve. */ @@ -34,9 +60,18 @@ export class CurveLocationDetail { /** The point on the curve */ public point: Point3d; /** A vector (e.g. tangent vector) in context */ - public vector: Vector3d; + public vectorInCurveLocationDetail?: Vector3d; /** A context-specific numeric value. (E.g. a distance) */ public a: number; + /** optional CurveLocationDetail with more detail of location. For instance, a detail for fractional position within + * a CurveChainWithDistanceIndex returns fraction and distance along the chain as its primary data and + * further detail of the particular curve within the chain in the childDetail. + */ + public childDetail?: CurveLocationDetail; + /** A status indicator for certain searches. + * * e.g. CurvePrimitive.moveSignedDistanceFromFraction + */ + public curveSearchStatus?: CurveSearchStatus; /** A context-specific addtional point */ public pointQ: Point3d; // extra point for use in computations @@ -44,7 +79,6 @@ export class CurveLocationDetail { this.pointQ = Point3d.createZero(); this.fraction = 0; this.point = Point3d.createZero(); - this.vector = Vector3d.unitX(); this.a = 0.0; } /** Set the (optional) intervalRole field */ @@ -57,37 +91,50 @@ export class CurveLocationDetail { || this.intervalRole === CurveIntervalRole.isolated || this.intervalRole === CurveIntervalRole.isolatedAtVertex; } - /** @returns Return a complete copy */ + + /** Return a complete copy, WITH CAVEATS . . . + * * curve member is copied as a reference. + * * point and vector members are cloned. + */ public clone(result?: CurveLocationDetail): CurveLocationDetail { if (result === this) return result; result = result ? result : new CurveLocationDetail(); result.curve = this.curve; result.fraction = this.fraction; - result.point = this.point; - result.vector = this.vector; + result.point.setFromPoint3d(this.point); + result.vectorInCurveLocationDetail = optionalVectorUpdate(this.vectorInCurveLocationDetail, result.vectorInCurveLocationDetail); result.a = this.a; + result.curveSearchStatus = this.curveSearchStatus; return result; } - // Set the fraction, point, with optional vector and number. - // (curve is unchanged) - public setFP(fraction: number, point: Point3d, vector?: Vector3d, a?: number) { + /** + * Updated in this instance. + * * Note that if caller omits `vector` and `a`, those fields are updated to the call-list defaults (NOT left as-is) + * * point and vector updates are by data copy (not capture of arglist pointers) + * @param fraction (required) fraction to install + * @param point (required) point to install + * @param vector (optional) vector to install. + * @param a (optional) numeric value to install. + */ + public setFP(fraction: number, point: Point3d, vector?: Vector3d, a: number = 0.0) { this.fraction = fraction; this.point.setFrom(point); - if (vector) - this.vector.setFrom(vector); - else - this.vector.set(0, 0, 0); - this.a = a ? a : 0; + this.vectorInCurveLocationDetail = optionalVectorUpdate(vector, this.vectorInCurveLocationDetail); + this.a = a; } - // Set the fraction, point, and vector - public setFR(fraction: number, ray: Ray3d, a?: number) { - this.fraction = fraction; - this.point.setFrom(ray.origin); - this.vector.setFrom(ray.direction); - this.a = a ? a : 0; + /** + * Updated in this instance. + * * Note that if caller omits a`, that field is updated to the call-list default (NOT left as-is) + * * point and vector updates are by data copy (not capture of arglist data. + * @param fraction (required) fraction to install + * @param ray (required) point and vector to install + * @param a (optional) numeric value to install. + */ + public setFR(fraction: number, ray: Ray3d, a: number = 0) { + return this.setFP(fraction, ray.origin, ray.direction, a); } /** Set the CurvePrimitive pointer, leaving all other properties untouched. */ @@ -119,8 +166,61 @@ export class CurveLocationDetail { result.curve = curve; result.fraction = fraction; result.point.setFromPoint3d(point); - result.vector.set(0, 0, 0); + result.vectorInCurveLocationDetail = undefined; result.a = 0.0; + result.curveSearchStatus = undefined; + return result; + } + + /** create with CurvePrimitive pointer, fraction, and point coordinates + */ + public static createCurveFractionPointDistanceCurveSearchStatus( + curve: CurvePrimitive, + fraction: number, + point: Point3d, + distance: number, + status: CurveSearchStatus, + result?: CurveLocationDetail): CurveLocationDetail { + result = result ? result : new CurveLocationDetail(); + result.curve = curve; + result.fraction = fraction; + result.point.setFromPoint3d(point); + result.vectorInCurveLocationDetail = undefined; + result.a = distance; + result.curveSearchStatus = status; + return result; + } + /** create with curveSearchStatus affected by allowExtension. + * * + */ + public static createConditionalMoveSignedDistance( + allowExtension: boolean, + curve: CurvePrimitive, + startFraction: number, + endFraction: number, + requestedSignedDistance: number, + result?: CurveLocationDetail): CurveLocationDetail { + let a = requestedSignedDistance; + let status = CurveSearchStatus.success; + if (!allowExtension && !Geometry.isIn01(endFraction)) { + // cap the movement at the endponit + if (endFraction < 0.0) { + a = - curve.curveLengthBetweenFractions(startFraction, 0.0); + endFraction = 0.0; + status = CurveSearchStatus.stoppedAtBoundary; + } else if (endFraction > 1.0) { + endFraction = 1.0; + a = curve.curveLengthBetweenFractions(startFraction, 1.0); + status = CurveSearchStatus.stoppedAtBoundary; + } + } + result = result ? result : new CurveLocationDetail(); + result.curve = curve; + result.fraction = endFraction; + result.point = curve.fractionToPoint(endFraction, result.point); + result.vectorInCurveLocationDetail = undefined; + result.a = a; + result.curveSearchStatus = status; return result; } @@ -133,8 +233,9 @@ export class CurveLocationDetail { result = result ? result : new CurveLocationDetail(); result.curve = curve; result.fraction = fraction; - result.point = curve.fractionToPoint (fraction); - result.vector.set(0, 0, 0); + result.point = curve.fractionToPoint(fraction); + result.vectorInCurveLocationDetail = undefined; + result.curveSearchStatus = undefined; result.a = 0.0; return result; } @@ -150,8 +251,9 @@ export class CurveLocationDetail { result.curve = curve; result.fraction = fraction; result.point.setFromPoint3d(point); - result.vector.set(0, 0, 0); + result.vectorInCurveLocationDetail = undefined; result.a = a; + result.curveSearchStatus = undefined; return result; } diff --git a/core/geometry/src/curve/CurvePrimitive.ts b/core/geometry/src/curve/CurvePrimitive.ts index 828f294..fa635a8 100644 --- a/core/geometry/src/curve/CurvePrimitive.ts +++ b/core/geometry/src/curve/CurvePrimitive.ts @@ -17,7 +17,7 @@ import { Quadrature } from "../numerics/Quadrature"; import { IStrokeHandler } from "../geometry3d/GeometryHandler"; import { LineString3d } from "./LineString3d"; import { Clipper } from "../clipping/ClipUtils"; -import { CurveLocationDetail } from "./CurveLocationDetail"; +import { CurveLocationDetail, CurveSearchStatus } from "./CurveLocationDetail"; import { GeometryQuery } from "./GeometryQuery"; /** Type for callback function which announces a pair of numbers, such as a fractional interval, along with a containing CurvePrimitive. */ export type AnnounceNumberNumberCurvePrimitive = (a0: number, a1: number, cp: CurvePrimitive) => void; @@ -106,16 +106,171 @@ export abstract class CurvePrimitive extends GeometryQuery { * * * Curve length is always positive. * @returns Returns a (high accuracy) length of the curve between fractional positions - * @returns Returns the length of the curve. */ public curveLengthBetweenFractions(fraction0: number, fraction1: number): number { if (fraction0 === fraction1) return 0.0; + const scale = this.getFractionToDistanceScale(); + if (scale !== undefined) { + // We are in luck! simple proportions determine it all !!! + // (for example, a LineSegment3d or a circular arc) + const totalLength = this.curveLength(); + return Math.abs((fraction1 - fraction0) * totalLength); + } const context = new CurveLengthContext(fraction0, fraction1); this.emitStrokableParts(context); - return context.getSum(); + return Math.abs(context.getSum()); + } + + /** + * + * * Run an integration (with a default gaussian quadrature) with a fixed fractional step + * * This is typically called by specific curve type implementations of curveLengthBetweenFrations. + * * For example, in Arc3d implementation of curveLengthBetweenFrations: + * * If the Arc3d is true circular, it the arc is true circular, use the direct `arcLength = radius * sweepRadians` + * * If the Arc3d is not true circular, call this method with an interval count appropriate to eccentricity and sweepRadians. + * @returns Returns an integral estimated by numerical quadrature between the fractional positions. + * @param fraction0 start fraction for integration + * @param fraction1 end fraction for integration + * @param numInterval number of quadrature intervals + */ + public curveLengthWithFixedIntervalCountQuadrature(fraction0: number, fraction1: number, numInterval: number, numGauss: number = 5): number { + if (fraction0 > fraction1) { + const fSave = fraction0; + fraction0 = fraction1; + fraction1 = fSave; + } + const context = new CurveLengthContext(fraction0, fraction1, numGauss); + context.announceIntervalForUniformStepStrokes(this, numInterval, fraction0, fraction1); + return Math.abs(context.getSum()); + } + + /** + * + * * (Attempt to) find a position on the curve at a signed distance from start fraction. + * * Return the postion as a CurveLocationDetail. + * * In the `CurveLocationDetail`, record: + * * `fractional` position + * * `fraction` = coordinates of the point + * * `search + * * `a` = (signed!) distance moved. If `allowExtension` is false and the move reached the start or end of the curve, this distance is smaller than the requested signedDistance. + * * `curveSearchStatus` indicates one of: + * * `error` (unusual) computation failed not supported for this curve. + * * `success` full movement completed + * * `stoppedAtBoundary` partial movement completed. This can be due to either + * * `allowExtendsion` parameter sent as `false` + * * the curve type (e.g. bspline) does not support extended range. + * * if `allowExtension` is true, movement may still end at the startpoint or endpoint for curves that do not support extended geometry (specifically bsplines) + * * if the curve returns a value (i.e. not `undefined`) for `curve.getFractionToDistanceScale()`, the base class carries out the computation + * and returns a final location. + * * LineSegment3d relies on this. + * * If the curve does not implement the computation or the curve has zero length, the returned `CurveLocationDetail` has + * * `fraction` = the value of `startFraction` + * * `point` = result of `curve.fractionToPoint(startFraction)` + * * `a` = 0 + * * `curveStartState` = `CurveSearchStatus.error` + * @param startFraction fractional position where the move starts + * @param signedDistance distance to move. Negative distance is backwards in the fraction space + * @param allowExtension if true, all the move to go beyond the startpoint or endpoint of the curve. If false, do not allow movement beyond the startpoint or endpoint + * @param result optional result. + * @returns A CurveLocationDetail annotated as above. Note that if the curve does not support the calculation, there is still a result which contains the point at the input startFraction, with failure indicated in the `curveStartState` member + */ + public moveSignedDistanceFromFraction(startFraction: number, signedDistance: number, allowExtension: boolean, result?: CurveLocationDetail): CurveLocationDetail { + const scale = this.getFractionToDistanceScale(); + if (scale !== undefined) { + // We are in luck! simple proportions determine it all !!! + // (for example, a LineSegment3d or a circular arc) + const totalLength = this.curveLength(); + const signedFractionMove = Geometry.conditionalDivideFraction(signedDistance, totalLength); + if (signedFractionMove === undefined) { + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus( + this, startFraction, this.fractionToPoint(startFraction), 0.0, CurveSearchStatus.error); + } + return CurveLocationDetail.createConditionalMoveSignedDistance( + allowExtension, + this, + startFraction, + startFraction + signedFractionMove, + signedDistance, + result); + } + return this.moveSignedDistanceFromFractionGeneric(startFraction, signedDistance, allowExtension, result); + } + /** + * Generic algorithm to search for point at signed distance from a fractional start point. + * * This will work for well for smooth curves. + * * Curves with tangent or other low-order-derivative discontinuities may need to implement specialized algorithms. + * * We need to find an endFraction which is the end-of-interval (usually upper) limit of integration of the tangent magnitude from startFraction to endFraction + * * That integral is a function of endFraction. + * * The derivative of that integral with respect to end fraction is the tangent magnitude at end fraction. + * * Use that function and (easily evaluated!) derivative for a Newton iteration + * * TO ALL WHO HAVE FUZZY MEMORIES OF CALCULUS CLASS: "The derivative of the integral wrt upper limit is the value of the integrand there" is the + * fundamental theorem of integral calculus !!! The fundeamental theorem is not just an abstraction !!! It is being used + * here in its barest possible form !!! + * * See https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus + * @param startFraction + * @param signedDistance + * @param _allowExtension + * @param result + */ + protected moveSignedDistanceFromFractionGeneric(startFraction: number, signedDistance: number, allowExtension: boolean, result?: CurveLocationDetail): CurveLocationDetail { + const limitFraction = signedDistance > 0.0 ? 1.0 : 0.0; + const absDistance = Math.abs(signedDistance); + const directionFactor = signedDistance < 0.0 ? -1.0 : 1.0; + const availableLength = this.curveLengthBetweenFractions(startFraction, limitFraction); // that is always positive + if (availableLength < absDistance && !allowExtension) + return CurveLocationDetail.createConditionalMoveSignedDistance(allowExtension, this, startFraction, limitFraction, signedDistance, result); + const fractionStep = absDistance / availableLength; + let fractionB = Geometry.interpolate(startFraction, fractionStep, limitFraction); + let fractionA = startFraction; + let distanceA = 0.0; + const tol = 1.0e-12 * availableLength; + let numConverged = 0; + const tangent = Ray3d.createXAxis(); + // on each loop entry: + // fractionA is the most recent endOfInterval. (It may have been reached by a mixtrueo forward and backward step.) + // distanceA is the distance to (the point at) fractionA + // fractionB is the next end fraction + for (let iterations = 0; iterations < 10; iterations++) { + const distanceAB = this.curveLengthBetweenFractions(fractionA, fractionB); + const directionAB = fractionB > fractionA ? directionFactor : -directionFactor; + const distance0B = distanceA + directionAB * distanceAB; + const distanceError = absDistance - distance0B; + if (Math.abs(distanceError) < tol) { + numConverged++; + if (numConverged > 1) + break; + } else { + numConverged = 0; + } + this.fractionToPointAndDerivative(fractionB, tangent); + const tangentMagnitude = tangent.direction.magnitude(); + fractionA = fractionB; + fractionB = fractionA + directionFactor * distanceError / tangentMagnitude; + if (fractionA === fractionB) { // YES -- that is an exact equality test. When it happens, there's no need for confirming with another iteration. + numConverged = 100; + break; + } + distanceA = distance0B; + } + if (numConverged > 1) + return CurveLocationDetail.createConditionalMoveSignedDistance(false, this, startFraction, fractionB, signedDistance, result); + + result = CurveLocationDetail.createCurveEvaluatedFraction(this, startFraction, result); + result.a = 0.0; + result.curveSearchStatus = CurveSearchStatus.error; + return result; } + /** + * * Returns true if the curve's fraction queries extend beyond 0..1. + * * Base class default implementation returns false. + * * These class (and perhaps others in the future) will return true: + * * LineSegment3d + * * LineString3d + * * Arc3d + */ + public get isExtensibleFractionSpace(): boolean { return false; } /** * Compute a length which may be an fast approximation to the true length. * This is expected to be either (a) exact or (b) larger than the actual length, but by no more than @@ -374,7 +529,7 @@ class CurveLengthContext implements IStrokeHandler { } public getSum() { return this._summedLength; } - public constructor(fraction0: number = 0.0, fraction1: number = 1.0) { + public constructor(fraction0: number = 0.0, fraction1: number = 1.0, numGaussPoints: number = 5) { this.startCurvePrimitive(undefined); this._summedLength = 0.0; this._ray = Ray3d.createZero(); @@ -385,11 +540,20 @@ class CurveLengthContext implements IStrokeHandler { this._fraction0 = fraction1; this._fraction1 = fraction0; } - - const maxGauss = 7; + const maxGauss = 7; // (As of Nov 2 2018, 7 is a fluffy overallocation-- the quadrature class only handles up to 5.) this._gaussX = new Float64Array(maxGauss); this._gaussW = new Float64Array(maxGauss); - this._gaussMapper = Quadrature.setupGauss5; + // This sets the number of gauss points. This intgetes exactly for polynomials of (degree 2*numGauss - 1). + if (numGaussPoints > 5 || numGaussPoints < 1) + numGaussPoints = 5; + switch (numGaussPoints) { + case 1: this._gaussMapper = Quadrature.setupGauss1; break; + case 2: this._gaussMapper = Quadrature.setupGauss2; break; + case 3: this._gaussMapper = Quadrature.setupGauss3; break; + case 4: this._gaussMapper = Quadrature.setupGauss4; break; + default: this._gaussMapper = Quadrature.setupGauss5; break; + } + } public startCurvePrimitive(curve: CurvePrimitive | undefined) { this._curve = curve; diff --git a/core/geometry/src/curve/LineSegment3d.ts b/core/geometry/src/curve/LineSegment3d.ts index f91a4b2..4842808 100644 --- a/core/geometry/src/curve/LineSegment3d.ts +++ b/core/geometry/src/curve/LineSegment3d.ts @@ -40,6 +40,16 @@ export class LineSegment3d extends CurvePrimitive implements BeJSONFunctions { private _point1: Point3d; public get point0Ref(): Point3d { return this._point0; } public get point1Ref(): Point3d { return this._point1; } + /** + * A LineSegment3d extends along its infinite line. + */ + public get isExtensibleFractionSpace(): boolean { return true; } + + /** + * CAPTURE point references as a `LineSegment3d` + * @param point0 + * @param point1 + */ private constructor(point0: Point3d, point1: Point3d) { super(); this._point0 = point0; this._point1 = point1; } /** Set the start and endpoints by capturing input references. */ public setRefs(point0: Point3d, point1: Point3d) { this._point0 = point0; this._point1 = point1; } @@ -131,7 +141,7 @@ export class LineSegment3d extends CurvePrimitive implements BeJSONFunctions { public fractionToPoint(fraction: number, result?: Point3d): Point3d { return this._point0.interpolate(fraction, this._point1, result); } public curveLength(): number { return this._point0.distance(this._point1); } public curveLengthBetweenFractions(fraction0: number, fraction1: number): number { - return Math.abs (fraction1 - fraction0) * this._point0.distance(this._point1); + return Math.abs(fraction1 - fraction0) * this._point0.distance(this._point1); } public quickLength(): number { return this.curveLength(); } @@ -149,12 +159,15 @@ export class LineSegment3d extends CurvePrimitive implements BeJSONFunctions { fraction = 0.0; } result = CurveLocationDetail.create(this, result); + // remark: This can be done by result.setFP (fraction, thePoint, undefined, a) + // but that creates a temporary point. result.fraction = fraction; this._point0.interpolate(fraction, this._point1, result.point); - this._point0.vectorTo(this._point1, result.vector); + result.vectorInCurveLocationDetail = undefined; result.a = result.point.distance(spacePoint); return result; } + /** swap the endpoint references. */ public reverseInPlace(): void { const a = this._point0; this._point0 = this._point1; @@ -218,8 +231,8 @@ export class LineSegment3d extends CurvePrimitive implements BeJSONFunctions { this._point1.setFromJSON(json[1]); } } -/** A simple line segment's fraction and distance are proportional. */ - public getFractionToDistanceScale(): number | undefined { return this.curveLength (); } + /** A simple line segment's fraction and distance are proportional. */ + public getFractionToDistanceScale(): number | undefined { return this.curveLength(); } /** * Place the lineSegment3d start and points in a json object * @return {*} [[x,y,z],[x,y,z]] diff --git a/core/geometry/src/curve/LineString3d.ts b/core/geometry/src/curve/LineString3d.ts index dba06b7..43d0e05 100644 --- a/core/geometry/src/curve/LineString3d.ts +++ b/core/geometry/src/curve/LineString3d.ts @@ -15,16 +15,96 @@ import { Matrix3d } from "../geometry3d/Matrix3d"; import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; import { Ray3d } from "../geometry3d/Ray3d"; import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors"; -import { GrowableXYZArray, GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { GeometryHandler, IStrokeHandler } from "../geometry3d/GeometryHandler"; import { StrokeOptions } from "./StrokeOptions"; import { CurvePrimitive, AnnounceNumberNumberCurvePrimitive } from "./CurvePrimitive"; import { GeometryQuery } from "./GeometryQuery"; -import { CurveLocationDetail } from "./CurveLocationDetail"; +import { CurveLocationDetail, CurveSearchStatus } from "./CurveLocationDetail"; import { CurveIntervalRole } from "./CurveLocationDetail"; import { AxisOrder } from "../Geometry"; import { Clipper } from "../clipping/ClipUtils"; +import { LineSegment3d } from "./LineSegment3d"; /* tslint:disable:variable-name no-empty*/ +/** + * context to be called to incrementally accumulate distance along line segments. + */ +class MoveByDistanceContext { + public distance0: number; // accumulated distance through point0 + public point0: Point3d; // most recent point + public fraction0: number; // most recent fraction position + public targetDistance: number; // this is always positive. + /** CAPTURE point0, fraction0, targetDistance */ + public constructor(point0: Point3d, fraction0: number, targetDistance: number) { + this.point0 = point0; + this.distance0 = 0.0; + this.targetDistance = Math.abs(targetDistance); + this.fraction0 = fraction0; + } + // Return CurveSearchStatus indicating whether the accumulated distance has reached the target. + public distanceStatus(): CurveSearchStatus { + return Geometry.isSameCoordinate(this.distance0, this.targetDistance) ? + CurveSearchStatus.success : CurveSearchStatus.stoppedAtBoundary; + } + /** + * Announce next point on the polyline. + * * if the additional segment does NOT reach the target: + * * accumulate the segment length + * * update point0 and fraction0 + * * return false + * * if the additional segment DOES reach the target: + * * update point0 and fraction0 to the (possibly interpolated) final point and fraction + * * return true + * @param point1 new point + * @param fraction1 fraction at point1 + * @return true if targetDistance reached. + */ + public announcePoint(point1: Point3d, fraction1: number): boolean { + const a = this.point0.distance(point1); + const distance1 = this.distance0 + a; + if (distance1 < this.targetDistance && !Geometry.isSameCoordinate(distance1, this.targetDistance)) { + this.point0.setFromPoint3d(point1); + this.distance0 = distance1; + this.fraction0 = fraction1; + return false; + } + const b = this.targetDistance - this.distance0; + const intervalFraction = Geometry.safeDivideFraction(b, a, 0.0); + this.point0.interpolate(intervalFraction, point1, this.point0); + this.fraction0 = Geometry.interpolate(this.fraction0, intervalFraction, fraction1); + this.distance0 = this.targetDistance; + return true; + } + /** + * Update point0, fraction0, and distance0 based on extrapolation of a segment between indices of a point array. + * @returns true if extraploation succeeded. (False if indexed points are coincident) + * @param points + * @param index0 + * @param index1 + * @param fraction0 + * @param fraction1 + * @param result + * @param CurveLocationDetail + */ + public announceExtrapolation(points: GrowableXYZArray, + index0: number, index1: number, + fraction0: number, fraction1: number): boolean { + const residual = this.targetDistance - this.distance0; + const d01 = points.distance(index0, index1); + if (!d01) + return false; + const extensionFraction = Geometry.conditionalDivideFraction(residual, d01); + if (extensionFraction === undefined) + return false; + // (Remark: indices are swapped and extensionFraction negated to prevent incidental precision + // loss with the alternative call with (index0, 1 + extensionFraction, index1); + points.interpolate(index1, -extensionFraction, index0, this.point0); + this.distance0 = this.targetDistance; + this.fraction0 = Geometry.interpolate(fraction1, -extensionFraction, fraction0); + return true; + } +} /* Starting wtih baseIndex and moving index by stepDirection: If the vector from baseIndex to baseIndex +1 crossed with vectorA can be normalized, accumulate it (scaled) to normal. @@ -63,13 +143,21 @@ function accumulateGoodUnitPerpendicular( } return false; } - +/** + * * A LineString3d (sometimes called a PolyLine) is a sequence of xyz coordinates that are to be joined by line segments. + * * The point coordinates are stored in a GrowableXYZArray. + */ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { private static _workPointA = Point3d.create(); private static _workPointB = Point3d.create(); private static _workPointC = Point3d.create(); public isSameGeometryClass(other: GeometryQuery): boolean { return other instanceof LineString3d; } + /** + * A LineString3d extends along its first and final segments. + */ + public get isExtensibleFractionSpace(): boolean { return true; } + private _points: GrowableXYZArray; /** return the points array (cloned). */ public get points(): Point3d[] { return this._points.getPoint3dArray(); } @@ -105,7 +193,7 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { } if (enforceClosure && points.length > 1) { const distance = xyz.distance(0, xyz.length - 1); - if (distance !== 0.0) { + if (distance !== undefined && distance !== 0.0) { if (Geometry.isSameCoordinate(0, distance)) { xyz.pop(); // nonzero but small distance -- to be replaced by point 0 exactly. const xyzA = xyz.front(); @@ -113,7 +201,6 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { } } } - result.addPoints(points); return result; } @@ -143,13 +230,11 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { * If the linestring is not already closed, add a closure point. */ public addClosurePoint() { - const n = this._points.length; - if (n > 1) { - if (!Geometry.isSameCoordinate(0, this._points.distance(0, n - 1))) - this._points.pushWrap(1); - } + const distance = this._points.distance(0, this._points.length - 1); + if (distance !== undefined && !Geometry.isSameCoordinate(distance, 0)) + this._points.pushWrap(1); } - + /** Elminate (but do not return!!) the final point of the linestring */ public popPoint() { this._points.pop(); } @@ -186,7 +271,7 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { let s; let radians; if (!radiusToVertices) - radius = radius / (1.0 - Math.cos(2.0 * radiansStep)); + radius = radius / Math.cos(radiansStep); for (let i = 0; i < edgeCount; i++) { radians = (i0 + 2 * i) * radiansStep; c = Angle.cleanupTrigValue(Math.cos(radians)); @@ -377,12 +462,14 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { } /** If i is a valid index, return that point. */ public pointAt(i: number, result?: Point3d): Point3d | undefined { - return this._points.getPoint3dAt(i, result); + if (this._points.isIndexValid(i)) + return this._points.getPoint3dAt(i, result); + return undefined; } /** If i and j are both valid indices, return the vector from point i to point j */ public vectorBetween(i: number, j: number, result?: Vector3d): Vector3d | undefined { - return this._points.getVector3dBetweenIndices(i, j, result); + return this._points.vectorIndexIndex(i, j, result); } public numPoints(): number { return this._points.length; } @@ -427,17 +514,66 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { const localFraction1 = scaledFraction1 - index1; if (index0 > index1) { // the interval is entirely within a single segment - return Math.abs(scaledFraction1 - scaledFraction0) * this._points.distance(index0 - 1, index0); + return Math.abs(scaledFraction1 - scaledFraction0) * this._points.distance(index0 - 1, index0)!; } else { // there is leading partial interval, 0 or more complete segments, and a trailing partial interval. // (either or both partial may be zero length) - let sum = localFraction0 * this._points.distance(index0 - 1, index0) - + localFraction1 * this._points.distance(index1, index1 + 1); + let sum = localFraction0 * this._points.distance(index0 - 1, index0)! + + localFraction1 * (this._points.distance(index1, index1 + 1))!; for (let i = index0; i < index1; i++) - sum += this._points.distance(i, i + 1); + sum += this._points.distance(i, i + 1)!; return sum; } } + /** + * * Implementation of `CurvePrimitive.moveSignedDistanceFromFraction`. (see comments there!) + * * Find the segment that contains the start fraction + * * Move point-by-point from that position to the start or end (respectively for negative or positive signedDistance) + * * Optionally extrapolate + * @param startFraction + * @param signedDistance + * @param allowExtension + * @param result + */ + public moveSignedDistanceFromFraction(startFraction: number, signedDistance: number, allowExtension: false, result?: CurveLocationDetail): CurveLocationDetail { + const numSegments = this._points.length - 1; + const scaledFraction = startFraction * numSegments; + let leftPointIndex = Geometry.restrictToInterval(Math.floor(scaledFraction), 0, numSegments - 1); // lower point index on active segment. + const localFraction = scaledFraction - leftPointIndex; + const point0 = this._points.interpolate(leftPointIndex, localFraction, leftPointIndex + 1, LineString3d._workPointA)!; + const point1 = LineString3d._workPointB; + const context = new MoveByDistanceContext(point0, startFraction, signedDistance); + + if (signedDistance > 0.0) { + for (; leftPointIndex <= numSegments;) { + leftPointIndex++; + this._points.atPoint3dIndex(leftPointIndex, point1); + if (context.announcePoint(point1, leftPointIndex / numSegments)) + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus(this, context.fraction0, context.point0, + signedDistance, CurveSearchStatus.success, result); + } + // fall through for extrapolation from final segment + if (allowExtension) + context.announceExtrapolation(this._points, numSegments - 1, numSegments, + (numSegments - 1) / numSegments, 1.0); + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus(this, context.fraction0, context.point0, + signedDistance, context.distanceStatus(), result); + } else { // (moving backwards) + if (localFraction <= 0.0) + leftPointIndex--; + for (; leftPointIndex >= 0; leftPointIndex--) { + this._points.atPoint3dIndex(leftPointIndex, point1); + if (context.announcePoint(point1, leftPointIndex / numSegments)) + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus(this, context.fraction0, context.point0, + signedDistance, CurveSearchStatus.success, result); + } + // fall through for backward extrapolation from initial segment + if (allowExtension) + context.announceExtrapolation(this._points, 1, 0, 1.0 / numSegments, 0.0); + return CurveLocationDetail.createCurveFractionPointDistanceCurveSearchStatus(this, context.fraction0, context.point0, + -context.distance0, context.distanceStatus(), result); + } + } public quickLength(): number { return this.curveLength(); } @@ -720,6 +856,12 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions { } return result; } + /** Return (if possible) a specific segment of the linestring */ + public getIndexedSegment(index: number): LineSegment3d | undefined { + if (index >= 0 && index + 1 < this._points.length) + return LineSegment3d.create(this._points.atPoint3dIndex(index)!, this._points.atPoint3dIndex(index + 1)!); + return undefined; + } } /** An AnnotatedLineString3d is a linestring with additional data attached to each point diff --git a/core/geometry/src/curve/TransitionSpiral.ts b/core/geometry/src/curve/TransitionSpiral.ts index 0b3659f..3773f1e 100644 --- a/core/geometry/src/curve/TransitionSpiral.ts +++ b/core/geometry/src/curve/TransitionSpiral.ts @@ -338,9 +338,11 @@ export class TransitionSpiral3d extends CurvePrimitive { public setFrom(other: TransitionSpiral3d): TransitionSpiral3d { this.localToWorld.setFrom(other.localToWorld); this.radius01.setFrom(other.radius01); - this.radius01.setFrom(other.radius01); + this._curvature01.setFrom(other._curvature01); this.bearing01.setFrom(other.bearing01); this.localToWorld.setFrom(other.localToWorld); + this.activeFractionInterval.setFrom(other.activeFractionInterval); + this._arcLength01 = other._arcLength01; return this; } public clone(): TransitionSpiral3d { @@ -443,6 +445,7 @@ export class TransitionSpiral3d extends CurvePrimitive { && this.bearing01.isAlmostEqualAllowPeriodShift(other.bearing01) && this.localToWorld.isAlmostEqual(other.localToWorld) && Geometry.isSameCoordinate(this._arcLength01, other._arcLength01) + && this.activeFractionInterval.isAlmostEqual(other.activeFractionInterval) && this._curvature01.isAlmostEqual(other._curvature01); } return false; diff --git a/core/geometry/src/geometry-core.ts b/core/geometry/src/geometry-core.ts index 49725d2..fb20707 100644 --- a/core/geometry/src/geometry-core.ts +++ b/core/geometry/src/geometry-core.ts @@ -126,7 +126,9 @@ export * from "./geometry3d/PointHelpers"; export * from "./geometry3d/Transform"; export * from "./geometry3d/Matrix3d"; export * from "./geometry3d/Range"; -export * from "./geometry3d/GrowableArray"; +export * from "./geometry3d/GrowableFloat64Array"; +export * from "./geometry3d/GrowableXYZArray"; +export * from "./geometry3d/GrowableBlockedArray"; export * from "./geometry3d/FrameBuilder"; export * from "./geometry3d/GeometryHandler"; export * from "./geometry3d/IndexedXYZCollection"; @@ -193,6 +195,7 @@ export * from "./bspline/BSplineCurve3dH"; export * from "./bspline/BSplineSurface"; export * from "./bspline/KnotVector"; export * from "./polyface/BoxTopology"; +export * from "./polyface/PolyfaceData"; export * from "./polyface/Polyface"; export * from "./polyface/PolyfaceBuilder"; export * from "./polyface/PolyfaceQuery"; diff --git a/core/geometry/src/geometry3d/AngleSweep.ts b/core/geometry/src/geometry3d/AngleSweep.ts index ec4b469..ea45858 100644 --- a/core/geometry/src/geometry3d/AngleSweep.ts +++ b/core/geometry/src/geometry3d/AngleSweep.ts @@ -2,7 +2,7 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { GrowableFloat64Array } from "./GrowableArray"; +import { GrowableFloat64Array } from "./GrowableFloat64Array"; import { Angle } from "./Angle"; import { BeJSONFunctions, Geometry, AngleSweepProps } from "../Geometry"; /** diff --git a/core/geometry/src/geometry3d/FrameBuilder.ts b/core/geometry/src/geometry3d/FrameBuilder.ts index 4874b71..c12f572 100644 --- a/core/geometry/src/geometry3d/FrameBuilder.ts +++ b/core/geometry/src/geometry3d/FrameBuilder.ts @@ -232,9 +232,9 @@ export class FrameBuilder { if (points.length > 2) { const origin = points[0].clone(); const vector01 = Vector3d.create(); - Point3dArray.vectorToMostDistantPoint(points, points[0], vector01); + Point3dArray.indexOfMostDistantPoint(points, points[0], vector01); const vector02 = Vector3d.create(); - Point3dArray.vectorToPointWithMaxCrossProductMangitude(points, origin, vector01, vector02); + Point3dArray.indexOfPointWithMaxCrossProductMagnitude(points, origin, vector01, vector02); const matrix = Matrix3d.createRigidFromColumns(vector01, vector02, AxisOrder.XYZ); if (matrix) return Transform.createRefs(origin, matrix); diff --git a/core/geometry/src/geometry3d/GrowableBlockedArray.ts b/core/geometry/src/geometry3d/GrowableBlockedArray.ts new file mode 100644 index 0000000..0b975bb --- /dev/null +++ b/core/geometry/src/geometry3d/GrowableBlockedArray.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import { BlockComparisonFunction } from "./GrowableFloat64Array"; +/** @module ArraysAndInterfaces */ + +/** + * Array of contiguous doubles, indexed by block number and index within block. + * * This is essentially a rectangular matrix, with each block being a row of the matrix. + */ +export class GrowableBlockedArray { + protected _data: Float64Array; + protected _inUse: number; + protected _blockSize: number; // positive integer !!! + public constructor(blockSize: number, initialBlocks: number = 8) { + this._data = new Float64Array(initialBlocks * blockSize); + this._inUse = 0; + this._blockSize = blockSize; + } + /** computed property: length (in blocks, not doubles) */ + public get numBlocks(): number { return this._inUse; } + /** property: number of data values per block */ + public get numPerBlock(): number { return this._blockSize; } + /** + * Return a single value indexed within a blcok + * @param blockIndex index of block to read + * @param indexInBlock offset within the block + */ + public getWithinBlock(blockIndex: number, indexWithinBlock: number): number { + return this._data[blockIndex * this._blockSize + indexWithinBlock]; + } + /** clear the block count to zero, but maintain the allocated memory */ + public clear() { this._inUse = 0; } + /** Return the capacity in blocks (not doubles) */ + public blockCapacity() { + return this._data.length / this._blockSize; + } + /** ensure capacity (in blocks, not doubles) */ + public ensureBlockCapacity(blockCapacity: number) { + if (blockCapacity > this.blockCapacity()) { + const newData = new Float64Array(blockCapacity * this._blockSize); + for (let i = 0; i < this._data.length; i++) { + newData[i] = this._data[i]; + } + this._data = newData; + } + } + /** Add a new block of data. + * * If newData has fewer than numPerBlock entries, the remaining part of the new block is zeros. + * * If newData has more entries, only the first numPerBlock are taken. + */ + public addBlock(newData: number[]) { + const k0 = this.newBlockIndex(); + let numValue = newData.length; + if (numValue > this._blockSize) + numValue = this._blockSize; + for (let i = 0; i < numValue; i++) + this._data[k0 + i] = newData[i]; + } + /** + * Return the starting index of a block of (zero-initialized) doubles at the end. + * + * * this.data is reallocated if needed to include the new block. + * * The inUse count is incremented to include the new block. + * * The returned block is an index to the Float64Array (not a block index) + */ + protected newBlockIndex(): number { + const index = this._blockSize * this._inUse; + if ((index + 1) > this._data.length) + this.ensureBlockCapacity(2 * this._inUse); + this._inUse++; + for (let i = index; i < index + this._blockSize; i++) + this._data[i] = 0.0; + return index; + } + /** reduce the block count by one. */ + public popBlock() { + if (this._inUse > 0) + this._inUse--; + } + /** convert a block index to the simple index to the underlying Float64Array. */ + protected blockIndexToDoubleIndex(blockIndex: number) { return this._blockSize * blockIndex; } + /** Access a single double at offset within a block, with index checking and return undefined if indexing is invalid. */ + public checkedComponent(blockIndex: number, componentIndex: number): number | undefined { + if (blockIndex >= this._inUse || blockIndex < 0 || componentIndex < 0 || componentIndex >= this._blockSize) + return undefined; + return this._data[this._blockSize * blockIndex + componentIndex]; + } + /** Access a single double at offset within a block. This has no index checking. */ + public component(blockIndex: number, componentIndex: number): number { + return this._data[this._blockSize * blockIndex + componentIndex]; + } + /** compre two blocks in simple lexical order. + * @param data data array + * @param blockSize number of items to compare + * @param ia raw index (not block index) of first block + * @param ib raw index (not block index) of second block + */ + public static compareLexicalBlock(data: Float64Array, blockSize: number, ia: number, ib: number): number { + let ax = 0; + let bx = 0; + for (let i = 0; i < blockSize; i++) { + ax = data[ia + i]; + bx = data[ib + i]; + if (ax > bx) return 1; + if (ax < bx) return -1; + } + return ia - ib; // so original order is maintained among duplicates !!!! + } + /** Return an array of block indices sorted per compareLexicalBlock function */ + public sortIndicesLexical(compareBlocks: BlockComparisonFunction = GrowableBlockedArray.compareLexicalBlock): Uint32Array { + const n = this._inUse; + // let numCompare = 0; + const result = new Uint32Array(n); + const data = this._data; + const blockSize = this._blockSize; + for (let i = 0; i < n; i++)result[i] = i; + result.sort( + (blockIndexA: number, blockIndexB: number) => { + // numCompare++; + return compareBlocks(data, blockSize, blockIndexA * blockSize, blockIndexB * blockSize); + }); + // console.log (n, numCompare); + return result; + } + public distanceBetweenBlocks(blockIndexA: number, blockIndexB: number): number { + let dd = 0.0; + let iA = this.blockIndexToDoubleIndex(blockIndexA); + let iB = this.blockIndexToDoubleIndex(blockIndexB); + let a = 0; + const data = this._data; + for (let i = 0; i < this._blockSize; i++) { + a = data[iA++] - data[iB++]; + dd += a * a; + } + return Math.sqrt(dd); + } + + public distanceBetweenSubBlocks(blockIndexA: number, blockIndexB: number, iBegin: number, iEnd: number): number { + let dd = 0.0; + const iA = this.blockIndexToDoubleIndex(blockIndexA); + const iB = this.blockIndexToDoubleIndex(blockIndexB); + let a = 0; + const data = this._data; + for (let i = iBegin; i < iEnd; i++) { + a = data[iA + i] - data[iB + i]; + dd += a * a; + } + return Math.sqrt(dd); + } +} diff --git a/core/geometry/src/geometry3d/GrowableFloat64Array.ts b/core/geometry/src/geometry3d/GrowableFloat64Array.ts new file mode 100644 index 0000000..f93917a --- /dev/null +++ b/core/geometry/src/geometry3d/GrowableFloat64Array.ts @@ -0,0 +1,243 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +/** @module ArraysAndInterfaces */ + +export type OptionalGrowableFloat64Array = GrowableFloat64Array | undefined; +export type BlockComparisonFunction = (data: Float64Array, blockSize: number, index0: number, index1: number) => number; +/** + * A `GrowableFloat64Array` is Float64Array accompanied by a count of how many of the array's entries are considered in use. + * * In C++ terms, this is like an std::vector + * * As entries are added to the array, the buffer is reallocated as needed to accomodate. + * * The reallocations leave unused space to accept further additional entries without reallocation. + * * The `length` property returns the number of entries in use. + * * the `capacity` property returns the (usually larger) length of the (overallocated) Float64Array. + * + */ +export class GrowableFloat64Array { + private _data: Float64Array; + private _inUse: number; + constructor(initialCapacity: number = 8) { + this._data = new Float64Array(initialCapacity); + this._inUse = 0; + } + /** + * Create a GrowableFloat64Array with given contents. + * @param contents data to copy into the array + */ + public static create(contents: Float64Array | number[]): GrowableFloat64Array { + const result = new GrowableFloat64Array(contents.length); + for (const a of contents) { + result.push(a); + } + return result; + } + public static compare(a: any, b: any): number { + return a - b; + } + /** Return a new array with + * * All active entries copied from this instance + * * optionally trimmed capacity to the active length or replicate the capacity and unused space. + */ + public clone(maintainExcessCapacity: boolean = false): GrowableFloat64Array { + const n = this._inUse; + const data = this._data; + const out = new GrowableFloat64Array(maintainExcessCapacity ? this.capacity() : n); + for (let i = 0; i < n; i++) + out.push(data[i]); + return out; + } + /** + * @returns the number of entries in use. + */ + public get length() { + return this._inUse; + } + /** + * Set the value at specified index. + * @param index index of entry to set + * @param value value to set + */ + public setAt(index: number, value: number) { + this._data[index] = value; + } + + /** + * Move the value at index i to index j. + * @param i source index + * @param j destination index. + */ + public move(i: number, j: number) { + this._data[j] = this._data[i]; + } + /** + * swap the values at indices i and j + * @param i first index + * @param j second index + */ + public swap(i: number, j: number) { + const a = this._data[i]; + this._data[i] = this._data[j]; + this._data[j] = a; + } + + /** + * append a single value to the array. + * @param toPush value to append to the active array. + */ + public push(toPush: number) { + if (this._inUse + 1 <= this._data.length) { + this._data[this._inUse] = toPush; + this._inUse++; + } else { + // Make new array (double size), copy values, then push toPush + const newData = new Float64Array(this._inUse * 2); + for (let i = 0; i < this._inUse; i++) { + newData[i] = this._data[i]; + } + this._data = newData; + this._data[this._inUse] = toPush; + this._inUse++; + } + } + /** Push a `numToCopy` consecutive values starting at `copyFromIndex` to the end of the array. */ + public pushBlockCopy(copyFromIndex: number, numToCopy: number) { + const newLength = this._inUse + numToCopy; + this.ensureCapacity(newLength); + const limit = copyFromIndex + numToCopy; + for (let i = copyFromIndex; i < limit; i++) + this._data[this._inUse++] = this._data[i]; + } + /** Clear the array to 0 length. The underlying memory remains allocated for reuse. */ + public clear() { + while (this._inUse > 0) + this.pop(); + } + /** + * @returns the number of entries in the supporting Float64Array buffer. This number is always at least as large as the `length` property. + */ + public capacity() { + return this._data.length; + } + /** + * * If the capacity (Float64Array length) is less than or equal to the requested newCapacity, do nothing + * * If the requested newCapacity is larger than the existing capacity, reallocate (and copy existing values) with the larger capacity. + * @param newCapacity + */ + public ensureCapacity(newCapacity: number) { + if (newCapacity > this.capacity()) { + const oldInUse = this._inUse; + const newData = new Float64Array(newCapacity); + for (let i = 0; i < oldInUse; i++) + newData[i] = this._data[i]; + this._data = newData; + } + } + /** + * * If newLength is less than current (active) length, just set (active) length. + * * If newLength is greater, ensureCapacity (newSize) and pad with padValue up to newSize; + * @param newLength new data count + * @param padValue value to use for padding if the length increases. + */ + public resize(newLength: number, padValue: number = 0) { + // quick out for easy case ... + if (newLength <= this._inUse) { + this._inUse = newLength; + return; + } + const oldLength = this._inUse; + this.ensureCapacity(newLength); + for (let i = oldLength; i < newLength; i++) + this._data[i] = padValue; + this._inUse = newLength; + } + /** + * * Reduce the length by one. + * * Note that there is no method return value -- use `back` to get that value before `pop()` + * * (As with std::vector, seprating the `pop` from the value access elmiinates error testing from `pop` call) + */ + public pop() { + // Could technically access outside of array, if filled and then reduced using pop (similar to C + // and accessing out of bounds), but with adjusted inUse counter, that data will eventually be overwritten + if (this._inUse > 0) { + this._inUse--; + } + } + + public at(index: number): number { + return this._data[index]; + } + + public front() { + return this._data[0]; + } + public back() { + return this._data[this._inUse - 1]; + } + public reassign(index: number, value: number) { + this._data[index] = value; + } + + /** + * * Sort the array entries. + * * Uses insertion sort -- fine for small arrays (less than 30), slow for larger arrays + * @param compareMethod comparison method + */ + public sort(compareMethod: (a: any, b: any) => number = GrowableFloat64Array.compare) { + for (let i = 0; i < this._inUse; i++) { + for (let j = i + 1; j < this._inUse; j++) { + const tempI = this._data[i]; + const tempJ = this._data[j]; + if (compareMethod(tempI, tempJ) > 0) { + this._data[i] = tempJ; + this._data[j] = tempI; + } + } + } + } + + /** + * * compress out values not within the [a,b] interval. + * * Note that if a is greater than b all values are rejected. + * @param a low value for accepted interval + * @param b high value for accepted interval + */ + public restrictToInterval(a: number, b: number) { + const data = this._data; + const n = data.length; + let numAccept = 0; + let q = 0; + for (let i = 0; i < n; i++) { + q = data[i]; + if (q >= a && q <= b) + data[numAccept++] = q; + } + this._inUse = numAccept; + } + + /** + * * compress out multiple copies of values. + * * this is done in the current order of the array. + */ + public compressAdjcentDuplicates(tolerance: number = 0.0) { + const data = this._data; + const n = this._inUse; + if (n === 0) + return; + + let numAccepted = 1; + let a = data[0]; + let b; + for (let i = 1; i < n; i++) { + b = data[i]; + if (Math.abs(b - a) > tolerance) { + data[numAccepted++] = b; + a = b; + } + } + this._inUse = numAccepted; + } + +} diff --git a/core/geometry/src/geometry3d/GrowableArray.ts b/core/geometry/src/geometry3d/GrowableXYZArray.ts similarity index 55% rename from core/geometry/src/geometry3d/GrowableArray.ts rename to core/geometry/src/geometry3d/GrowableXYZArray.ts index 3696ff0..aa0e3f7 100644 --- a/core/geometry/src/geometry3d/GrowableArray.ts +++ b/core/geometry/src/geometry3d/GrowableXYZArray.ts @@ -1,860 +1,517 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ - -/** @module ArraysAndInterfaces */ - -import { Geometry } from "../Geometry"; -import { XYAndZ } from "./XYZProps"; -import { Point3d, Vector3d } from "./Point3dVector3d"; -import { Range3d } from "./Range"; -import { Transform } from "./Transform"; -import { IndexedXYZCollection } from "./IndexedXYZCollection"; - -import { Plane3dByOriginAndUnitNormal } from "./Plane3dByOriginAndUnitNormal"; - -export type OptionalGrowableFloat64Array = GrowableFloat64Array | undefined; -export type BlockComparisonFunction = (data: Float64Array, blockSize: number, index0: number, index1: number) => number; - -export class GrowableFloat64Array { - private _data: Float64Array; - private _inUse: number; - constructor(initialCapacity: number = 8) { - this._data = new Float64Array(initialCapacity); - this._inUse = 0; - } - public static compare(a: any, b: any): number { - return a - b; - } - public get length() { - return this._inUse; - } - /** - * Set the value at specified index. - * @param index index of entry to set - * @param value value to set - */ - public setAt(index: number, value: number) { - this._data[index] = value; - } - - /** - * Move the value at index i to index j. - * @param i source index - * @param j destination index. - */ - public move(i: number, j: number) { - this._data[j] = this._data[i]; - } - - public push(toPush: number) { - if (this._inUse + 1 < this._data.length) { - this._data[this._inUse] = toPush; - this._inUse++; - } else { - // Make new array (double size), copy values, then push toPush - const newData = new Float64Array(this._inUse * 2); - for (let i = 0; i < this._inUse; i++) { - newData[i] = this._data[i]; - } - this._data = newData; - this._data[this._inUse] = toPush; - this._inUse++; - } - } - /** Push a `numToCopy` consecutive values starting at `copyFromIndex` to the end of the array. */ - public pushBlockCopy(copyFromIndex: number, numToCopy: number) { - const newLength = this._inUse + numToCopy; - this.ensureCapacity(newLength); - const limit = copyFromIndex + numToCopy; - for (let i = copyFromIndex; i < limit; i++) - this._data[this._inUse++] = this._data[i]; - } - /** Clear the array to 0 length. The underlying memory remains allocated for reuse. */ - public clear() { - while (this._inUse > 0) - this.pop(); - } - public capacity() { - return this._data.length; - } - public ensureCapacity(newCapacity: number) { - if (newCapacity > this.capacity()) { - const oldInUse = this._inUse; - const newData = new Float64Array(newCapacity); - for (let i = 0; i < oldInUse; i++) - newData[i] = this._data[i]; - this._data = newData; - } - } - /** - * * If newLength is less than current (active) length, just set (active) length. - * * If newLength is greater, ensureCapacity (newSize) and pad with padValue up to newSize; - * @param newLength new data count - * @param padValue value to use for padding if the length increases. - */ - public resize(newLength: number, padValue: number = 0) { - // quick out for easy case ... - if (newLength <= this._inUse) { - this._inUse = newLength; - return; - } - const oldLength = this._inUse; - this.ensureCapacity(newLength); - for (let i = oldLength; i < newLength; i++) - this._data[i] = padValue; - this._inUse = newLength; - } - public pop() { - // Could technically access outside of array, if filled and then reduced using pop (similar to C - // and accessing out of bounds), but with adjusted inUse counter, that data will eventually be overwritten - if (this._inUse > 0) { - this._inUse--; - } - } - - public at(index: number): number { - return this._data[index]; - } - - public front() { - return this._data[0]; - } - public back() { - return this._data[this._inUse - 1]; - } - public reassign(index: number, value: number) { - this._data[index] = value; - } - - /** - * * Sort the array entries. - * * Uses insertion sort -- fine for small arrays (less than 30), slow for larger arrays - * @param compareMethod comparison method - */ - public sort(compareMethod: (a: any, b: any) => number = GrowableFloat64Array.compare) { - for (let i = 0; i < this._inUse; i++) { - for (let j = i + 1; j < this._inUse; j++) { - const tempI = this._data[i]; - const tempJ = this._data[j]; - if (compareMethod(tempI, tempJ) > 0) { - this._data[i] = tempJ; - this._data[j] = tempI; - } - } - } - } - /** - * * compress out values not within the [a,b] interval. - * * Note that if a is greater than b all values are rejected. - * @param a low value for accepted interval - * @param b high value for accepted interval - */ - public restrictToInterval(a: number, b: number) { - const data = this._data; - const n = data.length; - let numAccept = 0; - let q = 0; - for (let i = 0; i < n; i++) { - q = data[i]; - if (q >= a && q <= b) - data[numAccept++] = q; - } - this._inUse = numAccept; - } - /** - * * For each index `i0 <= i < i1` overwrite `data[i+1]` by `f0*data[i]+f1*data[i+1] - * * This is the essential step of a bezier polynomial subdivision step - * @param i0 first index to update - * @param i1 one beyond last index to update. - * @param f0 left scale - * @param f1 right scale - */ - public overwriteWithScaledCombinations(i0: number, i1: number, f0: number, f1: number) { - // work right to left for simplest overwrite - for (let i = i1; i > i0; i--) { - this._data[i] = f0 * this._data[i - 1] + f1 * this._data[i]; - } - } - /** - * @returns Return the weighted sum `data[i0+i]*weights[i]`. - * @param i0 first index of data - * @param weights array of weights. - * @note The length of the weight array is the number of summed terms. - */ - public weightedSum(i0: number, weights: Float64Array) { - let i = i0; - let sum: number = 0.0; - const data = this._data; - for (const w of weights) - sum += w * data[i++]; - return sum; - } - /** - * @returns Return the weighted sum `(data[i0+i] - data[i])*weights[i]`. - * @param i0 first index of data - * @param weights array of weights. - * @note The length of the weight array is the number of summed terms. - */ - public weightedDifferenceSum(i0: number, weights: Float64Array) { - let i = i0; - let sum: number = 0.0; - const data = this._data; - for (const w of weights) { - sum += w * (data[i + 1] - data[i]); - i++; - } - return sum; - } - -} -/** - * Array of contiguous doubles, indexed by block number and index within block. - * * This is essentially a rectangular matrix, with each block being a row of the matrix. - */ -export class GrowableBlockedArray { - protected _data: Float64Array; - protected _inUse: number; - protected _blockSize: number; // positive integer !!! - protected constructor(blockSize: number, initialBlocks: number = 8) { - this._data = new Float64Array(initialBlocks * blockSize); - this._inUse = 0; - this._blockSize = blockSize; - } - /** computed property: length (in blocks, not doubles) */ - public get numBlocks(): number { return this._inUse; } - /** property: number of data values per block */ - public get numPerBlock(): number { return this._blockSize; } - /** - * Return a single value indexed within a blcok - * @param blockIndex index of block to read - * @param indexInBlock offset within the block - */ - public getWithinBlock(blockIndex: number, indexWithinBlock: number): number { - return this._data[blockIndex * this._blockSize + indexWithinBlock]; - } - /** clear the block count to zero, but maintain the allocated memory */ - public clear() { this._inUse = 0; } - /** Return the capacity in blocks (not doubles) */ - public blockCapacity() { - return this._data.length / this._blockSize; - } - /** ensure capacity (in blocks, not doubles) */ - public ensureBlockCapacity(blockCapacity: number) { - if (blockCapacity > this.blockCapacity()) { - const newData = new Float64Array(blockCapacity * this._blockSize); - for (let i = 0; i < this._data.length; i++) { - newData[i] = this._data[i]; - } - this._data = newData; - } - } - /** - * Return the starting index of a block of (zero-initialized) doubles at the end. - * - * * this.data is reallocated if needed to include the new block. - * * The inUse count is incremented to include the new block. - * * The returned block is an index to the Float64Array (not a block index) - */ - protected newBlockIndex(): number { - const index = this._blockSize * this._inUse; - if (this._blockSize * (index + 1) > this._data.length) - this.ensureBlockCapacity(2 * this._inUse); - this._inUse++; - for (let i = index; i < index + this._blockSize; i++) - this._data[i] = 0.0; - return index; - } - /** reduce the block count by one. */ - public popBlock() { - if (this._inUse > 0) - this._inUse--; - } - /** convert a block index to the simple index to the underlying Float64Array. */ - protected blockIndexToDoubleIndex(blockIndex: number) { return this._blockSize * blockIndex; } - /** Access a single double at offset within a block, with index checking and return undefined if indexing is invalid. */ - public checkedComponent(blockIndex: number, componentIndex: number): number | undefined { - if (blockIndex >= this._inUse || blockIndex < 0 || componentIndex < 0 || componentIndex >= this._blockSize) - return undefined; - return this._data[this._blockSize * blockIndex + componentIndex]; - } - /** Access a single double at offset within a block. This has no index checking. */ - public component(blockIndex: number, componentIndex: number): number { - return this._data[this._blockSize * blockIndex + componentIndex]; - } - /** compre two blocks in simple lexical order. - * @param data data array - * @param blockSize number of items to compare - * @param ia raw index (not block index) of first block - * @param ib raw index (not block index) of second block - */ - public static compareLexicalBlock(data: Float64Array, blockSize: number, ia: number, ib: number): number { - let ax = 0; - let bx = 0; - for (let i = 0; i < blockSize; i++) { - ax = data[ia + i]; - bx = data[ib + i]; - if (ax > bx) return 1; - if (ax < bx) return -1; - } - return ia - ib; // so original order is maintained among duplicates !!!! - } - /** Return an array of block indices sorted per compareLexicalBlock function */ - public sortIndicesLexical(compareBlocks: BlockComparisonFunction = GrowableBlockedArray.compareLexicalBlock): Uint32Array { - const n = this._inUse; - // let numCompare = 0; - const result = new Uint32Array(n); - const data = this._data; - const blockSize = this._blockSize; - for (let i = 0; i < n; i++)result[i] = i; - result.sort( - (blockIndexA: number, blockIndexB: number) => { - // numCompare++; - return compareBlocks(data, blockSize, blockIndexA * blockSize, blockIndexB * blockSize); - }); - // console.log (n, numCompare); - return result; - } - public distanceBetweenBlocks(blockIndexA: number, blockIndexB: number): number { - let dd = 0.0; - let iA = this.blockIndexToDoubleIndex(blockIndexA); - let iB = this.blockIndexToDoubleIndex(blockIndexB); - let a = 0; - const data = this._data; - for (let i = 0; i < this._blockSize; i++) { - a = data[iA++] - data[iB++]; - dd += a * a; - } - return Math.sqrt(dd); - } - - public distanceBetweenSubBlocks(blockIndexA: number, blockIndexB: number, iBegin: number, iEnd: number): number { - let dd = 0.0; - const iA = this.blockIndexToDoubleIndex(blockIndexA); - const iB = this.blockIndexToDoubleIndex(blockIndexB); - let a = 0; - const data = this._data; - for (let i = iBegin; i < iEnd; i++) { - a = data[iA + i] - data[iB + i]; - dd += a * a; - } - return Math.sqrt(dd); - } -} -/** Use a Float64Array to pack xyz coordinates. */ -export class GrowableXYZArray extends IndexedXYZCollection { - private _data: Float64Array; - private _inUse: number; - private _capacity: number; - /** Construct a new GrowablePoint3d array. - * @param numPoints [in] initial capacity. - */ - public constructor(numPoints: number = 8) { - super(); - this._data = new Float64Array(numPoints * 3); // 8 Points to start (3 values each) - this._inUse = 0; - this._capacity = numPoints; - } - /** @returns Return the number of points in use. */ - public get length() { return this._inUse; } - /** @returns Return the number of float64 in use. */ - public get float64Length() { return this._inUse * 3; } - /** If necessary, increase the capacity to a new pointCount. Current coordinates and point count (length) are unchnaged. */ - public ensureCapacity(pointCapacity: number) { - if (pointCapacity > this._capacity) { - const newData = new Float64Array(pointCapacity * 3); - const numCopy = this.length * 3; - for (let i = 0; i < numCopy; i++) newData[i] = this._data[i]; - this._data = newData; - this._capacity = pointCapacity; - } - } - /** Resize the actual point count, preserving excess capacity. */ - public resize(pointCount: number) { - if (pointCount < this.length) { - this._inUse = pointCount >= 0 ? pointCount : 0; - } else if (pointCount > this._capacity) { - const newArray = new Float64Array(pointCount * 3); - // Copy contents - for (let i = 0; i < this._data.length; i += 3) { - newArray[i] = this._data[i]; - newArray[i + 1] = this._data[i + 1]; - newArray[i + 2] = this._data[i + 2]; - } - this._data = newArray; - this._capacity = pointCount; - } - } - /** - * Make a copy of the (active) points in this array. - * (The clone does NOT get excess capacity) - */ - public clone(): GrowableXYZArray { - const newPoints = new GrowableXYZArray(this.length); - const numValue = this.length * 3; - const newData = newPoints._data; - const data = this._data; - for (let i = 0; i < numValue; i++) newData[i] = data[i]; - newPoints._inUse = this.length; - return newPoints; - } - - public static create(data: XYAndZ[]): GrowableXYZArray { - const newPoints = new GrowableXYZArray(data.length); - for (const p of data) newPoints.push(p); - return newPoints; - } - - /** push a point to the end of the array */ - public push(toPush: XYAndZ) { - this.pushXYZ(toPush.x, toPush.y, toPush.z); - } - - /** push all points of an array */ - public pushAll(points: Point3d[]) { - for (const p of points) this.push(p); - } - /** - * Replicate numWrap xyz values from the front of the array as new values at the end. - * @param numWrap number of xyz values to replicate - */ - public pushWrap(numWrap: number) { - if (this._inUse > 0) { - let k; - for (let i = 0; i < numWrap; i++) { - k = 3 * i; - this.pushXYZ(this._data[k], this._data[k + 1], this._data[k + 2]); - } - } - } - - public pushXYZ(x: number, y: number, z: number) { - const index = this._inUse * 3; - if (index >= this._data.length) - this.ensureCapacity(this.length * 2); - this._data[index] = x; - this._data[index + 1] = y; - this._data[index + 2] = z; - this._inUse++; - } - - /** Remove one point from the back. */ - public pop() { - if (this._inUse > 0) - this._inUse--; - } - /** - * Test if index is valid for an xyz (point or vector) withibn this array - * @param index xyz index to test. - */ - public isIndexValid(index: number): boolean { - if (index >= this._inUse || index < 0) - return false; - return true; - } - /** - * Clear all xyz data, but leave capacity unchanged. - */ - public clear() { - this._inUse = 0; - } - /** - * Get a point by index, strongly typed as a Point3d. This is unchecked. Use atPoint3dIndex to have validity test. - * @param pointIndex index to access - * @param result optional result - */ - public getPoint3dAt(pointIndex: number, result?: Point3d): Point3d { - const index = 3 * pointIndex; - return Point3d.create(this._data[index], this._data[index + 1], this._data[index + 2], result); - } - /** - * Get a point by index, strongly typed as a Point3d. - * @param pointIndexA start point index - * @param pointIndexB end point index - * @param result optional result - */ - public getVector3dBetweenIndices(pointIndexA: number, pointIndexB: number, result?: Vector3d): Vector3d | undefined { - const indexA = 3 * pointIndexA; - const indexB = 3 * pointIndexB; - const inuse = this._inUse; // this is a point count, not double count. - if (pointIndexA >= 0 && pointIndexA < inuse - && pointIndexB >= 0 && pointIndexB < inuse) - return Vector3d.createStartEndXYZXYZ( - this._data[indexA], this._data[indexA + 1], this._data[indexA + 2], - this._data[indexB], this._data[indexB + 1], this._data[indexB + 2], result); - return undefined; - } - - /** copy xyz into strongly typed Point3d */ - public atPoint3dIndex(pointIndex: number, result?: Point3d): Point3d | undefined { - const index = 3 * pointIndex; - if (pointIndex >= 0 && pointIndex < this._inUse) { - if (!result) result = Point3d.create(); - result.x = this._data[index]; - result.y = this._data[index + 1]; - result.z = this._data[index + 2]; - return result; - } - return undefined; - } - - /** copy xyz into strongly typed Vector3d */ - public atVector3dIndex(vectorIndex: number, result?: Vector3d): Vector3d | undefined { - const index = 3 * vectorIndex; - if (vectorIndex >= 0 && vectorIndex < this._inUse) { - if (!result) result = Vector3d.create(); - result.x = this._data[index]; - result.y = this._data[index + 1]; - result.z = this._data[index + 2]; - return result; - } - return undefined; - } - - /** - * Read coordinates from source array, place them at indexe within this array. - * @param destIndex point index where coordinats are to be placed in this array - * @param source source array - * @param sourceIndex point index in source array - * @returns true if destIndex and sourceIndex are both valid. - */ - public transferFromGrowableXYZArray(destIndex: number, source: GrowableXYZArray, sourceIndex: number): boolean { - if (destIndex < this.length && sourceIndex < source.length) { - const i = destIndex * 3; - const j = sourceIndex * 3; - this._data[i] = source._data[j]; - this._data[i + 1] = source._data[j + 1]; - this._data[i + 2] = source._data[j + 2]; - return true; - } - return false; - } - /** - * push coordinates from the source array to the end of this array. - * @param source source array - * @param sourceIndex xyz index within the source - * @returns true if sourceIndex is valid. - */ - public pushFromGrowableXYZArray(source: GrowableXYZArray, sourceIndex: number) { - if (sourceIndex < source.length) { - const j = sourceIndex * 3; - this.pushXYZ(source._data[j], source._data[j + 1], source._data[j + 2]); - return true; - } - return false; - } - - /** - * @returns Return the first point, or undefined if the array is empty. - */ - public front(result?: Point3d): Point3d | undefined { - if (this._inUse === 0) return undefined; - return this.getPoint3dAt(0, result); - } - /** - * @returns Return the last point, or undefined if the array is empty. - */ - public back(result?: Point3d): Point3d | undefined { - if (this._inUse - 1 < 0) return undefined; - return this.getPoint3dAt(this._inUse - 1, result); - } - /** - * Set the coordinates of a single point. - * @param pointIndex index of point to set - * @param value coordinates to set - */ - public setAt(pointIndex: number, value: XYAndZ): boolean { - if (pointIndex < 0 || pointIndex >= this._inUse) return false; - let index = pointIndex * 3; - this._data[index++] = value.x; - this._data[index++] = value.y; - this._data[index] = value.z; - return true; - } - /** - * Set the coordinates of a single point given as coordintes - * @param pointIndex index of point to set - * @param x x coordinate - * @param y y coordinate - * @param z z coordinate - */ - public setCoordinates(pointIndex: number, x: number, y: number, z: number): boolean { - if (pointIndex < 0 || pointIndex >= this._inUse) return false; - let index = pointIndex * 3; - this._data[index++] = x; - this._data[index++] = y; - this._data[index] = z; - return true; - } - - /** - * @returns Copy all points into a simple array of Point3d - */ - public getPoint3dArray(): Point3d[] { - const result = []; - const data = this._data; - const n = this.length; - for (let i = 0; i < n; i++) { - result.push(Point3d.create(data[i * 3], data[i * 3 + 1], data[i * 3 + 2])); - } - return result; - } - /** multiply each point by the transform, replace values. */ - public transformInPlace(transform: Transform) { - const data = this._data; - const nDouble = this.float64Length; - const coffs = transform.matrix.coffs; - const origin = transform.origin; - const x0 = origin.x; - const y0 = origin.y; - const z0 = origin.z; - let x = 0; - let y = 0; - let z = 0; - for (let i = 0; i + 3 <= nDouble; i += 3) { - x = data[i]; - y = data[i + 1]; - z = data[i + 2]; - data[i] = coffs[0] * x + coffs[1] * y + coffs[2] * z + x0; - data[i + 1] = coffs[3] * x + coffs[4] * y + coffs[5] * z + y0; - data[i + 2] = coffs[6] * x + coffs[7] * y + coffs[8] * z + z0; - } - } - - /** multiply each point by the transform, replace values. */ - public tryTransformInverseInPlace(transform: Transform): boolean { - const data = this._data; - const nDouble = this.float64Length; - const matrix = transform.matrix; - matrix.computeCachedInverse(true); - const coffs = matrix.inverseCoffs; - if (!coffs) - return false; - const origin = transform.origin; - const x0 = origin.x; - const y0 = origin.y; - const z0 = origin.z; - let x = 0; - let y = 0; - let z = 0; - for (let i = 0; i + 3 <= nDouble; i += 3) { - x = data[i] - x0; - y = data[i + 1] - y0; - z = data[i + 2] - z0; - data[i] = coffs[0] * x + coffs[1] * y + coffs[2] * z; - data[i + 1] = coffs[3] * x + coffs[4] * y + coffs[5] * z; - data[i + 2] = coffs[6] * x + coffs[7] * y + coffs[8] * z; - } - return true; - } - public extendRange(rangeToExtend: Range3d, transform?: Transform) { - const numDouble = this.float64Length; - const data = this._data; - if (transform) { - for (let i = 0; i + 3 <= numDouble; i += 3) - rangeToExtend.extendTransformedXYZ(transform, data[i], data[i + 1], data[i + 2]); - } else { - for (let i = 0; i + 3 <= numDouble; i += 3) - rangeToExtend.extendXYZ(data[i], data[i + 1], data[i + 2]); - - } - } - public sumLengths(): number { - let sum = 0.0; - const n = 3 * (this._inUse - 1); // Length already takes into account what specifically is in use - const data = this._data; - for (let i = 0; i < n; i += 3) sum += Geometry.hypotenuseXYZ( - data[i + 3] - data[i], - data[i + 4] - data[i + 1], - data[i + 5] - data[i + 2]); - return sum; - } - - public isCloseToPlane(plane: Plane3dByOriginAndUnitNormal, tolerance: number = Geometry.smallMetricDistance): boolean { - const numCoordinate = 3 * this._inUse; - const data = this._data; - for (let i = 0; i < numCoordinate; i += 3) - if (Math.abs(plane.altitudeXYZ(data[i], data[i + 1], data[i + 2])) > tolerance) - return false; - return true; - } - /** Compute a point at fractional coordinate between points i and j */ - public interpolate(i: number, fraction: number, j: number, result?: Point3d): Point3d | undefined { - if (i >= 0 && i < this._inUse) { - const fraction0 = 1.0 - fraction; - const data = this._data; - i = 3 * i; - j = 3 * j; - return Point3d.create( - fraction0 * data[i] + fraction * data[j], - fraction0 * data[i + 1] + fraction * data[j + 1], - fraction0 * data[i + 2] + fraction * data[j + 2], result); - } - return undefined; - } - - /** Sum the signed areas of the projection to xy plane */ - public areaXY(): number { - let area = 0.0; - const n = this._data.length - 6; // at least two points needed !!!! - if (n > 2) { - const x0 = this._data[0]; - const y0 = this._data[1]; - let dx1 = this._data[3] - x0; - let dy1 = this._data[4] - y0; - let dx2 = 0; - let dy2 = 0; - for (let i = 6; i < n; i += 3, dx1 = dx2, dy1 = dy2) { - dx2 = this._data[i] - x0; - dy2 = this._data[i + 1] - y0; - area += Geometry.crossProductXYXY(dx1, dy1, dx2, dy2); - } - } - return 0.5 * area; - } - - /** Compute a vector from index target i to indexed target j */ - public vectorIndexIndex(i: number, j: number, result?: Vector3d): Vector3d | undefined { - const n = this._inUse; - if (i < 0 || i >= n) - return undefined; - if (j < 0 || j >= n) - return undefined; - if (!result) result = Vector3d.create(); - const data = this._data; - i = 3 * i; - j = 3 * j; - result.x = data[j] - data[i]; - result.y = data[j + 1] - data[i + 1]; - result.z = data[j + 2] - data[i + 2]; - return result; - } - - /** Compute a vector from origin to indexed target j */ - public vectorXYAndZIndex(origin: XYAndZ, j: number, result?: Vector3d): Vector3d | undefined { - if (j >= 0 && j < this._inUse) { - const data = this._data; - j = 3 * j; - return Vector3d.create( - data[j] - origin.x, - data[j + 1] - origin.y, - data[j + 2] - origin.z, result); - } - return undefined; - } - - /** Compute the cross product of vectors from from indexed origin to indexed targets i and j */ - public crossProductIndexIndexIndex(originIndex: number, targetAIndex: number, targetBIndex: number, result?: Vector3d): Vector3d | undefined { - const i = originIndex * 3; - const j = targetAIndex * 3; - const k = targetBIndex * 3; - const data = this._data; - if (this.isIndexValid(originIndex) && this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) - return Geometry.crossProductXYZXYZ( - data[j] - data[i], data[j + 1] - data[i + 1], data[j + 2] - data[i + 2], - data[k] - data[i], data[k + 1] - data[i + 1], data[k + 2] - data[i + 2], - result); - return undefined; - } - - /** - * * compute the cross product from indexed origin t indexed targets targetAIndex and targetB index. - * * accumulate it to the result. - */ - public accumulateCrossProductIndexIndexIndex(originIndex: number, targetAIndex: number, targetBIndex: number, result: Vector3d): void { - const i = originIndex * 3; - const j = targetAIndex * 3; - const k = targetBIndex * 3; - const data = this._data; - if (this.isIndexValid(originIndex) && this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) - result.addCrossProductToTargetsInPlace( - data[i], data[i + 1], data[i + 2], - data[j], data[j + 1], data[j + 2], - data[k], data[k + 1], data[k + 2]); - return undefined; - } - - /** Compute the cross product of vectors from from origin to indexed targets i and j */ - public crossProductXYAndZIndexIndex(origin: XYAndZ, targetAIndex: number, targetBIndex: number, result?: Vector3d): Vector3d | undefined { - const j = targetAIndex * 3; - const k = targetBIndex * 3; - const data = this._data; - if (this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) - return Geometry.crossProductXYZXYZ( - data[j] - origin.x, data[j + 1] - origin.y, data[j + 2] - origin.z, - data[k] - origin.x, data[k + 1] - origin.y, data[k + 2] - origin.z, - result); - return undefined; - } - - /** Return the distance between two points in the array. */ - public distance(i: number, j: number): number { - if (i >= 0 && i < this._inUse && j >= 0 && j <= this._inUse) { - const i0 = 3 * i; - const j0 = 3 * j; - return Geometry.hypotenuseXYZ( - this._data[j0] - this._data[i0], - this._data[j0 + 1] - this._data[i0 + 1], - this._data[j0 + 2] - this._data[i0 + 2]); - } - return 0.0; - } - /** Return the distance between an array point and the input point. */ - public distanceIndexToPoint(i: number, spacePoint: Point3d): number { - if (i >= 0 && i < this._inUse) { - const i0 = 3 * i; - return Geometry.hypotenuseXYZ( - spacePoint.x - this._data[i0], - spacePoint.y - this._data[i0 + 1], - spacePoint.z - this._data[i0 + 2]); - } - return 0.0; - } - - public static isAlmostEqual(dataA: GrowableXYZArray | undefined, dataB: GrowableXYZArray | undefined): boolean { - if (dataA && dataB) { - if (dataA.length !== dataB.length) - return false; - for (let i = 0; i < dataA.length; i++) - if (!dataA.getPoint3dAt(i).isAlmostEqual(dataB.getPoint3dAt(i))) - return false; - return true; - } - // if both are null it is equal, otherwise unequal - return (!dataA && !dataB); - } - - /** Return an array of block indices sorted per compareLexicalBlock function */ - public sortIndicesLexical(): Uint32Array { - const n = this._inUse; - // let numCompare = 0; - const result = new Uint32Array(n); - for (let i = 0; i < n; i++)result[i] = i; - result.sort( - (blockIndexA: number, blockIndexB: number) => { - // numCompare++; - return this.compareLexicalBlock(blockIndexA, blockIndexB); - }); - // console.log (n, numCompare); - return result; - } - - /** compare two blocks in simple lexical order. */ - public compareLexicalBlock(ia: number, ib: number): number { - let ax = 0; - let bx = 0; - for (let i = 0; i < 3; i++) { - ax = this._data[ia * 3 + i]; - bx = this._data[ib * 3 + i]; - if (ax > bx) return 1; - if (ax < bx) return -1; - } - return ia - ib; // so original order is maintained among duplicates !!!! - } - - /** Access a single double at offset within a block. This has no index checking. */ - public component(pointIndex: number, componentIndex: number): number { - return this._data[3 * pointIndex + componentIndex]; - } -} +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +/** @module ArraysAndInterfaces */ + +import { Geometry } from "../Geometry"; +import { XYAndZ } from "./XYZProps"; +import { Point3d, Vector3d } from "./Point3dVector3d"; +import { Range3d } from "./Range"; +import { Transform } from "./Transform"; +import { IndexedXYZCollection } from "./IndexedXYZCollection"; + +import { Plane3dByOriginAndUnitNormal } from "./Plane3dByOriginAndUnitNormal"; + +/** Use a Float64Array to pack xyz coordinates. */ +export class GrowableXYZArray extends IndexedXYZCollection { + private _data: Float64Array; + private _inUse: number; + private _capacity: number; + /** Construct a new GrowablePoint3d array. + * @param numPoints [in] initial capacity. + */ + public constructor(numPoints: number = 8) { + super(); + this._data = new Float64Array(numPoints * 3); // 8 Points to start (3 values each) + this._inUse = 0; + this._capacity = numPoints; + } + /** @returns Return the number of points in use. */ + public get length() { return this._inUse; } + /** @returns Return the number of float64 in use. */ + public get float64Length() { return this._inUse * 3; } + + /** If necessary, increase the capacity to a new pointCount. Current coordinates and point count (length) are unchnaged. */ + public ensureCapacity(pointCapacity: number) { + if (pointCapacity > this._capacity) { + const newData = new Float64Array(pointCapacity * 3); + const numCopy = this.length * 3; + for (let i = 0; i < numCopy; i++) newData[i] = this._data[i]; + this._data = newData; + this._capacity = pointCapacity; + } + } + /** Resize the actual point count, preserving excess capacity. */ + public resize(pointCount: number) { + if (pointCount < this.length) { + this._inUse = pointCount >= 0 ? pointCount : 0; + } else if (pointCount > this._capacity) { + const newArray = new Float64Array(pointCount * 3); + // Copy contents + for (let i = 0; i < this._data.length; i += 3) { + newArray[i] = this._data[i]; + newArray[i + 1] = this._data[i + 1]; + newArray[i + 2] = this._data[i + 2]; + } + this._data = newArray; + this._capacity = pointCount; + this._inUse = pointCount; + } + } + /** + * Make a copy of the (active) points in this array. + * (The clone does NOT get excess capacity) + */ + public clone(): GrowableXYZArray { + const newPoints = new GrowableXYZArray(this.length); + const numValue = this.length * 3; + const newData = newPoints._data; + const data = this._data; + for (let i = 0; i < numValue; i++) newData[i] = data[i]; + newPoints._inUse = this.length; + return newPoints; + } + + public static create(data: XYAndZ[]): GrowableXYZArray { + const newPoints = new GrowableXYZArray(data.length); + for (const p of data) newPoints.push(p); + return newPoints; + } + + /** push a point to the end of the array */ + public push(toPush: XYAndZ) { + this.pushXYZ(toPush.x, toPush.y, toPush.z); + } + + /** push all points of an array */ + public pushAll(points: Point3d[]) { + for (const p of points) this.push(p); + } + /** + * Replicate numWrap xyz values from the front of the array as new values at the end. + * @param numWrap number of xyz values to replicate + */ + public pushWrap(numWrap: number) { + if (this._inUse > 0) { + let k; + for (let i = 0; i < numWrap; i++) { + k = 3 * i; + this.pushXYZ(this._data[k], this._data[k + 1], this._data[k + 2]); + } + } + } + + public pushXYZ(x: number, y: number, z: number) { + const index = this._inUse * 3; + if (index >= this._data.length) + this.ensureCapacity(this.length * 2); + this._data[index] = x; + this._data[index + 1] = y; + this._data[index + 2] = z; + this._inUse++; + } + + /** Remove one point from the back. */ + public pop() { + if (this._inUse > 0) + this._inUse--; + } + /** + * Test if index is valid for an xyz (point or vector) withibn this array + * @param index xyz index to test. + */ + public isIndexValid(index: number): boolean { + if (index >= this._inUse || index < 0) + return false; + return true; + } + /** + * Clear all xyz data, but leave capacity unchanged. + */ + public clear() { + this._inUse = 0; + } + /** + * Get a point by index, strongly typed as a Point3d. This is unchecked. Use atPoint3dIndex to have validity test. + * @param pointIndex index to access + * @param result optional result + */ + public getPoint3dAt(pointIndex: number, result?: Point3d): Point3d { + const index = 3 * pointIndex; + return Point3d.create(this._data[index], this._data[index + 1], this._data[index + 2], result); + } + + /** copy xyz into strongly typed Point3d */ + public atPoint3dIndex(pointIndex: number, result?: Point3d): Point3d | undefined { + const index = 3 * pointIndex; + if (this.isIndexValid(pointIndex)) { + if (!result) result = Point3d.create(); + result.x = this._data[index]; + result.y = this._data[index + 1]; + result.z = this._data[index + 2]; + return result; + } + return undefined; + } + + /** copy xyz into strongly typed Vector3d */ + public atVector3dIndex(vectorIndex: number, result?: Vector3d): Vector3d | undefined { + const index = 3 * vectorIndex; + if (vectorIndex >= 0 && vectorIndex < this._inUse) { + if (!result) result = Vector3d.create(); + result.x = this._data[index]; + result.y = this._data[index + 1]; + result.z = this._data[index + 2]; + return result; + } + return undefined; + } + + /** + * Read coordinates from source array, place them at indexe within this array. + * @param destIndex point index where coordinats are to be placed in this array + * @param source source array + * @param sourceIndex point index in source array + * @returns true if destIndex and sourceIndex are both valid. + */ + public transferFromGrowableXYZArray(destIndex: number, source: GrowableXYZArray, sourceIndex: number): boolean { + if (this.isIndexValid(destIndex) && source.isIndexValid(sourceIndex)) { + const i = destIndex * 3; + const j = sourceIndex * 3; + this._data[i] = source._data[j]; + this._data[i + 1] = source._data[j + 1]; + this._data[i + 2] = source._data[j + 2]; + return true; + } + return false; + } + + /** + * push coordinates from the source array to the end of this array. + * @param source source array + * @param sourceIndex xyz index within the source + * @returns true if sourceIndex is valid. + */ + public pushFromGrowableXYZArray(source: GrowableXYZArray, sourceIndex: number) { + if (source.isIndexValid(sourceIndex)) { + const j = sourceIndex * 3; + this.pushXYZ(source._data[j], source._data[j + 1], source._data[j + 2]); + return true; + } + return false; + } + + /** + * @returns Return the first point, or undefined if the array is empty. + */ + public front(result?: Point3d): Point3d | undefined { + if (this._inUse === 0) return undefined; + return this.getPoint3dAt(0, result); + } + /** + * @returns Return the last point, or undefined if the array is empty. + */ + public back(result?: Point3d): Point3d | undefined { + if (this._inUse < 1) return undefined; + return this.getPoint3dAt(this._inUse - 1, result); + } + /** + * Set the coordinates of a single point. + * @param pointIndex index of point to set + * @param value coordinates to set + */ + public setAt(pointIndex: number, value: XYAndZ): boolean { + if (!this.isIndexValid(pointIndex)) + return false; + let index = pointIndex * 3; + this._data[index++] = value.x; + this._data[index++] = value.y; + this._data[index] = value.z; + return true; + } + /** + * Set the coordinates of a single point given as coordintes + * @param pointIndex index of point to set + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + */ + public setCoordinates(pointIndex: number, x: number, y: number, z: number): boolean { + if (!this.isIndexValid(pointIndex)) + return false; + let index = pointIndex * 3; + this._data[index++] = x; + this._data[index++] = y; + this._data[index] = z; + return true; + } + + /** + * @returns Copy all points into a simple array of Point3d + */ + public getPoint3dArray(): Point3d[] { + const result = []; + const data = this._data; + const n = this.length; + for (let i = 0; i < n; i++) { + result.push(Point3d.create(data[i * 3], data[i * 3 + 1], data[i * 3 + 2])); + } + return result; + } + /** multiply each point by the transform, replace values. */ + public transformInPlace(transform: Transform) { + const data = this._data; + const nDouble = this.float64Length; + const coffs = transform.matrix.coffs; + const origin = transform.origin; + const x0 = origin.x; + const y0 = origin.y; + const z0 = origin.z; + let x = 0; + let y = 0; + let z = 0; + for (let i = 0; i + 3 <= nDouble; i += 3) { + x = data[i]; + y = data[i + 1]; + z = data[i + 2]; + data[i] = coffs[0] * x + coffs[1] * y + coffs[2] * z + x0; + data[i + 1] = coffs[3] * x + coffs[4] * y + coffs[5] * z + y0; + data[i + 2] = coffs[6] * x + coffs[7] * y + coffs[8] * z + z0; + } + } + + /** multiply each point by the transform, replace values. */ + public tryTransformInverseInPlace(transform: Transform): boolean { + const data = this._data; + const nDouble = this.float64Length; + const matrix = transform.matrix; + matrix.computeCachedInverse(true); + const coffs = matrix.inverseCoffs; + if (!coffs) + return false; + const origin = transform.origin; + const x0 = origin.x; + const y0 = origin.y; + const z0 = origin.z; + let x = 0; + let y = 0; + let z = 0; + for (let i = 0; i + 3 <= nDouble; i += 3) { + x = data[i] - x0; + y = data[i + 1] - y0; + z = data[i + 2] - z0; + data[i] = coffs[0] * x + coffs[1] * y + coffs[2] * z; + data[i + 1] = coffs[3] * x + coffs[4] * y + coffs[5] * z; + data[i + 2] = coffs[6] * x + coffs[7] * y + coffs[8] * z; + } + return true; + } + public extendRange(rangeToExtend: Range3d, transform?: Transform) { + const numDouble = this.float64Length; + const data = this._data; + if (transform) { + for (let i = 0; i + 3 <= numDouble; i += 3) + rangeToExtend.extendTransformedXYZ(transform, data[i], data[i + 1], data[i + 2]); + } else { + for (let i = 0; i + 3 <= numDouble; i += 3) + rangeToExtend.extendXYZ(data[i], data[i + 1], data[i + 2]); + + } + } + public sumLengths(): number { + let sum = 0.0; + const n = 3 * (this._inUse - 1); // Length already takes into account what specifically is in use + const data = this._data; + for (let i = 0; i < n; i += 3) sum += Geometry.hypotenuseXYZ( + data[i + 3] - data[i], + data[i + 4] - data[i + 1], + data[i + 5] - data[i + 2]); + return sum; + } + + public isCloseToPlane(plane: Plane3dByOriginAndUnitNormal, tolerance: number = Geometry.smallMetricDistance): boolean { + const numCoordinate = 3 * this._inUse; + const data = this._data; + for (let i = 0; i < numCoordinate; i += 3) + if (Math.abs(plane.altitudeXYZ(data[i], data[i + 1], data[i + 2])) > tolerance) + return false; + return true; + } + /** Compute a point at fractional coordinate between points i and j */ + public interpolate(i: number, fraction: number, j: number, result?: Point3d): Point3d | undefined { + if (this.isIndexValid(i) && this.isIndexValid(j)) { + const fraction0 = 1.0 - fraction; + const data = this._data; + i = 3 * i; + j = 3 * j; + return Point3d.create( + fraction0 * data[i] + fraction * data[j], + fraction0 * data[i + 1] + fraction * data[j + 1], + fraction0 * data[i + 2] + fraction * data[j + 2], result); + } + return undefined; + } + + /** Sum the signed areas of the projection to xy plane */ + public areaXY(): number { + let area = 0.0; + const n = this._data.length - 6; // at least two points needed !!!! + if (n > 2) { + const x0 = this._data[0]; + const y0 = this._data[1]; + let dx1 = this._data[3] - x0; + let dy1 = this._data[4] - y0; + let dx2 = 0; + let dy2 = 0; + for (let i = 6; i < n; i += 3, dx1 = dx2, dy1 = dy2) { + dx2 = this._data[i] - x0; + dy2 = this._data[i + 1] - y0; + area += Geometry.crossProductXYXY(dx1, dy1, dx2, dy2); + } + } + return 0.5 * area; + } + + /** Compute a vector from index origin i to indexed target j */ + public vectorIndexIndex(i: number, j: number, result?: Vector3d): Vector3d | undefined { + if (!this.isIndexValid(i) || !this.isIndexValid(j)) + return undefined; + if (!result) result = Vector3d.create(); + const data = this._data; + i = 3 * i; + j = 3 * j; + result.x = data[j] - data[i]; + result.y = data[j + 1] - data[i + 1]; + result.z = data[j + 2] - data[i + 2]; + return result; + } + + /** Compute a vector from origin to indexed target j */ + public vectorXYAndZIndex(origin: XYAndZ, j: number, result?: Vector3d): Vector3d | undefined { + if (this.isIndexValid(j)) { + const data = this._data; + j = 3 * j; + return Vector3d.create( + data[j] - origin.x, + data[j + 1] - origin.y, + data[j + 2] - origin.z, result); + } + return undefined; + } + + /** Compute the cross product of vectors from from indexed origin to indexed targets i and j */ + public crossProductIndexIndexIndex(originIndex: number, targetAIndex: number, targetBIndex: number, result?: Vector3d): Vector3d | undefined { + const i = originIndex * 3; + const j = targetAIndex * 3; + const k = targetBIndex * 3; + const data = this._data; + if (this.isIndexValid(originIndex) && this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) + return Geometry.crossProductXYZXYZ( + data[j] - data[i], data[j + 1] - data[i + 1], data[j + 2] - data[i + 2], + data[k] - data[i], data[k + 1] - data[i + 1], data[k + 2] - data[i + 2], + result); + return undefined; + } + + /** + * * compute the cross product from indexed origin t indexed targets targetAIndex and targetB index. + * * accumulate it to the result. + */ + public accumulateCrossProductIndexIndexIndex(originIndex: number, targetAIndex: number, targetBIndex: number, result: Vector3d): void { + const i = originIndex * 3; + const j = targetAIndex * 3; + const k = targetBIndex * 3; + const data = this._data; + if (this.isIndexValid(originIndex) && this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) + result.addCrossProductToTargetsInPlace( + data[i], data[i + 1], data[i + 2], + data[j], data[j + 1], data[j + 2], + data[k], data[k + 1], data[k + 2]); + return undefined; + } + + /** Compute the cross product of vectors from from origin to indexed targets i and j */ + public crossProductXYAndZIndexIndex(origin: XYAndZ, targetAIndex: number, targetBIndex: number, result?: Vector3d): Vector3d | undefined { + const j = targetAIndex * 3; + const k = targetBIndex * 3; + const data = this._data; + if (this.isIndexValid(targetAIndex) && this.isIndexValid(targetBIndex)) + return Geometry.crossProductXYZXYZ( + data[j] - origin.x, data[j + 1] - origin.y, data[j + 2] - origin.z, + data[k] - origin.x, data[k + 1] - origin.y, data[k + 2] - origin.z, + result); + return undefined; + } + + /** Return the distance between two points in the array. */ + public distance(i: number, j: number): number | undefined { + if (i >= 0 && i < this._inUse && j >= 0 && j <= this._inUse) { + const i0 = 3 * i; + const j0 = 3 * j; + return Geometry.hypotenuseXYZ( + this._data[j0] - this._data[i0], + this._data[j0 + 1] - this._data[i0 + 1], + this._data[j0 + 2] - this._data[i0 + 2]); + } + return undefined; + } + /** Return the distance between an array point and the input point. */ + public distanceIndexToPoint(i: number, spacePoint: Point3d): number | undefined { + if (i >= 0 && i < this._inUse) { + const i0 = 3 * i; + return Geometry.hypotenuseXYZ( + spacePoint.x - this._data[i0], + spacePoint.y - this._data[i0 + 1], + spacePoint.z - this._data[i0 + 2]); + } + return undefined; + } + + public static isAlmostEqual(dataA: GrowableXYZArray | undefined, dataB: GrowableXYZArray | undefined): boolean { + if (dataA && dataB) { + if (dataA.length !== dataB.length) + return false; + for (let i = 0; i < dataA.length; i++) + if (!dataA.getPoint3dAt(i).isAlmostEqual(dataB.getPoint3dAt(i))) + return false; + return true; + } + // if both are null it is equal, otherwise unequal + return (!dataA && !dataB); + } + + /** Return an array of block indices sorted per compareLexicalBlock function */ + public sortIndicesLexical(): Uint32Array { + const n = this._inUse; + // let numCompare = 0; + const result = new Uint32Array(n); + for (let i = 0; i < n; i++)result[i] = i; + result.sort( + (blockIndexA: number, blockIndexB: number) => { + // numCompare++; + return this.compareLexicalBlock(blockIndexA, blockIndexB); + }); + // console.log (n, numCompare); + return result; + } + + /** compare two blocks in simple lexical order. */ + public compareLexicalBlock(ia: number, ib: number): number { + let ax = 0; + let bx = 0; + for (let i = 0; i < 3; i++) { + ax = this._data[ia * 3 + i]; + bx = this._data[ib * 3 + i]; + if (ax > bx) return 1; + if (ax < bx) return -1; + } + return ia - ib; // so original order is maintained among duplicates !!!! + } + + /** Access a single double at offset within a block. This has no index checking. */ + public component(pointIndex: number, componentIndex: number): number { + return this._data[3 * pointIndex + componentIndex]; + } +} diff --git a/core/geometry/src/geometry3d/Matrix3d.ts b/core/geometry/src/geometry3d/Matrix3d.ts index 42d977d..210afd9 100644 --- a/core/geometry/src/geometry3d/Matrix3d.ts +++ b/core/geometry/src/geometry3d/Matrix3d.ts @@ -164,7 +164,15 @@ export class Matrix3d implements BeJSONFunctions { } /** Freeze this Matrix3d. */ - public freeze() { this.computeCachedInverse(true); Object.freeze(this); } + public freeze() { + this.computeCachedInverse(true); + /* hm.. can't freeze the Float64Arrays . . . + Object.freeze(this.coffs); + if (this.inverseCoffs) + Object.freeze(this.inverseCoffs); + */ + Object.freeze(this); + } /** * * @param coffs optional coefficient array. This is captured. @@ -288,19 +296,17 @@ export class Matrix3d implements BeJSONFunctions { // install all matrix entries. public static createColumnsInAxisOrder(axisOrder: AxisOrder, columnA: Vector3d, columnB: Vector3d, columnC: Vector3d | undefined, result?: Matrix3d) { if (!result) result = new Matrix3d(); - if (axisOrder === AxisOrder.XYZ) { - result.setColumns(columnA, columnB, columnC); - } else if (axisOrder === AxisOrder.YZX) { - result.setColumns(columnB, columnC, columnA); - } else if (axisOrder === AxisOrder.ZXY) { + if (axisOrder === AxisOrder.YZX) { result.setColumns(columnC, columnA, columnB); + } else if (axisOrder === AxisOrder.ZXY) { + result.setColumns(columnB, columnC, columnA); } else if (axisOrder === AxisOrder.XZY) { result.setColumns(columnA, columnC, columnB); } else if (axisOrder === AxisOrder.YXZ) { result.setColumns(columnB, columnA, columnC); } else if (axisOrder === AxisOrder.ZYX) { result.setColumns(columnC, columnB, columnA); - } else { // should not happen -- go to default + } else { // fallthrough should only happen for AxisOrder.XYZ result.setColumns(columnA, columnB, columnC); } return result; @@ -528,9 +534,7 @@ export class Matrix3d implements BeJSONFunctions { */ public static createStandardWorldToView(index: StandardViewIndex, invert: boolean = false, result?: Matrix3d): Matrix3d { switch (index) { - case StandardViewIndex.Top: - result = Matrix3d.createIdentity(result); - break; + case StandardViewIndex.Bottom: result = Matrix3d.createRowValues( 1, 0, 0, @@ -573,6 +577,7 @@ export class Matrix3d implements BeJSONFunctions { -0.408248290463863, 0.40824829046386302, 0.81649658092772603, 0.577350269189626, -0.57735026918962573, 0.57735026918962573); break; + case StandardViewIndex.Top: default: result = Matrix3d.createIdentity(result); } @@ -688,15 +693,18 @@ export class Matrix3d implements BeJSONFunctions { } // 180 degree flip around some other axis ... + // eigenvalues will have 1.0 once, -1.0 twice. + // These cases look for each place (x,y,z) that the 1.0 might appear. + // But fastSymmetricEigenvalues reliably always seems to put the 1.0 as the x eigenvalue. + // so only the getColumn(0) return seems reachable in unit tests. const eigenvectors = Matrix3d.createIdentity(); const eigenvalues = Vector3d.create(0, 0, 0); if (this.fastSymmetricEigenvalues(eigenvectors, eigenvalues)) { - if (Geometry.isAlmostEqualNumber(1, eigenvalues.x)) - return { axis: eigenvectors.getColumn(0), angle: theta180, ok: true }; - if (Geometry.isAlmostEqualNumber(1, eigenvalues.y)) - return { axis: eigenvectors.getColumn(1), angle: theta180, ok: true }; - if (Geometry.isAlmostEqualNumber(1, eigenvalues.z)) - return { axis: eigenvectors.getColumn(2), angle: theta180, ok: true }; + for (let axisIndex = 0; axisIndex < 2; axisIndex++) { + const lambda = eigenvalues.at(axisIndex); + if (Geometry.isAlmostEqualNumber(1, lambda)) + return { axis: eigenvectors.getColumn(axisIndex), angle: theta180, ok: true }; + } // Don't know if this can be reached .... return { axis: Vector3d.create(0, 0, 1), angle: Angle.createRadians(0), ok: false }; } @@ -786,6 +794,13 @@ export class Matrix3d implements BeJSONFunctions { /** @returns Return the Z column magnitude */ public columnZMagnitude(): number { return Math.hypot(this.coffs[2], this.coffs[5], this.coffs[8]); } + /** @returns Return magntiude of columnX cross columnY. */ + public columnXYCrossProductMagnitude(): number { + return Geometry.crossProductMagnitude( + this.coffs[0], this.coffs[3], this.coffs[6], + this.coffs[1], this.coffs[4], this.coffs[7]); + } + /** @returns Return the X row magnitude d */ public rowXMagnitude(): number { return Math.hypot(this.coffs[0], this.coffs[1], this.coffs[2]); } /** @returns Return the Y row magnitude */ diff --git a/core/geometry/src/geometry3d/Plane3dByOriginAndVectors.ts b/core/geometry/src/geometry3d/Plane3dByOriginAndVectors.ts index 7d31460..80fb01e 100644 --- a/core/geometry/src/geometry3d/Plane3dByOriginAndVectors.ts +++ b/core/geometry/src/geometry3d/Plane3dByOriginAndVectors.ts @@ -6,6 +6,7 @@ /** @module CartesianGeometry */ import { Point3d, Vector3d } from "./Point3dVector3d"; import { BeJSONFunctions, Geometry } from "../Geometry"; +import { Transform } from "./Transform"; /** * A Point3dVector3dVector3d is an origin and a pair of vectors. * This defines a plane with (possibly skewed) uv coordinates @@ -28,6 +29,35 @@ export class Plane3dByOriginAndVectors implements BeJSONFunctions { } return new Plane3dByOriginAndVectors(origin.clone(), vectorU.clone(), vectorV.clone()); } + /** + * Return a Plane3dByOriginAndVectors, with + * * irigin is the translation (aka origin) from the Transform + * * vectorU is the X column of the transform + * * vectorV is the Y column of the transform. + * @param transform source trnasform + * @param xLength optional length to impose on vectorU. + * @param yLength optional length to impose on vectorV. + * @param result optional preexisting result + */ + public static createFromTransformColumnsXYAndLengths(transform: Transform, + xLength: number | undefined, yLength: number | undefined, + result?: Plane3dByOriginAndVectors): Plane3dByOriginAndVectors { + if (result) { + result.origin.setFrom(transform.getOrigin()); + transform.matrix.columnX(result.vectorU); + transform.matrix.columnY(result.vectorV); + } else { + result = new Plane3dByOriginAndVectors( + transform.getOrigin(), + transform.matrix.columnX(), + transform.matrix.columnY()); + } + if (xLength !== undefined) + result.vectorU.scaleToLength(xLength, result.vectorU); + if (yLength !== undefined) + result.vectorV.scaleToLength(yLength, result.vectorV); + return result; + } /** Capture origin and directions in a new planed. */ public static createCapture(origin: Point3d, vectorU: Vector3d, vectorV: Vector3d, result?: Plane3dByOriginAndVectors): Plane3dByOriginAndVectors { if (!result) diff --git a/core/geometry/src/geometry3d/Point3dVector3d.ts b/core/geometry/src/geometry3d/Point3dVector3d.ts index 90c8553..3d518a6 100644 --- a/core/geometry/src/geometry3d/Point3dVector3d.ts +++ b/core/geometry/src/geometry3d/Point3dVector3d.ts @@ -211,13 +211,13 @@ export class XYZ implements XYAndZ { /** Freeze this XYZ */ public freeze() { Object.freeze(this); } } -/** 3D vector with x,y,z properties */ +/** 3D point with x,y,z properties */ export class Point3d extends XYZ { /** Constructor for Point3d */ constructor(x: number = 0, y: number = 0, z: number = 0) { super(x, y, z); } public static fromJSON(json?: XYZProps): Point3d { const val = new Point3d(); val.setFromJSON(json); return val; } /** Return a new Point3d with the same coordinates */ - public clone(): Point3d { return new Point3d(this.x, this.y, this.z); } + public clone(result?: Point3d): Point3d { return Point3d.create(this.x, this.y, this.z, result); } /** Create a new Point3d with given coordinates * @param x x part * @param y y part @@ -366,7 +366,10 @@ export class Point3d extends XYZ { } /** Return point + vectorA * scalarA + vectorB * scalarB + vectorC * scalarC */ public plus3Scaled(vectorA: XYAndZ, scalarA: number, vectorB: XYAndZ, scalarB: number, vectorC: XYAndZ, scalarC: number, result?: Point3d): Point3d { - return Point3d.create(this.x + vectorA.x * scalarA + vectorB.x * scalarB + vectorC.x * scalarC, this.y + vectorA.y * scalarA + vectorB.y * scalarB + vectorC.y * scalarC, this.z + vectorA.z * scalarA + vectorB.z * scalarB + vectorC.z * scalarC, result); + return Point3d.create( + this.x + vectorA.x * scalarA + vectorB.x * scalarB + vectorC.x * scalarC, + this.y + vectorA.y * scalarA + vectorB.y * scalarB + vectorC.y * scalarC, + this.z + vectorA.z * scalarA + vectorB.z * scalarB + vectorC.z * scalarC, result); } /** * Return a point that is scaled from the source point. @@ -420,7 +423,18 @@ export class Point3d extends XYZ { /** 3D vector with x,y,z properties */ export class Vector3d extends XYZ { constructor(x: number = 0, y: number = 0, z: number = 0) { super(x, y, z); } - public clone(): Vector3d { return new Vector3d(this.x, this.y, this.z); } + /** + * Copy xyz from this instance to a new (or optionally resused) Vector3d + * @param result optional instance to reuse. + */ + public clone(result?: Vector3d): Vector3d { return Vector3d.create(this.x, this.y, this.z, result); } + /** + * return a Vector3d (new or reused from optional result) + * @param x x component + * @param y y component + * @param z z component + * @param result optional instance to reuse + */ public static create(x: number = 0, y: number = 0, z: number = 0, result?: Vector3d): Vector3d { if (result) { result.x = x; @@ -790,20 +804,36 @@ export class Vector3d extends XYZ { } return undefined; } - // products - public crossProductMagnitudeSquared(vectorB: Vector3d): number { + /** + * Compute the squared magnitude of a cross product (without allocating a temporary vector object) + * @param vectorB second vector of cross product + * @returns the squared magnitude of the cross product of this instance with vectorB. + */ + public crossProductMagnitudeSquared(vectorB: XYAndZ): number { const xx = this.y * vectorB.z - this.z * vectorB.y; const yy = this.z * vectorB.x - this.x * vectorB.z; const zz = this.x * vectorB.y - this.y * vectorB.x; return xx * xx + yy * yy + zz * zz; } - public crossProductMagnitude(vectorB: Vector3d): number { + /** + * Compute the magnitude of a cross product (without allocating a temporary vector object) + * @param vectorB second vector of cross product + * @returns the magnitude of the cross product of this instance with vectorB. + */ + public crossProductMagnitude(vectorB: XYAndZ): number { return Math.sqrt(this.crossProductMagnitudeSquared(vectorB)); } + /** + * @param vectorB second vector of cross product + * @returns the dot product of this instance with vectorB + */ public dotProduct(vectorB: XYAndZ): number { return this.x * vectorB.x + this.y * vectorB.y + this.z * vectorB.z; } - /** Dot product with vector from pointA to pointB */ + /** @returns the dot product of this instance with the with vector from pointA to pointB + * @param pointA start point of second vector of dot product + * @param pointB end point of second vector of dot product + */ public dotProductStartEnd(pointA: Point3d, pointB: Point3d): number { return this.x * (pointB.x - pointA.x) + this.y * (pointB.y - pointA.y) diff --git a/core/geometry/src/geometry3d/PointHelpers.ts b/core/geometry/src/geometry3d/PointHelpers.ts index 0735e36..1d8ca11 100644 --- a/core/geometry/src/geometry3d/PointHelpers.ts +++ b/core/geometry/src/geometry3d/PointHelpers.ts @@ -132,7 +132,11 @@ export class Point2dArray { public static clonePoint2dArray(data: Point2d[]): Point2d[] { return data.map((p: Point2d) => p.clone()); } - public static lengthWithoutWraparound(data: XAndY[]): number { + /** + * Return the number of points when trailing points that match point 0 are excluded. + * @param data array of XAndY points. + */ + public static pointCountExcludingTrailingWraparound(data: XAndY[]): number { let n = data.length; if (n < 2) return n; @@ -385,9 +389,9 @@ export class Point3dArray { } /** Return the index of the point most distant from spacePoint */ - public static vectorToMostDistantPoint(points: Point3d[], spacePoint: XYZ, farVector: Vector3d): number { + public static indexOfMostDistantPoint(points: Point3d[], spacePoint: XYZ, farVector: Vector3d): number | undefined { if (points.length === 0) - return -1; + return undefined; let dMax = -1; let d; let result = -1; @@ -402,9 +406,9 @@ export class Point3dArray { return result; } /** return the index of the point whose vector from space point has the largest magnitude of cross product with given vector. */ - public static vectorToPointWithMaxCrossProductMangitude(points: Point3d[], spacePoint: Point3d, vector: Vector3d, farVector: Vector3d): number { + public static indexOfPointWithMaxCrossProductMagnitude(points: Point3d[], spacePoint: Point3d, vector: Vector3d, farVector: Vector3d): number | undefined { if (points.length === 0) - return -1; + return undefined; let dMax = -1; let d; let result = -1; @@ -455,19 +459,31 @@ export class Point3dArray { } return true; } - - public static sumLengths(data: Point3d[] | Float64Array): number { + /** + * Sum lengths of edges. + * @param data points. + */ + public static sumEdgeLengths(data: Point3d[] | Float64Array, addClosureEdge: boolean = false): number { let sum = 0.0; if (Array.isArray(data)) { const n = data.length - 1; for (let i = 0; i < n; i++) sum += data[i].distance(data[i + 1]); + if (addClosureEdge && n > 0) + sum += data[0].distance(data[n]); + } else if (data instanceof Float64Array) { const numXYZ = data.length; - for (let i = 0; i + 5 < numXYZ; i += 3) { - sum += Math.hypot(data[i + 3] - data[i], + let i = 0; + for (; i + 5 < numXYZ; i += 3) { // final i points at final point x + sum += Geometry.hypotenuseXYZ(data[i + 3] - data[i], data[i + 4] - data[i + 1], data[i + 5] - data[i + 2]); } + if (addClosureEdge && i >= 3) { + sum += Geometry.hypotenuseXYZ(data[0] - data[i], + data[1] - data[i + 1], + data[2] - data[i + 2]); + } } return sum; } @@ -634,7 +650,7 @@ export class PolygonOps { // Has the potential to be combined with centroidAreaNormal for point3d array and Ray3d return listed above... // Returns undefined if given point array less than 3 or if not safe to divide at any point - public static centroidAndArea(points: Point2d[], centroid: Point2d): number | undefined { + public static centroidAndAreaXY(points: Point2d[], centroid: Point2d): number | undefined { let area = 0.0; centroid.set(0, 0); if (points.length < 3) diff --git a/core/geometry/src/geometry3d/Range.ts b/core/geometry/src/geometry3d/Range.ts index d977bbf..e868389 100644 --- a/core/geometry/src/geometry3d/Range.ts +++ b/core/geometry/src/geometry3d/Range.ts @@ -13,7 +13,7 @@ import { Matrix3d } from "./Matrix3d"; import { Range1dProps, Range2dProps, Range3dProps } from "./XYZProps"; import { LowAndHighXYZ, LowAndHighXY } from "./XYZProps"; import { XAndY, XYAndZ } from "./XYZProps"; -import { GrowableXYZArray } from "./GrowableArray"; +import { GrowableXYZArray } from "./GrowableXYZArray"; export abstract class RangeBase { protected static readonly _EXTREME_POSITIVE: number = 1.0e200; diff --git a/core/geometry/src/geometry3d/Segment1d.ts b/core/geometry/src/geometry3d/Segment1d.ts index 6c37780..f6ae10c 100644 --- a/core/geometry/src/geometry3d/Segment1d.ts +++ b/core/geometry/src/geometry3d/Segment1d.ts @@ -49,6 +49,13 @@ export class Segment1d { * clone this Segment1d, return as a separate object. */ public clone(): Segment1d { return new Segment1d(this.x0, this.x1); } + /** + * @returns true if both coordinates (`x0` and `x1`) are in the 0..1 range. + */ + public get isIn01() { + return Geometry.isIn01(this.x0) && Geometry.isIn01(this.x1); + + } /** * Evalauate the segment at fractional position * @returns position within the segment diff --git a/core/geometry/src/geometry3d/Transform.ts b/core/geometry/src/geometry3d/Transform.ts index 1e3dbbe..481a9bf 100644 --- a/core/geometry/src/geometry3d/Transform.ts +++ b/core/geometry/src/geometry3d/Transform.ts @@ -316,13 +316,12 @@ export class Transform implements BeJSONFunctions { return result; } /** - * * for each point: multiply transform * point - * * if result is given, resize to match source and replace each corresponding pi - * * if result is not given, return a new array. + * * for each point in source: multiply transformInverse * point in place inthe point. + * * return false if not invertible. */ - public multiplyInversePoint3dArrayInPlace(source: Point3d[]): void { + public multiplyInversePoint3dArrayInPlace(source: Point3d[]): boolean { if (!this._matrix.computeCachedInverse(true)) - return undefined; + return false; const originX = this.origin.x; const originY = this.origin.y; const originZ = this.origin.z; @@ -333,6 +332,7 @@ export class Transform implements BeJSONFunctions { source[i].y - originY, source[i].z - originZ, source[i]); + return true; } // modify destination so it has non-null points for the same length as the source. // (ASSUME existing elements of dest are non-null, and that parameters are given as either Point2d or Point3d arrays) diff --git a/core/geometry/src/geometry4d/Point4d.ts b/core/geometry/src/geometry4d/Point4d.ts index 42f90f0..e47c408 100644 --- a/core/geometry/src/geometry4d/Point4d.ts +++ b/core/geometry/src/geometry4d/Point4d.ts @@ -9,6 +9,7 @@ import { XYAndZ } from "../geometry3d/XYZProps"; import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Ray3d } from "../geometry3d/Ray3d"; import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors"; +import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; export type Point4dProps = number[]; /** @@ -93,6 +94,20 @@ export class Point4d implements BeJSONFunctions { && Geometry.isSameCoordinate(this.z, other.z) && Geometry.isSameCoordinate(this.w, other.w); } + /** + * Test for same coordinate by direct x,y,z,w args + * @param x x to test + * @param y y to test + * @param z z to test + * @param w w to test + */ + public isAlmostEqualXYZW(x: number, y: number, z: number, w: number): boolean { + return Geometry.isSameCoordinate(this.x, x) + && Geometry.isSameCoordinate(this.y, y) + && Geometry.isSameCoordinate(this.z, z) + && Geometry.isSameCoordinate(this.w, w); + } + /** * Convert an Angle to a JSON object. * @return {*} [[x,y,z,w] @@ -135,6 +150,11 @@ export class Point4d implements BeJSONFunctions { public magnitudeXYZW(): number { return Geometry.hypotenuseXYZW(this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3]); } + /** @returns Returns the magnitude of the leading xyz components */ + public magnitudeSquaredXYZ(): number { + return Geometry.hypotenuseSquaredXYZ(this.xyzw[0], this.xyzw[1], this.xyzw[2]); + } + /** @returns Return the difference (this-other) using all 4 components x,y,z,w */ public minus(other: Point4d, result?: Point4d): Point4d { return Point4d.create(this.xyzw[0] - other.xyzw[0], this.xyzw[1] - other.xyzw[1], this.xyzw[2] - other.xyzw[2], this.xyzw[3] - other.xyzw[3], result); @@ -368,4 +388,39 @@ export class Point4d implements BeJSONFunctions { result = result ? result : new Point4d(); return this.safeDivideOrNull(mag, result); } + + /** + * Return the determinant of the 3x3 matrix using components i,j,k of the 3 inputs. + */ + public static determinantIndexed3X3(pointA: Point4d, pointB: Point4d, pointC: Point4d, i: number, j: number, k: number) { + return Geometry.tripleProduct( + pointA.xyzw[i], pointA.xyzw[j], pointA.xyzw[k], + pointB.xyzw[i], pointB.xyzw[j], pointB.xyzw[k], + pointC.xyzw[i], pointC.xyzw[j], pointC.xyzw[k]); + } + /** + * Return a Point4d perpendicular to all 3 inputs. (A higher level cross product concept) + * @param pointA first point + * @param pointB second point + * @param pointC third point + */ + public static perpendicularPoint4dPlane(pointA: Point4d, pointB: Point4d, pointC: Point4d): Point4d { + return Point4d.create( + Point4d.determinantIndexed3X3(pointA, pointB, pointC, 1, 2, 3), + -Point4d.determinantIndexed3X3(pointA, pointB, pointC, 2, 3, 0), + Point4d.determinantIndexed3X3(pointA, pointB, pointC, 3, 0, 1), + -Point4d.determinantIndexed3X3(pointA, pointB, pointC, 0, 1, 2)); + } + public toPlane3dByOriginAndUnitNormal(result?: Plane3dByOriginAndUnitNormal): Plane3dByOriginAndUnitNormal | undefined { + const aa = this.magnitudeSquaredXYZ(); + const direction = Vector3d.create(this.x, this.y, this.z); + const w = this.w; + const divW = Geometry.conditionalDivideFraction(1.0, w); + if (divW !== undefined) { + const b = -w / aa; + direction.scaleInPlace(1.0 / Math.sqrt(aa)); + return Plane3dByOriginAndUnitNormal.create(Point3d.create(this.x * b, this.y * b, this.z * b), direction, result); + } + return undefined; + } } // DPoint4d diff --git a/core/geometry/src/numerics/BezierPolynomials.ts b/core/geometry/src/numerics/BezierPolynomials.ts index 32c5e84..a1f0651 100644 --- a/core/geometry/src/numerics/BezierPolynomials.ts +++ b/core/geometry/src/numerics/BezierPolynomials.ts @@ -7,7 +7,7 @@ // import { Angle, AngleSweep, Geometry } from "../Geometry"; import { Geometry } from "../Geometry"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { PascalCoefficients } from "./PascalCoefficients"; import { Degree2PowerPolynomial, Degree3PowerPolynomial, Degree4PowerPolynomial, AnalyticRoots } from "./Polynomials"; /* tslint:disable:variable-name*/ @@ -385,7 +385,7 @@ export class UnivariateBezier extends BezierCoffs { if (!result) result = new UnivariateBezier(order); else if (result.order !== order) - result.allocateToOrder (order); + result.allocateToOrder(order); for (let i = 0; i < order; i++)result.coffs[i] = coffs[index0 + i]; return result; } diff --git a/core/geometry/src/numerics/ClusterableArray.ts b/core/geometry/src/numerics/ClusterableArray.ts index afb64e5..830508e 100644 --- a/core/geometry/src/numerics/ClusterableArray.ts +++ b/core/geometry/src/numerics/ClusterableArray.ts @@ -8,7 +8,8 @@ import { Geometry } from "../Geometry"; import { Point2d } from "../geometry3d/Point2dVector2d"; import { Point3d } from "../geometry3d/Point3dVector3d"; -import { GrowableBlockedArray, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableBlockedArray } from "../geometry3d/GrowableBlockedArray"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; export class ClusterableArray extends GrowableBlockedArray { private static readonly _vectorFactor = 0.8732; // use 1.0 to rig easy tests. diff --git a/core/geometry/src/numerics/Polynomials.ts b/core/geometry/src/numerics/Polynomials.ts index ffa8369..9e5b074 100644 --- a/core/geometry/src/numerics/Polynomials.ts +++ b/core/geometry/src/numerics/Polynomials.ts @@ -9,7 +9,7 @@ import { Point2d, Vector2d } from "../geometry3d/Point2dVector2d"; import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; // import { Angle, AngleSweep, Geometry } from "../Geometry"; import { Geometry } from "../Geometry"; -import { OptionalGrowableFloat64Array, GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { OptionalGrowableFloat64Array, GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Point4d } from "../geometry4d/Point4d"; // import { Arc3d } from "../curve/Arc3d"; @@ -334,14 +334,12 @@ export class SphereImplicit { return x * x + y * y + z * z - this.radius * this.radius; } - // Evaluate the implicit function at weighted space point (wx/w, wy/w, wz/w) + // Evaluate the implicit function at weighted space point (wx, wy, wz, w) // @param [in] wx (preweighted) x coordinate // @param [in] wy (preweighted) y coordinate // @param [in] wz (preweighted) z coordinate // @param [in] w weight public evaluateImplicitFunctionXYZW(wx: number, wy: number, wz: number, w: number): number { - if (w === 0.0) - return 0.0; return (wx * wx + wy * wy + wz * wz) - this.radius * this.radius * w * w; } @@ -459,7 +457,11 @@ export class AnalyticRoots { } // @returns the principal (always real) cube root of x. public static cbrt(x: number): number { - return ((x) > 0.0 ? Math.pow((x), 1.0 / 3.0) : ((x) < 0.0 ? -Math.pow(-(x), 1.0 / 3.0) : 0.0)); + return ((x) > 0.0 + ? Math.pow((x), 1.0 / 3.0) + : ((x) < 0.0 + ? -Math.pow(-(x), 1.0 / 3.0) + : 0.0)); } /** * Try to divide `numerator/denominator` and place the result (or defaultValue) in `values[offset]` @@ -567,7 +569,7 @@ export class AnalyticRoots { AnalyticRoots.appendSolution(Geometry.conditionalDivideFraction(-c0, c1), values); } // Search an array for the value which is farthest from the average of all the values. - private static mostDistantFromMean(data: GrowableFloat64Array | undefined): number { + public static mostDistantFromMean(data: GrowableFloat64Array | undefined): number { if (!data || data.length === 0) return 0; let a = 0.0; // to become the sum and finally the average. for (let i = 0; i < data.length; i++) a += data.at(i); @@ -576,7 +578,7 @@ export class AnalyticRoots { let result = data.at(0); for (let i = 0; i < data.length; i++) { const d = Math.abs(data.at(i) - a); - if (d < dMax) { + if (d > dMax) { dMax = d; result = data.at(i); } @@ -686,12 +688,15 @@ export class AnalyticRoots { results.push(origin + t * Math.cos(phi)); results.push(origin - t * Math.cos(phi + Math.PI / 3)); results.push(origin - t * Math.cos(phi - Math.PI / 3)); + this.improveSortedRoots(c, 3, results); + return; } else { // One real solution const sqrt_D = Math.sqrt(D); const u = this.cbrt(sqrt_D - q); const v = -(this.cbrt(sqrt_D + q)); results.push(origin + u + v); + this.improveSortedRoots(c, 3, results); return; } } @@ -1011,7 +1016,7 @@ export class TrigPolynomial { } const coffTol = relTol * maxCoff; let degree = nominalDegree; - while (degree > 0 && (Math.abs(coff[degree - 1]) <= coffTol)) { + while (degree > 0 && (Math.abs(coff[degree]) <= coffTol)) { degree--; } // let bstat = false; diff --git a/core/geometry/src/numerics/Quadrature.ts b/core/geometry/src/numerics/Quadrature.ts index c2f022b..ca51b3f 100644 --- a/core/geometry/src/numerics/Quadrature.ts +++ b/core/geometry/src/numerics/Quadrature.ts @@ -14,6 +14,10 @@ * The method installs particular x and weight values. */ export class Quadrature { + + public static readonly gaussX1Interval01 = new Float64Array([0.5]); + public static readonly gaussW1Interval01 = new Float64Array([1.0]); + public static readonly gaussX2Interval01 = new Float64Array([0.21132486540518708, 0.7886751345948129]); public static readonly gaussW2Interval01 = new Float64Array([0.5, 0.5]); @@ -49,6 +53,11 @@ export class Quadrature { return n; } + /* Install 1 (ONE) x and weight values for quadrature from xA to xB. */ + public static setupGauss1(xA: number, xB: number, xMapped: Float64Array, wMapped: Float64Array): number { + return Quadrature.mapWeights(xA, xB - xA, Quadrature.gaussX1Interval01, Quadrature.gaussW1Interval01, xMapped, wMapped); + } + /* Install 2 (TWO) x and weight values for quadrature from xA to xB. */ public static setupGauss2(xA: number, xB: number, xMapped: Float64Array, wMapped: Float64Array): number { return Quadrature.mapWeights(xA, xB - xA, Quadrature.gaussX2Interval01, Quadrature.gaussW2Interval01, xMapped, wMapped); diff --git a/core/geometry/src/numerics/Range1dArray.ts b/core/geometry/src/numerics/Range1dArray.ts index ea45be2..1103d53 100644 --- a/core/geometry/src/numerics/Range1dArray.ts +++ b/core/geometry/src/numerics/Range1dArray.ts @@ -6,7 +6,7 @@ /** @module Numerics */ import { Range1d } from "../geometry3d/Range"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; /** * A Range1d array is a set of intervals, such as occur when a line is clipped to a (nonconvex) polygon */ @@ -161,7 +161,7 @@ export class Range1dArray { retVal.push(range.clone()); // Sort the array - retVal.sort(compareRange1d); + retVal.sort(compareRange1dLexicalLowHigh); Range1dArray.simplifySortParity(retVal, true); return retVal; @@ -169,7 +169,7 @@ export class Range1dArray { /** Uses the Range1d specific compare function for sorting the array of ranges */ public static sort(data: Range1d[]) { - data.sort(compareRange1d); + data.sort(compareRange1dLexicalLowHigh); } /** Cleans up the array, compressing any overlapping ranges. If removeZeroLengthRanges is set to true, will also remove any Ranges in the form (x, x) */ @@ -177,7 +177,7 @@ export class Range1dArray { if (data.length < 2) return; - data.sort(compareRange1d); + data.sort(compareRange1dLexicalLowHigh); let currIdx = 0; let toInsert = false; @@ -245,11 +245,7 @@ export class Range1dArray { * * This considers all intervals-- i.e. does not expect or take advantage of sorting. */ public static testUnion(data: Range1d[], value: number): boolean { - for (const range of data) { - if (range.containsX(value)) - return true; - } - return false; + return this.countContainingRanges(data, value) > 0; } /** test if value is "in" by parity rules. * * This considers all intervals-- i.e. does not expect or take advantage of sorting. @@ -262,15 +258,35 @@ export class Range1dArray { } return inside; } + + /** linear search to count number of intervals which contain `value`. + */ + public static countContainingRanges(data: Range1d[], value: number): number { + let n = 0; + for (const range of data) { + if (range.containsX(value)) + n++; + } + return n; + } + /** return an array with all the low and high values of all the ranges. - * * the coordinates are not sorted. + * @param data array of ranges. + * @param sort optionally request immediate sort. + * @param compress optionally request removal of duplicates. */ - public static getBreaks(data: Range1d[], result?: GrowableFloat64Array): GrowableFloat64Array { + public static getBreaks(data: Range1d[], result?: GrowableFloat64Array, sort: boolean = false, compress: boolean = false): GrowableFloat64Array { if (!result) result = new GrowableFloat64Array(2 * data.length); + result.clear(); for (const range of data) { result.push(range.low); result.push(range.high); } + if (sort) + result.sort(); + if (compress) + result.compressAdjcentDuplicates(); + return result; } /** sum the lengths of all ranges */ @@ -305,7 +321,7 @@ export class Range1dArray { } /** Checks low's first, then high's */ -function compareRange1d(a: Range1d, b: Range1d): number { +export function compareRange1dLexicalLowHigh(a: Range1d, b: Range1d): number { if (a.low < b.low) return -1; if (a.low > b.low) return 1; if (a.high < b.high) return -1; diff --git a/core/geometry/src/polyface/Polyface.ts b/core/geometry/src/polyface/Polyface.ts index 1b93a38..4ddb1c5 100644 --- a/core/geometry/src/polyface/Polyface.ts +++ b/core/geometry/src/polyface/Polyface.ts @@ -12,11 +12,11 @@ import { Point2d } from "../geometry3d/Point2dVector2d"; import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Range3d, Range2d, Range1d } from "../geometry3d/Range"; import { Transform } from "../geometry3d/Transform"; -import { NumberArray, Vector3dArray, Point2dArray } from "../geometry3d/PointHelpers"; -import { GrowableFloat64Array, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { NumberArray } from "../geometry3d/PointHelpers"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { GeometryQuery } from "../curve/GeometryQuery"; import { GeometryHandler } from "../geometry3d/GeometryHandler"; -import { ClusterableArray } from "../numerics/ClusterableArray"; +import { PolyfaceData } from "./PolyfaceData"; /** * Check validity of indices into a data array. @@ -46,53 +46,6 @@ function areIndicesValid(indices: number[] | undefined, indexPositionA: number, function allDefined(valueA: any, valueB: any, valueC: any): boolean { return valueA !== undefined && valueB !== undefined && valueC !== undefined; } -/** - * Test if facetStartIndex is (minimally!) valid: - * * length must be nonzero (recall that for "no facets" the facetStartIndexArray still must contain a 0) - * * Each entry must be strictly smaller than the one that follows. - * @param facetStartIndex array of facetStart data. facet `i` has indices at `facetsStartIndex[i]` to (one before) `facetStartIndex[i+1]` - */ -function isValidFacetStartIndexArray(facetStartIndex: number[]): boolean { - // facetStartIndex for empty facets has a single entry "0" -- empty array is not allowed - if (facetStartIndex.length === 0) - return false; - for (let i = 0; i + 1 < facetStartIndex.length; i++) - if (facetStartIndex[i] >= facetStartIndex[i + 1]) - return false; - return true; -} -function reverseIndices(facetStartIndex: number[], indices: T[] | undefined, preserveStart: boolean): boolean { - if (!indices || indices.length === 0) - return true; // empty case - if (indices.length > 0) { - if (facetStartIndex[facetStartIndex.length - 1] === indices.length) { - for (let i = 0; i + 1 < facetStartIndex.length; i++) { - let index0 = facetStartIndex[i]; - let index1 = facetStartIndex[i + 1]; - if (preserveStart) { - // leave [index0] as is so reversed facet starts at same vertex - while (index1 > index0 + 2) { - index1--; index0++; - const a = indices[index0]; - indices[index0] = indices[index1]; - indices[index1] = a; - } - } else { - // reverse all - while (index1 > index0 + 1) { - index1--; - const a = indices[index0]; - indices[index0] = indices[index1]; - indices[index1] = a; - index0++; - } - } - } - return true; - } - } - return false; -} /** * Data for a face in a polyface containing facets. @@ -237,39 +190,83 @@ export class FacetFaceData { return true; } } +/** The data types of [[AuxChannel]]. The scalar types are used to produce thematic vertex colors. */ export enum AuxChannelDataType { + /** General scalar type - no scaling is applied if associated [[Polyface]] is transformed. */ Scalar = 0, + /** Distance (scalar) scaling is applied if associated [[Polyface]] is scaled. 3 Data values (x,y.z) per entry. */ Distance = 1, + /** Displacement added to vertex position. Transformed and scaled with associated [[Polyface]]. 3 Data values (x,y.z) per entry.,*/ Vector = 2, + /** Normal -- replaces vertex normal. Rotated with associated [[Polyface]] transformation. 3 Data values (x,y.z) per entry. */ Normal = 3, - Point = 4, } +/** Represents the [[AuxChannel]] data at a single input value. */ export class AuxChannelData { + /** The input value for this data. */ public input: number; + /** The vertex values for this data. A single value per vertex for scalar types and 3 values (x,y,z) for normal or vector channels. */ public values: number[]; - + /** Construct a new [[AuxChannelData]] from input value and vertex values. */ constructor(input: number, values: number[]) { this.input = input; this.values = values; } + public copyValues(other: AuxChannelData, thisIndex: number, otherIndex: number, blockSize: number) { + for (let i = 0; i < blockSize; i++) + this.values[thisIndex * blockSize + i] = other.values[otherIndex * blockSize + i]; + } + public clone() { + return new AuxChannelData(this.input, this.values.slice()); + } + public isAlmostEqual(other: AuxChannelData, tol?: number) { + const tolerance = tol ? tol : 1.0E-8; + return Math.abs(this.input - other.input) < tolerance && NumberArray.isAlmostEqual(this.values, other.values, tolerance); + } } - +/** Represents a single [[PolyfaceAuxData]] channel. A channel may represent a single scalar value such as stress or temperature or may represent displacements from vertex position or replacements for normals. */ export class AuxChannel { + /** An array of [[AuxChannelData]] that represents the vertex data at one or more input values. */ public data: AuxChannelData[]; public dataType: AuxChannelDataType; + /** The channel name. This is used to present the [[AuxChannel]] to the user and also to select the [[AuxChannel]] for display from [[AnalysisStyle]] */ public name?: string; + /** The input name. */ public inputName?: string; - + /** create a [[AuxChannel]] */ public constructor(data: AuxChannelData[], dataType: AuxChannelDataType, name?: string, inputName?: string) { this.data = data; this.dataType = dataType; this.name = name; this.inputName = inputName; } + public clone() { + const clonedData = []; + for (const data of this.data) clonedData.push(data.clone()); + return new AuxChannel(clonedData, this.dataType, this.name, this.inputName); + } + public isAlmostEqual(other: AuxChannel, tol?: number) { + if (this.dataType !== other.dataType || + this.name !== other.name || + this.inputName !== other.inputName || + this.data.length !== other.data.length) + return false; + + for (let i = 0; i < this.data.length; i++) + if (!this.data[i].isAlmostEqual(other.data[i], tol)) + return false; + + return true; + } + /** return true if the data for this channel is of scalar type (single data entry per value) */ get isScalar(): boolean { return this.dataType === AuxChannelDataType.Distance || this.dataType === AuxChannelDataType.Scalar; } + /** return the number of data values per entry (1 for scalar, 3 for point or vector */ + get entriesPerValue(): number { return this.isScalar ? 1 : 3; } + /** return value count */ + get valueCount(): number { return 0 === this.data.length ? 0 : this.data[0].values.length / this.entriesPerValue; } + /** return the range of the scalar data. (undefined if not scalar) */ get scalarRange(): Range1d | undefined { if (!this.isScalar) return undefined; - const range = Range1d.createNull(); for (const data of this.data) { range.extendArray(data.values); @@ -277,7 +274,16 @@ export class AuxChannel { return range; } } +/** The `PolyfaceAuxData` structure contains one or more analytical data channels for each vertex of a `Polyface`. + * Typically a `Polyface` will contain only vertex data required for its basic display,the vertex position, normal + * and possibly texture parameter. The `PolyfaceAuxData` structure contains supplemental data that is generally computed + * in an analysis program or other external data source. This can be scalar data used to either overide the vertex colors through *Thematic Colorization* or + * XYZ data used to deform the mesh by adjusting the vertex postions or normals. + */ export class PolyfaceAuxData { + /** @param channels Array with one or more channels of auxilliary data for the associated polyface. + * @param indices The indices (shared by all data in all channels) mapping the data to the mesh facets. + */ public channels: AuxChannel[]; public indices: number[]; @@ -285,264 +291,35 @@ export class PolyfaceAuxData { this.channels = channels; this.indices = indices; } -} -/** - * PolyfaceData carries data arrays for point, normal, param, color and their indices. - * - * * IndexedPolyface carries a PolyfaceData as a member. (NOT as a base class -- it already has GeometryQuery as base) - * * IndexedPolyfaceVisitor uses PolyfaceData as a base class. - */ - -export class PolyfaceData { - //
      optional arrays (normal, uv, color) must be indicated at constructor time. - //
    • all arrays are (independently) indexed. - //
    • with regret, the point, param, normal, and color arrays are exposed publicly. - //
    • getX methods are "trusting" -- no bounds check - //
    • getX methods return references to X. - //
    • EXCEPT -- for optional arrays, the return 000. - //
    • copyX methods move data to caller-supplied result.. - //
    - - public static readonly planarityLocalRelTol = 1.0e-13; - public point: GrowableXYZArray; - public pointIndex: number[]; - // edgeVisible[i] = true if the edge following pointIndex[i] is visible - public edgeVisible: boolean[]; - - public normal: Vector3d[] | undefined; - public normalIndex: number[] | undefined; - public param: Point2d[] | undefined; - public paramIndex: number[] | undefined; - public color: number[] | undefined; - public colorIndex: number[] | undefined; - /** Face data will remain empty until a face is specified. */ - public face: FacetFaceData[]; - public auxData: PolyfaceAuxData | undefined; - - public constructor(needNormals: boolean = false, needParams: boolean = false, needColors: boolean = false) { - this.point = new GrowableXYZArray(); - this.pointIndex = []; this.edgeVisible = []; - this.face = []; - if (needNormals) { this.normal = []; this.normalIndex = []; } - if (needParams) { this.param = []; this.paramIndex = []; } - if (needColors) { this.color = []; this.colorIndex = []; } - } - - public clone(): PolyfaceData { - const result = new PolyfaceData(); - result.point = this.point.clone(); - result.pointIndex = this.pointIndex.slice(); - result.edgeVisible = this.edgeVisible.slice(); - result.face = this.face.slice(); - - if (this.normal) - result.normal = Vector3dArray.cloneVector3dArray(this.normal); - if (this.param) - result.param = Point2dArray.clonePoint2dArray(this.param); - if (this.color) - result.color = this.color.slice(); - - if (this.normalIndex) - result.normalIndex = this.normalIndex.slice(); - if (this.paramIndex) - result.paramIndex = this.paramIndex.slice(); - if (this.colorIndex) - result.colorIndex = this.colorIndex.slice(); - return result; + public clone() { + const clonedChannels = []; + for (const channel of this.channels) clonedChannels.push(channel.clone()); + return new PolyfaceAuxData(clonedChannels, this.indices.slice()); } + public isAlmostEqual(other: PolyfaceAuxData, tol?: number) { + if (!NumberArray.isExactEqual(this.indices, other.indices) || this.channels.length !== other.channels.length) + return false; - public isAlmostEqual(other: PolyfaceData): boolean { - if (!GrowableXYZArray.isAlmostEqual(this.point, other.point)) return false; - if (!NumberArray.isExactEqual(this.pointIndex, other.pointIndex)) return false; - - if (!Vector3dArray.isAlmostEqual(this.normal, other.normal)) return false; - if (!NumberArray.isExactEqual(this.normalIndex, other.normalIndex)) return false; - - if (!Point2dArray.isAlmostEqual(this.param, other.param)) return false; - if (!NumberArray.isExactEqual(this.paramIndex, other.paramIndex)) return false; - - if (!NumberArray.isExactEqual(this.color, other.color)) return false; - if (!NumberArray.isExactEqual(this.colorIndex, other.colorIndex)) return false; + for (let i = 0; i < this.channels.length; i++) + if (!this.channels[i].isAlmostEqual(other.channels[i], tol)) + return false; - if (!NumberArray.isExactEqual(this.edgeVisible, other.edgeVisible)) return false; return true; } - public get requireNormals(): boolean { return undefined !== this.normal; } - public get pointCount() { return this.point.length; } - public get normalCount() { return this.normal ? this.normal.length : 0; } - public get paramCount() { return this.param ? this.param.length : 0; } - public get colorCount() { return this.color ? this.color.length : 0; } - public get indexCount() { return this.pointIndex.length; } // ALWAYS INDEXED ... all index vectors must have same length. - /** Will return 0 if no faces were specified during construction. */ - public get faceCount() { return this.face.length; } - - /** return indexed point. This is a copy of the coordinates, not a reference. */ - public getPoint(i: number): Point3d { return this.point.getPoint3dAt(i); } - /** return indexed normal. This is the REFERENCE to the normal, not a copy. */ - public getNormal(i: number): Vector3d { return this.normal ? this.normal[i] : Vector3d.create(); } - /** return indexed param. This is the REFERENCE to the param, not a copy. */ - public getParam(i: number): Point2d { return this.param ? this.param[i] : Point2d.create(); } - /** return indexed color */ - public getColor(i: number): number { return this.color ? this.color[i] : 0; } - /** return indexed visibility */ - public getEdgeVisible(i: number): boolean { return this.edgeVisible[i]; } - /** Copy the contents (not pointer) of point[i] into dest. */ - public copyPointTo(i: number, dest: Point3d): void { this.point.getPoint3dAt(i, dest); } - /** Copy the contents (not pointer) of normal[i] into dest. */ - public copyNormalTo(i: number, dest: Vector3d): void { if (this.normal) dest.setFrom(this.normal[i]); } - /** Copy the contents (not pointer) of param[i] into dest. */ - public copyParamTo(i: number, dest: Point2d): void { if (this.param) dest.setFrom(this.param[i]); } - /** - * * Copy data from other to this. - * * This is the essense of transfering coordinates spread throughout a large polyface into a visitor's single facet. - * * "other" is the large polyface - * * "this" is the visitor - * * does NOT copy face data - visitors reference the FacetFaceData array for the whole polyface!! - * @param other polyface data being mined. - * @param index0 start index in other's index arrays - * @param index1 end index (one beyond last data accessed0 in other's index arrays - * @param numWrap number of points to replicate as wraparound. - */ - public gatherIndexedData(other: PolyfaceData, index0: number, index1: number, numWrap: number) { - const numEdge = index1 - index0; - const numTotal = numEdge + numWrap; - this.resizeAllDataArrays(numTotal); - // copy wrapped points - for (let i = 0; i < numEdge; i++) - this.point.transferFromGrowableXYZArray(i, other.point, other.pointIndex[index0 + i]); - for (let i = 0; i < numWrap; i++) - this.point.transferFromGrowableXYZArray(numEdge + i, this.point, i); - - // copy wrapped pointIndex - for (let i = 0; i < numEdge; i++) - this.pointIndex[i] = other.pointIndex[index0 + i]; - for (let i = 0; i < numWrap; i++) - this.pointIndex[numEdge + i] = this.pointIndex[i]; - // copy wrapped edge visibility - for (let i = 0; i < numEdge; i++) - this.edgeVisible[i] = other.edgeVisible[index0 + i]; - for (let i = 0; i < numWrap; i++) - this.edgeVisible[numEdge + i] = this.edgeVisible[i]; - - if (this.normal && this.normalIndex && other.normal && other.normalIndex) { - for (let i = 0; i < numEdge; i++) - this.normal[i].setFrom(other.normal[other.normalIndex[index0 + i]]); - for (let i = 0; i < numWrap; i++) - this.normal[numEdge + i].setFrom(this.normal[i]); - - for (let i = 0; i < numEdge; i++) - this.normalIndex[i] = other.normalIndex[index0 + i]; - for (let i = 0; i < numWrap; i++) - this.normalIndex[numEdge + i] = this.normalIndex[i]; - } - - if (this.param && this.paramIndex && other.param && other.paramIndex) { - for (let i = 0; i < numEdge; i++) - this.param[i].setFrom(other.param[other.paramIndex[index0 + i]]); - for (let i = 0; i < numWrap; i++) - this.param[numEdge + i].setFrom(this.param[i]); - - for (let i = 0; i < numEdge; i++) - this.paramIndex[i] = other.paramIndex[index0 + i]; - for (let i = 0; i < numWrap; i++) - this.paramIndex[numEdge + i] = this.paramIndex[i]; - } - - if (this.color && this.colorIndex && other.color && other.colorIndex) { - for (let i = 0; i < numEdge; i++) - this.color[i] = other.color[this.colorIndex[index0 + i]]; - for (let i = 0; i < numWrap; i++) - this.color[numEdge + i] = this.color[i]; - - for (let i = 0; i < numEdge; i++) - this.colorIndex[i] = other.colorIndex[index0 + i]; - for (let i = 0; i < numWrap; i++) - this.colorIndex[numEdge + i] = this.colorIndex[i]; - } - } - private static trimArray(data: any[] | undefined, length: number) { if (data && length < data.length) data.length = length; } - - public trimAllIndexArrays(length: number): void { - PolyfaceData.trimArray(this.pointIndex, length); - PolyfaceData.trimArray(this.paramIndex, length); - PolyfaceData.trimArray(this.normalIndex, length); - PolyfaceData.trimArray(this.colorIndex, length); - PolyfaceData.trimArray(this.edgeVisible, length); - } + public createForVisitor() { + const visitorChannels: AuxChannel[] = []; - public resizeAllDataArrays(length: number): void { - if (length > this.point.length) { - while (this.point.length < length) this.point.push(Point3d.create()); - while (this.pointIndex.length < length) this.pointIndex.push(-1); - while (this.edgeVisible.length < length) this.edgeVisible.push(false); - if (this.normal) - while (this.normal.length < length) this.normal.push(Vector3d.create()); - if (this.param) - while (this.param.length < length) this.param.push(Point2d.create()); - if (this.color) - while (this.color.length < length) this.color.push(0); - } else if (length < this.point.length) { - this.point.resize(length); - this.edgeVisible.length = length; - this.pointIndex.length = length; - if (this.normal) this.normal.length = length; - if (this.param) this.param.length = length; - if (this.color) this.color.length = length; - } - } - public range(result?: Range3d, transform?: Transform): Range3d { - result = result ? result : Range3d.createNull(); - result.extendArray(this.point, transform); - return result; - } - /** reverse indices facet-by-facet, with the given facetStartIndex array delimiting faces. - * - * * facetStartIndex[0] == 0 always -- start of facet zero. - * * facet k has indices from facetStartIndex[k] <= i < facetStartIndex[k+1] - * * hence for "internal" k, facetStartIndex[k] is both the upper limit of facet k-1 and the start of facet k. - * * - */ - public reverseIndices(facetStartIndex?: number[]) { - if (facetStartIndex && isValidFacetStartIndexArray(facetStartIndex)) { - reverseIndices(facetStartIndex, this.pointIndex, true); - reverseIndices(facetStartIndex, this.normalIndex, true); - reverseIndices(facetStartIndex, this.paramIndex, true); - reverseIndices(facetStartIndex, this.colorIndex, true); - reverseIndices(facetStartIndex, this.edgeVisible, false); - } - } - public reverseNormals() { - if (this.normal) - for (const normal of this.normal) - normal.scaleInPlace(-1.0); - } - // This base class is just a data carrier. It does not know if the index order and normal directions have special meaning. - // 1) Caller must reverse normals if semanitically needed. - // 2) Caller must reverse indices if semantically needed. - public tryTransformInPlace( - transform: Transform): boolean { - const inverseTranspose = transform.matrix.inverse(); - this.point.transformInPlace(transform); - - if (inverseTranspose) { - // apply simple Matrix3d to normals ... - if (this.normal) { - inverseTranspose.multiplyVectorArrayInPlace(this.normal); + for (const parentChannel of this.channels) { + const visitorChannelData: AuxChannelData[] = []; + for (const parentChannelData of parentChannel.data) { + visitorChannelData.push(new AuxChannelData(parentChannelData.input, [])); } + visitorChannels.push(new AuxChannel(visitorChannelData, parentChannel.dataType, parentChannel.name, parentChannel.inputName)); } - return true; - } - public compress() { - const packedData = ClusterableArray.clusterGrowablePoint3dArray(this.point); - this.point = packedData.growablePackedPoints!; - packedData.updateIndices(this.pointIndex); - // if (this.paramIndex) // Tracking uv params - // packedData.updateIndices(this.paramIndex); - // if (this.normalIndex) // Tracking normals - // packedData.updateIndices(this.normalIndex); + return new PolyfaceAuxData(visitorChannels, []); } + } /** @@ -869,7 +646,7 @@ export class IndexedPolyface extends Polyface { /** (read-only property) number of facets */ public get facetCount(): number { return this._facetStart.length - 1; } /** (read-only property) number of faces */ - public get faceCount(): number { return this.data.face.length; } + public get faceCount(): number { return this.data.faceCount; } /** (read-only property) number of points */ public get pointCount(): number { return this.data.pointCount; } /** (read-only property) number of colors */ @@ -972,6 +749,7 @@ export interface PolyfaceVisitor extends PolyfaceData { clientParamIndex(i: number): number; clientNormalIndex(i: number): number; clientColorIndex(i: number): number; + clientAuxIndex(i: number): number; } export class IndexedPolyfaceVisitor extends PolyfaceData implements PolyfaceVisitor { @@ -985,10 +763,14 @@ export class IndexedPolyfaceVisitor extends PolyfaceData implements PolyfaceVisi super(facets.data.normalCount > 0, facets.data.paramCount > 0, facets.data.colorCount > 0); this._polyface = facets; this._numWrap = numWrap; + if (facets.data.auxData) + this.auxData = facets.data.auxData.createForVisitor(); + this.reset(); this._numEdges = 0; this._nextFacetIndex = 0; this._currentFacetIndex = -1; + } public get numEdgesThisFacet(): number { return this._numEdges; } @@ -1055,4 +837,5 @@ export class IndexedPolyfaceVisitor extends PolyfaceData implements PolyfaceVisi public clientParamIndex(i: number): number { return this.paramIndex ? this.paramIndex[i] : -1; } public clientNormalIndex(i: number): number { return this.normalIndex ? this.normalIndex[i] : -1; } public clientColorIndex(i: number): number { return this.colorIndex ? this.colorIndex[i] : -1; } + public clientAuxIndex(i: number): number { return this.auxData ? this.auxData.indices[i] : -1; } } diff --git a/core/geometry/src/polyface/PolyfaceBuilder.ts b/core/geometry/src/polyface/PolyfaceBuilder.ts index abcc789..1d1886b 100644 --- a/core/geometry/src/polyface/PolyfaceBuilder.ts +++ b/core/geometry/src/polyface/PolyfaceBuilder.ts @@ -7,7 +7,7 @@ // import { Geometry, AxisOrder, Angle, AngleSweep, BSIJSONValues } from "./Geometry"; import { IndexedPolyface } from "./Polyface"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Point2d } from "../geometry3d/Point2dVector2d"; import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Transform } from "../geometry3d/Transform"; diff --git a/core/geometry/src/polyface/PolyfaceData.ts b/core/geometry/src/polyface/PolyfaceData.ts new file mode 100644 index 0000000..868e108 --- /dev/null +++ b/core/geometry/src/polyface/PolyfaceData.ts @@ -0,0 +1,369 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +/** @module Polyface */ + +import { Point2d } from "../geometry3d/Point2dVector2d"; +import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; +import { Range3d } from "../geometry3d/Range"; +import { Transform } from "../geometry3d/Transform"; +import { NumberArray, Vector3dArray, Point2dArray } from "../geometry3d/PointHelpers"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; +import { ClusterableArray } from "../numerics/ClusterableArray"; +import { FacetFaceData, PolyfaceAuxData } from "./Polyface"; + +/** + * PolyfaceData carries data arrays for point, normal, param, color and their indices. + * + * * IndexedPolyface carries a PolyfaceData as a member. (NOT as a base class -- it already has GeometryQuery as base) + * * IndexedPolyfaceVisitor uses PolyfaceData as a base class. + */ + +export class PolyfaceData { + //
      optional arrays (normal, uv, color) must be indicated at constructor time. + //
    • all arrays are (independently) indexed. + //
    • with regret, the point, param, normal, and color arrays are exposed publicly. + //
    • getX methods are "trusting" -- no bounds check + //
    • getX methods return references to X. + //
    • EXCEPT -- for optional arrays, the return 000. + //
    • copyX methods move data to caller-supplied result.. + //
    + + public static readonly planarityLocalRelTol = 1.0e-13; + public point: GrowableXYZArray; + public pointIndex: number[]; + // edgeVisible[i] = true if the edge following pointIndex[i] is visible + public edgeVisible: boolean[]; + + public normal: Vector3d[] | undefined; + public normalIndex: number[] | undefined; + public param: Point2d[] | undefined; + public paramIndex: number[] | undefined; + public color: number[] | undefined; + public colorIndex: number[] | undefined; + /** Face data will remain empty until a face is specified. */ + public face: FacetFaceData[]; + public auxData: PolyfaceAuxData | undefined; + + public constructor(needNormals: boolean = false, needParams: boolean = false, needColors: boolean = false) { + this.point = new GrowableXYZArray(); + this.pointIndex = []; this.edgeVisible = []; + this.face = []; + if (needNormals) { this.normal = []; this.normalIndex = []; } + if (needParams) { this.param = []; this.paramIndex = []; } + if (needColors) { this.color = []; this.colorIndex = []; } + } + + public clone(): PolyfaceData { + const result = new PolyfaceData(); + result.point = this.point.clone(); + result.pointIndex = this.pointIndex.slice(); + result.edgeVisible = this.edgeVisible.slice(); + result.face = this.face.slice(); + + if (this.normal) + result.normal = Vector3dArray.cloneVector3dArray(this.normal); + if (this.param) + result.param = Point2dArray.clonePoint2dArray(this.param); + if (this.color) + result.color = this.color.slice(); + + if (this.normalIndex) + result.normalIndex = this.normalIndex.slice(); + if (this.paramIndex) + result.paramIndex = this.paramIndex.slice(); + if (this.colorIndex) + result.colorIndex = this.colorIndex.slice(); + if (this.auxData) + result.auxData = this.auxData.clone(); + return result; + } + + public isAlmostEqual(other: PolyfaceData): boolean { + if (!GrowableXYZArray.isAlmostEqual(this.point, other.point)) + return false; + if (!NumberArray.isExactEqual(this.pointIndex, other.pointIndex)) + return false; + + if (!Vector3dArray.isAlmostEqual(this.normal, other.normal)) return false; + if (!NumberArray.isExactEqual(this.normalIndex, other.normalIndex)) return false; + + if (!Point2dArray.isAlmostEqual(this.param, other.param)) return false; + if (!NumberArray.isExactEqual(this.paramIndex, other.paramIndex)) return false; + + if (!NumberArray.isExactEqual(this.color, other.color)) return false; + if (!NumberArray.isExactEqual(this.colorIndex, other.colorIndex)) return false; + + if (!NumberArray.isExactEqual(this.edgeVisible, other.edgeVisible)) return false; + return true; + } + public get requireNormals(): boolean { return undefined !== this.normal; } + public get pointCount() { return this.point.length; } + public get normalCount() { return this.normal ? this.normal.length : 0; } + public get paramCount() { return this.param ? this.param.length : 0; } + public get colorCount() { return this.color ? this.color.length : 0; } + public get indexCount() { return this.pointIndex.length; } // ALWAYS INDEXED ... all index vectors must have same length. + /** Will return 0 if no faces were specified during construction. */ + public get faceCount() { return this.face.length; } + + /** return indexed point. This is a copy of the coordinates, not a reference. */ + public getPoint(i: number): Point3d { return this.point.getPoint3dAt(i); } + /** return indexed normal. This is the REFERENCE to the normal, not a copy. */ + public getNormal(i: number): Vector3d { return this.normal ? this.normal[i] : Vector3d.create(); } + /** return indexed param. This is the REFERENCE to the param, not a copy. */ + public getParam(i: number): Point2d { return this.param ? this.param[i] : Point2d.create(); } + /** return indexed color */ + public getColor(i: number): number { return this.color ? this.color[i] : 0; } + /** return indexed visibility */ + public getEdgeVisible(i: number): boolean { return this.edgeVisible[i]; } + /** Copy the contents (not pointer) of point[i] into dest. */ + public copyPointTo(i: number, dest: Point3d): void { this.point.getPoint3dAt(i, dest); } + /** Copy the contents (not pointer) of normal[i] into dest. */ + public copyNormalTo(i: number, dest: Vector3d): void { if (this.normal) dest.setFrom(this.normal[i]); } + /** Copy the contents (not pointer) of param[i] into dest. */ + public copyParamTo(i: number, dest: Point2d): void { if (this.param) dest.setFrom(this.param[i]); } + /** + * * Copy data from other to this. + * * This is the essense of transfering coordinates spread throughout a large polyface into a visitor's single facet. + * * "other" is the large polyface + * * "this" is the visitor + * * does NOT copy face data - visitors reference the FacetFaceData array for the whole polyface!! + * @param other polyface data being mined. + * @param index0 start index in other's index arrays + * @param index1 end index (one beyond last data accessed0 in other's index arrays + * @param numWrap number of points to replicate as wraparound. + */ + public gatherIndexedData(other: PolyfaceData, index0: number, index1: number, numWrap: number) { + const numEdge = index1 - index0; + const numTotal = numEdge + numWrap; + this.resizeAllDataArrays(numTotal); + // copy wrapped points + for (let i = 0; i < numEdge; i++) + this.point.transferFromGrowableXYZArray(i, other.point, other.pointIndex[index0 + i]); + for (let i = 0; i < numWrap; i++) + this.point.transferFromGrowableXYZArray(numEdge + i, this.point, i); + + // copy wrapped pointIndex + for (let i = 0; i < numEdge; i++) + this.pointIndex[i] = other.pointIndex[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.pointIndex[numEdge + i] = this.pointIndex[i]; + // copy wrapped edge visibility + for (let i = 0; i < numEdge; i++) + this.edgeVisible[i] = other.edgeVisible[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.edgeVisible[numEdge + i] = this.edgeVisible[i]; + + if (this.normal && this.normalIndex && other.normal && other.normalIndex) { + for (let i = 0; i < numEdge; i++) + this.normal[i].setFrom(other.normal[other.normalIndex[index0 + i]]); + for (let i = 0; i < numWrap; i++) + this.normal[numEdge + i].setFrom(this.normal[i]); + + for (let i = 0; i < numEdge; i++) + this.normalIndex[i] = other.normalIndex[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.normalIndex[numEdge + i] = this.normalIndex[i]; + } + + if (this.param && this.paramIndex && other.param && other.paramIndex) { + for (let i = 0; i < numEdge; i++) + this.param[i].setFrom(other.param[other.paramIndex[index0 + i]]); + for (let i = 0; i < numWrap; i++) + this.param[numEdge + i].setFrom(this.param[i]); + + for (let i = 0; i < numEdge; i++) + this.paramIndex[i] = other.paramIndex[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.paramIndex[numEdge + i] = this.paramIndex[i]; + } + + if (this.color && this.colorIndex && other.color && other.colorIndex) { + for (let i = 0; i < numEdge; i++) + this.color[i] = other.color[this.colorIndex[index0 + i]]; + for (let i = 0; i < numWrap; i++) + this.color[numEdge + i] = this.color[i]; + + for (let i = 0; i < numEdge; i++) + this.colorIndex[i] = other.colorIndex[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.colorIndex[numEdge + i] = this.colorIndex[i]; + } + if (this.auxData && other.auxData && this.auxData.channels.length === other.auxData.channels.length) { + for (let iChannel = 0; iChannel < this.auxData.channels.length; iChannel++) { + const thisChannel = this.auxData.channels[iChannel]; + const otherChannel = other.auxData.channels[iChannel]; + const blockSize = thisChannel.entriesPerValue; + if (thisChannel.data.length === otherChannel.data.length) { + for (let iData = 0; iData < thisChannel.data.length; iData++) { + const thisData = thisChannel.data[iData]; + const otherData = otherChannel.data[iData]; + for (let i = 0; i < numEdge; i++) + thisData.copyValues(otherData, i, index0 + i, blockSize); + for (let i = 0; i < numWrap; i++) + thisData.copyValues(thisData, numEdge + i, i, blockSize); + } + } + } + for (let i = 0; i < numEdge; i++) + this.auxData.indices[i] = other.auxData.indices[index0 + i]; + for (let i = 0; i < numWrap; i++) + this.auxData.indices[numEdge + i] = this.auxData.indices[i]; + } + } + private static trimArray(data: any[] | undefined, length: number) { if (data && length < data.length) data.length = length; } + + public trimAllIndexArrays(length: number): void { + PolyfaceData.trimArray(this.pointIndex, length); + PolyfaceData.trimArray(this.paramIndex, length); + PolyfaceData.trimArray(this.normalIndex, length); + PolyfaceData.trimArray(this.colorIndex, length); + PolyfaceData.trimArray(this.edgeVisible, length); + if (this.auxData) { + PolyfaceData.trimArray(this.auxData.indices, length); + for (const channel of this.auxData.channels) { + for (const data of channel.data) + PolyfaceData.trimArray(data.values, channel.entriesPerValue * length); + } + } + } + + public resizeAllDataArrays(length: number): void { + if (length > this.point.length) { + while (this.point.length < length) this.point.push(Point3d.create()); + while (this.pointIndex.length < length) this.pointIndex.push(-1); + while (this.edgeVisible.length < length) this.edgeVisible.push(false); + if (this.normal) + while (this.normal.length < length) this.normal.push(Vector3d.create()); + if (this.param) + while (this.param.length < length) this.param.push(Point2d.create()); + if (this.color) + while (this.color.length < length) this.color.push(0); + if (this.auxData) { + for (const channel of this.auxData.channels) { + for (const channelData of channel.data) { + while (channelData.values.length < length * channel.entriesPerValue) channelData.values.push(0); + } + } + } + } else if (length < this.point.length) { + this.point.resize(length); + this.edgeVisible.length = length; + this.pointIndex.length = length; + if (this.normal) this.normal.length = length; + if (this.param) this.param.length = length; + if (this.color) this.color.length = length; + if (this.auxData) { + for (const channel of this.auxData.channels) { + for (const channelData of channel.data) { + channelData.values.length = length * channel.entriesPerValue; + } + } + } + } + } + public range(result?: Range3d, transform?: Transform): Range3d { + result = result ? result : Range3d.createNull(); + result.extendArray(this.point, transform); + return result; + } + /** reverse indices facet-by-facet, with the given facetStartIndex array delimiting faces. + * + * * facetStartIndex[0] == 0 always -- start of facet zero. + * * facet k has indices from facetStartIndex[k] <= i < facetStartIndex[k+1] + * * hence for "internal" k, facetStartIndex[k] is both the upper limit of facet k-1 and the start of facet k. + * * + */ + public reverseIndices(facetStartIndex?: number[]) { + if (facetStartIndex && PolyfaceData.isValidFacetStartIndexArray(facetStartIndex)) { + PolyfaceData.reverseIndices(facetStartIndex, this.pointIndex, true); + PolyfaceData.reverseIndices(facetStartIndex, this.normalIndex, true); + PolyfaceData.reverseIndices(facetStartIndex, this.paramIndex, true); + PolyfaceData.reverseIndices(facetStartIndex, this.colorIndex, true); + PolyfaceData.reverseIndices(facetStartIndex, this.edgeVisible, false); + } + } + public reverseNormals() { + if (this.normal) + for (const normal of this.normal) + normal.scaleInPlace(-1.0); + } + // This base class is just a data carrier. It does not know if the index order and normal directions have special meaning. + // 1) Caller must reverse normals if semanitically needed. + // 2) Caller must reverse indices if semantically needed. + public tryTransformInPlace( + transform: Transform): boolean { + const inverseTranspose = transform.matrix.inverse(); + this.point.transformInPlace(transform); + + if (inverseTranspose) { + // apply simple Matrix3d to normals ... + if (this.normal) { + inverseTranspose.multiplyVectorArrayInPlace(this.normal); + } + } + return true; + } + + public compress() { + const packedData = ClusterableArray.clusterGrowablePoint3dArray(this.point); + this.point = packedData.growablePackedPoints!; + packedData.updateIndices(this.pointIndex); + // if (this.paramIndex) // Tracking uv params + // packedData.updateIndices(this.paramIndex); + // if (this.normalIndex) // Tracking normals + // packedData.updateIndices(this.normalIndex); + } + + /** + * Test if facetStartIndex is (minimally!) valid: + * * length must be nonzero (recall that for "no facets" the facetStartIndexArray still must contain a 0) + * * Each entry must be strictly smaller than the one that follows. + * @param facetStartIndex array of facetStart data. facet `i` has indices at `facetsStartIndex[i]` to (one before) `facetStartIndex[i+1]` + */ + public static isValidFacetStartIndexArray(facetStartIndex: number[]): boolean { + // facetStartIndex for empty facets has a single entry "0" -- empty array is not allowed + if (facetStartIndex.length === 0) + return false; + for (let i = 0; i + 1 < facetStartIndex.length; i++) + if (facetStartIndex[i] >= facetStartIndex[i + 1]) + return false; + return true; + } + public static reverseIndices(facetStartIndex: number[], indices: T[] | undefined, preserveStart: boolean): boolean { + if (!indices || indices.length === 0) + return true; // empty case + if (indices.length > 0) { + if (facetStartIndex[facetStartIndex.length - 1] === indices.length) { + for (let i = 0; i + 1 < facetStartIndex.length; i++) { + let index0 = facetStartIndex[i]; + let index1 = facetStartIndex[i + 1]; + if (preserveStart) { + // leave [index0] as is so reversed facet starts at same vertex + while (index1 > index0 + 2) { + index1--; index0++; + const a = indices[index0]; + indices[index0] = indices[index1]; + indices[index1] = a; + } + } else { + // reverse all + while (index1 > index0 + 1) { + index1--; + const a = indices[index0]; + indices[index0] = indices[index1]; + indices[index1] = a; + index0++; + } + } + } + return true; + } + } + return false; + } + +} diff --git a/core/geometry/src/serialization/GeometrySamples.ts b/core/geometry/src/serialization/GeometrySamples.ts index da1b9c3..099f85e 100644 --- a/core/geometry/src/serialization/GeometrySamples.ts +++ b/core/geometry/src/serialization/GeometrySamples.ts @@ -45,12 +45,14 @@ import { LineString3d } from "../curve/LineString3d"; import { PointString3d } from "../curve/PointString3d"; import { ClipPlane } from "../clipping/ClipPlane"; import { ConvexClipPlaneSet } from "../clipping/ConvexClipPlaneSet"; -import { GrowableFloat64Array, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { UnionOfConvexClipPlaneSets } from "../clipping/UnionOfConvexClipPlaneSets"; import { BSplineCurve3dH } from "../bspline/BSplineCurve3dH"; import { BezierCurve3d } from "../bspline/BezierCurve3d"; import { BezierCurve3dH } from "../bspline/BezierCurve3dH"; -import { CurveChainWithDistanceIndex } from "../curve/PathWithDistanceIndex"; +import { CurveChainWithDistanceIndex } from "../curve/CurveChainWithDistanceIndex"; +import { KnotVector } from "../bspline/KnotVector"; /* tslint:disable:no-console */ @@ -852,6 +854,42 @@ export class Sample { return BSplineSurface3d.create( Sample.createXYGrid(numU, numV, 1.0, 1.0), numU, orderU, undefined, numV, orderV, undefined); } + /** + * @param radiusU major radius + * @param radiusV minor radius + * @param numU number of facets around major hoop + * @param numV number of facets around minor hoop + * @param orderU major hoop order + * @param orderV minor hoop order + */ + public static createPseudoTorusBsplineSurface(radiusU: number, radiusV: number, numU: number, numV: number, orderU: number, orderV: number): BSplineSurface3d | undefined { + const points = []; + const numUPole = numU + orderU - 1; + const numVPole = numV + orderV - 1; + const uKnots = KnotVector.createUniformWrapped(numU, orderU - 1, 0, 1); + const vKnots = KnotVector.createUniformWrapped(numV, orderV - 1, 0, 1); + const dURadians = 2.0 * Math.PI / numU; + const dVRadians = 2.0 * Math.PI / numV; + for (let iV = 0; iV < numVPole; iV++) { + const vRadians = iV * dVRadians; + const cV = Math.cos(vRadians); + const sV = Math.sin(vRadians); + for (let iU = 0; iU < numUPole; iU++) { + const uRadians = iU * dURadians; + const cU = Math.cos(uRadians); + const sU = Math.sin(uRadians); + const rho = radiusU + cV * radiusV; + points.push(Point3d.create(rho * cU, rho * sU, sV * radiusV)); + + } + } + const result = BSplineSurface3d.create(points, numUPole, orderU, uKnots.knots, numVPole, orderV, vKnots.knots); + if (result) { + result.setWrappable(0, true); + result.setWrappable(1, true); + } + return result; + } public static createWeightedXYGridBsplineSurface( numU: number, numV: number, orderU: number, orderV: number, @@ -1395,5 +1433,47 @@ export class Sample { LineSegment3d.create(pointsA[3], pointsA[4])))); return result; } + /** + * Create various elliptic arcs + * * circle with vector0, vector90 aligned with x,y + * * circle with axes rotated + * * + * @param radiusRatio = vector90.magnitude / vector0.magnitude + */ + public static createArcs(radiusRatio: number = 1.0, sweep: AngleSweep = AngleSweep.create360()): Arc3d[] { + const arcs = []; + const center0 = Point3d.create(0, 0, 0); + const a = 1.0; + const b = radiusRatio; + const direction0 = Vector3d.createPolar(a, Angle.createDegrees(35.0)); + const direction90 = direction0.rotate90CCWXY(); + direction90.scaleInPlace(radiusRatio); + arcs.push(Arc3d.create(center0, Vector3d.create(a, 0, 0), Vector3d.create(0, b, 0), sweep)); + arcs.push(Arc3d.create(center0, direction0, direction90, sweep)); + return arcs; + } + /** + * Create many arcs, optionally including skews + * * @param skewFactor array of skew factors. for each skew factor, all base arcs are replicated with vector90 shifted by the factor times vector0 + */ + public static createManyArcs(skewFactors: number[] = []): Arc3d[] { + const result: Arc3d[] = []; + const sweep1 = AngleSweep.createStartEndDegrees(-10, 75); + const sweep2 = AngleSweep.createStartEndDegrees(160.0, 380.0); + for (const arcs of [ + Sample.createArcs(1.0), Sample.createArcs(0.5), + Sample.createArcs(1.0, sweep1), Sample.createArcs(0.3, sweep2)]) { + for (const arc of arcs) + result.push(arc); + } + const numBase = result.length; + for (const skewFactor of skewFactors) { + for (let i = 0; i < numBase; i++) { + const originalArc = result[i]; + result.push(Arc3d.create(originalArc.center, originalArc.vector0, originalArc.vector90.plusScaled(originalArc.vector0, skewFactor), originalArc.sweep)); + } + } + return result; + } } diff --git a/core/geometry/src/serialization/IModelJsonSchema.ts b/core/geometry/src/serialization/IModelJsonSchema.ts index db5b38a..8030774 100644 --- a/core/geometry/src/serialization/IModelJsonSchema.ts +++ b/core/geometry/src/serialization/IModelJsonSchema.ts @@ -45,11 +45,12 @@ import { Point4d } from "../geometry4d/Point4d"; import { CurveCollection } from "../curve/CurveCollection"; import { BezierCurve3dH } from "../bspline/BezierCurve3dH"; import { BezierCurve3d } from "../bspline/BezierCurve3d"; + /* tslint:disable: object-literal-key-quotes no-console*/ export namespace IModelJson { - export interface GeometryProps extends CurvePrimitiveProps, SolidPrimitiveProps { + export interface GeometryProps extends CurvePrimitiveProps, SolidPrimitiveProps, CurveCollectionProps { indexedMesh?: IndexedMeshProps; point?: XYZProps; bsurf?: BSplineSurfaceProps; @@ -254,6 +255,7 @@ export namespace IModelJson { startRadius?: number; endRadius?: number; curveLength?: number; + fractionInterval?: number[]; /** TransitionSpiral type. Default is `"clothoid"` */ type?: string; // one of: "clothoid" | "biquadratic" | "bloss" | "cosine" | "sine"; /** A fractional portion of the spiral may be selected. @@ -719,7 +721,11 @@ export namespace IModelJson { outChannels.push(new AuxChannel(outChannelData, inChannel.dataType as AuxChannelDataType, inChannel.name, inChannel.inputName)); } } - return new PolyfaceAuxData(outChannels, data.indices); + + const auxData = new PolyfaceAuxData(outChannels, []); + Reader.addZeroBasedIndicesFromSignedOneBased(data.indices, (x: number) => { auxData.indices.push(x); }); + + return auxData; } public static parseIndexedMesh(data?: any): any | undefined { @@ -1139,7 +1145,8 @@ export namespace IModelJson { Writer.insertOrientationFromMatrix(value, data.localToWorld.matrix, true); if (!data.activeFractionInterval.isExact01) - Object.defineProperty(value, "fractionInterval", [data.activeFractionInterval.x0, data.activeFractionInterval.x1]); + value.fractionInterval = [data.activeFractionInterval.x0, data.activeFractionInterval.x1]; + // Object.defineProperty(value, "fractionInterval", { value: [data.activeFractionInterval.x0, data.activeFractionInterval.x1] }); // if possible, do selective output of defining data (omit exactly one out of the 5, matching original definition) if (originalProperties !== undefined && originalProperties.numDefinedProperties() === 4) { @@ -1380,10 +1387,18 @@ export namespace IModelJson { return out; } - private handlePolyfaceAuxData(auxData: PolyfaceAuxData): any { + private handlePolyfaceAuxData(auxData: PolyfaceAuxData, pf: IndexedPolyface): any { const contents: { [k: string]: any } = {}; + contents.indices = []; + const visitor = pf.createVisitor(0); + if (!visitor.auxData) return; - contents.indices = auxData.indices.slice(0); + while (visitor.moveToNextFacet()) { + for (let i = 0; i < visitor.indexCount; i++) { + contents.indices.push(visitor.auxData.indices[i] + 1); + } + contents.indices.push(0); // facet terminator. + } contents.channels = []; for (const inChannel of auxData.channels) { const outChannel: { [k: string]: any } = {}; @@ -1461,7 +1476,7 @@ export namespace IModelJson { const contents: { [k: string]: any } = {}; if (pf.data.auxData) - contents.auxData = this.handlePolyfaceAuxData(pf.data.auxData); + contents.auxData = this.handlePolyfaceAuxData(pf.data.auxData, pf); if (pf.data.color) contents.color = colors; if (pf.data.colorIndex) contents.colorIndex = colorIndex; @@ -1564,9 +1579,34 @@ export namespace IModelJson { public handleBSplineSurface3d(surface: BSplineSurface3d): any { // ASSUME -- if the curve originated "closed" the knot and pole replication are unchanged, // so first and last knots can be re-assigned, and last (degree - 1) poles can be deleted. - if (surface.isClosable(0) - || surface.isClosable(1)) { - // TODO + const periodicU = surface.isClosable(0); + const periodicV = surface.isClosable(1); + if (periodicU || periodicV) { + let numUPoles = surface.numPolesUV(0); + let numVPoles = surface.numPolesUV(1); + if (periodicU) numUPoles -= surface.degreeUV(0); + if (periodicV) numVPoles -= surface.degreeUV(1); + const xyz = Point3d.create(); + const grid = []; + for (let j = 0; j < numVPoles; j++) { + const stringer = []; + for (let i = 0; i < numUPoles; i++) { + surface.getPoint3dPole(i, j, xyz)!; + stringer.push([xyz.x, xyz.y, xyz.z]); + } + grid.push(stringer); + } + return { + "bsurf": { + "points": grid, + "uKnots": surface.copyKnots(0, true), + "vKnots": surface.copyKnots(1, true), + "orderU": surface.orderUV(0), + "orderV": surface.orderUV(1), + "closedU": periodicU, + "closedV": periodicV, + }, + }; } else { return { "bsurf": { diff --git a/core/geometry/src/test/AnalyticRoots.test.ts b/core/geometry/src/test/AnalyticRoots.test.ts index 19a3a5f..5738e4f 100644 --- a/core/geometry/src/test/AnalyticRoots.test.ts +++ b/core/geometry/src/test/AnalyticRoots.test.ts @@ -6,8 +6,11 @@ import { expect } from "chai"; import { Checker } from "./Checker"; import { NumberArray } from "../geometry3d/PointHelpers"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; -import { AnalyticRoots, Degree2PowerPolynomial } from "../numerics/Polynomials"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { AnalyticRoots, Degree2PowerPolynomial, PowerPolynomial, TrigPolynomial, SmallSystem, Degree3PowerPolynomial } from "../numerics/Polynomials"; +import { Vector2d, Point2d } from "../geometry3d/Point2dVector2d"; +import { Point4d } from "../geometry4d/Point4d"; +import { Point3d } from "../geometry3d/Point3dVector3d"; /* tslint:disable:no-console no-trailing-whitespace */ @@ -83,6 +86,61 @@ describe("AnalyticRoots.SolveQuadric", () => { } expect(ck.getNumErrors()).equals(0); }); + it("ImplicitLineUnitCircle", () => { + const ck = new Checker(); + const cosValues = new GrowableFloat64Array(); + const sinValues = new GrowableFloat64Array(); + const radiansValues = new GrowableFloat64Array(); + const tol = 1.0e-12; + ck.testExactNumber(1, AnalyticRoots.appendImplicitLineUnitCircleIntersections(1, 0, 1, cosValues, sinValues, radiansValues, tol)); + ck.testExactNumber(2, AnalyticRoots.appendImplicitLineUnitCircleIntersections(0.1, 0, 1, cosValues, sinValues, radiansValues, tol)); + ck.testExactNumber(-2, AnalyticRoots.appendImplicitLineUnitCircleIntersections(0, 0, 0, cosValues, sinValues, radiansValues, tol)); + ck.testExactNumber(-1, AnalyticRoots.appendImplicitLineUnitCircleIntersections(1, 0, 0, cosValues, sinValues, radiansValues, -1)); + + ck.checkpoint("ImplicitLineUnitCircle"); + expect(ck.getNumErrors()).equals(0); + }); + it("PowerPolynomial", () => { + const ck = new Checker(); + const coffs = new Float64Array([1, 2, 3, 4]); + ck.testExactNumber(0, PowerPolynomial.degreeKnownEvaluate(coffs, -1, 4)); + const radians: number[] = []; + ck.testTrue(TrigPolynomial.SolveAngles(new Float64Array([1, 1, 0, 0, 0]), 4, 100, radians)); + ck.testTrue(TrigPolynomial.SolveAngles(new Float64Array([0, 1, 1, 0, 0, 0]), 4, 100, radians)); + + const z3 = new Degree3PowerPolynomial(); + const z2 = new Degree2PowerPolynomial(); + ck.testExactNumber(4, z3.coffs.length); + ck.testExactNumber(3, z2.coffs.length); + ck.checkpoint("PowerPolynomial"); + expect(ck.getNumErrors()).equals(0); + }); + it("SmallSystemFailures", () => { + const ck = new Checker(); + ck.testUndefined(SmallSystem.linearSystem3d( + 1, 2, 3, + 1, 2, 5, + 1, 2, 0, + 1, 2, 3)); + const result = Vector2d.create(); + ck.testFalse(SmallSystem.linearSystem2d( + 1, 2, + 1, 2, + 1, 2, result)); + + ck.testUndefined(SmallSystem.lineSegment3dHXYTransverseIntersectionUnbounded( + Point4d.create(0, 0, 0, 1), Point4d.create(1, 0, 0, 1), + Point4d.create(1, 0, 0, 1), Point4d.create(2, 0, 0, 1))); + ck.testFalse(SmallSystem.lineSegment3dXYTransverseIntersectionUnbounded( + Point3d.create(0, 0, 0), Point3d.create(1, 0, 0), + Point3d.create(1, 0, 0), Point3d.create(2, 0, 0), result)); + ck.testFalse(SmallSystem.lineSegment2dXYTransverseIntersectionUnbounded( + Point2d.create(0, 0), Point2d.create(1, 0), + Point2d.create(1, 0), Point2d.create(2, 0), result)); + ck.checkpoint("SmallSystemFailures"); + expect(ck.getNumErrors()).equals(0); + }); + }); // Cubic and Quartic tests (taken from t_analyticRoots.cpp) -------------------------------- diff --git a/core/geometry/src/test/Angle.test.ts b/core/geometry/src/test/Angle.test.ts index b3f3013..0ed9194 100644 --- a/core/geometry/src/test/Angle.test.ts +++ b/core/geometry/src/test/Angle.test.ts @@ -316,7 +316,7 @@ function multipleDegreeRotationsByAxisOrder(xDegrees: number, yDegrees: number, axisOrder); } -/** This function only compares the angle values for rotations around x, y, and z. It does not care about order for going back to a matrix. */ +/** Compare the matrix images of two ordered rotations. */ function testEqualOrderedRotationAngles(ck: Checker, a: OrderedRotationAngles, b: OrderedRotationAngles) { const matrixA = a.toMatrix3d(); const matrixB = b.toMatrix3d(); @@ -345,6 +345,18 @@ function testMultiAngleEquivalence(ck: Checker, xDegrees: number, yDegrees: numb [AxisOrder.ZXY, AxisOrder.YXZ], [AxisOrder.ZYX, AxisOrder.XYZ]]) { const angles = OrderedRotationAngles.createDegrees(xDegrees, yDegrees, zDegrees, orderPair[0]); + const angles1 = OrderedRotationAngles.createDegrees(0, 0, 0, orderPair[0]); + const angles2 = OrderedRotationAngles.createDegrees(xDegrees, yDegrees, zDegrees, orderPair[0], angles1); + testEqualOrderedRotationAngles(ck, angles, angles2); + ck.testTrue(angles1 === angles2, "reuse prior object"); + ck.testTightNumber(xDegrees, angles.xDegrees, " x degrees"); + ck.testTightNumber(yDegrees, angles.yDegrees, " y degrees"); + ck.testTightNumber(zDegrees, angles.zDegrees, " z degrees"); + + ck.testTightNumber(Angle.degreesToRadians(xDegrees), angles.xRadians, "x radians"); + ck.testTightNumber(Angle.degreesToRadians(yDegrees), angles.yRadians, "y radians"); + ck.testTightNumber(Angle.degreesToRadians(zDegrees), angles.zRadians, "Z radians"); + const matrixA = angles.toMatrix3d(); const matrixB = multipleDegreeRotationsByAxisOrder(-xDegrees, -yDegrees, -zDegrees, orderPair[1]); ck.testMatrix3d(matrixA, matrixB, "Compound rotation pair with order and sign reversal"); diff --git a/core/geometry/src/test/Arc3d.test.ts b/core/geometry/src/test/Arc3d.test.ts index 572227b..657ee1a 100644 --- a/core/geometry/src/test/Arc3d.test.ts +++ b/core/geometry/src/test/Arc3d.test.ts @@ -14,6 +14,7 @@ import { Angle } from "../geometry3d/Angle"; import { prettyPrint } from "./testFunctions"; import { Checker } from "./Checker"; import { expect } from "chai"; +import { Sample } from "../serialization/GeometrySamples"; /* tslint:disable:no-console */ function exerciseArcSet(ck: Checker, arcA: Arc3d) { @@ -135,11 +136,108 @@ describe("Arc3d", () => { } } } - console.log(prettyPrint(sweep) + prettyPrint(factorRange1)); + // console.log(prettyPrint(sweep) + prettyPrint(factorRange1)); } console.log("Arc3d QuickLength FactorRange" + prettyPrint(factorRange)); ck.checkpoint("Arc3d.QuickLength"); expect(ck.getNumErrors()).equals(0); }); + it("EccentricEllipseLengthAccuracyTable", () => { + const noisy = false; + const ck = new Checker(); + // Construct 90 degree elliptic arcs of varying eccentricity. + // Integrate with 4,8,16... gauss intervals until the results settle. + // record factor = N/e + // By trial and error, we observe the factor is 8 or less. + for (const numGauss of [1, 2, 3, 4, 5]) { + let maxFactor = 0; + if (noisy) + console.log("\n\n ******************* numGauss" + numGauss); + for (let e2 = 1.0; e2 < 1000.0; e2 *= 2.0) { + const e = Math.sqrt(e2); + const arc = Arc3d.create(Point3d.createZero(), + Vector3d.create(e, 0, 0), + Vector3d.create(0, 1, 0), AngleSweep.createStartEndDegrees(0, 90)); + const lengths = []; + const deltas = []; + const counts = []; + let lastNumInterval = 0; + let done = false; + for (let baseNumInterval = 4; baseNumInterval < 600; baseNumInterval *= 2) { + for (const numInterval of [baseNumInterval, 1.25 * baseNumInterval, 1.5 * baseNumInterval, 1.75 * baseNumInterval]) { + lengths.push(arc.curveLengthWithFixedIntervalCountQuadrature(0.0, 1.0, numInterval, numGauss)); + counts.push(numInterval); + lastNumInterval = numInterval; + const k = lengths.length - 1; + if (k >= 1) { + const q = (lengths[k] - lengths[k - 1]) / lengths[k]; + deltas.push(q); + if (Math.abs(q) < 4.0e-15) { done = true; break; } + } + } + if (done) + break; + } + const factor = lastNumInterval / e; + if (noisy) { + console.log("---"); + console.log(" eccentricity " + e + " " + + lengths.toString() + + " (n " + lastNumInterval + ") (n/(fe) " + factor + ")"); + console.log(" deltas " + deltas.toString()); + } + maxFactor = Math.max(factor, maxFactor); + } + console.log("Eccentric ellipse integration (numGauss " + numGauss + ") (maxFactor " + maxFactor + ")"); + if (numGauss === 5) + ck.testLE(maxFactor, 20.0, "Eccentric Ellipse integraton factor"); + } + ck.checkpoint("Arc3d.EccentricEllipseLengthAccuracyTable"); + expect(ck.getNumErrors()).equals(0); + }); + it("ValidateEllipseIntegrationHeuristic", () => { + const ck = new Checker(); + const degreesInterval = 10.0; + const numGauss = 5; + // Construct elliptic arcs of varying eccentricity and angles + // Integrate with 5 gauss points in intervals of 10 degrees. + // By trial and error, we have concluded that this should be accurate. + // Compare to integral with twice as many points. + for (const sweepDegrees of [30, 90, 135, 180, 239, 360]) { + for (let e2 = 1.0; e2 < 1000.0; e2 *= 2.0) { + const e = Math.sqrt(e2); + const arc = Arc3d.create(Point3d.createZero(), + Vector3d.create(e, 0, 0), + Vector3d.create(0, 1, 0), AngleSweep.createStartEndDegrees(0, sweepDegrees)); + const numA = Math.ceil(arc.sweep.sweepDegrees * e / degreesInterval); + const lengthA = arc.curveLengthWithFixedIntervalCountQuadrature(0.0, 1.0, numA, numGauss); + const lengthB = arc.curveLengthWithFixedIntervalCountQuadrature(0.0, 1.0, 2 * numA, numGauss); + const lengthC = arc.curveLength(); + ck.testLE(Math.abs(lengthB - lengthA) / lengthA, 5.0e-15, "direct quadrature", e, numA); + ck.testLE(Math.abs(lengthB - lengthC) / lengthA, 5.0e-15, "compare to method", e, numA); + } + } + ck.checkpoint("Arc3d.ValidateEllipseIntegrationHeuristic"); + expect(ck.getNumErrors()).equals(0); + }); + + it("ScaledForm", () => { + const ck = new Checker(); + const arcs = Sample.createManyArcs([0.2, -0.25]); + for (const arc of arcs) { + const scaledForm = arc.toScaledMatrix3d(); + const arc1 = Arc3d.createScaledXYColumns( + scaledForm.center, + scaledForm.axes, + scaledForm.r0, + scaledForm.r90, + scaledForm.sweep); + for (const fraction of [0.0, 0.2, 0.4, 0.6, 0.8]) { + ck.testPoint3d(arc.fractionToPoint(fraction), arc1.fractionToPoint(fraction)); + } + } + ck.checkpoint("Arc3d.ScaledForm"); + expect(ck.getNumErrors()).equals(0); + }); }); diff --git a/core/geometry/src/test/BSplineSurface.test.ts b/core/geometry/src/test/BSplineSurface.test.ts index c4c0d16..fa5b76f 100644 --- a/core/geometry/src/test/BSplineSurface.test.ts +++ b/core/geometry/src/test/BSplineSurface.test.ts @@ -14,6 +14,7 @@ import { Transform } from "../geometry3d/Transform"; import { BSplineSurface3dQuery, BSplineSurface3dH } from "../bspline/BSplineSurface"; import { expect } from "chai"; import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; +import { GeometryCoreTestIO } from "./GeometryCoreTestIO"; /* tslint:disable:no-console */ function testBasisValues(ck: Checker, data: Float64Array, expectedValue: number = 1) { let s = 0.0; for (const a of data) s += a; @@ -110,22 +111,22 @@ function testBSplineSurface(ck: Checker, surfaceA: BSplineSurface3dQuery) { const uknot = surfaceA.spanFractionToKnot(0, i, f); const vknot = surfaceA.spanFractionToKnot(1, j, f); const knotPoint4d = surfaceA.knotToPoint4d(uknot, vknot); - const point3d = surfaceA.knotToPoint (uknot, vknot); + const point3d = surfaceA.knotToPoint(uknot, vknot); const uFraction = uKnots.spanFractionToFraction(i, f); const vFraction = vKnots.spanFractionToFraction(j, f); const fractionPoint = surfaceA.fractionToPoint(uFraction, vFraction); - const fractionPoint4d = surfaceA.fractionToPoint4d (uFraction, vFraction); - const fractionPoint4dto3d = fractionPoint4d.realPointDefault000 (); + const fractionPoint4d = surfaceA.fractionToPoint4d(uFraction, vFraction); + const fractionPoint4dto3d = fractionPoint4d.realPointDefault000(); const knotPoint4dto3d = knotPoint4d.realPointDefault000(); ck.testPoint3d(knotPoint4dto3d, fractionPoint); ck.testPoint3d(fractionPoint4dto3d, fractionPoint); - ck.testPoint3d (point3d, knotPoint4dto3d); - surfaceA.spanFractionsToBasisFunctions (0, i, f, uBasis, duBasis); - testBasisValues (ck, uBasis, 1.0); - testBasisValues (ck, duBasis, 0.0); - surfaceA.spanFractionsToBasisFunctions (1, j, f, vBasis, dvBasis); - testBasisValues (ck, vBasis, 1.0); - testBasisValues (ck, dvBasis, 0.0); + ck.testPoint3d(point3d, knotPoint4dto3d); + surfaceA.spanFractionsToBasisFunctions(0, i, f, uBasis, duBasis); + testBasisValues(ck, uBasis, 1.0); + testBasisValues(ck, duBasis, 0.0); + surfaceA.spanFractionsToBasisFunctions(1, j, f, vBasis, dvBasis); + testBasisValues(ck, vBasis, 1.0); + testBasisValues(ck, dvBasis, 0.0); } } } @@ -141,6 +142,7 @@ describe("BSplineSurface", () => { surfaceA.setWrappable(1, true); testBSplineSurface(ck, surfaceA); ck.testFalse(surfaceA.isClosable(1)); + ck.testFalse(surfaceA.isClosable(0)); } // A rational surface with unit weigths ... This is just a plane const surfaceAH1 = Sample.createWeightedXYGridBsplineSurface(4, 3, 3, 2); @@ -154,4 +156,34 @@ describe("BSplineSurface", () => { ck.checkpoint("BSplineSurface.Hello"); expect(ck.getNumErrors()).equals(0); }); + + it("Wrapped", () => { + const ck = new Checker(); + const allGeometry = []; + let dx = 0.0; + let dy = 0.0; + for (const orderU of [2, 3, 4, 5]) { + dy = 0.0; + for (const orderV of [2, 3, 4, 5]) { + const bsurf = Sample.createPseudoTorusBsplineSurface( + 4.0, 1.0, // radii + Math.max(12, orderU + 1), Math.max(6, orderV + 1), // grid edges + orderU, orderV); // order} + if (ck.testPointer(bsurf) && bsurf) { + + ck.testTrue(bsurf.isClosable(0)); + ck.testTrue(bsurf.isClosable(1)); + + bsurf.tryTranslateInPlace(dx, dy); + allGeometry.push(bsurf); + } + dy += 20.0; + } + dx += 20.0; + } + GeometryCoreTestIO.saveGeometry(allGeometry, "BSplineSurface", "Wrapped"); + ck.checkpoint("BSplineSurface.Wrapped"); + expect(ck.getNumErrors()).equals(0); + }); + }); diff --git a/core/geometry/src/test/BezierCurve.test.ts b/core/geometry/src/test/BezierCurve.test.ts index 4c5fede..f92d08b 100644 --- a/core/geometry/src/test/BezierCurve.test.ts +++ b/core/geometry/src/test/BezierCurve.test.ts @@ -13,6 +13,10 @@ import { Point2d } from "../geometry3d/Point2dVector2d"; import { KnotVector } from "../bspline/KnotVector"; import { GeometryCoreTestIO } from "./GeometryCoreTestIO"; import { GeometryQuery } from "../curve/GeometryQuery"; +import { BSplineCurve3d } from "../bspline/BSplineCurve"; +import { LineString3d } from "../curve/LineString3d"; +import { LineSegment3d } from "../curve/LineSegment3d"; +import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; function exercise1dNdBase(ck: Checker, curve: Bezier1dNd) { ck.testLE(1, curve.order, "Bezier1dNd has nontrivial order"); @@ -42,9 +46,13 @@ describe("BsplineCurve", () => { const bezierCurves = [BezierCurve3d.create(allPoints)!, BezierCurve3dH.create(allPoints)!]; ck.testFalse(bezierCurves[0].isAlmostEqual(bezierCurves[1])); ck.testFalse(bezierCurves[1].isAlmostEqual(bezierCurves[0])); + const plane0 = Plane3dByOriginAndUnitNormal.createXYPlane(); + const plane1 = Plane3dByOriginAndUnitNormal.createZXPlane(); for (const bezier of bezierCurves) { ck.testUndefined(bezier.getPolePoint3d(100)); ck.testUndefined(bezier.getPolePoint4d(100)); + ck.testTrue(bezier.isInPlane(plane0)); + ck.testFalse(bezier.isInPlane(plane1)); for (let i = 0; i < allPoints.length; i++) { const pole3d = bezier.getPolePoint3d(i); const pole4d = bezier.getPolePoint4d(i); @@ -143,4 +151,79 @@ describe("BsplineCurve", () => { expect(ck.getNumErrors()).equals(0); }); + it("BsplineGrid", () => { + const ck = new Checker(); + const geometry: GeometryQuery[] = []; + const a = 3.0; + const controlPoints: Point3d[] = [ + Point3d.create(0, 0, 0), + Point3d.create(a, 10, 0), + Point3d.create(10, 10, 0), + Point3d.create(10 + a, 0, 0), + Point3d.create(20, 0, 0), + Point3d.create(20 + a, 10, 0), + Point3d.create(10, 20, 0)]; + const dx = 100.0; + let x0 = 0.0; + let y0 = 0.0; + const f0 = 0.05; + const f1 = 1.0 - f0; + const dy = 30.0; + const tickLength = 0.5; + const setbackDistance = 0.5; + GeometryCoreTestIO.captureGeometry(geometry, LineString3d.create(controlPoints), x0, y0); + for (const order of [3, 2, 3, 4, 5]) { + x0 += dx; + y0 = 0.0; + const y1 = y0 + 2 * dy; + const y2 = y0 + 1 * dy; + const y3 = y0 + 3 * dy; + const y4 = y3; + // Output the full bspline with tic marks at the knot breaks + const bcurve = BSplineCurve3d.createUniformKnots(controlPoints, order)!; + GeometryCoreTestIO.captureGeometry(geometry, bcurve.clone(), x0, y0); + const knots = bcurve.copyKnots(false); + if (order > 2) { + for (const u of knots) { + if (u > 0.0 && u < 1.0) { + const curvePoint = bcurve.knotToPointAndDerivative(u); // evaluate the knot point + const perp = curvePoint.direction.rotate90CCWXY(); + perp.normalizeInPlace(); + GeometryCoreTestIO.captureGeometry(geometry, LineSegment3d.create(curvePoint.origin, curvePoint.origin.plusScaled(perp, tickLength)), x0, y0); + } + } + } + const allBeziers = bcurve.collectBezierSpans(false); + // output each bezier, clipped off near the end points to emphasize that they are separate. + let bezierIndex = 0; + for (const bezier of allBeziers) { + const bezier1 = bezier.clonePartialCurve(f0, f1)!; + GeometryCoreTestIO.captureGeometry(geometry, bezier1, x0, y1); + const detail0 = bezier.moveSignedDistanceFromFraction(0.0, setbackDistance, false); + const detail1 = bezier.moveSignedDistanceFromFraction(1.0, -setbackDistance, false); + const bezier2 = bezier.clonePartialCurve(detail0.fraction, detail1.fraction)!; + GeometryCoreTestIO.captureGeometry(geometry, bezier2, x0, y2); + if (bezierIndex === 0) + GeometryCoreTestIO.createAndCaptureXYCircle(geometry, bezier.fractionToPoint(0.0), setbackDistance, x0, y2); + GeometryCoreTestIO.createAndCaptureXYCircle(geometry, bezier.fractionToPoint(1.0), setbackDistance, x0, y2); + // make sure partialClones overlap original . . . + const g0 = 0.2342345; + const g1 = 0.82342367; + GeometryCoreTestIO.captureGeometry(geometry, bezier.clone()!, x0, y3); + + const bezier4 = bezier.clonePartialCurve(g0, 1.0)!; + const bezier5 = bezier4.clonePartialCurve(0.0, (g1 - g0) / (1 - g0))!; // Remark: This uses the opposite left/right order of what happen in clone partial. (Same result expected) + const bezier6 = bezier.clonePartialCurve(g0, g1)!; + ck.testTrue(bezier5.isAlmostEqual(bezier6), "bezier subdivision"); // wow, math is right. + GeometryCoreTestIO.captureGeometry(geometry, bezier4, x0, y4); + GeometryCoreTestIO.captureGeometry(geometry, bezier5, x0, y4); + GeometryCoreTestIO.captureGeometry(geometry, bezier6, x0, y4); + bezierIndex++; + } + } + + GeometryCoreTestIO.saveGeometry(geometry, "BezierCurve3d", "BsplineGrid"); + expect(ck.getNumErrors()).equals(0); + }); + }); diff --git a/core/geometry/src/test/BsplineCurve.test.ts b/core/geometry/src/test/BsplineCurve.test.ts index 9483fed..1a7682e 100644 --- a/core/geometry/src/test/BsplineCurve.test.ts +++ b/core/geometry/src/test/BsplineCurve.test.ts @@ -21,11 +21,47 @@ import { BSplineCurve3dH } from "../bspline/BSplineCurve3dH"; import { Sample } from "../serialization/GeometrySamples"; import { CurvePrimitive } from "../curve/CurvePrimitive"; import { Path } from "../curve/Path"; -import { prettyPrint } from "./testFunctions"; +// import { prettyPrint } from "./testFunctions"; import { CurveLocationDetail } from "../curve/CurveLocationDetail"; import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; import { Matrix3d } from "../geometry3d/Matrix3d"; import { LineSegment3d } from "../curve/LineSegment3d"; +import { Range3d } from "../geometry3d/Range"; +import { Point4d } from "../geometry4d/Point4d"; +/** return knots [0,0,0, step, 2*step, ... N,N,N] + * where there are: + * * (order-1) leading and trailing clamp values. + * * internal knots with given step. + */ +function buildClampedSteppedKnots(numPoints: number, order: number, step: number): number[] { + const knots = []; + const degree = order - 1; + // left clamp always at 0 . . . + for (let i = 0; i < degree; i++) knots.push(0); + let b = step; + // true internal knots + for (let i = 1; i + order <= numPoints; i++) { + b = i * step; + knots.push(b); + } + // right clamp + b = (numPoints - order + 1) * step; + for (let i = 0; i < degree; i++) knots.push(b); + return knots; +} +/** return knots [-K*step, -(K-1)*step, .. 0, step, ....N, N+step, N+2*step] + * where there are: + * * (order-1) leading and trailing values, uniformly stepped + * * internal knots with given step. + * * traling values wrap with period N + */ +function buildWrappableSteppedKnots(numInterval: number, order: number, step: number): number[] { + const knots = []; + const knot0 = - step * (order - 2); + for (let i = 0; i < numInterval + order - 2; i++) + knots.push(knot0 + i * step); + return knots; +} function translateAndPush(allGeometry: GeometryQuery[], g: GeometryQuery | undefined, dx: number, dy: number) { if (g) { @@ -221,18 +257,26 @@ describe("BsplineCurve", () => { }); it("DoubleKnots", () => { + // stroke a bcurve with double knots .. bug was that the double knot intervals generated 0 or undefined stroke coordinates. + // Be sure the curve is all in one quadrant so 00 is NOT in the storke range. const ck = new Checker(); const bcurve = BSplineCurve3d.create( - [Point3d.create(0, 0), - Point3d.create(1, 0, 0), - Point3d.create(1, 1, 0), + [Point3d.create(1, 0), + Point3d.create(2, 0, 0), Point3d.create(2, 1, 0), - Point3d.create(3, 0, 0), - Point3d.create(4, 1, 0)], + Point3d.create(3, 1, 0), + Point3d.create(4, 0, 0), + Point3d.create(5, 1, 0)], [0, 0, 0.5, 0.5, 0.75, 1, 1], 3)!; const path = Path.create(bcurve); const strokes = path.getPackedStrokes()!; - console.log(prettyPrint(strokes)); + // console.log(prettyPrint(strokes)); + const strokeRange = Range3d.create(); + strokes.extendRange(strokeRange); + const curveRange = bcurve.range(); + curveRange.expandInPlace(0.00001); + ck.testTrue(curveRange.containsRange(strokeRange)); + ck.testFalse(strokeRange.containsXYZ(0, 0, 0)); expect(ck.getNumErrors()).equals(0); }); it("SaturateBspline", () => { @@ -316,4 +360,173 @@ describe("BsplineCurve", () => { expect(ck.getNumErrors()).equals(0); }); + it("BsplineCurve3dHCoverage", () => { + const ck = new Checker(); + const poleBuffer = new Float64Array([ + 0, 0, 0, 1, + 1, 0, 0, 1, + 1, 1, 0, 2, // weights vary !! + 0, 1, 1, 1, // non planar + 0, 2, 2, 1]); + const myKnots = [0, 0, 0, 1, 2, 2, 2]; + const bcurve = BSplineCurve3dH.create(poleBuffer, myKnots, 4)!; + const bcurveB = BSplineCurve3dH.createUniformKnots(poleBuffer, 4); + + ck.testFalse(bcurve.isInPlane(Plane3dByOriginAndUnitNormal.createXYPlane())); + + ck.testUndefined(BSplineCurve3dH.createUniformKnots(poleBuffer, 10)); + ck.testUndefined(BSplineCurve3dH.create(poleBuffer, myKnots, 10)); + ck.testPointer(bcurveB); + const poleBufferA = bcurve.copyPointsFloat64Array(); + const poleArray = bcurve.copyPoints(); + if (ck.testExactNumber(poleArray.length * 4, poleBufferA.length)) { + for (let i = 0, k = 0; i < poleArray.length; i++) { + ck.testExactNumber(poleArray[i][0], poleBufferA[k++]); + ck.testExactNumber(poleArray[i][1], poleBufferA[k++]); + ck.testExactNumber(poleArray[i][2], poleBufferA[k++]); + ck.testExactNumber(poleArray[i][3], poleBufferA[k++]); + } + } + + let n = 0; + const myPole = Point3d.create(); + const myPoleH = Point4d.create(); + ck.testUndefined(bcurve.getPolePoint4d(100)); + for (; bcurve.getPolePoint3d(n, myPole) !== undefined; n++) { + const q = bcurve.getPolePoint4d(n, myPoleH); + if (ck.testPointer(q) && q) { + const w = myPoleH.w; + ck.testTrue(myPoleH.isAlmostEqualXYZW(myPole.x * w, myPole.y * w, myPole.z * w, w)); + } + } + expect(ck.getNumErrors()).equals(0); + }); + + it("BsplineCurve3dCoverage", () => { + const ck = new Checker(); + const poleBuffer = new Float64Array([ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 1, + 0, 2, 2]); + const myKnots = [0, 0, 0, 1, 2, 2, 2]; + const bcurve = BSplineCurve3d.create(poleBuffer, myKnots, 4)!; + const bcurveB = BSplineCurve3d.createUniformKnots(poleBuffer, 4); + ck.testFalse(bcurve.isInPlane(Plane3dByOriginAndUnitNormal.createXYPlane())); + ck.testUndefined(BSplineCurve3d.createUniformKnots(poleBuffer, 10)); + ck.testUndefined(BSplineCurve3d.create(poleBuffer, myKnots, 10)); + ck.testPointer(bcurveB); + const poleBufferA = bcurve.copyPointsFloat64Array(); + const poleArray = bcurve.copyPoints(); + if (ck.testExactNumber(poleArray.length * 3, poleBufferA.length)) { + for (let i = 0, k = 0; i < poleArray.length; i++) { + ck.testExactNumber(poleArray[i][0], poleBufferA[k++]); + ck.testExactNumber(poleArray[i][1], poleBufferA[k++]); + ck.testExactNumber(poleArray[i][2], poleBufferA[k++]); + } + } + + let n = 0; + const myPole = Point3d.create(); + const myPoleH = Point4d.create(); + ck.testUndefined(bcurve.getPolePoint4d(100)); + ck.testUndefined(bcurve.getPolePoint3d(100)); + for (; bcurve.getPolePoint3d(n, myPole) !== undefined; n++) { + const q = bcurve.getPolePoint4d(n, myPoleH); + if (ck.testPointer(q) && q) { + const w = myPoleH.w; + ck.testTrue(myPoleH.isAlmostEqualXYZW(myPole.x * w, myPole.y * w, myPole.z * w, w)); + } + } + expect(ck.getNumErrors()).equals(0); + }); + + it("WeightedCurveMatch", () => { + const ck = new Checker(); + const allGeometry: GeometryQuery[] = []; + const poleArray = [ + Point3d.create(0, 0, 0), + Point3d.create(1, 0, 0), + Point3d.create(1, 1, 0), + Point3d.create(0, 1, 1), + Point3d.create(0, 2, 2)]; + for (const order of [3, 4, 5]) { + const myKnots = buildClampedSteppedKnots(poleArray.length, order, 1.0); + const bcurve3d = BSplineCurve3d.create(poleArray, myKnots, order)!; + const bcurve4d = BSplineCurve3dH.create(poleArray, myKnots, order)!; + allGeometry.push(bcurve3d); + allGeometry.push(bcurve4d); + for (const u of [0.2, 0.4, 0.5, 0.65, 1.0]) { + const point3d = bcurve3d.fractionToPoint(u); + const point4d = bcurve4d.fractionToPoint(u); + if (!ck.testPoint3d(point3d, point4d, u)) { + bcurve3d.fractionToPoint(u); + bcurve4d.fractionToPoint(u); + } + + } + } + GeometryCoreTestIO.saveGeometry(allGeometry, "BSplineCurve", "WeightedCurveMatch"); + expect(ck.getNumErrors()).equals(0); + }); + + it("WrappedCurves", () => { + const ck = new Checker(); + const allGeometry: GeometryQuery[] = []; + const xStep = 10.0; + const poleArray = [ + Point3d.create(0, 0, 0), + Point3d.create(1, 0, 0), + Point3d.create(1, 1, 0), + Point3d.create(0, 1, 1), + Point3d.create(0, 2, 2)]; + for (const order of [2, 3, 4, 5]) { + // wrap the points + const wrappedPoleArray = []; + for (const p of poleArray) wrappedPoleArray.push(p.clone()); + for (let i = 0; i + 1 < order; i++) wrappedPoleArray.push(poleArray[i].clone()); + const myKnots = buildWrappableSteppedKnots(poleArray.length, order, 1.0); + const bcurve3d = BSplineCurve3d.create(wrappedPoleArray, myKnots, order)!; + bcurve3d.setWrappable(true); + const bcurve4d = BSplineCurve3dH.create(wrappedPoleArray, myKnots, order)!; + ck.testUndefined(bcurve3d.getSaturatedBezierSpan3d(100)); + ck.testUndefined(bcurve4d.getSaturatedBezierSpan3dH(100)); + + bcurve4d.setWrappable(true); + GeometryCoreTestIO.captureGeometry(allGeometry, bcurve3d.clone(), (order - 2) * xStep, 0, 0); + GeometryCoreTestIO.captureGeometry(allGeometry, bcurve4d.clone(), (order - 2) * xStep, xStep, 0); + ck.testTrue(bcurve3d.isClosable); + ck.testTrue(bcurve4d.isClosable); + ck.testFalse(bcurve3d.isAlmostEqual(bcurve4d)); + ck.testFalse(bcurve4d.isAlmostEqual(bcurve3d)); + for (const u of [0.2, 0.4, 0.5, 0.65, 1.0]) { + const point3d = bcurve3d.fractionToPoint(u); + const point4d = bcurve4d.fractionToPoint(u); + if (!ck.testPoint3d(point3d, point4d, u)) { + bcurve3d.fractionToPoint(u); + bcurve4d.fractionToPoint(u); + } + // mess up poles first, then knots to reach failure branchs in closure tests ... + wrappedPoleArray[0].x += 0.1; + const bcurve3dA = BSplineCurve3d.create(wrappedPoleArray, myKnots, order)!; + bcurve3dA.setWrappable(true); + ck.testFalse(bcurve3dA.isClosable); + const bcurve4dA = BSplineCurve3dH.create(wrappedPoleArray, myKnots, order)!; + bcurve4dA.setWrappable(true); + ck.testFalse(bcurve4dA.isClosable); + + // mess up knots. The knot test precedes the pole test, so this failure gets hit before the poles (which are already altered) + myKnots[order - 2] -= 0.1; + const bcurve3dB = BSplineCurve3d.create(wrappedPoleArray, myKnots, order)!; + bcurve3dB.setWrappable(true); + ck.testFalse(bcurve3dB.isClosable); + const bcurve4dB = BSplineCurve3dH.create(wrappedPoleArray, myKnots, order)!; + bcurve4dB.setWrappable(true); + ck.testFalse(bcurve4dB.isClosable); + } + } + GeometryCoreTestIO.saveGeometry(allGeometry, "BSplineCurve", "WeightedCurveMatch"); + expect(ck.getNumErrors()).equals(0); + }); }); diff --git a/core/geometry/src/test/Checker.ts b/core/geometry/src/test/Checker.ts index d0bc178..6a09779 100644 --- a/core/geometry/src/test/Checker.ts +++ b/core/geometry/src/test/Checker.ts @@ -10,7 +10,7 @@ import { Segment1d } from "../geometry3d/Segment1d"; import { Transform } from "../geometry3d/Transform"; import { Matrix3d } from "../geometry3d/Matrix3d"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Range3d } from "../geometry3d/Range"; import { GeometryQuery } from "../curve/GeometryQuery"; import { Arc3d } from "../curve/Arc3d"; @@ -147,7 +147,25 @@ export class Checker { } return this.announceOK(); } - + /** + * Test if number arrays (either or both possibly undefined) match. + */ + public testNumberArrayGG(dataA: GrowableFloat64Array | undefined, dataB: GrowableFloat64Array | undefined, ...params: any[]): boolean { + const numA = dataA === undefined ? 0 : dataA.length; + const numB = dataB === undefined ? 0 : dataB.length; + if (numA !== numB) + return this.announceError("array length mismatch", dataA, dataB, params); + if (dataA && dataB) { + let numError = 0; + for (let i = 0; i < dataA.length; i++) { + if (!Geometry.isSameCoordinate(dataA.at(i), dataB.at(i))) + numError++; + } + if (numError !== 0) + return this.announceError("contents different", dataA, dataB, params); + } + return this.announceOK(); + } public testRange3d(dataA: Range3d, dataB: Range3d, ...params: any[]): boolean { if (dataA.isAlmostEqual(dataB)) return this.announceOK(); @@ -310,13 +328,22 @@ export class Checker { return this.announceError("Expect perpendicular", dataA, dataB, params); } - // return true if dataA is strictly before dataB as a signed toleranced coordinate value. + // return true for exact numeric equality public testExactNumber(dataA: number, dataB: number, ...params: any[]): boolean { if (dataA === dataB) return this.announceOK(); return this.announceError("Expect exact number", dataA, dataB, params); } + // return true if numbers are nearly identical, tolerance e * (1 + abs(dataA) + abs (dataB)) for e = 8e-16 + public testTightNumber(dataA: number, dataB: number, ...params: any[]): boolean { + const d = Math.abs(dataB - dataA); + const tol = 8.0e-16 * (1.0 + Math.abs(dataA) + Math.abs(dataB)); + if (d < tol) + return this.announceOK(); + return this.announceError("Expect exact number", dataA, dataB, params); + } + // return true if dataA is strictly before dataB as a signed toleranced coordinate value. public testContainsCoordinate(dataA: GrowableFloat64Array, dataB: number, ...params: any[]): boolean { for (let i = 0; i < dataA.length; i++) diff --git a/core/geometry/src/test/ConvexPolygon2d.test.ts b/core/geometry/src/test/ConvexPolygon2d.test.ts index 91c57b8..2312bd2 100644 --- a/core/geometry/src/test/ConvexPolygon2d.test.ts +++ b/core/geometry/src/test/ConvexPolygon2d.test.ts @@ -18,7 +18,7 @@ import { PolygonOps } from "../geometry3d/PointHelpers"; function checkHullRaysFromCentroid(hull: ConvexPolygon2d, ck: Checker) { const hullPoints = hull.points; const centroid = new Point2d(); - ck.testTrue(PolygonOps.centroidAndArea(hullPoints, centroid) !== undefined, "CentroidAndArea call is not undefined"); + ck.testTrue(PolygonOps.centroidAndAreaXY(hullPoints, centroid) !== undefined, "CentroidAndArea call is not undefined"); const fractions: number[] = [0.0, 0.43, 0.96, 1.08, 2.5]; for (const i of hullPoints) { diff --git a/core/geometry/src/test/Curve.test.ts b/core/geometry/src/test/Curve.test.ts index 7088771..87c762c 100644 --- a/core/geometry/src/test/Curve.test.ts +++ b/core/geometry/src/test/Curve.test.ts @@ -30,10 +30,10 @@ import { GeometryCoreTestIO } from "./GeometryCoreTestIO"; import { BezierCurve3dH } from "../bspline/BezierCurve3dH"; import { BezierCurve3d } from "../bspline/BezierCurve3d"; import { Point4d } from "../geometry4d/Point4d"; -import { CurveLocationDetail, CurveIntervalRole } from "../curve/CurveLocationDetail"; +import { CurveLocationDetail, CurveIntervalRole, CurveSearchStatus } from "../curve/CurveLocationDetail"; import { CoordinateXYZ } from "../curve/CoordinateXYZ"; import { Path } from "../curve/Path"; -import { CurveChainWithDistanceIndex } from "../curve/PathWithDistanceIndex"; +import { CurveChainWithDistanceIndex } from "../curve/CurveChainWithDistanceIndex"; /* tslint:disable:no-console */ class ExerciseCurve { @@ -55,10 +55,11 @@ class ExerciseCurve { ck.testCoordinate(scaleFactor * pointA0.distance(pointA1), pointB0.distance(pointB1)); const frameA0 = curveA.fractionToFrenetFrame(u0); const frameB0 = curveB.fractionToFrenetFrame(u0); - if (ck.testPointer(frameA0) + if (frameA0 && frameB0 + /* ck.testPointer(frameA0) && ck.testPointer(frameB0) && frameA0 - && frameB0) { + && frameB0*/ ) { ck.testTransform(frameA0, frameB0); const frameA0Inverse = frameA0.inverse(); if (ck.testPointer(frameA0Inverse) && frameA0Inverse) { @@ -91,9 +92,9 @@ class ExerciseCurve { if (curve instanceof BSplineCurve3d) return; // TODO // if (curve instanceof TransitionSpiral3d) return; // TODO for (const fractionA of [0.421, 0.421, 0.45, 0.45]) { - const frameA = curve.fractionToFrenetFrame(fractionA)!; // just point and tangent needed, but exercise this . . - if (ck.testPointer(frameA) && frameA) { - const plane = Plane3dByOriginAndUnitNormal.create(frameA.getOrigin(), frameA.matrix.columnX())!; + const tangentA = curve.fractionToPointAndDerivative(fractionA)!; + if (ck.testPointer(tangentA) && tangentA) { + const plane = Plane3dByOriginAndUnitNormal.create(tangentA.origin, tangentA.direction)!; const intersections: CurveLocationDetail[] = []; curve.appendPlaneIntersectionPoints(plane!, intersections); const foundAt = intersections.filter( @@ -107,6 +108,49 @@ class ExerciseCurve { } } } + + public static exerciseMoveSignedDistance(ck: Checker, curve: CurvePrimitive) { + for (const segment of [ + Segment1d.create(0.0, 0.5), + Segment1d.create(0.5, 1.0), + Segment1d.create(0.1, 0.35), + Segment1d.create(0.38, 0.92), + Segment1d.create(-0.1, 0.2), + Segment1d.create(-0.1, 0.2), + Segment1d.create(0.9, 1.2), + Segment1d.create(0.9, 1.2)]) { + const a = segment.x0; + const b = segment.x1; + const distanceAB = curve.curveLengthBetweenFractions(a, b); + const distanceBA = curve.curveLengthBetweenFractions(b, a); + if (!ck.testCoordinate(distanceAB, distanceBA)) { + curve.curveLengthBetweenFractions(a, b); + curve.curveLengthBetweenFractions(b, a); + } + let detailAtoB = curve.moveSignedDistanceFromFraction(a, distanceAB, true); + let detailBtoA = curve.moveSignedDistanceFromFraction(b, -distanceAB, true); + if (!segment.isIn01 && + (detailAtoB.curveSearchStatus === CurveSearchStatus.stoppedAtBoundary + || detailBtoA.curveSearchStatus === CurveSearchStatus.stoppedAtBoundary)) { + // um .. not sure what to test for + } else if (detailAtoB.curveSearchStatus === undefined + || detailBtoA.curveSearchStatus === undefined + || detailAtoB.curveSearchStatus !== CurveSearchStatus.success + || detailBtoA.curveSearchStatus !== CurveSearchStatus.success) { + detailAtoB = curve.moveSignedDistanceFromFraction(a, distanceAB, true); + detailBtoA = curve.moveSignedDistanceFromFraction(b, -distanceAB, true); + ck.announceError("Incomplete moveSignedDistanceFromFraction", a, b, curve); + } else { + if (curve.isExtensibleFractionSpace || segment.isIn01) { + ck.testCoordinate(b, detailAtoB.fraction); + ck.testCoordinate(a, detailBtoA.fraction); + } else { + + } + } + } + } + public static exerciseFractionToPoint(ck: Checker, curve: CurvePrimitive | undefined, expectProportionalDistance: boolean, expectEqualChordLength: boolean) { if (!curve) { ck.announceError("Null CurvePrimitive provided to exerciseFractionAndPoint"); @@ -152,14 +196,23 @@ class ExerciseCurve { const approximateDerivative2 = delta012.scale(1.0 / (derivativeIncrement * derivativeIncrement)); ck.testTrue(aproximateDerivative.distance(ray1.direction) < derivativeTolerance * (1 + ray1.direction.magnitude()), "approximate derivative", ray1.direction, aproximateDerivative, curve, fraction); - if (plane1 && !(curve instanceof BSplineCurve3d)) { // curve instanceof TransitionSpiral3d + if (plane1) { // curve instanceof TransitionSpiral3d ck.testPoint3d(ray1.origin, plane1.origin, "points with derivatives"); if (!(curve instanceof TransitionSpiral3d)) { // TransitionSpiral has wierd derivative behavior? - ck.testTrue(approximateDerivative2.distance(plane1.vectorV) < derivative2Tolerance * (1 + plane1.vectorV.magnitude()), - "approximate 2nd derivative", plane1.vectorV, approximateDerivative2, curve, fraction); - ck.testTrue(approximateDerivative2.distance(plane1.vectorV) < derivative2Tolerance * (1 + plane1.vectorV.magnitude()), - "approximate 2nd derivative", plane1.vectorV, approximateDerivative2, curve, fraction); + // if (!ck.testTrue(approximateDerivative2.distance(plane1.vectorV) < derivative2Tolerance * (1 + plane1.vectorV.magnitude()))) + // curve.fractionToPointAnd2Derivatives(fraction); + const radians = approximateDerivative2.angleTo(plane1.vectorV).radians; + if (!ck.testLE(radians, 0.001)) + curve.fractionToPointAnd2Derivatives(fraction); + if (!ck.testTrue(approximateDerivative2.distance(plane1.vectorV) < derivative2Tolerance * (1 + plane1.vectorV.magnitude()))) { + const magU = plane1.vectorU.magnitude(); + const magV = plane1.vectorV.magnitude(); + const magV2 = approximateDerivative2.magnitude(); + const ratio = magV / magV2; + console.log(" (magU " + magU + ") (magV " + magV + ") (magV2 " + magV2 + ") (magV/magV2 " + ratio + ") (L " + curve.curveLength() + ") (radians " + radians + ")"); + curve.fractionToPointAnd2Derivatives(fraction); + } } } } @@ -193,7 +246,8 @@ class ExerciseCurve { if (detail.curve === curve) { if (!ck.testCoordinate(fractionA, detail.fraction, "fraction round trip") || !ck.testPoint3d(pointA, detail.point, "round trip point")) { - detail = curve.closestPoint(pointA, false); + const pointB = curve.fractionToPoint(fractionA); + detail = curve.closestPoint(pointB, false); } else { // The search tunneled into a contained curve. Only verify the point. if (!ck.testPoint3d(pointA, detail.point, "round trip point") @@ -236,39 +290,67 @@ class ExerciseCurve { } public static RunTest(ck: Checker) { - const segment = LineSegment3d.create(Point3d.create(1, 2, 3), Point3d.create(4, 5, 10)); - ExerciseCurve.exerciseFractionToPoint(ck, segment, true, true); - ExerciseCurve.exerciseStroke(ck, segment); - ExerciseCurve.exerciseClosestPoint(ck, segment, 0.1); - ExerciseCurve.exerciseCloneAndTransform(ck, segment); - - const arc = Arc3d.create(Point3d.create(1, 2, 3), - Vector3d.create(2, 0, 0), - Vector3d.create(0, 2, 0), - AngleSweep.createStartEndDegrees(0, 180)); - if (arc) { - ExerciseCurve.exerciseFractionToPoint(ck, arc, false, true); - ExerciseCurve.exerciseClosestPoint(ck, arc, 0.1); - ExerciseCurve.exerciseStroke(ck, arc); - ExerciseCurve.exerciseCloneAndTransform(ck, arc); + { + const segment = LineSegment3d.create(Point3d.create(1, 2, 3), Point3d.create(4, 5, 10)); + ExerciseCurve.exerciseFractionToPoint(ck, segment, true, true); + ExerciseCurve.exerciseMoveSignedDistance(ck, segment); + ExerciseCurve.exerciseStroke(ck, segment); + ExerciseCurve.exerciseClosestPoint(ck, segment, 0.1); + ExerciseCurve.exerciseCloneAndTransform(ck, segment); + } + { // a circular arc . . . + const arc = Arc3d.create(Point3d.create(1, 2, 3), + Vector3d.create(2, 0, 0), + Vector3d.create(0, 2, 0), + AngleSweep.createStartEndDegrees(0, 180)); + if (arc) { + ExerciseCurve.exerciseFractionToPoint(ck, arc, false, true); + ExerciseCurve.exerciseMoveSignedDistance(ck, arc); + ExerciseCurve.exerciseClosestPoint(ck, arc, 0.1); + ExerciseCurve.exerciseStroke(ck, arc); + ExerciseCurve.exerciseCloneAndTransform(ck, arc); + } } - let linestring = LineString3d.createPoints([ - Point3d.create(0, 0, 0), - Point3d.create(1, 0, 0), - Point3d.create(1, 1, 0)]); - ExerciseCurve.exerciseFractionToPoint(ck, linestring, false, false); - ExerciseCurve.exerciseStroke(ck, linestring); - ExerciseCurve.exerciseCloneAndTransform(ck, linestring); - - linestring = LineString3d.create( - Point3d.create(0, 0, 0), - Point3d.create(1, 0, 0), - Point3d.create(1, 1, 0)); - ExerciseCurve.exerciseFractionToPoint(ck, linestring, false, false); - ExerciseCurve.exerciseCloneAndTransform(ck, linestring); - - linestring = LineString3d.create(); + { // a non-circular arc . . . (much harder computations!!) + const arc = Arc3d.create(Point3d.create(1, 2, 3), + Vector3d.create(3, 0, 0), + Vector3d.create(0, 2, 0), + AngleSweep.createStartEndDegrees(0, 180)); + if (arc) { + ExerciseCurve.exerciseFractionToPoint(ck, arc, false, false); + ExerciseCurve.exerciseMoveSignedDistance(ck, arc); + ExerciseCurve.exerciseClosestPoint(ck, arc, 0.1); + ExerciseCurve.exerciseStroke(ck, arc); + ExerciseCurve.exerciseCloneAndTransform(ck, arc); + } + } + + { + const linestring = LineString3d.createPoints([ + Point3d.create(0, 0, 0), + Point3d.create(1, 0, 0), + Point3d.create(1, 1, 0)]); + ExerciseCurve.exerciseFractionToPoint(ck, linestring, false, false); + ExerciseCurve.exerciseMoveSignedDistance(ck, linestring); + ExerciseCurve.exerciseStroke(ck, linestring); + ExerciseCurve.exerciseCloneAndTransform(ck, linestring); + } + { + const linestring = LineString3d.create( + Point3d.create(0, 0, 0), + Point3d.create(1, 0, 0), + Point3d.create(2, 1, 0)); + ExerciseCurve.exerciseFractionToPoint(ck, linestring, false, false); + ExerciseCurve.exerciseMoveSignedDistance(ck, linestring); + ExerciseCurve.exerciseStroke(ck, linestring); + ExerciseCurve.exerciseCloneAndTransform(ck, linestring); + } + + { + const linestring = LineString3d.create(); + ck.testExactNumber(0, linestring.points.length); + } const bcurve = BSplineCurve3d.createUniformKnots( [Point3d.create(0, 0, 0), Point3d.create(5, 0, 0), Point3d.create(10, 4, 0)], 3); @@ -299,6 +381,7 @@ class ExerciseCurve { if (ck.testPointer(bcurveH) && bcurveH) { ExerciseCurve.exerciseFractionToPoint(ck, bcurveH, false, false); ExerciseCurve.exerciseStroke(ck, bcurveH); + ExerciseCurve.exerciseMoveSignedDistance(ck, bcurveH); ExerciseCurve.exerciseClosestPoint(ck, bcurveH, 0.1); ExerciseCurve.exerciseClosestPoint(ck, bcurveH, 0.48); ExerciseCurve.exerciseClosestPoint(ck, bcurveH, 0.82); @@ -308,11 +391,13 @@ class ExerciseCurve { const bezierCurve0 = BezierCurve3d.create([ Point2d.create(0, 0), Point2d.create(0.5, 0.0), Point2d.create(1, 1)])!; ExerciseCurve.exerciseFractionToPoint(ck, bezierCurve0, false, false); + ExerciseCurve.exerciseMoveSignedDistance(ck, bezierCurve0); ExerciseCurve.exerciseStroke(ck, bezierCurve0); ExerciseCurve.exerciseClosestPoint(ck, bezierCurve0, 0.1); const bezierCurve = BezierCurve3dH.create([ Point2d.create(0, 0), Point2d.create(0.5, 0.0), Point2d.create(1, 1)])!; + ExerciseCurve.exerciseMoveSignedDistance(ck, bezierCurve); ExerciseCurve.exerciseFractionToPoint(ck, bezierCurve, false, false); ExerciseCurve.exerciseStroke(ck, bezierCurve); ExerciseCurve.exerciseClosestPoint(ck, bezierCurve, 0.1); @@ -320,6 +405,7 @@ class ExerciseCurve { const bezierCurve3d = BezierCurve3dH.create([ Point3d.create(0, 0), Point3d.create(0.5, 0.0), Point3d.create(1, 1), Point3d.create(2, 1, 1)])!; ExerciseCurve.exerciseFractionToPoint(ck, bezierCurve, false, false); + ExerciseCurve.exerciseMoveSignedDistance(ck, bezierCurve3d); ExerciseCurve.exerciseStroke(ck, bezierCurve3d); ExerciseCurve.exerciseClosestPoint(ck, bezierCurve3d, 0.1); @@ -335,13 +421,12 @@ class ExerciseCurve { ExerciseCurve.exerciseClosestPoint(ck, spiral, 0.3); } } - ck.testExactNumber(0, linestring.points.length); } } -describe("CurvePrimitive.Evaluations", () => { - it("Create and exercise curves", () => { +describe("CurveChainWithDistanceIndex", () => { + it("Exercise", () => { const ck = new Checker(); ExerciseCurve.RunTest(ck); ck.checkpoint("End CurvePrimitive.Evaluations"); @@ -360,6 +445,7 @@ describe("CurvePrimitive.Evaluations", () => { ExerciseCurve.exerciseStroke(ck, p); ExerciseCurve.exerciseClosestPoint(ck, p, 0.1); ExerciseCurve.exerciseCloneAndTransform(ck, p); + ExerciseCurve.exerciseMoveSignedDistance(ck, p); } ck.checkpoint("CurvePrimitive.Create and exercise distanceIndex"); diff --git a/core/geometry/src/test/CurveLocationDetail.test.ts b/core/geometry/src/test/CurveLocationDetail.test.ts index 0e7772f..b4c8561 100644 --- a/core/geometry/src/test/CurveLocationDetail.test.ts +++ b/core/geometry/src/test/CurveLocationDetail.test.ts @@ -19,11 +19,17 @@ describe("CurveLocationDetail", () => { const detailA0 = CurveLocationDetail.createCurveFractionPoint(segmentA, f0, segmentA.fractionToPoint(f0)); const detailA1 = CurveLocationDetail.createCurveFractionPoint(segmentA, f1, segmentA.fractionToPoint(f1)); detailA0.setCurve(segmentA); - detailA0.setCurve(segmentA); + detailA1.setCurve(segmentA); ck.testTrue(detailA0.isIsolated); const pairA = CurveLocationDetailPair.createDetailRef(detailA0, detailA1); const pairAClone = pairA.clone(); ck.testPointer(pairAClone); + + const detailB0 = detailA0.clone(); + detailA0.fraction += 0.5; + const detailB1 = detailB0.clone(detailB0); // nothing happens, but a return gets reached. + ck.testPointer(detailB0, detailB1); + ck.checkpoint("CurveLocationDetail.HelloWorld"); expect(ck.getNumErrors()).equals(0); }); diff --git a/core/geometry/src/test/GeometryCoreTestIO.ts b/core/geometry/src/test/GeometryCoreTestIO.ts index 3225ad9..33658bd 100644 --- a/core/geometry/src/test/GeometryCoreTestIO.ts +++ b/core/geometry/src/test/GeometryCoreTestIO.ts @@ -7,6 +7,8 @@ import { prettyPrint } from "./testFunctions"; import { Geometry } from "../Geometry"; import * as fs from "fs"; import { IModelJson } from "../serialization/IModelJsonSchema"; +import { Arc3d } from "../curve/Arc3d"; +import { Point3d } from "../geometry3d/Point3dVector3d"; // Methods (called from other files in the test suite) for doing I/O of tests files. export class GeometryCoreTestIO { public static outputRootDirectory = "./src/test/output"; @@ -28,4 +30,25 @@ export class GeometryCoreTestIO { collection.push(newGeometry); } } + /** + * Create a circle (or many circles) given center and radius. Save the arcs in collection, shifted by [dx,dy,dz] + * @param collection growing array of geometry + * @param center single or multiple center point data + * @param radius radius of circles + * @param dx x shift + * @param dy y shift + * @param dz z shift + */ + public static createAndCaptureXYCircle(collection: GeometryQuery[], center: Point3d | Point3d[], radius: number, dx: number = 0, dy: number = 0, dz: number = 0) { + if (Array.isArray(center)) { + for (const c of center) + this.createAndCaptureXYCircle(collection, c, radius, dx, dy, dz); + return; + } + if (!Geometry.isSameCoordinate(0, radius)) { + const newGeometry = Arc3d.createXY(center, radius); + newGeometry.tryTranslateInPlace(dx, dy, dz); + collection.push(newGeometry); + } + } } diff --git a/core/geometry/src/test/GrowableArray.test.ts b/core/geometry/src/test/GrowableArray.test.ts index e954e90..186c8d1 100644 --- a/core/geometry/src/test/GrowableArray.test.ts +++ b/core/geometry/src/test/GrowableArray.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Checker } from "./Checker"; import { expect } from "chai"; -import { GrowableFloat64Array, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { ClusterableArray } from "../numerics/ClusterableArray"; import { prettyPrint } from "./testFunctions"; import { PolyfaceQuery } from "../polyface/PolyfaceQuery"; @@ -16,6 +17,7 @@ import { Transform } from "../geometry3d/Transform"; import { Matrix3d } from "../geometry3d/Matrix3d"; import { Sample } from "../serialization/GeometrySamples"; import { Angle } from "../geometry3d/Angle"; +import { GrowableBlockedArray } from "../geometry3d/GrowableBlockedArray"; /* tslint:disable: no-console */ @@ -44,8 +46,8 @@ describe("GrowableFloat64Array.HelloWorld", () => { const capacityB = 100; const lB = arr.length; arr.ensureCapacity(capacityB); - ck.testLE (capacityB, arr.capacity(), "adequate ensure capacity"); - ck.testExactNumber (lB, arr.length, "length after expanding capacity"); + ck.testLE(capacityB, arr.capacity(), "adequate ensure capacity"); + ck.testExactNumber(lB, arr.length, "length after expanding capacity"); ck.checkpoint("GrowableArray.float64"); expect(ck.getNumErrors()).equals(0); }); @@ -61,6 +63,45 @@ describe("GrowableFloat64Array.HelloWorld", () => { expect(ck.getNumErrors()).equals(0); }); + it("move", () => { + const ck = new Checker(); + const arr = new GrowableFloat64Array(); + arr.ensureCapacity(16); + const data = Sample.createGrowableArrayCountedSteps(0, 1, 101); + const numRemaining = 11; + data.restrictToInterval(9.5, 9.5 + numRemaining); + ck.testExactNumber(numRemaining, data.length, "restrictToInterval"); + const data0 = data.clone(); + const data1 = data.clone(true); + ck.testExactNumber(data0.length, data1.length); + ck.testExactNumber(data.length, data0.capacity()); + ck.testExactNumber(data.capacity(), data1.capacity()); + + const n = data.length; + // tedious reverse to use methods ... + for (let i = 0, j = n - 1; i < j; i++ , j--) { + // swap odd i by logic at this level. others swap with single method call .swap + if ((i % 2) === 1) { + const a = data.at(i); + data.move(j, i); + data.setAt(j, a); + } else { + data.swap(i, j); + } + } + for (let i = 0; i < n; i++) + ck.testExactNumber(data0.at(i), data.at(n - 1 - i)); + // block copy a subset to the end .... + const numCopy = n - 4; + const c0 = 2; + data.pushBlockCopy(2, numCopy); + for (let i = 0; i < numCopy; i++) + ck.testExactNumber(data.at(c0 + i), data.at(n + i)); + + ck.checkpoint("GrowableArray.move"); + expect(ck.getNumErrors()).equals(0); + }); + }); describe("BlockedArray", () => { @@ -177,6 +218,36 @@ describe("BlockedArray", () => { ck.checkpoint("GrowableArray.annotateClusters"); expect(ck.getNumErrors()).equals(0); }); + it("HelloWorld", () => { + // REMARK: GrowableBlockedArray gets significant testing via GrowableXYZArray. + const ck = new Checker(); + const numPerBlock = 5; + const numInitialBlocks = 7; + const data0 = new GrowableBlockedArray(numPerBlock, numInitialBlocks); + ck.testExactNumber(numPerBlock, data0.numPerBlock); + ck.testExactNumber(0, data0.numBlocks); + const blockStep = 10; + const baseBlock = [1, 3, 5, 7, 9]; // all less than blockStep to simplify testing. + const numAdd = 9; + for (let i = 0; i < numAdd; i++) { + const newBlock = []; + const blockBaseValue = blockStep * data0.numBlocks; + for (const a of baseBlock) newBlock.push(a + blockBaseValue); + if (i === 3 || i === 7) + newBlock.push(999); // an extraneous value to test handling of oversize inputs + data0.addBlock(newBlock); + } + + for (let blockIndex = 0; blockIndex < numAdd; blockIndex++) { + for (let j = 0; j < numPerBlock; j++) { + ck.testExactNumber(baseBlock[j] + blockIndex * blockStep, data0.component(blockIndex, j)); + } + } + ck.testExactNumber(numAdd, data0.numBlocks); + ck.checkpoint("GrowableBlockedArray.HelloWorld"); + expect(ck.getNumErrors()).equals(0); + }); + }); describe("GrowablePoint3dArray", () => { @@ -371,4 +442,152 @@ describe("GrowablePoint3dArray", () => { expect(ck.getNumErrors()).equals(0); }); + it("resizeAndBoundsChecks", () => { + const ck = new Checker(); + const points = Sample.createFractalDiamonConvexPattern(1, -0.5); + + const xyzPoints = new GrowableXYZArray(points.length); // just enough so we know the initial capacity. + for (const p of points) + xyzPoints.push(p); + + ck.testTrue(GrowableXYZArray.isAlmostEqual(xyzPoints, xyzPoints), "isAlmostEqual duplicate pair"); + ck.testTrue(GrowableXYZArray.isAlmostEqual(undefined, undefined), "isAlmostEqual undfined pair"); + + ck.testFalse(GrowableXYZArray.isAlmostEqual(undefined, xyzPoints), "isAlmostEqual one undefined"); + ck.testFalse(GrowableXYZArray.isAlmostEqual(xyzPoints, undefined), "isAlmostEqual one undefined"); + + const n0 = xyzPoints.length; + ck.testExactNumber(n0, points.length); + const deltaN = 5; + const n1 = n0 + deltaN; + xyzPoints.resize(n1); + ck.testExactNumber(n1, xyzPoints.length); + + const n2 = n0 - deltaN; + xyzPoints.resize(n2); // blow away some points. + + ck.testUndefined(xyzPoints.atVector3dIndex(-4)); + ck.testUndefined(xyzPoints.atVector3dIndex(n2)); + + // verify duplicate methods .... + for (let i0 = 3; i0 < n2; i0 += 5) { + for (let i1 = 0; i1 < n2; i1 += 3) { + const vectorA = points[i0].vectorTo(points[i1]); + const vectorB = xyzPoints.vectorIndexIndex(i0, i1); + if (vectorB) + ck.testVector3d(vectorA, vectorB); + else + ck.announceError("vectorIndexIndex?", i0, i1, vectorA, vectorB); + } + } + + const spacePoint = Point3d.create(1, 4, 3); + for (let i0 = 2; i0 < n2; i0 += 6) { + const distance0 = xyzPoints.distanceIndexToPoint(i0, spacePoint); + const distance1 = xyzPoints.atPoint3dIndex(i0)!.distance(spacePoint); + const vectorI0 = xyzPoints.vectorXYAndZIndex(spacePoint, i0); + if (ck.testPointer(vectorI0) && vectorI0 && distance0 !== undefined) { + ck.testCoordinate(vectorI0.magnitude(), distance0)!; + ck.testCoordinate(distance0, distance1); + } + } + + ck.testUndefined(xyzPoints.distance(-1, 0), "distance to invalid indexA"); + ck.testUndefined(xyzPoints.distance(0, -1), "distance to invalid indexB"); + ck.testUndefined(xyzPoints.distanceIndexToPoint(-1, spacePoint), "distance to invalid indexA"); + + ck.testFalse(xyzPoints.setCoordinates(-5, 1, 2, 3), "negative index for setCoordinates"); + ck.testFalse(xyzPoints.setCoordinates(100, 1, 2, 3), "huge index for setCoordinates"); + + ck.testFalse(xyzPoints.setAt(-5, spacePoint), "negative index for setAt"); + ck.testFalse(xyzPoints.setAt(100, spacePoint), "huge index for setAt"); + ck.testUndefined(xyzPoints.vectorXYAndZIndex(spacePoint, -5), "negative index for vectorXYAndZIndex"); + + expect(ck.getNumErrors()).equals(0); + }); + + it("transferAndSet", () => { + const ck = new Checker(); + const points = Sample.createFractalDiamonConvexPattern(1, -0.5); + + const array0 = new GrowableXYZArray(points.length); // just enough so we know the initial capacity. + for (const p of points) + array0.push(p); + const n0 = array0.length; + + const array1 = new GrowableXYZArray(); + // transfers with bad source index + ck.testFalse(array1.pushFromGrowableXYZArray(array0, -1), "invalide source index for pushFromGrowable"); + ck.testFalse(array1.pushFromGrowableXYZArray(array0, n0 + 1), "invalide source index for pushFromGrowable"); + // Any trasnfer into empty array is bad . .. + ck.testFalse(array1.transferFromGrowableXYZArray(-1, array0, 1), "invalid source index transferFromGrowable"); + ck.testFalse(array1.transferFromGrowableXYZArray(0, array0, 1), "invalid source index transferFromGrowable"); + ck.testFalse(array1.transferFromGrowableXYZArray(100, array0, 1), "invalid source index transferFromGrowable"); + + ck.testUndefined(array1.crossProductIndexIndexIndex(-1, 0, 1), "bad index0 for cross prodcut"); + ck.testUndefined(array1.crossProductIndexIndexIndex(0, 100, 1), "bad index1 for cross prodcut"); + ck.testUndefined(array1.crossProductIndexIndexIndex(0, 1, 100), "bad index2 for cross prodcut"); + const spacePoint = Point3d.create(1, 2, 3); + ck.testUndefined(array1.crossProductXYAndZIndexIndex(spacePoint, -1, 0), "bad indexA for cross product"); + ck.testUndefined(array1.crossProductXYAndZIndexIndex(spacePoint, 0, -1), "bad indexB for cross product"); + + const resultA = Point3d.create(); + const interpolationFraction = 0.321; + for (let k = 1; k + 2 < n0; k++) { + ck.testTrue(array1.pushFromGrowableXYZArray(array0, k), "transformFromGrowable"); + + ck.testUndefined(array1.interpolate(-1, 0.3, k), "interpolate with bad index"); + ck.testUndefined(array1.interpolate(100, 0.3, k), "interpolate with bad index"); + ck.testUndefined(array1.vectorIndexIndex(-1, k), "invalid index vectorIndexIndex"); + ck.testUndefined(array1.vectorIndexIndex(k, -1), "invalid index vectorIndexIndex"); + + ck.testUndefined(array1.interpolate(k, 0.3, n0 + 1), "interpolate with bad index"); + ck.testUndefined(array1.interpolate(k, 0.3, n0 + 3), "interpolate with bad index"); + const k1 = (2 * k) % n0; // this should be a valid index !!! + if (ck.testTrue(array0.isIndexValid(k1) + && ck.testPointer(array0.interpolate(k, interpolationFraction, k1, resultA)))) { + const k2 = (2 * k + 1) % n0; + + const point0 = array0.getPoint3dAt(k); + const point1 = array0.getPoint3dAt(k1); + const resultB = point0.interpolate(interpolationFraction, point1); + ck.testPoint3d(resultA, resultB, "compare interpolation paths"); + const crossA = array0.crossProductIndexIndexIndex(k, k1, k2); + const crossB = array0.crossProductXYAndZIndexIndex(point0, k1, k2); + if (ck.testPointer(crossA) && crossA && ck.testPointer(crossB) && crossB) { + ck.testVector3d(crossA, crossB, "cross products to indexed points"); + } + } + } + // bad transfers when the dest is not empty . . . + ck.testFalse(array1.transferFromGrowableXYZArray(-1, array0, 1), "invalid source index transferFromGrowable"); + ck.testFalse(array1.transferFromGrowableXYZArray(100, array0, 1), "invalid source index transferFromGrowable"); + + expect(ck.getNumErrors()).equals(0); + }); + it("Compress", () => { + const ck = new Checker(); + const data = new GrowableFloat64Array(); + data.compressAdjcentDuplicates(); // nothing happens on empty array. + const n0 = 22; + for (let i = 0; i < n0; i++) { + const c = Math.cos(i * i); + let n = 1; + if (c < -0.6) + n = 3; + else if (c > 0.1) { + if (c < 0.8) + n = 2; + else + n = 4; + } + for (let k = 0; k < n; k++) + data.push(i); + } + const n1 = data.length; + data.compressAdjcentDuplicates(0.0001); + ck.testExactNumber(n0, data.length, "compressed array big length", n1); + expect(ck.getNumErrors()).equals(0); + }); + }); diff --git a/core/geometry/src/test/LineSegment3d.test.ts b/core/geometry/src/test/LineSegment3d.test.ts index 634dfb5..6f6c364 100644 --- a/core/geometry/src/test/LineSegment3d.test.ts +++ b/core/geometry/src/test/LineSegment3d.test.ts @@ -9,6 +9,8 @@ import { Matrix3d } from "../geometry3d/Matrix3d"; import { LineSegment3d } from "../curve/LineSegment3d"; import { Checker } from "./Checker"; import { expect } from "chai"; +import { CoordinateXYZ } from "../curve/CoordinateXYZ"; +import { Geometry } from "../Geometry"; function exerciseLineSegment3d(ck: Checker, segmentA: LineSegment3d) { const a = 4.2; @@ -36,14 +38,25 @@ function exerciseLineSegment3d(ck: Checker, segmentA: LineSegment3d) { ck.testTrue(segmentB.isAlmostEqual(segmentD), "shared pointers are transformed together"); ck.testCoordinate(segmentB.curveLength(), a * segmentA.curveLength()); // we expect quickLength to match curveLength ... - ck.testCoordinate (segmentA.quickLength (), segmentA.curveLength (), "LineSegment quickLength is true curveLength"); - - segmentC.setFrom (segmentA); - segmentC.reverseInPlace (); - const xyz0 = Point3d.create (); - const xyz1 = Point3d.create (); - ck.testPoint3d (segmentC.startPoint (xyz0), segmentA.endPoint (xyz1)); - ck.testPoint3d (segmentC.endPoint (xyz0), segmentA.startPoint (xyz1)); + ck.testCoordinate(segmentA.quickLength(), segmentA.curveLength(), "LineSegment quickLength is true curveLength"); + + segmentC.setFrom(segmentA); + segmentC.reverseInPlace(); + const xyz0 = Point3d.create(); + const xyz1 = Point3d.create(); + ck.testPoint3d(segmentC.startPoint(xyz0), segmentA.endPoint(xyz1)); + ck.testPoint3d(segmentC.endPoint(xyz0), segmentA.startPoint(xyz1)); + + for (const f of [-1, 0, 0.5, 1, 1.5]) { + const spacePoint = segmentA.fractionToPoint(f); + const detailT = segmentA.closestPoint(spacePoint, true); + const detailF = segmentA.closestPoint(spacePoint, false); + if (Geometry.isIn01(f)) { + ck.testTrue(Geometry.isSameCoordinate(detailT.fraction, detailF.fraction)); + } else { + ck.testFalse(Geometry.isSameCoordinate(detailT.fraction, detailF.fraction)); + } + } } describe("LineSegment3d", () => { it("HelloWorld", () => { @@ -51,6 +64,25 @@ describe("LineSegment3d", () => { const segmentA = LineSegment3d.createXYXY(1, 2, 6, 3, 1); exerciseLineSegment3d(ck, segmentA); + + const segmentB = LineSegment3d.fromJSON({ startPoint: [1, 2, 3], endPoint: [4, 2, -1] }); + const segmentC = LineSegment3d.fromJSON(false); + ck.testFalse(segmentB.isAlmostEqual(segmentC)); + ck.testPointer(segmentB, "LineSegment3d.fromJSON"); + const coordinate = CoordinateXYZ.create(segmentB.startPoint()); + ck.testFalse(segmentB.isAlmostEqual(coordinate)); + ck.testFalse(coordinate.isAlmostEqual(segmentB)); + + const segmentD = LineSegment3d.createXYZXYZ(1, 2, 3, 4, 5, 6); + const segmentE = LineSegment3d.createXYZXYZ(1, 2, 3, 4, 5, 6, segmentC); // overwrite the default segmentC. + ck.testPointer (segmentE, segmentC, "reuse of optional arg"); + ck.testTrue (segmentD.isAlmostEqual (segmentE)); + + const segmentF = LineSegment3d.create (segmentA.endPoint(), segmentA.startPoint (), segmentD); // another optional + ck.testFalse (segmentF.isAlmostEqual (segmentA)); + segmentF.reverseInPlace (); + ck.testTrue (segmentF.isAlmostEqual (segmentA)); + ck.checkpoint("LineSegment3d.HelloWorld"); expect(ck.getNumErrors()).equals(0); }); diff --git a/core/geometry/src/test/LineString3d.test.ts b/core/geometry/src/test/LineString3d.test.ts index 79ebfde..6600e97 100644 --- a/core/geometry/src/test/LineString3d.test.ts +++ b/core/geometry/src/test/LineString3d.test.ts @@ -9,6 +9,10 @@ import { Matrix3d } from "../geometry3d/Matrix3d"; import { LineString3d } from "../curve/LineString3d"; import { Checker } from "./Checker"; import { expect } from "chai"; +import { ClipPlane } from "../clipping/ClipPlane"; +import { CurvePrimitive } from "../curve/CurvePrimitive"; +import { Point2d } from "../geometry3d/Point2dVector2d"; +/* tslint:disable:no-console */ function exerciseLineString3d(ck: Checker, lsA: LineString3d) { const expectValidResults = lsA.numPoints() > 1; @@ -66,4 +70,122 @@ describe("LineString3d", () => { ck.checkpoint("LineString3d.HelloWorld"); expect(ck.getNumErrors()).equals(0); }); + + it("createXY", () => { + const ck = new Checker(); + const ls = LineString3d.createXY( + [Point2d.create(1, 1), + Point2d.create(4, 1), + Point2d.create(4, 2), + Point2d.create(0, 2)], + 10.0); + ck.testExactNumber(4, ls.numPoints()); + expect(ck.getNumErrors()).equals(0); + }); + + it("RegularPolygon", () => { + const ck = new Checker(); + const center = Point3d.create(3, 2, 1); + const radius = 2.0; + const poly1 = LineString3d.createRegularPolygonXY(center, 2, radius, true); + const poly4 = LineString3d.createRegularPolygonXY(center, 4, radius, true); + const poly4F = LineString3d.createRegularPolygonXY(center, 4, radius, false); + ck.testUndefined(poly1.getIndexedSegment(5)); + ck.testFalse(poly4.isAlmostEqual(poly1)); + for (let i = 0; i < 4; i++) { + ck.testCoordinate(radius, center.distance(poly4.pointAt(i)!)); // TRUE poly has points on the radius + ck.testLE(radius, center.distance(poly4F.pointAt(i)!)); // FALSE poly has points outside the radius + // const segment = poly4.getIndexedSegment(i); + const segmentF = poly4F.getIndexedSegment(i)!; + const detail = segmentF.closestPoint(center, false); + ck.testCoordinate(0.5, detail.fraction); + ck.testCoordinate(radius, center.distance(detail.point)); + } + const data64 = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const polyF64 = LineString3d.createFloat64Array(data64); + ck.testExactNumber(3 * polyF64.numPoints(), data64.length); + expect(ck.getNumErrors()).equals(0); + }); + it("AnnounceClipIntervals", () => { + const ck = new Checker(); + const ls = LineString3d.create(Point3d.create(1, 1, 0), Point3d.create(4, 1, 0), Point3d.create(4, 2, 0), Point3d.create(0, 2, 0)); + const clipper = ClipPlane.createEdgeXY(Point3d.create(2, 0, 0), Point3d.create(0, 5, 0))!; + // The linestring starts in, goes out, and comes back. Verify 2 segments announced. + let numAnnounce = 0; + ls.announceClipIntervals(clipper, + (_a0: number, _a1: number, _cp: CurvePrimitive) => { + numAnnounce++; + }); + ck.testExactNumber(numAnnounce, 2); + expect(ck.getNumErrors()).equals(0); + }); + +}); +/** + * Class to act as an iterator over points in a linestring. + * * Internal data is: + * * pointer to the parent linestring + * * index of index of the next ponit to read. + * * the parent LineString class + */ +class IterableLineStringPoint3dIterator implements Iterator { + private _linestring: LineStringWithIterator; + private _nextReadIndex: number; + public constructor(linestring: LineStringWithIterator) { + this._linestring = linestring; + this._nextReadIndex = 0; + } + public next(): IteratorResult { + const point = this._linestring.pointAt(this._nextReadIndex++); + if (point) + return { done: false, value: point }; + return { done: true, value: undefined } as any as IteratorResult; + } + public [Symbol.iterator](): IterableIterator { return this; } +} +/** + * This is a linestring class which + * * Stores its point data in a Float64Array (NOT as individual Point3d objects) + * * has a `pointIterator ()` method which returns an iterator so that users can visit points with `for (const p of linestring.pointIterator()){}` + */ +class LineStringWithIterator { + private _data: Float64Array; + public constructor(points: Point3d[]) { + this._data = new Float64Array(3 * points.length); + let i = 0; + for (const p of points) { + this._data[i++] = p.x; + this._data[i++] = p.y; + this._data[i++] = p.z; + } + } + public [Symbol.iterator](): IterableIterator { return new IterableLineStringPoint3dIterator(this); } + /** + * access a point by index. The point coordinates are returned as a first class point object. + * @param index index of point to access + */ + public pointAt(index: number): Point3d | undefined { + const k = index * 3; + if (k < this._data.length) + return new Point3d(this._data[k], this._data[k + 1], this._data[k + 2]); + return undefined; + } +} +describe("LineStringIterator", () => { + + it("HelloWorld", () => { + const ck = new Checker(); + const allPoints: Point3d[] = [ + Point3d.create(0, 0, 0), + Point3d.create(0, 10, 0), + Point3d.create(10, 10, 0), + Point3d.create(10, 0, 0), + Point3d.create(20, 0, 0), + Point3d.create(20, 10, 0)]; + const ls = new LineStringWithIterator(allPoints); + for (const p of ls) { + console.log("for..of ", p.toJSON()); + } + expect(ck.getNumErrors()).equals(0); + }); }); diff --git a/core/geometry/src/test/Plane3dByOriginAndVectors.test.ts b/core/geometry/src/test/Plane3dByOriginAndVectors.test.ts index 5a32530..764a8aa 100644 --- a/core/geometry/src/test/Plane3dByOriginAndVectors.test.ts +++ b/core/geometry/src/test/Plane3dByOriginAndVectors.test.ts @@ -8,6 +8,7 @@ import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors"; import { Checker } from "./Checker"; import { expect } from "chai"; +import { Transform } from "../geometry3d/Transform"; describe("Plane3dByOriginAndVectors", () => { it("HelloWorld", () => { @@ -71,8 +72,36 @@ describe("Plane3dByOriginAndVectors", () => { new Float64Array([1, 1, 1, 0]), // weight 0 at origin fails !!! new Float64Array([2, 1, 3, 0]), new Float64Array([4, 9, 1, 1])); - ck.testTrue (errorPlane1.isAlmostEqual (Plane3dByOriginAndVectors.createXYPlane ())); + ck.testTrue(errorPlane1.isAlmostEqual(Plane3dByOriginAndVectors.createXYPlane())); ck.checkpoint("Plane3dByOriginAndVectors.HelloWorld"); expect(ck.getNumErrors()).equals(0); }); + it("CreateFromTransform", () => { + const ck = new Checker(); + const transform = Transform.createRowValues( + 20, 1, 2, 4, + 3, 10, 5, 9, + -1.5, 0.2, 30, 7); + const plane0 = Plane3dByOriginAndVectors.createXYPlane(); // to be reused. + const a = 1.5; + const b = 3.9; + const plane1 = Plane3dByOriginAndVectors.createFromTransformColumnsXYAndLengths(transform, a, b); + const plane2 = Plane3dByOriginAndVectors.createFromTransformColumnsXYAndLengths(transform, a, b, plane0); + ck.testTrue(plane2 === plane0, "reused plane expected"); + ck.testFalse(plane1 === plane0, "new plane expected"); + ck.testTrue(plane1.isAlmostEqual(plane2), "matching planes"); + ck.testTrue(plane1.vectorU.isParallelTo(transform.matrix.columnX(), false, false)); + ck.testCoordinate(a, plane1.vectorU.magnitude(), "vectorU magnitude"); + + ck.testTrue(plane1.vectorV.isParallelTo(transform.matrix.columnY(), false, false)); + ck.testCoordinate(b, plane1.vectorV.magnitude(), "vectorV magnitude"); + + ck.testPoint3d(plane1.origin, transform.getOrigin()); + + const plane3 = Plane3dByOriginAndVectors.createFromTransformColumnsXYAndLengths(transform, undefined, undefined); + ck.testVector3d(plane3.vectorU, transform.matrix.columnX()); + ck.testVector3d(plane3.vectorV, transform.matrix.columnY()); + expect(ck.getNumErrors()).equals(0); + + }); }); diff --git a/core/geometry/src/test/PointHelper.test.ts b/core/geometry/src/test/PointHelper.test.ts index 1081910..d2118cd 100644 --- a/core/geometry/src/test/PointHelper.test.ts +++ b/core/geometry/src/test/PointHelper.test.ts @@ -6,7 +6,7 @@ import { Geometry, AxisScaleSelect } from "../Geometry"; import { Angle } from "../geometry3d/Angle"; import { Plane3dByOriginAndUnitNormal } from "../geometry3d/Plane3dByOriginAndUnitNormal"; import { Matrix4d } from "../geometry4d/Matrix4d"; -import { Point2d } from "../geometry3d/Point2dVector2d"; +import { Point2d, Vector2d } from "../geometry3d/Point2dVector2d"; import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; import { Range3d } from "../geometry3d/Range"; import { Transform } from "../geometry3d/Transform"; @@ -14,14 +14,15 @@ import { Matrix3d } from "../geometry3d/Matrix3d"; import { LineString3d } from "../curve/LineString3d"; import { Arc3d } from "../curve/Arc3d"; import { StrokeOptions } from "../curve/StrokeOptions"; -import { PolygonOps, Point3dArray, Point2dArray, Vector3dArray, Point4dArray, NumberArray } from "../geometry3d/PointHelpers"; +import { PolygonOps, Point3dArray, Point2dArray, Vector3dArray, Point4dArray, NumberArray, Point3dArrayCarrier } from "../geometry3d/PointHelpers"; import { FrameBuilder } from "../geometry3d/FrameBuilder"; import { MatrixTests } from "./Point3dVector3d.test"; import { Checker } from "./Checker"; import { expect } from "chai"; import { Sample } from "../serialization/GeometrySamples"; import { MomentData } from "../geometry4d/MomentData"; -import { GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; +import { Point4d } from "../geometry4d/Point4d"; /* tslint:disable:no-console */ describe("FrameBuilder.HelloWorld", () => { @@ -277,9 +278,14 @@ describe("PolygonOps", () => { Point2d.create(0, 0), Point2d.create(10, 0), Point2d.create(10, ay), + Point2d.create(ax, ay), Point2d.create(ax, 8), Point2d.create(0, 8), Point2d.create(0, 0)]; + const q = 0.1; + const onEdge = Point2d.create(0, 1); + const tol = 1.0e-8; + ck.testExactNumber(0, PolygonOps.parityVectorTest(onEdge, 1.5, points, tol)!); const easyIn = Point2d.create(1, 1); const easyOut = Point2d.create(20, 20); const xHit = Point2d.create(2, ay); @@ -287,9 +293,19 @@ describe("PolygonOps", () => { const xyHit = Point2d.create(ax, ay); ck.testExactNumber(1, PolygonOps.parity(easyIn, points), " IN with no vertex hits"); ck.testExactNumber(-1, PolygonOps.parity(easyOut, points), "OUT with no vertex hits"); + ck.testExactNumber(-1, PolygonOps.parityXTest(Point2d.create(-1, 0.5), points, tol)!, "OUT by simple X"); + ck.testExactNumber(-1, PolygonOps.parityXTest(Point2d.create(20, 0.5), points, tol)!, "OUT by simple X"); + ck.testExactNumber(-1, PolygonOps.parityXTest(Point2d.create(1, -0.5), points, tol)!, "OUT by simple Y"); + ck.testExactNumber(-1, PolygonOps.parityXTest(Point2d.create(1, 14.5), points, tol)!, "OUT by simple Y"); + ck.testExactNumber(1, PolygonOps.parity(xHit, points), "IN with horizontal vertex hits"); ck.testExactNumber(1, PolygonOps.parity(yHit, points), "IN with vertical vertex hits"); - ck.testExactNumber(1, PolygonOps.parity(xyHit, points), "IN with xy vertex hits"); + ck.testExactNumber(0, PolygonOps.parity(xyHit, points), "ON with xy vertex hits"); + ck.testExactNumber(-1, PolygonOps.parityVectorTest(easyOut, 1.5, points, tol)!); + // This should have 4 crossings + ck.testExactNumber(-1, PolygonOps.parityVectorTest(Point2d.create(ax + q, ay + q), Math.atan(-1.0), points, tol)!); + + ck.testExactNumber(0, PolygonOps.testXYPolygonTurningDirections([])); ck.checkpoint("FrameBuilder"); expect(ck.getNumErrors()).equals(0); }); @@ -305,11 +321,16 @@ describe("Point3dArray", () => { const frame = FrameBuilder.createFrameToDistantPoints(pointsB); const noFrame = FrameBuilder.createFrameToDistantPoints([Point3d.create(0, 0, 0)]); ck.testUndefined(noFrame, "Expect undefined frame from 1 point"); + const spacePoint = Point3d.create(3, 2, 5); + const spaceVector = Vector3d.create(-1, 2, 4); + const resultVector = Vector3d.create(); + ck.testUndefined(Point3dArray.indexOfMostDistantPoint([], spacePoint, resultVector)); + ck.testUndefined(Point3dArray.indexOfPointWithMaxCrossProductMagnitude([], spacePoint, spaceVector, resultVector)); if (ck.testPointer(frame, "frame to points") && frame) { const origin = frame.origin; const longVector = Vector3d.create(); - Point3dArray.vectorToMostDistantPoint(pointsB, origin, longVector); + Point3dArray.indexOfMostDistantPoint(pointsB, origin, longVector); // We expect the frame encloses sll points with uv coordinates in [-1,1] const range = Range3d.createInverseTransformedArray(frame, pointsB); @@ -379,6 +400,13 @@ describe("Point3dArray", () => { const xyzw = Point4dArray.packPointsAndWeightsToFloat64Array(pointsA, weights); ck.testExactNumber(4.0 * weights.length, xyzw.length, "Point4dArray.packToFloat64Array length"); const point4dB = Point4dArray.unpackToPoint4dArray(xyzw); + + const point4dC = Point4dArray.unpackToPoint4dArray(xyzw); + point4dC.pop(); + ck.testFalse(Point4dArray.isAlmostEqual(point4dB, point4dC)); + ck.testTrue(Point4dArray.isAlmostEqual(point4dB, point4dB)); + ck.testTrue(Point4dArray.isAlmostEqual(undefined, undefined)); + const xyzwB = Point4dArray.packToFloat64Array(point4dB); ck.testTrue(NumberArray.isExactEqual(xyzw, xyzwB), "packed point4d variants"); xyzwB[3] += 1.0; @@ -389,6 +417,248 @@ describe("Point3dArray", () => { Point4dArray.unpackFloat64ArrayToPointsAndWeights(xyzw, pointsB, weightB); ck.testTrue(Point3dArray.isAlmostEqual(pointsA, pointsB), "point3d from point4d trips"); ck.testTrue(NumberArray.isExactEqual(weights, weightB), "weights from point4d trips"); + + const point4dBChanged = point4dB!.map((x: Point4d) => x.clone()); + point4dBChanged[1].x = 0.213213; + ck.testFalse(Point4dArray.isAlmostEqual(point4dB, point4dBChanged)); + expect(ck.getNumErrors()).equals(0); + }); + + it("Point4dArrayPlane", () => { + const ck = new Checker(); + const center = Point4d.create(1, 2, 3, 1); + const vector0 = Point4d.create(0, 3, 4, 6); + const vector90 = Point4d.create(4, 2, -3, 2); + const allPoints = []; + const plane4d = Point4d.perpendicularPoint4dPlane(center, vector0, vector90); + // confirm plane contains all 3 of the basis points . . . + ck.testCoordinate(plane4d.dotProduct(center), 0); + ck.testCoordinate(plane4d.dotProduct(vector0), 0); + ck.testCoordinate(plane4d.dotProduct(vector90), 0); + for (const degrees of [0, 30, 69, 123]) { + const theta = Angle.createDegrees(degrees); + const c = theta.cos(); + const s = theta.sin(); + allPoints.push(center.plus2Scaled(vector0, c, vector90, s)); + } + const plane3d = plane4d.toPlane3dByOriginAndUnitNormal(); + if (ck.testPointer(plane3d) && plane3d) { + ck.testCoordinate(plane3d.altitudeXYZW(center.x, center.y, center.z, center.w), 0); + ck.testCoordinate(plane3d.altitudeXYZW(vector0.x, vector0.y, vector0.z, vector0.w), 0); + ck.testCoordinate(plane3d.altitudeXYZW(vector90.x, vector90.y, vector90.z, vector90.w), 0); + ck.testTrue(Point4dArray.isCloseToPlane(allPoints, plane3d)); + // throw on another point sure to be off plane: + allPoints.push(center.plusScaled(plane4d, 0.1)); + ck.testFalse(Point4dArray.isCloseToPlane(allPoints, plane3d)); + } + expect(ck.getNumErrors()).equals(0); + }); + + it("ParityTests", () => { + const ck = new Checker(); + const a = 5; + const b = 2; + const polygon = [ + Point2d.create(0, 0), + Point2d.create(8, 0), + Point2d.create(8, b), + Point2d.create(a, b), + Point2d.create(a, 6), + Point2d.create(0, 6)]; + const tol = 0.01; + const theta = Angle.createDegrees(45); + for (const p of polygon) { + ck.testUndefined(PolygonOps.parityYTest(p, polygon, tol)); + ck.testUndefined(PolygonOps.parityXTest(p, polygon, tol)); + ck.testUndefined(PolygonOps.parityVectorTest(p, theta.radians, polygon, tol)); + ck.testExactNumber(0, PolygonOps.parity(p, polygon, tol)); + } + ck.testUndefined(PolygonOps.parityYTest(Point2d.create(1, b), polygon, tol)); + ck.testUndefined(PolygonOps.parityXTest(Point2d.create(a, 1), polygon, tol)); + + const pointA = Point2d.create(1, 2); + const pointB = Point2d.create(1.5, 0.2); + ck.testExactNumber(0, PolygonOps.parity(pointA, [pointA])); + ck.testExactNumber(-1, PolygonOps.parity(pointA, [pointB])); + const radiansQ = 0.276234342921378; + const vectorQ = Vector2d.create(Math.cos(radiansQ), Math.sin(radiansQ)); + const pointQ = polygon[3].plusScaled(vectorQ, -0.2); + // make a polygon with exact pointQ hits for x,y, and the ("secret") special angle for secondary testing + const polygonQ = [ + polygon[0], + Point2d.create(pointQ.x, 0), + polygon[1], + Point2d.create(8, pointQ.y), + polygon[2], + polygon[3], + polygon[4], + polygon[5]]; + ck.testExactNumber(1, PolygonOps.parity(pointQ, polygonQ, tol)); + ck.testExactNumber(1, PolygonOps.parity(pointQ, polygonQ, tol)); + expect(ck.getNumErrors()).equals(0); + }); + + it("PolylineLength", () => { + const ck = new Checker(); + const packedPoints = [ + 0, 0, 0, + 2, 0, 0, + 2, 2, 0, + 2, 2, 2, + 0, 2, 2, + 0, 2, 0, + 0, 0, 0]; + + const packed64 = new Float64Array(packedPoints); + const points = Point3dArray.unpackNumbersToPoint3dArray(packed64); + + for (const addClosureEdge of [false, true]) { + const a0 = Point3dArray.sumEdgeLengths(points, addClosureEdge); + const a64 = Point3dArray.sumEdgeLengths(packed64, addClosureEdge); + const transform = Sample.createMessyRigidTransform(Point3d.create(2, 5, 9)); + transform.multiplyPoint3dArrayInPlace(points); + const a1 = Point3dArray.sumEdgeLengths(points, addClosureEdge); + ck.testCoordinate(a1, a0, "rigid transform does not change distances"); + ck.testExactNumber(a0, a64); + } + expect(ck.getNumErrors()).equals(0); + }); + + it("Point3dArrayCarrierBadIndex", () => { + const ck = new Checker(); + const carrier = new Point3dArrayCarrier([Point3d.create(1, 2, 3), Point3d.create(6, 2, 9), Point3d.create(6, 2, 0), Point3d.create(-4, 2, 8)]); + const a = carrier.length; + // These methdos should return undefined if any index is bad. + // (we know the index tests happen in a single validation function -- "some" calls need to test both extremes of out-of-bounds, but any pariticular arg only has to be tested in one direction) + ck.testUndefined(carrier.atPoint3dIndex(-1)); + ck.testUndefined(carrier.atPoint3dIndex(a)); + ck.testUndefined(carrier.atVector3dIndex(-1)); + ck.testUndefined(carrier.atVector3dIndex(a)); + + const cross = Vector3d.create(); + ck.testUndefined(carrier.accumulateCrossProductIndexIndexIndex(-1, 1, 3, cross)); + ck.testUndefined(carrier.accumulateCrossProductIndexIndexIndex(1, 21, 3, cross)); + ck.testUndefined(carrier.accumulateCrossProductIndexIndexIndex(1, 3, -1, cross)); + + const origin = Point3d.create(1, 4, 2); + + ck.testUndefined(carrier.crossProductIndexIndexIndex(-1, 1, 3, cross)); + ck.testUndefined(carrier.crossProductIndexIndexIndex(1, 21, 3, cross)); + ck.testUndefined(carrier.crossProductIndexIndexIndex(1, 3, -1, cross)); + + ck.testUndefined(carrier.crossProductXYAndZIndexIndex(origin, -1, 3, cross)); + ck.testUndefined(carrier.crossProductXYAndZIndexIndex(origin, 21, a, cross)); + + ck.testUndefined(carrier.vectorIndexIndex(-1, 3)); + ck.testUndefined(carrier.vectorIndexIndex(1, 30)); + ck.testUndefined(carrier.vectorXYAndZIndex(origin, -1)); + ck.testPointer(carrier.vectorXYAndZIndex(origin, 1)); + expect(ck.getNumErrors()).equals(0); + }); + + it("Point2dArray", () => { + const ck = new Checker(); + const pointsA = Sample.createFractalDiamonConvexPattern(1, -0.5); + const numA = pointsA.length; + const numB = Point2dArray.pointCountExcludingTrailingWraparound(pointsA); + ck.testExactNumber(0, Point2dArray.pointCountExcludingTrailingWraparound([])); + ck.testExactNumber(numA, numB + 1); + const centroid = Point2d.create(); + const pointsOnLine = []; + for (const xRight of [1, 2, 4, 6]) { + ck.testUndefined(PolygonOps.centroidAndAreaXY(pointsOnLine, centroid)); + pointsOnLine.push(Point2d.create(xRight, 0)); + } + + expect(ck.getNumErrors()).equals(0); + }); + + it("Vector3dArray", () => { + const ck = new Checker(); + const arrayA = []; + const arrayB = []; + for (const i of [1, 2, 4, 3]) { + const vector = Vector3d.create(i, i * i, i - 1); + arrayA.push(vector); + arrayB.push(vector.clone()); + } + ck.testTrue(Vector3dArray.isAlmostEqual(arrayA, arrayB)); + arrayB.pop(); + ck.testFalse(Vector3dArray.isAlmostEqual(arrayA, arrayB)); + expect(ck.getNumErrors()).equals(0); + }); + it("NumberArrayComparison", () => { + const ck = new Checker(); + const dataA0 = [1, 2, 3, 4, 5]; + const dataA1 = new Float64Array([1, 2, 3, 4, 5]); + const dataB0 = dataA0.map((x) => x); + dataB0.pop(); + ck.testTrue(NumberArray.isAlmostEqual(dataA0, dataA1, 0.1)); + ck.testFalse(NumberArray.isAlmostEqual(dataA0, dataB0, 0.01)); + ck.testFalse(NumberArray.isExactEqual(dataA0, dataB0)); + ck.testFalse(NumberArray.isAlmostEqual(dataA0, [], 0.01)); + ck.testFalse(NumberArray.isAlmostEqual([], dataA0, 0.01)); + ck.testFalse(NumberArray.isAlmostEqual(dataA0, undefined, 0.01)); + ck.testFalse(NumberArray.isAlmostEqual(undefined, dataA0, 0.01)); + ck.testExactNumber(0, NumberArray.PreciseSum([])); + const e = 0.01; + dataA1[3] += e; + ck.testTrue(NumberArray.isAlmostEqual(dataA0, dataA1, 2 * e)); + ck.testFalse(NumberArray.isAlmostEqual(dataA0, dataA1, 0.5 * e)); + ck.testFalse(NumberArray.isCoordinateInArray(1, [])); + for (const x of dataA0) { + ck.testTrue(NumberArray.isCoordinateInArray(x, dataA0)); + ck.testFalse(NumberArray.isCoordinateInArray(x + 0.1231897897, dataA0)); + } + ck.testExactNumber(0, NumberArray.MaxAbsArray([])); + expect(ck.getNumErrors()).equals(0); + }); + + it("CentroidBranches", () => { + const ck = new Checker(); + const pointsA = [Point3d.create(0, 0, 0), Point3d.create(1, 0, 0), Point3d.create(0, 4, 0)]; + const pointsC = pointsA.map((xyz: Point3d) => xyz.clone()); + pointsC.push(Point3d.create(0, 2, 0)); // more points, same normal and area!!! + pointsC.push(Point3d.create(0, 1, 0)); // more points, same normal and area!!! + + const pointsB = Point3dArray.clonePoint3dArray(pointsA); + pointsB.push(pointsB[0].clone()); // degenerate quad !!!! + + ck.testExactNumber(3, Point2dArray.pointCountExcludingTrailingWraparound(pointsA)); + // single point ... + const point0 = Point3d.create(1, 2, 3); + const point1 = Point3d.create(3, 2, 9); + ck.testExactNumber(1, Point2dArray.pointCountExcludingTrailingWraparound([point0, point0, point0, point0])); + ck.testExactNumber(3, Point2dArray.pointCountExcludingTrailingWraparound(pointsB)); + ck.testExactNumber(0.0, PolygonOps.sumTriangleAreas([])); + ck.testExactNumber(0.0, PolygonOps.sumTriangleAreas([point0])); + ck.testExactNumber(0.0, PolygonOps.sumTriangleAreas([point0, point1])); + const carrierA = new Point3dArrayCarrier(pointsA); + const carrierB = new Point3dArrayCarrier(pointsB); + const carrierC = new Point3dArrayCarrier(pointsC); + + const rayA = PolygonOps.centroidAreaNormal(pointsA)!; + const rayB = PolygonOps.centroidAreaNormal(pointsB)!; + + const unitA = Vector3d.create(); + const unitB = Vector3d.create(); + const unitC = Vector3d.create(); + + ck.testTrue(PolygonOps.unitNormal(carrierA, unitA)); + ck.testTrue(PolygonOps.unitNormal(carrierB, unitB)); + ck.testTrue(PolygonOps.unitNormal(carrierC, unitC)); + + ck.testVector3d(unitA, unitB); + ck.testVector3d(unitA, unitC); + + ck.testPoint3d(rayA.origin, rayB.origin); + ck.testVector3d(rayA.direction, rayB.direction); + // degenerate -- points on a line . . . + const pointsOnLine = []; + for (let i = 0; i < 6; i++) { + pointsOnLine.push(Point3d.create(i, i, i)); + ck.testUndefined(PolygonOps.centroidAreaNormal(pointsOnLine)); + } expect(ck.getNumErrors()).equals(0); }); }); diff --git a/core/geometry/src/test/Polyface.test.ts b/core/geometry/src/test/Polyface.test.ts index e3b903d..793658b 100644 --- a/core/geometry/src/test/Polyface.test.ts +++ b/core/geometry/src/test/Polyface.test.ts @@ -269,6 +269,8 @@ describe("Polyface.Box", () => { ck.testBoolean(true, polyface.isAlmostEqual(polyfaceB), "polyface round trip"); polyfaceB.data.pointIndex[0] += 1; ck.testBoolean(false, polyface.isAlmostEqual(polyfaceB), "index change detection"); + polyfaceB.data.pointIndex[0] -= 1; + ck.testTrue(polyface.isAlmostEqual(polyfaceB), "index change undo"); // console.log(polyfaceB); expect(ck.getNumErrors()).equals(0); }); @@ -615,3 +617,29 @@ it("facets from sweep contour with holes", () => { expect(ck.getNumErrors()).equals(0); }); + +it("LargeMeshComporession", () => { + const ck = new Checker(); + const allGeometry: GeometryQuery[] = []; + const options = StrokeOptions.createForFacets(); + options.shouldTriangulate = true; + const builder = PolyfaceBuilder.create(options); + const nDimensions = 100; + const spacing = 1.0; + + /** Create a simple flat mesh with 10,000 points (100x100) */ + for (let iRow = 0; iRow < nDimensions - 1; iRow++) { + for (let iColumn = 0; iColumn < nDimensions - 1; iColumn++) { + const quad = [Point3d.create(iRow * spacing, iColumn * spacing, 0.0), + Point3d.create((iRow + 1) * spacing, iColumn * spacing, 0.0), + Point3d.create((iRow + 1) * spacing, (iColumn + 1) * spacing, 0.0), + Point3d.create(iRow * spacing, (iColumn + 1) * spacing)]; + builder.addQuadFacet(quad); + } + } + + const polyface = builder.claimPolyface(); + allGeometry.push(polyface); + GeometryCoreTestIO.saveGeometry(allGeometry, "Polyface", "LargeMeshComporession"); + expect(ck.getNumErrors()).equals(0); +}); diff --git a/core/geometry/src/test/PolyfaceAuxData.test.ts b/core/geometry/src/test/PolyfaceAuxData.test.ts new file mode 100644 index 0000000..ebf7c08 --- /dev/null +++ b/core/geometry/src/test/PolyfaceAuxData.test.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { Checker } from "./Checker"; +import { PolyfaceBuilder } from "../polyface/PolyfaceBuilder"; +import { PolyfaceAuxData, AuxChannel, AuxChannelData, AuxChannelDataType } from "../polyface/Polyface"; +import { StrokeOptions } from "../curve/StrokeOptions"; +import { Arc3d } from "../curve/Arc3d"; +import { LineString3d } from "../curve/LineString3d"; +import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d"; +import { Transform } from "../geometry3d/Transform"; +import { IModelJson } from "../serialization/IModelJsonSchema"; +import { expect } from "chai"; + +/** Create a polyface representing a cantilever beam with [[PolyfaceAuxData]] representing the stress and deflection. */ +function createCantileverBeamPolyface() { + const beamRadius = 10.0; + const beamLength = 100.0; + const facetSize = 1.0; + const builder = PolyfaceBuilder.create(); + const crossSectionArc = Arc3d.create(Point3d.createZero(), Vector3d.create(0.0, beamRadius, 0.0), Vector3d.create(0.0, 0.0, beamRadius)); + const strokedCrossSection = LineString3d.create(); + const strokeOptions = StrokeOptions.createForCurves(); + strokeOptions.maxEdgeLength = facetSize; + crossSectionArc.emitStrokes(strokedCrossSection, strokeOptions); + + for (let x = 0.0; x < beamLength; x += facetSize) + builder.addBetweenTransformedLineStrings(strokedCrossSection, Transform.createTranslationXYZ(x, 0.0, 0.0), Transform.createTranslationXYZ(x + facetSize, 0.0, 0.0), true); + + const polyface = builder.claimPolyface(); + const heightData: number[] = []; + + const scratchPoint = Point3d.create(); + for (let i = 0; i < polyface.data.point.length; i++) { + const point = polyface.data.point.atPoint3dIndex(i, scratchPoint) as Point3d; + heightData.push(point.z); + } + const heightChannel = new AuxChannel([new AuxChannelData(0.0, heightData)], AuxChannelDataType.Distance, "Height", ""); + + polyface.data.auxData = new PolyfaceAuxData([heightChannel], polyface.data.pointIndex); + + return polyface; +} +// --------------------------------------------------------------------------------------------------- + +it("testRoundTrip", () => { + const ck = new Checker(); + const beam = createCantileverBeamPolyface(); + + const writer = new IModelJson.Writer(); + + const json = writer.emit(beam); + const roundTrippedBeam = IModelJson.Reader.parse(json); + + ck.testTrue(beam.isAlmostEqual(roundTrippedBeam)); + expect(ck.getNumErrors()).equals(0); +}); diff --git a/core/geometry/src/test/PolyfaceData.test.ts b/core/geometry/src/test/PolyfaceData.test.ts new file mode 100644 index 0000000..6e79948 --- /dev/null +++ b/core/geometry/src/test/PolyfaceData.test.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { Checker } from "./Checker"; +import { expect } from "chai"; +import { IndexedPolyface } from "../polyface/Polyface"; +import { PolyfaceData } from "../polyface/PolyfaceData"; +/* tslint:disable:no-console */ + +describe("PolyfaceData", () => { + + it("IndexArrays", () => { + const ck = new Checker(); + ck.testFalse(PolyfaceData.isValidFacetStartIndexArray([]), "Facet Start minimal index must have leading 0"); + ck.testFalse(PolyfaceData.isValidFacetStartIndexArray([2, 1]), "Facet Start index must be sorted"); + ck.testFalse(PolyfaceData.isValidFacetStartIndexArray([0, 4, 7, 6]), "Facet Start index must be sorted"); + ck.testTrue(PolyfaceData.isValidFacetStartIndexArray([0, 4, 6, 12]), "Facet Start index must have trailing 0"); + expect(ck.getNumErrors()).equals(0); + + }); + + it("HelloWorld", () => { + const ck = new Checker(); + const pf = IndexedPolyface.create(false, false, false); + ck.testFalse(pf.data.requireNormals); + pf.addPointXYZ(0, 0, 0); + pf.addPointXYZ(1, 0, 0); + pf.addPointXYZ(1, 1, 0); + pf.addPointXYZ(0, 1, 0); + + pf.addPointXYZ(1, 0, 0); + pf.addPointXYZ(1, 1, 0); + pf.addPointXYZ(2, 0, 1); + pf.addPointXYZ(2, 1, 1); + + pf.addPointIndex(0); pf.addPointIndex(1); pf.addPointIndex(2); pf.addPointIndex(3); + const m0 = pf.terminateFacet(true); + ck.testUndefined(m0, m0); + pf.addPointIndex(4); pf.addPointIndex(5); pf.addPointIndex(6); pf.addPointIndex(7); + const m1 = pf.terminateFacet(true); + ck.testUndefined(m1, m1); + ck.testExactNumber(0, pf.faceCount); // "face" is "cluster of facets" -- no clustering has been defined. + + ck.testExactNumber(2, pf.facetCount); + pf.reverseIndices(); + ck.testExactNumber(8, pf.pointCount); + pf.data.compress(); + ck.testExactNumber(2, pf.facetCount); + ck.testExactNumber(6, pf.pointCount); + ck.testExactNumber(2, pf.facetCount); + expect(ck.getNumErrors()).equals(0); + + }); +}); diff --git a/core/geometry/src/test/Polynomial.test.ts b/core/geometry/src/test/Polynomial.test.ts index e599d90..70b2191 100644 --- a/core/geometry/src/test/Polynomial.test.ts +++ b/core/geometry/src/test/Polynomial.test.ts @@ -20,7 +20,7 @@ import { SphereImplicit, TorusImplicit } from "../numerics/Polynomials"; /* tslint:disable:no-console */ import { UnivariateBezier, Order2Bezier, Order3Bezier, Order4Bezier, Order5Bezier, BezierCoffs } from "../numerics/BezierPolynomials"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { Point4d } from "../geometry4d/Point4d"; function testBezier(ck: Checker, bezier: BezierCoffs) { @@ -62,35 +62,60 @@ describe("Bezier.HelloWorld", () => { ck.checkpoint("Order2Bezier"); expect(ck.getNumErrors()).equals(0); }); + it("degenerateCases", () => { + const ck = new Checker(); + const nullTorus = new TorusImplicit(0, 0); // DEGENERATE + ck.testExactNumber(1.0, nullTorus.implicitFunctionScale()); + const data = nullTorus.XYZToThetaPhiDistance(Point3d.create(0, 0, 0)); + ck.testFalse(data.safePhi); + ck.checkpoint("Bezier.degenerateCases"); + expect(ck.getNumErrors()).equals(0); + }); + it("rootServices", () => { + const ck = new Checker(); + const bigNum = 100; + const myArray = GrowableFloat64Array.create([1, 2, 3, 4, bigNum]); + + const a = AnalyticRoots.mostDistantFromMean(myArray); + ck.testCoordinate(0, AnalyticRoots.mostDistantFromMean(undefined)); + ck.testLT(20, a); + ck.checkpoint("Bezier.rootServices"); + expect(ck.getNumErrors()).equals(0); + }); }); describe("Cubic.Solutions", () => { it("Cubic.3Roots", () => { const ck = new Checker(); - const root0 = 0.0; - const root1 = -1.0; - for (const root2 of [0, 1, 2, 10]) { - const cubic = Degree3PowerPolynomial.fromRootsAndC3(root0, root1, root2, 1.0); - for (const x of [root0, root1, root2]) { - const f = cubic.evaluate(x); - ck.testCoordinate(f, 0, "known root"); - const f1 = cubic.evaluate(x + 0.5); - ck.testBoolean(false, Geometry.isAlmostEqualNumber(0.0, f1), "known non root"); - } - const df = 2.3; - cubic.addConstant(df); - for (const x of [root0, root1, root2]) { - ck.testCoordinate(df, cubic.evaluate(x), "shifted cubic evaluate"); - } - const a = 3.2; - const b = 0.3; - cubic.addSquaredLinearTerm(a, b); - for (const x of [root0, root1, root2]) { - const q = a + b * x; - ck.testCoordinate(df + q * q, cubic.evaluate(x), "shifted cubic evaluate"); - } + const root0a = 0.0; + const root1a = -1.0; + for (const sign of [-1, 1]) { + for (const root2a of [0, 1, 2, 10]) { + const root2 = root2a * sign; + const root1 = root1a * sign; + const root0 = root0a * sign; + const cubic = Degree3PowerPolynomial.fromRootsAndC3(root0, root1, root2, 1.0); + for (const x of [root0, root1, root2]) { + const f = cubic.evaluate(x); + ck.testCoordinate(f, 0, "known root"); + const f1 = cubic.evaluate(x + 0.5); + ck.testBoolean(false, Geometry.isAlmostEqualNumber(0.0, f1), "known non root"); + } + const df = 2.3; + cubic.addConstant(df); + for (const x of [root0, root1, root2]) { + ck.testCoordinate(df, cubic.evaluate(x), "shifted cubic evaluate"); + } + const a = 3.2; + const b = 0.3; + cubic.addSquaredLinearTerm(a, b); + for (const x of [root0, root1, root2]) { + const q = a + b * x; + ck.testCoordinate(df + q * q, cubic.evaluate(x), "shifted cubic evaluate"); + } + } } ck.checkpoint("Cubic3Roots"); expect(ck.getNumErrors()).equals(0); @@ -236,6 +261,7 @@ describe("ImplicitSurface", () => { ck.testCoordinate(0, sphere.evaluateImplicitFunction(r, 0, 0), "evaluate sphere"); ck.testCoordinate(0, sphere.evaluateImplicitFunction(0, r, 0), "evaluate sphere"); ck.testCoordinate(0, sphere.evaluateImplicitFunction(0, 0, r), "evaluate sphere"); + for (const xyz of [Point3d.create(1, 2, 4), Point3d.create(0, 0, 0), Point3d.create(r, 0, 0)]) { @@ -260,6 +286,13 @@ describe("ImplicitSurface", () => { ck.testCoordinate(thetaPhi[0], thetaPhiA.theta, "implicit sphere theta inverse"); ck.testCoordinate(thetaPhi[1], thetaPhiA.phi, "implicit sphere phi inverse"); } + for (const sphereA of [ + new SphereImplicit(0), new SphereImplicit(1)]) { + const thetaPhiB = sphereA.XYZToThetaPhiR(Point3d.create(0, 0, 0)); + const thetaPhiC = sphereA.XYZToThetaPhiR(Point3d.create(0, 0, 1)); + ck.testExactNumber(0.0, thetaPhiB.phi); + ck.testExactNumber(0.0, thetaPhiC.theta); + } ck.checkpoint("ImplicitSurface.Sphere"); expect(ck.getNumErrors()).equals(0); }); @@ -342,7 +375,9 @@ describe("PowerPolynomials", () => { /* equivalent Degree4PowerPolynomial ... */ for (const roots of [ [0, 0, 0, 0], - [0.5, 1, 2, 3]]) { + [0.5, 1, 2, 3], + // [0.1, 0.1, 0.5, 0.5] + ]) { const power = Degree4PowerPolynomial.fromRootsAndC4(roots[0], roots[1], roots[2], roots[3], 2); const bezier = Order5Bezier.createFromDegree4PowerPolynomial(power); const powerRoots = new GrowableFloat64Array(4); @@ -364,6 +399,39 @@ describe("PowerPolynomials", () => { ck.checkpoint("PowerPolynomials.Degree4PowerPolynomial"); expect(ck.getNumErrors()).equals(0); }); + + it("DegenerateDegree4PowerPolynomial", () => { + const ck = new Checker(); + /* equivalent Degree4PowerPolynomial ... */ + for (const coffs of [ + [0, 0, 0, 0, 0], + [1, 0, 0, 0, 0], + [1, -2, 0, 0, 0], + [1, 2, -3, 0, 0], + [0.5, 1, 2, 3, 0]]) { + const power = new Degree4PowerPolynomial(coffs[0], coffs[1], coffs[2], coffs[3], coffs[4]); + const bezier = Order5Bezier.createFromDegree4PowerPolynomial(power); + const powerRoots = new GrowableFloat64Array(4); + const bezierRoots = new GrowableFloat64Array(4); + AnalyticRoots.appendQuarticRoots(power.coffs, powerRoots); + bezier.realRoots(0.0, false, bezierRoots); + // make sure that each reported root is a root ... this does not confirm completeness + for (let i = 0; i < powerRoots.length; i++) { + const r = powerRoots.at(i); + const f = power.evaluate(r); + ck.testCoordinate(0, f); + } + for (let i = 0; i < bezierRoots.length; i++) { + const r = bezierRoots.at(i); + const f = bezier.evaluate(r); + ck.testCoordinate(0, f); + } + + } + ck.checkpoint("PowerPolynomials.DegenerateDegree4PowerPolynomial"); + expect(ck.getNumErrors()).equals(0); + }); + it("Degree3PowerPolynomial", () => { const ck = new Checker(); /* equivalent Degree4PowerPolynomial ... */ @@ -480,5 +548,17 @@ describe("LinearSystems", () => { ck.checkpoint("LinearSystems.lineSegment3dXYTransverseIntersectionUnbounded"); expect(ck.getNumErrors()).equals(0); }); - + it("cubeRoot", () => { + const ck = new Checker(); + ck.testExactNumber(0, AnalyticRoots.cbrt(0.0)); + ck.testExactNumber(0, AnalyticRoots.cbrt(-0.0)); + for (const a of [1000, 100, 2.234234, 0.347297, 1.0e-18]) { + const r0 = AnalyticRoots.cbrt(a); + const r1 = AnalyticRoots.cbrt(-a); + ck.testCoordinate(r0, -r1); + ck.testCoordinate(a, r0 * r0 * r0); + ck.testCoordinate(-a, r1 * r1 * r1); + } + expect(ck.getNumErrors()).equals(0); + }); }); diff --git a/core/geometry/src/test/Range1dArray.test.ts b/core/geometry/src/test/Range1dArray.test.ts index c14121a..0159ca1 100644 --- a/core/geometry/src/test/Range1dArray.test.ts +++ b/core/geometry/src/test/Range1dArray.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Geometry } from "../Geometry"; import { Range1d } from "../geometry3d/Range"; -import { Range1dArray } from "../numerics/Range1dArray"; +import { Range1dArray, compareRange1dLexicalLowHigh } from "../numerics/Range1dArray"; import { Checker } from "./Checker"; import { expect } from "chai"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; // import { prettyPrint } from "./testFunctions"; /* tslint:disable:no-console */ @@ -51,31 +51,39 @@ function testUnionSimplify(ck: Checker, dataA: Range1d[]): void { const dataB = cloneRanges(dataA); Range1dArray.simplifySortUnion(dataB, true); ck.testTrue(Range1dArray.isSorted(dataB), "Disjoint after Union fix"); - const breakData = Range1dArray.getBreaks(dataB); - const interiorData = constructGapPoints(breakData, -0.5, 0.4, 0.5); - for (let i = 0; i < interiorData.length; i++) { - const value = interiorData.at(i); - const inA = Range1dArray.testUnion(dataA, value); - const inB = Range1dArray.testUnion(dataB, value); - ck.testBoolean(inA, inB, "Union simplification agrees"); - } + const breakDataB = Range1dArray.getBreaks(dataB, undefined, true, true); + const testPointsB = constructGapPoints(breakDataB, -0.5, 0.4, 0.5); + const breakDataA = Range1dArray.getBreaks(dataA, undefined, true, true); + const testPointsA = constructGapPoints(breakDataA, -0.5, 0.4, 0.5); + for (const testPoints of [testPointsA, testPointsB]) + for (let i = 0; i < testPoints.length; i++) { + { + const value = testPoints.at(i); + const inA = Range1dArray.testUnion(dataA, value); + const inB = Range1dArray.testUnion(dataB, value); + ck.testBoolean(inA, inB, "Union simplification agrees"); + } + } } /* test that the ranges in dataA are properly simplified by parity rules */ function testParitySimplify(ck: Checker, dataA: Range1d[]): void { const dataB = cloneRanges(dataA); Range1dArray.simplifySortParity(dataB, true); - // simplified array should be sorted .... - ck.testTrue(Range1dArray.isSorted(dataB), "Disjoint after Parity fix"); - const breakData = Range1dArray.getBreaks(dataB); - const interiorData = constructGapPoints(breakData, -0.5, 0.4, 0.5); - // all points should get same parity evaluation whether in sorted or unsorted array .... - for (let i = 0; i < interiorData.length; i++) { - const value = interiorData.at(i); - const inA = Range1dArray.testParity(dataA, value); - const inB = Range1dArray.testParity(dataB, value); - ck.testBoolean(inA, inB, "Parity simplification agrees"); - } + ck.testTrue(Range1dArray.isSorted(dataB), "Disjoint after Union fix"); + const breakDataB = Range1dArray.getBreaks(dataB, undefined, true, true); + const testPointsB = constructGapPoints(breakDataB, -0.5, 0.4, 0.5); + const breakDataA = Range1dArray.getBreaks(dataA, undefined, true, true); + const testPointsA = constructGapPoints(breakDataA, -0.5, 0.4, 0.5); + for (const testPoints of [testPointsA, testPointsB]) + for (let i = 0; i < testPoints.length; i++) { + { + const value = testPoints.at(i); + const inA = Range1dArray.testParity(dataA, value); + const inB = Range1dArray.testParity(dataB, value); + ck.testBoolean(inA, inB, "Union simplification agrees"); + } + } } /** Returns an array of values that fall inside both sorted range arrays. @@ -162,51 +170,56 @@ function getParityArrayData(dataA: Range1d[], dataB: Range1d[]): any { return { insideParity: inResult, outsideParity: outResult }; } +// return an array of ranges with each range +// {low: cos (omega * i), high: cos (omega * i * i + alpha)} +function range1dSamples(numRange: number, omega: number = 3.0, alpha: number = 0.2): Range1d[] { + const result = []; + for (let i = 0; i < numRange; i++) { + const a = Math.cos(omega * i); + const b = Math.cos(omega * i * i + alpha); + result.push(Range1d.createXX(a, b)); + } + return result; +} + describe("Range1dArray", () => { - let ck: Checker; - let rangeArr: Range1d[]; - - before (() => { - ck = new Checker(); - rangeArr = []; - const omega = 3.0; - const alpha = 0.2; - for (let i = 0; i < 10; i++) { - const a = Math.cos(omega * i); - const b = Math.cos(omega * i * i + alpha); - rangeArr.push(Range1d.createXX(a, b)); - } + + it("compareRange1dLexicalLowHigh", () => { + const ck = new Checker(); + ck.testExactNumber(-1, compareRange1dLexicalLowHigh(Range1d.createXX(0, 1), Range1d.createXX(0, 2))); + ck.testExactNumber(1, compareRange1dLexicalLowHigh(Range1d.createXX(0, 3), Range1d.createXX(0, 2))); + + ck.testExactNumber(-1, compareRange1dLexicalLowHigh(Range1d.createXX(-1, 1), Range1d.createXX(0, 1))); + ck.testExactNumber(1, compareRange1dLexicalLowHigh(Range1d.createXX(2, 3), Range1d.createXX(0, 2))); + ck.testExactNumber(0, compareRange1dLexicalLowHigh(Range1d.createXX(2, 3), Range1d.createXX(2, 3))); + expect(ck.getNumErrors()).equals(0); + }); it("UnionParitySimplification", () => { + const ck = new Checker(); const range0 = [Range1d.createXX(0, 0), Range1d.createXX(0, 4), Range1d.createXX(2, 7), Range1d.createXX(8, 10), Range1d.createXX(9, 10)]; const range1 = [Range1d.createXX(0, 0), Range1d.createXX(0, 4), Range1d.createXX(2, 7), Range1d.createXX(8, 10), Range1d.createXX(9, 10)]; const range2 = [Range1d.createXX(0, 5), Range1d.createXX(3, 6), Range1d.createXX(7, 20), Range1d.createXX(8, 21)]; - const range3 = cloneRanges(rangeArr); - - // confirm that all of these are messy to start .... - ck.testFalse(Range1dArray.isSorted(range0), "Expect messy input", range0); - ck.testFalse(Range1dArray.isSorted(range1), "Expect messy input", range1); - ck.testFalse(Range1dArray.isSorted(range2), "Expect messy input", range2); - ck.testFalse(Range1dArray.isSorted(range3), "Expect messy input", range3); - - testParitySimplify(ck, range0); - testParitySimplify(ck, range1); - testParitySimplify(ck, range2); - testParitySimplify(ck, range3); - - testUnionSimplify(ck, range0); - testUnionSimplify(ck, range1); - testUnionSimplify(ck, range2); - testUnionSimplify(ck, range3); + const range3 = range1dSamples(10, 3.0, 0.2); + const range4 = [Range1d.createXX(0, 4), Range1d.createXX(0, 3), Range1d.createXX(2, 7), Range1d.createXX(2, 10)]; + + for (const ranges of [range0, range1, range2, range3, range4]) { + ck.testFalse(Range1dArray.isSorted(ranges), "Expect messy input", ranges); + const workA = cloneRanges(ranges); + const workB = cloneRanges(ranges); + testParitySimplify(ck, workA); + testUnionSimplify(ck, workB); + } ck.checkpoint("Range1dArray.UnionParitySimplification"); expect(ck.getNumErrors()).equals(0); }); it("IntersectDifferenceUnionParity", () => { + const ck = new Checker(); // Set up the arrays - const range0 = cloneRanges(rangeArr); + const range0 = range1dSamples(10, 3.0, 0.2); Range1dArray.simplifySortParity(range0); const range1 = [Range1d.createXX(-.9, -.75), Range1d.createXX(-.5, -.3), Range1d.createXX(0, .4), Range1d.createXX(.8, 1)]; @@ -249,4 +262,17 @@ describe("Range1dArray", () => { ck.checkpoint("Range1dArray.IntersectDifferenceUnionParity"); expect(ck.getNumErrors()).equals(0); }); + + it("UnionParitySimplificationA", () => { + const ck = new Checker(); + for (const allRanges of [ + range1dSamples(3, 2.0, 0.5), + range1dSamples(20, 2.94, 0.34234), + ]) { + testUnionSimplify(ck, allRanges); + testParitySimplify(ck, allRanges); + } + expect(ck.getNumErrors()).equals(0); + }); + }); diff --git a/core/geometry/src/test/Ray3d.test.ts b/core/geometry/src/test/Ray3d.test.ts index 5a6c76c..01f3a9c 100644 --- a/core/geometry/src/test/Ray3d.test.ts +++ b/core/geometry/src/test/Ray3d.test.ts @@ -96,6 +96,11 @@ describe("Ray3d", () => { ck.testCoordinate(spacePoint.distance(nullray.origin), nullray.distance(spacePoint), "distance to null ray"); ck.testFalse(nullray.trySetDirectionMagnitudeInPlace(), "trySetMagnnitude of nullray"); + + ck.testUndefined( + Ray3d.createWeightedDerivative( + new Float64Array([1, 2, 3, 0]), + new Float64Array([2, 1, 4, 0]))); expect(ck.getNumErrors()).equals(0); }); }); diff --git a/core/geometry/src/test/RotMatrix.test.ts b/core/geometry/src/test/RotMatrix.test.ts index 323b8e1..bbf94aa 100644 --- a/core/geometry/src/test/RotMatrix.test.ts +++ b/core/geometry/src/test/RotMatrix.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Point2d } from "../geometry3d/Point2dVector2d"; import { Vector3d, Point3d } from "../geometry3d/Point3dVector3d"; -import { Matrix3d } from "../geometry3d/Matrix3d"; +import { Matrix3d, InverseMatrixState } from "../geometry3d/Matrix3d"; import { Transform } from "../geometry3d/Transform"; import { AxisOrder, Geometry, AxisIndex } from "../Geometry"; import { Angle } from "../geometry3d/Angle"; @@ -52,6 +52,39 @@ describe("Matrix3d", () => { 10, 1, 2, 3, 20, 1, 4, 2, 15)); + + const singularX = Matrix3d.createScale(0, 1, 1); + const singularY = Matrix3d.createScale(1, 0, 1); + const singularZ = Matrix3d.createScale(1, 1, 0); + + const vector = Vector3d.create(4, 2.324324, 9.21); + + for (const matrix of [singularX, singularY, singularZ]) { + ck.testUndefined(matrix.inverseCoffs); + ck.testExactNumber(InverseMatrixState.singular, matrix.inverseState); + ck.testUndefined(singularX.multiplyInverseTranspose(vector)); + ck.testUndefined(singularX.multiplyInverse(vector)); + ck.testUndefined(singularX.multiplyInverseXYZAsVector3d(vector.x, vector.y, vector.z)); + ck.testUndefined(singularX.multiplyInverseXYZAsPoint3d(vector.x, vector.y, vector.z)); + const matrix1 = matrix.clone(); + const originalMagnitudes = Vector3d.create(); + ck.testFalse(matrix1.normalizeColumnsInPlace(originalMagnitudes)); + ck.testMatrix3d(matrix, matrix1, "failed normalize leaves matrix alone"); + ck.testFalse(matrix1.normalizeRowsInPlace(originalMagnitudes)); + ck.testMatrix3d(matrix, matrix1, "failed normalize leaves matrix alone"); + } + + // scaling columns updates inverse. + // scaling columns with a zero scale clears inverse. + for (const matrix of Sample.createScaleSkewMatrix3d()) { + const vectorQ = matrix.multiplyInverseXYZAsVector3d(4, 7, 11); + // There should now be a stored inversed . .. + if (vectorQ) { + ck.testPointer(matrix.inverseCoffs); + matrix.scaleColumnsInPlace(0, 3, 8); + ck.testExactNumber(matrix.inverseState, InverseMatrixState.singular); + } + } ck.checkpoint("Matrix3d.CachedInverse"); expect(ck.getNumErrors()).equals(0); }); @@ -118,10 +151,32 @@ describe("AxisOrder.Verify", () => { ck.testExactNumber(axis2, Geometry.cyclic3dAxis(axis1 + shift)); ck.testExactNumber(axis0, Geometry.cyclic3dAxis(axis2 + shift)); - ck.checkpoint("AxisOrder.Verify"); - expect(ck.getNumErrors()).equals(0); } + ck.checkpoint("AxisOrder.Verify"); + expect(ck.getNumErrors()).equals(0); + }); + it("AssembleColumns", () => { + const vector0 = Vector3d.create(1000, 2, 5); + const vector1 = Vector3d.create(1, 1001, -2); + const vector2 = Vector3d.create(-3, 1.234, 1002); + const ck = new Checker(); + for (const axisOrder of [AxisOrder.XYZ, AxisOrder.YZX, AxisOrder.ZXY, AxisOrder.ZXY, AxisOrder.XZY, AxisOrder.YXZ, AxisOrder.ZYX]) { + + const axis0 = Geometry.axisOrderToAxis(axisOrder, 0); + const axis1 = Geometry.axisOrderToAxis(axisOrder, 1); + const axis2 = Geometry.axisOrderToAxis(axisOrder, 2); + const matrix = Matrix3d.createColumnsInAxisOrder(axisOrder, vector0, vector1, vector2); + const vectorB0 = matrix.getColumn(axis0); + const vectorB1 = matrix.getColumn(axis1); + const vectorB2 = matrix.getColumn(axis2); + ck.testVector3d(vector0, vectorB0); + ck.testVector3d(vector1, vectorB1); + ck.testVector3d(vector2, vectorB2); + } + ck.checkpoint("AxisOrder.AssembleColumns"); + expect(ck.getNumErrors()).equals(0); }); + }); function verifyRigidScale(ck: Checker, candidate: Matrix3d, expectedScale: number, expectRigid: boolean) { @@ -158,6 +213,7 @@ describe("Matrix3d.Factors", () => { it("AxisAndAngleOfRotationA", () => { const ck = new Checker(); + const rotations = Sample.createRigidAxes(); for (const rigid of rotations) { ck.testTrue(rigid.isRigid(), "verify rigid"); @@ -172,6 +228,20 @@ describe("Matrix3d.Factors", () => { expect(ck.getNumErrors()).equals(0); }); + it("BadInputCases", () => { + const ck = new Checker(); + const failure1 = Matrix3d.createViewedAxes(Vector3d.unitX(), Vector3d.unitX()); + ck.testUndefined(failure1, "createViewedAxes with cross failure"); + + const failure2 = Matrix3d.createRotationAroundVector(Vector3d.createZero(), Angle.createDegrees(40)); + ck.testUndefined(failure2, "createRotationAroundVector with 000 axis"); + + const failure3 = Matrix3d.createDirectionalScale(Vector3d.createZero(), 2.0); + ck.testTrue(failure3.isDiagonal, "createDirectionalScale fails to uniform scale"); + ck.checkpoint("Matrix3d.BadInputCases"); + expect(ck.getNumErrors()).equals(0); + }); + it("AxisAndAngleOfRotationB", () => { const ck = new Checker(); const rotationVectors = Sample.createNonZeroVectors(); @@ -223,6 +293,11 @@ describe("Matrix3d.Factors", () => { Vector3d.unitY(), Vector3d.unitZ(), Vector3d.create(1, 1, 0), + Vector3d.create(-1, 1, 0), + Vector3d.create(0, 1, 1), + Vector3d.create(0, -1, 1), + Vector3d.create(1, 0, 1), + Vector3d.create(-1, 0, 1), Vector3d.create(-1, 2, 0), Vector3d.create(1, 2, 3), Vector3d.create(-1, 2, 3), @@ -338,6 +413,8 @@ describe("Matrix3d.ViewConstructions", () => { testRotateVectorToVector(Vector3d.create(1, 0, 0), Vector3d.create(-1, 0), ck); testRotateVectorToVector(Vector3d.create(0, -1, 0), Vector3d.create(-0, 1, 0), ck); testRotateVectorToVector(Vector3d.create(0, 0, 1), Vector3d.create(0, 0, -1), ck); + ck.testUndefined(Matrix3d.createPartialRotationVectorToVector(Vector3d.createZero(), 0.3, Vector3d.createZero()), "rotation with zero vector"); + ck.testUndefined(Matrix3d.createPartialRotationVectorToVector(Vector3d.createZero(), 0.2, Vector3d.unitX()), "rotation with zero vector"); const vectorA = Vector3d.create(1, 2, 3); const vectorB = Vector3d.create(4, 2, 9); @@ -415,8 +492,20 @@ describe("Matrix3d.ViewConstructions", () => { ck.testCoordinate(transposeDiff.sumSquares(), 2.0 * byColumn.sumSkewSquares(), "skew squares"); expect(ck.getNumErrors()).equals(0); }); + it("QuickDots", () => { + const ck = new Checker(); + const vectorX = Vector3d.create(1, 2, 4); + const vectorY = Vector3d.create(3, 9, 27); + const vectorZ = Vector3d.create(5, 25, 125); + const byRow = Matrix3d.createRows(vectorX, vectorY, vectorZ); + const vector = Vector3d.create(-3.12321, 0.28, 1.249); + ck.testCoordinate(vector.dotProduct(vectorX), byRow.dotRowXXYZ(vector.x, vector.y, vector.z)); + ck.testCoordinate(vector.dotProduct(vectorY), byRow.dotRowYXYZ(vector.x, vector.y, vector.z)); + ck.testCoordinate(vector.dotProduct(vectorZ), byRow.dotRowZXYZ(vector.x, vector.y, vector.z)); + expect(ck.getNumErrors()).equals(0); + }); - it("SgnedPerumtation", () => { + it("SignedPerumtation", () => { const ck = new Checker(); const unitX = Vector3d.unitX(); const unitY = Vector3d.unitY(); @@ -445,6 +534,15 @@ describe("Matrix3d.ViewConstructions", () => { expect(ck.getNumErrors()).equals(0); }); + it("StandardView", () => { + const ck = new Checker(); + for (let viewIndex = 0; viewIndex < 8; viewIndex++) { + const matrix = Matrix3d.createStandardWorldToView(viewIndex); + ck.testTrue(matrix.isRigid()); + } + expect(ck.getNumErrors()).equals(0); + }); + it("ScaleAlongVector", () => { const ck = new Checker(); for (const perpVector of [Vector3d.create(0, 0, 1), Vector3d.create(1, 2, 4)]) { @@ -509,6 +607,34 @@ describe("Matrix3d.ViewConstructions", () => { expect(ck.getNumErrors()).equals(0); }); + it("MultiplyXYZToFloat64Array", () => { + const ck = new Checker(); + const vectors = Sample.createNonZeroVectors(); + for (const perpVector of [ + Vector3d.unitX(), + Vector3d.unitY(), + Vector3d.unitZ(), + Vector3d.create(1, 2, 4)]) { + perpVector.normalizeInPlace(); + const matrix = Matrix3d.createDirectionalScale(perpVector, -1.0); + const columnX = matrix.columnX(); + const columnY = matrix.columnY(); + const columnZ = matrix.columnZ(); + const origin = Point3d.create(4, 3, 0.1231); + const w = 0.9213123678687689769; + for (const v of vectors) { + const resultBW = Matrix3d.XYZPlusMatrixTimesWeightedCoordinatesToFloat64Array(origin, matrix, v.x, v.y, v.z, w); + const resultAW = Point3d.createScale(origin, w).plus3Scaled(columnX, v.x, columnY, v.y, columnZ, v.z); + const resultB = Matrix3d.XYZPlusMatrixTimesCoordinatesToFloat64Array(origin, matrix, v.x, v.y, v.z); + const resultA = origin.plus3Scaled(columnX, v.x, columnY, v.y, columnZ, v.z); + ck.testXYZ(resultA, Vector3d.createFrom(resultB), "XYZPlusMatrixTimesWeightedCoordinatesToFloat64Array"); + ck.testXYZ(resultAW, Vector3d.createFrom(resultBW), "XYZPlusMatrixTimesCoordinatesToFloat64Array"); + + } + } + expect(ck.getNumErrors()).equals(0); + }); + it("AxisOrderConstructions", () => { const ck = new Checker(); const perpVector = Vector3d.create(1, 2, 4); @@ -567,6 +693,15 @@ describe("Matrix3d.ViewConstructions", () => { const matrixZ = Matrix3d.fromJSON([4, 3, 2, 1]); const matrixZ1 = Matrix3d.createRowValues(4, 3, 0, 2, 1, 0, 0, 0, 1); ck.testTrue(matrixZ.isAlmostEqual(matrixZ1, epsilon), "2d matrix"); + + const matrixC = Matrix3d.fromJSON(); // creates zeros + ck.testMatrix3d(matrixC, Matrix3d.createZero()); + + const matrixD = Matrix3d.fromJSON(matrixA); // clone! + ck.testMatrix3d(matrixA, matrixD); + + const matrixE = Matrix3d.fromJSON([1, 2, 3, 4, 5, 6, 7, 8, 9]); + ck.testMatrix3d(matrixA, matrixE); expect(ck.getNumErrors()).equals(0); }); @@ -623,3 +758,19 @@ describe("SkewFactorization", () => { }); }); + +describe("InverseVariants", () => { + it("CreateCapture", () => { + const ck = new Checker(); + for (const matrix of Sample.createScaleSkewMatrix3d()) { + const coffs = new Float64Array(matrix.coffs); + matrix.computeCachedInverse(true); + const inverseCoffs = new Float64Array(matrix.inverseCoffs!); + const matrix1 = Matrix3d.createCapture(coffs); // matrix1 uses coffs direction. + const matrix2 = Matrix3d.createCapture(new Float64Array(coffs), inverseCoffs); // uses copy of coffs; uses inverseCoffs directly + ck.testMatrix3d(matrix, matrix1); + ck.testMatrix3d(matrix, matrix2); + } + expect(ck.getNumErrors()).equals(0); + }); +}); diff --git a/core/geometry/src/test/Transform.test.ts b/core/geometry/src/test/Transform.test.ts index f10d763..68a9a0b 100644 --- a/core/geometry/src/test/Transform.test.ts +++ b/core/geometry/src/test/Transform.test.ts @@ -10,6 +10,7 @@ import { Checker } from "./Checker"; // import { prettyPrint } from "./testFunctions"; import { Sample } from "../serialization/GeometrySamples"; import { expect } from "chai"; +import { AxisOrder } from "../Geometry"; /* tslint:disable:no-console */ describe("Transform", () => { @@ -199,4 +200,64 @@ describe("Transform", () => { expect(ck.getNumErrors()).equals(0); }); + it("frozenIdentity", () => { + const ck = new Checker(); + // confirm that "createIdentity" returns new transforms, whereas the property "identity" returns the same object each time (which we believe is frozen) + const myIdentity0 = Transform.createIdentity(); + const myIdentity1 = Transform.createIdentity(); + const systemIdentity0 = Transform.identity; + const systemIdentity1 = Transform.identity; + ck.testTransform(myIdentity0, systemIdentity0, "matching identity contents"); + ck.testTransform(myIdentity1, systemIdentity1), "matching identity contents"; + ck.testTrue(systemIdentity0 === systemIdentity1, "system identity is unique"); + ck.testFalse(myIdentity0 === myIdentity1, "createIdentity makes distinct objects"); + ck.testFalse(systemIdentity0 === myIdentity0); + ck.testFalse(systemIdentity0 === myIdentity1); + expect(ck.getNumErrors()).equals(0); + }); + + it("finalCoverage", () => { + const ck = new Checker(); + // confirm that "createIdentity" returns new transforms, whereas the property "identity" returns the same object each time (which we believe is frozen) + const transformA = Transform.createRowValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + const transformB = Transform.createIdentity(); + const transformB1 = transformA.clone(transformB); // transformB is no longer an identity !!!! + ck.testTrue(transformB === transformB1); + + const transformC = Transform.createIdentity(); + const transformB2 = Transform.createRowValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, transformC); + ck.testTransform(transformA, transformB2); + ck.testTransform(transformA, transformB, "clone to result"); + + transformC.setFromJSON(); + ck.testTransform(transformC, Transform.identity, "setFromJSON defaults to identity"); + + transformC.setFromJSON(transformB); + ck.testTransform(transformC, transformB, "setFromJSON with Transform object input"); + + const singularA = Transform.createRowValues( + 1, 2, 4, 0, + 2, 4, 3, 1, + 3, 6, -10, 1); + const points = [Point3d.create(1, 2, 3), Point3d.create(3, 2, 9)]; + ck.testFalse(singularA.multiplyInversePoint3dArrayInPlace(points)); + ck.testUndefined(singularA.cloneRigid(AxisOrder.XYZ), "cloneRigid fail with known XY degenerate"); + const rigidA = singularA.cloneRigid(AxisOrder.ZXY); + if (ck.testPointer(rigidA, "cloneRigid corrects known Y degenerate") && rigidA) { + ck.testTrue(rigidA.matrix.isRigid()); + const xy = Point2d.create(1, 2); + const xyz = Point3d.create(xy.x, xy.y, 0.0); + const xyB = rigidA.multiplyPoint2d(xy); + const xyzB = rigidA.multiplyPoint3d(xyz); + ck.testCoordinate(xyB.x, xyzB.x); + ck.testCoordinate(xyB.y, xyzB.y); + } + + const shift = Point3d.create(1, 2, 3); + const translateA = Transform.createTranslation(shift); + const translateB = Transform.createOriginAndMatrix(shift, undefined); + ck.testTransform(translateA, translateB); + expect(ck.getNumErrors()).equals(0); + }); + }); diff --git a/core/geometry/src/test/TransitionSpiral3d.test.ts b/core/geometry/src/test/TransitionSpiral3d.test.ts index 7aeb55a..8fa96b0 100644 --- a/core/geometry/src/test/TransitionSpiral3d.test.ts +++ b/core/geometry/src/test/TransitionSpiral3d.test.ts @@ -7,10 +7,13 @@ // import { Range1d } from "../Range"; // import { Matrix3d, Transform } from "../geometry3d/Transform"; -import { TransitionConditionalProperties } from "../curve/TransitionSpiral"; +import { TransitionConditionalProperties, TransitionSpiral3d } from "../curve/TransitionSpiral"; import { Angle } from "../geometry3d/Angle"; import { Checker } from "./Checker"; import { expect } from "chai"; +import { AngleSweep } from "../geometry3d/AngleSweep"; +import { Transform } from "../geometry3d/Transform"; +import { Segment1d } from "../geometry3d/Segment1d"; describe("TransitionSprialProperties", () => { it("HelloWorld", () => { @@ -43,7 +46,14 @@ describe("TransitionSprialProperties", () => { ck.testTrue(dataA.isAlmostEqual(dataC), "dataC"); ck.testTrue(dataA.isAlmostEqual(dataD), "dataD"); ck.testTrue(dataA.isAlmostEqual(dataE), "dataE"); - + }); + it("CreateAndPoke", () => { + const ck = new Checker(); + const spiralA = TransitionSpiral3d.createRadiusRadiusBearingBearing(Segment1d.create(0, 1000), AngleSweep.createStartEndDegrees(0, 8), Segment1d.create(0, 1), Transform.createIdentity()); + const spiralB = TransitionSpiral3d.createRadiusRadiusBearingBearing(Segment1d.create(1000, 0), AngleSweep.createStartEndDegrees(10, 3), Segment1d.create(0, 1), Transform.createIdentity()); + ck.testFalse(spiralB.isAlmostEqual(spiralA)); + spiralB.setFrom(spiralA); + ck.testTrue(spiralA.isAlmostEqual(spiralB)); expect(ck.getNumErrors()).equals(0); }); }); diff --git a/core/geometry/src/test/performance.test.ts b/core/geometry/src/test/performance.test.ts index 23bfdcf..b670eef 100644 --- a/core/geometry/src/test/performance.test.ts +++ b/core/geometry/src/test/performance.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Matrix3d } from "../geometry3d/Matrix3d"; import { Geometry } from "../Geometry"; -import { GrowableFloat64Array, GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { Point3d } from "../geometry3d/Point3dVector3d"; /* tslint:disable:no-console no-unused-variable */ diff --git a/core/geometry/src/topology/Merging.ts b/core/geometry/src/topology/Merging.ts index 771193a..517706c 100644 --- a/core/geometry/src/topology/Merging.ts +++ b/core/geometry/src/topology/Merging.ts @@ -9,7 +9,7 @@ import { Geometry } from "../Geometry"; import { Point3d } from "../geometry3d/Point3dVector3d"; import { LineSegment3d } from "../curve/LineSegment3d"; import { HalfEdge, HalfEdgeGraph, HalfEdgeMask } from "./Graph"; -import { GrowableFloat64Array } from "../geometry3d/GrowableArray"; +import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array"; import { ClusterableArray } from "../numerics/ClusterableArray"; class SweepEvent { diff --git a/core/geometry/src/topology/Triangulation.ts b/core/geometry/src/topology/Triangulation.ts index fee748e..65785b4 100644 --- a/core/geometry/src/topology/Triangulation.ts +++ b/core/geometry/src/topology/Triangulation.ts @@ -9,7 +9,7 @@ import { HalfEdgeMask, HalfEdge, HalfEdgeGraph } from "./Graph"; import { XAndY } from "../geometry3d/XYZProps"; import { Point3d } from "../geometry3d/Point3dVector3d"; import { Geometry } from "../Geometry"; -import { GrowableXYZArray } from "../geometry3d/GrowableArray"; +import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray"; import { Range2d } from "../geometry3d/Range"; import { IndexedXYZCollection } from "../geometry3d/IndexedXYZCollection"; diff --git a/core/i18n/CHANGELOG.json b/core/i18n/CHANGELOG.json index 0af99e6..a90cb63 100644 --- a/core/i18n/CHANGELOG.json +++ b/core/i18n/CHANGELOG.json @@ -1,6 +1,66 @@ { "name": "@bentley/imodeljs-i18n", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-i18n_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "Unit tests" + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-i18n_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": {} + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-i18n_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-i18n_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-i18n_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": {} + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-i18n_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-i18n_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-i18n_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-i18n_v0.163.0", diff --git a/core/i18n/CHANGELOG.md b/core/i18n/CHANGELOG.md index b2a2c38..fa1fa67 100644 --- a/core/i18n/CHANGELOG.md +++ b/core/i18n/CHANGELOG.md @@ -1,6 +1,50 @@ # Change Log - @bentley/imodeljs-i18n -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- Unit tests + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +*Version update only* + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +*Version update only* + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/i18n/package.json b/core/i18n/package.json index 7753ac2..14d788e 100644 --- a/core/i18n/package.json +++ b/core/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-i18n", - "version": "0.163.0", + "version": "0.171.0", "description": "iModel.js localization code", "license": "MIT", "repository": { @@ -27,15 +27,15 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", "@types/i18next": "^8.4.2", "@types/i18next-browser-languagedetector": "^2.0.1", "@types/i18next-xhr-backend": "^1.4.1", @@ -44,7 +44,7 @@ "tslint": "^5.11.0", "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0" }, "//dependencies": [ "NOTE: these dependencies are specific to imodeljs-i18n", diff --git a/core/i18n/src/Localization.ts b/core/i18n/src/Localization.ts index 3c2c6c7..679fca1 100644 --- a/core/i18n/src/Localization.ts +++ b/core/i18n/src/Localization.ts @@ -107,13 +107,19 @@ export class I18N { return thisNamespace; } - public waitForAllRead(): Promise { + public async waitForAllRead(): Promise { const namespacePromises = new Array>(); for (const thisNamespace of this._namespaceRegistry.values()) { namespacePromises.push(thisNamespace.readFinished); } return Promise.all(namespacePromises); } + + /** @hidden */ + public unregisterNamespace(name: string): void { + this._namespaceRegistry.delete(name); + } + } export class I18NNamespace { diff --git a/core/quantity/CHANGELOG.json b/core/quantity/CHANGELOG.json index d6875ec..f4bc029 100644 --- a/core/quantity/CHANGELOG.json +++ b/core/quantity/CHANGELOG.json @@ -1,6 +1,60 @@ { "name": "@bentley/imodeljs-quantity", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/imodeljs-quantity_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": {} + }, + { + "version": "0.170.0", + "tag": "@bentley/imodeljs-quantity_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": {} + }, + { + "version": "0.169.0", + "tag": "@bentley/imodeljs-quantity_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/imodeljs-quantity_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/imodeljs-quantity_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": {} + }, + { + "version": "0.166.0", + "tag": "@bentley/imodeljs-quantity_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/imodeljs-quantity_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/imodeljs-quantity_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:20 GMT", + "comments": { + "none": [ + { + "comment": "Updated to TypeScript 3.1" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/imodeljs-quantity_v0.163.0", diff --git a/core/quantity/CHANGELOG.md b/core/quantity/CHANGELOG.md index 971af05..eb376e9 100644 --- a/core/quantity/CHANGELOG.md +++ b/core/quantity/CHANGELOG.md @@ -1,6 +1,48 @@ # Change Log - @bentley/imodeljs-quantity -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +*Version update only* + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +*Version update only* + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +*Version update only* + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:20 GMT + +### Updates + +- Updated to TypeScript 3.1 ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/core/quantity/package.json b/core/quantity/package.json index c96bd79..af7114a 100644 --- a/core/quantity/package.json +++ b/core/quantity/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/imodeljs-quantity", - "version": "0.163.0", + "version": "0.171.0", "description": "Quantity formatting and parsing concepts in typescript", "license": "MIT", "main": "lib/index.js", @@ -25,26 +25,26 @@ "url": "http://www.bentley.com" }, "devDependencies": { - "@bentley/bentleyjs-core": "0.163.0", - "@bentley/build-tools": "0.163.0", + "@bentley/bentleyjs-core": "0.171.0", + "@bentley/build-tools": "0.171.0", "@types/chai": "^4.1.4", "@types/chai-as-promised": "^7", "@types/glob": "^5.0.35", "@types/mocha": "^5.2.5", - "@types/sinon": "^5.0.1", + "@types/sinon": "^5.0.5", "chai": "^4.1.2", "chai-as-promised": "^7", "mocha": "^5.2.0", "nyc": "^13.0.1", "rimraf": "^2.6.2", - "sinon": "^6.1.4", + "sinon": "^7.1.1", "ts-node": "^7.0.1", "typedoc": "^0.11.1", - "typescript": "~3.0.0" + "typescript": "~3.1.0" }, "dependencies": {}, "peerDependencies": { - "@bentley/bentleyjs-core": "0.163.0" + "@bentley/bentleyjs-core": "0.171.0" }, "nyc": { "nycrc-path": "./node_modules/@bentley/build-tools/.nycrc" diff --git a/core/quantity/src/Parser.ts b/core/quantity/src/Parser.ts index 2b365a8..2eaee70 100644 --- a/core/quantity/src/Parser.ts +++ b/core/quantity/src/Parser.ts @@ -310,7 +310,7 @@ export class Parser { * @param format Defines the likely format of inString. * @param unitsProvider required to look up units that may be specified in inString */ - public static parseIntoQuantity(inString: string, format: Format, unitsProvider: UnitsProvider): Promise { + public static async parseIntoQuantity(inString: string, format: Format, unitsProvider: UnitsProvider): Promise { const tokens: ParseToken[] = Parser.parseQuantitySpecification(inString, format); if (tokens.length === 0) return Promise.resolve(new Quantity()); diff --git a/docs/core/bis/ec/differences-between-ec2-and-ec3.md b/docs/core/bis/ec/differences-between-ec2-and-ec3.md new file mode 100644 index 0000000..d268d24 --- /dev/null +++ b/docs/core/bis/ec/differences-between-ec2-and-ec3.md @@ -0,0 +1,37 @@ +# What's changed between ECObjects 2 supported in Power Platform and ECObjects 3 supported in iModel.js + +This page if for people who have used ECObjects in Power Platform and want to learn how it has changed in iModel.js. It does not cover all new features of EC 3, rather it focuses on what has changed between the two versions. + +## Changes to Schema + +1. Schema version now has three digits RR.WW.mm where equal RR indicates read compatibility, equal WW indicates write compatibility, mm is for additive changes or modifications that do not break read or write compatibility. +2. The namespacePrefix attribute has been replaced with 'alias'. + +## Changes to Classes + +1. ECClass has been split into 3 discrete sub types, [ECEntityClass](./ec-entity-class.md), [ECStructClass](./ec-struct-class.md) and [ECCustomAttributeClass](./ec-custom-attribute-class.md). These three types replace the individual 'isDomainClass', 'isStruct' and 'isCustomAttributeClass' attributes respectively. A consequence of this is that a class may be of only one type in EC 3, where you could make classes which were domain, struct and custom attribute in EC 2. +2. All 4 class types add a 'modifier' flag which can be None (concrete and not sealed/final), Abstract or Sealed. +3. A class may only have a base class of the same type (e.g. an ECRelationshipClass cannot have an ECEntityClass as a base class) +4. Multiple base classes are only supported on ECEntityClasses in EC 3 and additional restrictions are applied. + 1. Only one base class may be a concrete class (modifier=None) + 2. Additional base classes must be abstract and have the IsMixin custom attribute applied + 3. Properties must be unique across all base classes (e.g. Only one base class may have a 'Name' property defined or inherited) +5. ECStructClass and ECCustomAttributeClass definitions should not have any base classes + +## Changes to Relationships + +1. Relationship classes may only have one base class and that base class must have constraints which are equal to or more broad than the derived class +2. Relationship constraint classes and multiplicity on an endpoint of a relationship must have a common base class and that base class must be specified as the 'abstract constraint' of the relationship. +3. The cardinality attribute has been renamed multiplicity and the format has changed from (x,y) to (x..y), unbounded y is represented by * instead of N. +See [ECRelationships](./ec-relationship-class.md) for more details on relationship constraints. + +## Changes to Properties + +1. ECStructArrayProperty replaces ECArrayProperty with 'isStruct' set to true + +## Changes to important metadata + +1. Units - Now a first class concept with [Unit](./ec-unit.md), [KindOfQuantity](./kindOfQuantity.md) and [Format](./ec-format.md) definitions as top level EC items. The UnitSpecification custom attribute on ECProperty has been replaced with the 'kindOfQuantity' attribute. A KindOfQuantity definition defines the persistence and presentation of the value. +2. Property Category - Now a first class concept with [PropertyCategory](./property-category.md), a top level EC item. +3. The StandardValues custom attribute has been replaced with [ECEnumerations](./ec-enumeration.md), a top level EC item. +4. All standard custom attributes supported in EC 3 have been moved to the CoreCustomAttributes schema. EditorCustomAttributes and Bentley_Standard_CustomAttributes should no longer be used. \ No newline at end of file diff --git a/docs/core/bis/ec/index.md b/docs/core/bis/ec/index.md index b13b878..157200f 100644 --- a/docs/core/bis/ec/index.md +++ b/docs/core/bis/ec/index.md @@ -36,7 +36,7 @@ The remaining kinds of elements are also direct members of an ECSchema: - **[Unit](./ec-unit.md)** defines a unit of measure in terms of other units -- **[InvertedUnit](./ec-unit.md#InvertedUnit)** inverts an existing unit whose dimensional derivation is unitless, like slope. +- **[InvertedUnit](./ec-unit.md#Inverted-Unit)** inverts an existing unit whose dimensional derivation is unitless, like slope. - **[Constant](./ec-constant.md)** defines a constant value which can be used in a unit definition @@ -46,4 +46,6 @@ The remaining kinds of elements are also direct members of an ECSchema: - **[Format](./ec-format.md)** defines how to format a numeric value when displaying it to the user or for a report +- **[Changes Between ECObjects 2 and 3](./differences-between-ec2-and-ec3.md)** helpful if you have used EC in PowerPlatform. + The items that are direct members of an ECSchema must have names that are unique among all items that are direct members of an ECSchema, e.g., an ECEntityClass cannot have the same name as an ECStructClass. Uniqueness is determined using case-insensitive comparisons. See [ECName](./ec-name.md) for EC Naming Rules. \ No newline at end of file diff --git a/docs/core/bis/intro/bis-schema-validation.md b/docs/core/bis/intro/bis-schema-validation.md index 55c475b..fdd0926 100644 --- a/docs/core/bis/intro/bis-schema-validation.md +++ b/docs/core/bis/intro/bis-schema-validation.md @@ -28,6 +28,7 @@ The rules are broken into the different parts of the Schema they are validated a - A schema reference must specify a three-part version number (in the same format described above) - If the schema contains 'dynamic' (case-insensitive) in its name it must apply the **CoreCA:DynamicSchema** custom attribute - Classes within the same schema cannot have the same display label +- Classes may not use custom attributes that are deprecated ### Mixin Rules @@ -53,6 +54,7 @@ The rules are broken into the different parts of the Schema they are validated a - **bis:LinkModel** - Property overrides cannot change the persistence unit. - Subclasses of **bis:Model** cannot have additional properties defined outside of **BisCore**. +- Entity classes may not subclass deprecated classes ### Relationship Classes @@ -83,4 +85,5 @@ The rules are broken into the different parts of the Schema they are validated a - Properties must use the following supported ExtendedTypes: - **BeGuid** - **GeometryStream** - - **Json** \ No newline at end of file + - **Json** +- Properties must not use CustomAttribute **bis:CustomHandledProperty** unless CustomAttribute **bis:ClassHasHandler** is defined on their parent class (**not** derived from a base class) \ No newline at end of file diff --git a/docs/core/bis/intro/information-hierarchy.md b/docs/core/bis/intro/information-hierarchy.md index 2c8fad0..7e8a2dc 100644 --- a/docs/core/bis/intro/information-hierarchy.md +++ b/docs/core/bis/intro/information-hierarchy.md @@ -46,121 +46,7 @@ For example, a `DrawingModel` breaks down a `Drawing` Element and contains the ` ## Top of the World -The top of the information hierarchy is strictly controlled and is very similar in all BIS repositories. The top of the hierarchy is made of: - -* `RepositoryModel` -* `Subject` Elements -* `InformationPartitionElements` - - - -### RepositoryModel - -Every BIS repository has exactly 1 RepositoryModel that defines the top of the hierarchy. Elements can be inserted into to or updated within the RepositoryModel, but the RepositoryModel itself cannot be deleted. - -*The RepositoryModel is the only Model is a BIS repository that does not have a ModelModelsElement relationship and an "owning" Element* - -### Subjects - -Subjects are Elements that are used to identify things that the repository is about. The Subject class cannot be specialized (subclassed). The most important capabilities of Subject are: - -* It can have a UserLabel (inherited from Element) -* It can have a Description -* It can have child Subjects -* It can have child InformationPartitionElements - -Subjects only exist in the RepositoryModel. - -Every BIS repository has exactly 1 *root* Subject that describes what the repository is about. - -* The root Subject is contained by the RepositoryModel. -* The root Subject has no parent element as it is the top of the hierarchy. -* The root Subject can be updated, but it cannot be deleted. - -Child Subjects (optional) can be introduced to further organize the contents of the repository. - -* All child Subjects are contained by the RepositoryModel. -* Each child Subject will have another Subject as its parent. - -### InformationPartitionElements - -InformationPartitionElements are used to "partition" a Subject into different perspectives. The InformationPartitionElement class is always specialized (subclassed). - -InformationPartitionElements exist to record that a Subject is being modeled from a perspective and to be the Element that breaks down into a Model matching the correct perspective. For example a PhysicalPartition breaks down into a PhysicalModel. - -A single Subject can be modeled from multiple perspectives (physical, functional, informational, etc.), and hence can have multiple child InformationPartitionElements. - -All InformationPartitionElements are contained by the RepositoryModel. They are not used elsewhere in the information hierarchy. - -### PhysicalPartitions - -The top of a physical hierarchy starts with a `PhysicalModel` that models a `PhysicalPartition`. -It continues when another PhysicalModel breaks down a `PhysicalElement`. -For example, a plant physical layout model can break down a PhysicalElement that represents the overall plant. - -See [Physical Models and Elements](./physical-models-and-elements.md) for details of physical modeling. - - - - - -### DefinitionPartition - -The top of a definition hierarchy starts with a `DefinitionModel` that models a `DefinitionPartition`. -This allows `DefinitionElements` to be organized by how they relate to the parent `Subject` of the `DefinitionPartition`. -The can be multiple `DefinitionPartition` Elements and corresponding `DefinitionModel` Models so that definitions (instances of `DefinitionElement`) can be organized by source, discipline, or other criteria. -Each `DefinitionPartition` is identified by its [Code](./glossary.md#code). - -### DocumentPartition - -The top of a document hierarchy starts with a `DocumentListModel` that models a `DocumentPartition`. -This allows `Document` elements to be organized by how they relate to the parent `Subject` of the `DocumentPartition`. -`Drawing` and `Sheet` are 2 example subclasses of `Document`. -`Drawings` and `Sheets` are further broken down by `DrawingModels` and `SheetModels` which graphically break down the content of the drawing or sheet. - - - - - -## Links Between Perspectives - -The partitioning strategy provides a clear hierarchical organization of the data by perspective. However, the perspectives are often related. For example, a FunctionalModel may model the processes in a plant while a PhysicalModel may model the actual plant hardware. There are natural associations between the Elements in the different perspectives and those associations often should be modeled. - - - -The term "Physical Backbone" is sometimes used when discussing the organization of BIS repository data. This phrase refers to the pattern that most perspectives have relationships to the physical perspective, but not to other perspectives; this makes the physical perspective a coordinating and organizing structure for all the data in a repository. - -## Breaking Down Elements: `IParentElement` and `ISubModeledElement` - -A class of Element that can have child Elements implements the IParentElement interface. -A class of Element that can be broken down into more detail by a SubModel implements the ISubModeledElement interface. -Note that an Element class cannot implement both IParentElement and ISubModeledElement. - -## Portions - -A `Portion` Element is used to construct a breakdown hierarchy. -It identifies a *portion* of something at a higher level that is broken down into more detail by a lower level SubModel. -For example, a Floor is a portion of a Building that is broken down by a SubModel that contains the detail about that floor. -Example BIS Portion subclasses: - -* `PhysicalPortion` -* `SpatialLocationPortion` -* `FunctionalPortion` +The top of the information hierarchy is strictly controlled and is very similar in all BIS repositories. Its contents are explained in [Top of the World](./top-of-the-world.md) + +As discussed in [Modeling with BIS](./modeling-with-bis.md), objects in the real world can be thought about from different *modeling perspectives*. A modeling perspective is a way of conceptualizing the real world for a particular purpose. For example, a Sewer System can be thought about from many modeling perspectives: + +* As a physical 3D reality with form, material and mass (the *physical* perspective). +* As a system for hydrological conveyance (an *analytical* perspective) +* As a set of components that require scheduled and emergency maintenance (a *maintenance* perspective) +* As a load on a wastewater treatment facility that needs to have adequate capacity (a *functional* perspective) + +## Keeping Modeling Perspectives Segregated + +Each modeling perspective simplifies objects in the real world in a different way; this requires different specialized data structures for each perspective. This is manifested in BIS classes as explained in the following section. + +Each perspective's data +(`InformationPartitionElement`s, `Model`s, `Element`s, etc.) is segregated from other perspectives' data in order to allow each perspective to be optimally organized. Relationships between the `Element`s of different perspectives are used to indicate that they are all modeling the same objects, just from different perspectives. + +### Modeling Perspectives and BIS Class Hierarchy + +Modeling perspectives are represented directly in the BIS class hierarchies as: + +* `InformationPartitionElement` subclasses +* `Model` subclasses +* `Element` subclasses + +For every modeling perspective there is a corresponding `InformationPartitionElement` subclass and a `Model` subclass. + +Modeling perspectives are also manifested in `Element` subclasses. Often there is an `Element` subclass that directly corresponds to a modeling perspective. `Element`s placed in a `Model` need to have a modeling perspective that is compatible with the `Model`. + + + +[Top of the World](./top-of-the-world.md) discusses `InformationPartitionElement`s and [Model Fundamentals](model-fundamentals.md) discusses `Model`s. + + + +### Modeling Perspective Consistency of Partitions, Models and Elements + +As is described in [Top of the World](./top-of-the-world), for every Subject, there may be zero or more `InformationPartitionElement` child `Element`s. Each of those `InformationPartitionElement`s is effectively a declaration of modeling perspective and starts a `Model` hierarchy that is of that the declared modeling perspective. + +Each `InformationPartitionElement` breaks down into a `Model` that is of the same modeling perspective. That `Model` in turn contains only `Element`s of the same modeling perspective. Some of those `Element`s will have breakdown `Model`s; the breakdown `Model`s must be of the same modeling perspective as the `Element` they break down. + +These modeling perspective rules enforce a minimum level of logical data consistency. For example, they prevent the placement of a physical fire hydrant `Element` into a section drawing `Model`. + + + + + +### Abstract, Concrete and Sealed Modeling Perspectives + +Modeling Perspectives can be considered to be abstract, concrete, or sealed to correspond with the `InformationPartitionElement` and `Model` subclasses that implement them: + +* An *abstract* modeling perspective is used only to logically group more-specialized perspectives and is implemented by abstract `InformationPartitionElement` and `Model` subclasses. + +* A *concrete* modeling perspective is used directly to model reality and is implemented by concrete `InformationPartitionElement` and `Model` subclasses. + +* A *sealed* modeling perspective is a concrete modeling perspective that is not allowed to be further specialized. A sealed modeling perspective is implemented with sealed `InformationPartitionElement` and `Model` subclasses. + +## Standard Modeling Perspectives + +It is not possible to predict all of the modeling perspectives that may eventually be needed in BIS. BIS does, however, provide a core set of modeling perspectives from which other modeling perspectives must derive. + +The core modeling perspectives are: + +* Geometric (abstract) + * Geometric2d (abstract) + * Graphical2d (abstract) + * Sheet (concrete) + * Drawing (concrete) + * SectionDrawing (concrete) + * Geometric3d (abstract) + * Spatial (abstract) + * Analytical (abstract) + * SpatialLocation (concrete) + * Physical (sealed) + * WebMercator (concrete) +* Role (abstract) + * Functional (concrete) +* Information (abstract) + * GroupInformation (abstract) + * InformationRecord (concrete) + * Definition (concrete) + * (Repository) (sealed) + * Dictionary (sealed) + * DocumentList (concrete) + * Link (concrete) + + + +If the need for a new core modeling perspective is discovered (none of the existing core modeling perspectives is appropriate as a parent perspective), new ones can be added. + +### Physical Modeling Perspective + +The Physical modeling perspective views reality as objects with form, material(s) and mass in 3D space. The Physical modeling perspective merits special discussion as it plays such an important role in BIS. + +There is one and only one Physical modeling perspective. The Physical modeling perspective is used and shared by most disciplines, just like the physical components of each discipline must co-exist in the same physical space. If there is one sewer pipe in reality, there can only be one physical representation of that sewer pipe. + +The Physical modeling perspective cannot be "subclassed". (For legacy reasons there are some subclasses of `PhysicalModel` in BIS schemas, but those subclasses are never used.) + +See [Physical Models and Elements](./physical-models-and-elements.md) for details of physical modeling. + +#### Physical Backbone + +The principle of a "physical backbone" in BIS states that the one thing that all disciplines can agree upon is physical reality, and thus the physical perspective should be the "touchstone" among other perspectives. `Elements` representing a non-physical perspective of a physical object will typically have a relationship to a `PhysicalElement` modeling the object from a Physical perspective. + +### Functional Modeling Perspectives + +Functional modeling perspectives view reality as objects intended to perform a function. Often those objects are connected to form a functional system. + +An example of a functional modeling perspective is viewing the interconnected components of a process plant as a system that performs a function. + +See [Functional Models and Elements](./functional-models-and-elements.md) for details of functional modeling. + +### Analytical Modeling Perspectives + +The analytical modeling perspective views reality as objects in 3D space that participate in a phenomenon that can be analyzed. + +An example of an analytical modeling perspective is thermal analysis of a building, where the components of the building have thermal properties and may be heat sources or sinks. + +There are similarities between the Functional and Analytical perspectives. The primary difference between the two is that for the Analytical perspective, 3D locations are critical to the behavior. + +Note that some analyses can be performed directly on the Physical Perspective data; these analyses do not require conceptually reality from a custom perspective. + +See [Analytical Models and Elements](./analysis-models-and-elements.md) for details of analytical modeling. + + + + + + + + + +## Domains and Modeling Perspectives + +A domain may or may not require a custom modeling perspective. The need for a custom modeling perspective corresponds to a need to model reality using concepts that are significantly different from other existing modeling perspectives. + +Structural Steel Detailing is an example of a domain that does ***not*** require its own modeling perspective. That domain will require custom classes to represent the physical items that are important to it, but all of those items are viewed from the Physical modeling perspective. Structural Steel Detailing might also need some scheduling or costing information; that information is unlikely to require a custom modeling perspective, as costing and scheduling are common needs. + +Hydraulic Analysis, on the other hand, does require a custom modeling perspective. This perspective will model reality as a system that transports and stores water. Reality will be simplified into a network of conduits and other items, with properties and relationships appropriate for hydraulic analysis. diff --git a/docs/core/bis/intro/top-of-the-world.md b/docs/core/bis/intro/top-of-the-world.md new file mode 100644 index 0000000..4e1d6a9 --- /dev/null +++ b/docs/core/bis/intro/top-of-the-world.md @@ -0,0 +1,87 @@ +# Top of the World + + + +BIS repositories have a strict hierarchical organization. This page describes the top of that hierarchy and how it functions as a *table of contents* for the repository as a whole. This *table of contents* consists of: + +* `RepositoryModel` +* `Subject`s +* `InformationPartitionElement`s + + + +## RepositoryModel + +Every BIS repository has exactly one `RepositoryModel` that defines the top of the hierarchy. `Element`s can be inserted into to or updated within the `RepositoryModel`, but the `RepositoryModel` itself cannot be deleted. + +*The RepositoryModel is the only Model in a BIS repository that does not have a `ModelModelsElement` relationship and an "owning" `Element`* + +## Subjects + +`Subject`s are `Element`s that are used to identify things that the repository is *about*. The `Subject` class cannot be specialized (subclassed). The most important capabilities of `Subject` are: + +* It can have a UserLabel (inherited from Element) +* It can have a Description +* It can have child `Subject`s +* It can have child `InformationPartitionElement`s + +`Subject`s only exist in the `RepositoryModel`. + +Every BIS repository has exactly one *root* `Subject` that describes what the repository as a whole is about. + +* The root `Subject` - like all `Subject`s - is contained by the `RepositoryModel`. +* The root `Subject` has no parent element as it is the top of the `Subject` hierarchy. +* The root `Subject` can be updated, but it cannot be deleted. + +Child `Subject`s (optional) can be introduced to further organize the contents of the repository. + +* Child `Subject`s - like all `Subject`s - are contained by the `RepositoryModel`. +* Child `Subject`s have another `Subject` as a parent. + +## InformationPartitionElements + +As discussed in [Modeling Perspectives](./modeling-perspectives.md) `Subject`s can be viewed and modeled from multiple modeling perspectives (physical, functional, analytical, etc.). `InformationPartitionElement`s are used to "partition" a `Subject` into different modeling perspectives. + +When it is determined that a `Subject` is to be modeled from a particular modeling perspective, an `InformationPartitionElement` of the appropriate modeling perspective is added as a child of the `Subject`. That InformationPartitionElement is the start of a Model hierarchy representing the modeling perspective. The `InformationPartitionElement` is immediately broken down into a `Model` of the same modeling perspective. + +It is possible for a `Subject` to have multiple `InformationPartitionElement`s of the same modeling perspective. An example of this would be having two `StructuralAnalyticalPartition`s for a building (the `Subject`) that has an isolation joint that divides the building into two separate structures. + +`InformationPartitionElement`s always have a parent `Subject` and are never used outside of the `RepositoryModel`. + + + + \ No newline at end of file diff --git a/docs/core/bis/leftNav.md b/docs/core/bis/leftNav.md index 5e32001..022affd 100644 --- a/docs/core/bis/leftNav.md +++ b/docs/core/bis/leftNav.md @@ -63,4 +63,5 @@ - [Phenomenon](./ec/ec-phenomenon.md) - [UnitSystem](./ec/ec-unitsystem.md) - [Format](./ec/ec-format.md) -- [ECName](./ec/ec-name.md) \ No newline at end of file +- [ECName](./ec/ec-name.md) +- [Changes Between ECObjects 2 and 3](./ec/differences-between-ec2-and-ec3.md) \ No newline at end of file diff --git a/docs/core/delta/NextVersion.md b/docs/core/changehistory/0.163.0.md similarity index 56% rename from docs/core/delta/NextVersion.md rename to docs/core/changehistory/0.163.0.md index ca15a8b..7f4eee9 100644 --- a/docs/core/delta/NextVersion.md +++ b/docs/core/changehistory/0.163.0.md @@ -1,27 +1,33 @@ +--- +deltaDoc: true +version: '0.163.0' +--- +# 0.163.0 Change Notes + ## Breaking changes to Id64 The use of [Id64]($bentleyjs-core) objects to represent 64-bit IDs was determined to produce measurable negative impact on both memory consumption and code execution time. This impact was amplified by inconsistencies within the iModel.js library - APIs dealing with 64-bit IDs would variously represent them as plain `string`s, `Id64` objects, or either through use of the type alias `Id64String = Id64 | string`. Frequent conversion from one representation to another would often be required when using these APIs. The naming of some functions was also ambiguous. To address these problems the following changes were made: -* The [Id64String]($bentleyjs-core) type alias was reduced to a simple alias for `string`. It now serves as a marker type indicating that a `string` variable or function argument is expected to conform to the semantics of a well-formed 64-bit ID. -* All function arguments, function return types, and class members of type `Id64` were changed to `Id64String`. -* The Id64 class was turned into a namespace. -* Functions for validating, and interrogating `Id64String`s were added to the `Id64` namespace. -* The code for parsing, validating, and interrogating `Id64String`s was optimized to execute more quickly. -* Id64.getLow() and Id64.getHigh() were renamed to [Id64.getLocalId]($bentleyjs-core) and [Id64.getBriefcaseId]($bentleyjs-core). +- The [Id64String]($bentleyjs-core) type alias was reduced to a simple alias for `string`. It now serves as a marker type indicating that a `string` variable or function argument is expected to conform to the semantics of a well-formed 64-bit ID. +- All function arguments, function return types, and class members of type `Id64` were changed to `Id64String`. +- The Id64 class was turned into a namespace. +- Functions for validating, and interrogating `Id64String`s were added to the `Id64` namespace. +- The code for parsing, validating, and interrogating `Id64String`s was optimized to execute more quickly. +- Id64.getLow() and Id64.getHigh() were renamed to [Id64.getLocalId]($bentleyjs-core) and [Id64.getBriefcaseId]($bentleyjs-core). See [Working with IDs](../learning/common/Id64.md) for an updated overview. The pervasiveness of 64-bit IDs in iModel.js make these changes very likely to break existing code in consumers of the iModel.js packages. To adapt such code: -* Replace all usage of `Id64` objects with `Id64String`. -* Replace all calls to the `Id64` constructor with a call to one of the functions in [Id64]($bentleyjs-core) returning an `Id64String` based on your input to the constructor: - * If your input is a JSON representation of an ID - type `string | undefined` - use [Id64.fromJSON]($bentleyjs-core). - * If your input is a string, use [Id64.fromString]($bentleyjs-core) if the string is not known to already contain a well-formed ID; otherwise elide the call. - * If your input is an array of 2 numbers indicating a briefcase ID and local ID, use [Id64.fromLocalAndBriefcaseIds]($bentleyjs-core). -* Replace all calls to non-static `Id64` methods with equivalent methods accepting an `Id64String`. -* Replace calls to `equals` and `areEqual` with the built-in comparison operators. +- Replace all usage of `Id64` objects with `Id64String`. +- Replace all calls to the `Id64` constructor with a call to one of the functions in [Id64]($bentleyjs-core) returning an `Id64String` based on your input to the constructor: + - If your input is a JSON representation of an ID - type `string | undefined` - use [Id64.fromJSON]($bentleyjs-core). + - If your input is a string, use [Id64.fromString]($bentleyjs-core) if the string is not known to already contain a well-formed ID; otherwise elide the call. + - If your input is an array of 2 numbers indicating a briefcase ID and local ID, use [Id64.fromLocalAndBriefcaseIds]($bentleyjs-core). +- Replace all calls to non-static `Id64` methods with equivalent methods accepting an `Id64String`. +- Replace calls to `equals` and `areEqual` with the built-in comparison operators. ## Breaking changes to Guid @@ -29,15 +35,19 @@ To keep Id64 and Guid consistent, similar changes were done to the Guid class. The following changes were made: -* The [GuidString]($bentleyjs-core) type alias was reduced to a simple alias for `string`. It now serves as a marker type indicating that a `string` variable or function argument is expected to conform to the semantics of a well-formed GUID. -* All function arguments, function return types, and class members of type `Guid` were changed to `GuidString`. -* The Guid class was turned into the [Guid]($bentleyjs-core) namespace. -* Functions for validating `GuidString`s were added to the `Guid` namespace. +- The [GuidString]($bentleyjs-core) type alias was reduced to a simple alias for `string`. It now serves as a marker type indicating that a `string` variable or function argument is expected to conform to the semantics of a well-formed GUID. +- All function arguments, function return types, and class members of type `Guid` were changed to `GuidString`. +- The Guid class was turned into the [Guid]($bentleyjs-core) namespace. +- Functions for validating `GuidString`s were added to the `Guid` namespace. The pervasiveness of GUIDs in iModel.js make these changes very likely to break existing code in consumers of the iModel.js packages. To adapt such code: -* Replace all usage of `Guid` objects with `GuidString`. -* Replace all calls to the `Guid` constructor with a call to one of the functions from the [Guid]($bentleyjs-core) namespace returning an `GuidString` based on your input to the constructor - * In particular, `new Guid(true)` is to be replaced by `Guid.createValue()`. -* Replace all calls to non-static `Guid` methods with equivalent methods accepting an `GuidString`. -* Replace calls to `equals` and `areEqual` with the built-in comparison operators. +- Replace all usage of `Guid` objects with `GuidString`. +- Replace all calls to the `Guid` constructor with a call to one of the functions from the [Guid]($bentleyjs-core) namespace returning an `GuidString` based on your input to the constructor + - In particular, `new Guid(true)` is to be replaced by `Guid.createValue()`. +- Replace all calls to non-static `Guid` methods with equivalent methods accepting an `GuidString`. +- Replace calls to `equals` and `areEqual` with the built-in comparison operators. + +## Breaking changes to RPC web server configuration + +If you do not use [IModelJsExpressServer]($backend) to manage your backend web server, you must add a GET route that invokes [WebAppRpcProtocol.handleOperationGetRequest]($common) to your express application. Cacheable RPC operations (including tile content requests) now use the HTTP GET method when configured using [WebAppRpcConfiguration]($common). diff --git a/docs/core/changehistory/0.164.0.md b/docs/core/changehistory/0.164.0.md new file mode 100644 index 0000000..c5687b5 --- /dev/null +++ b/docs/core/changehistory/0.164.0.md @@ -0,0 +1,18 @@ +--- +deltaDoc: true +version: '0.164.0' +--- +# 0.164.0 Change Notes + +## Breaking changes to DisplayStyles + +The JSON representation of [DisplayStyle]($backend) and [DisplayStyleState]($frontend) expressed by the [DisplayStyleProps]($common) interface did not match the actual persistent JSON representation. Additionally, `DisplayStyle` provided no API for querying or modifying most of its settings. + +To address these problems the following changes were made: + +- [DisplayStyleSettingsProps]($common) and [DisplayStyle3dSettingsProps]($common) were added to document the persistent JSON representation of a `DisplayStyle`'s settings. +- Various JSON types used by `DisplayStyleSettingsProps` were moved from imodeljs-frontend to imodeljs-common; and new ones were added. +- `DisplayStyleProps` and `DisplayStyle3dProps` were modified to reflect the persistent JSON representation: specifically, the presence of a `jsonProperties.styles` object to store the settings. +- [DisplayStyleSettings]($common) and [DisplayStyle3dSettings]($common) classes were added to provide access to all of the settings and to keep the JSON properties in sync with modifications to those settings. +- A `DisplayStyleSettings` member was added to `DisplayStyle` and `DisplayStyleState`. + diff --git a/docs/core/changehistory/0.165.0.md b/docs/core/changehistory/0.165.0.md new file mode 100644 index 0000000..36133b0 --- /dev/null +++ b/docs/core/changehistory/0.165.0.md @@ -0,0 +1,8 @@ +--- +deltaDoc: true +version: '0.165.0' +--- +# 0.165.0 Change Notes + + +Minor changes. \ No newline at end of file diff --git a/docs/core/changehistory/0.166.0.md b/docs/core/changehistory/0.166.0.md new file mode 100644 index 0000000..abb65f4 --- /dev/null +++ b/docs/core/changehistory/0.166.0.md @@ -0,0 +1,8 @@ +--- +deltaDoc: true +version: '0.166.0' +--- +# 0.166.0 Change Notes + + +Minor changes. \ No newline at end of file diff --git a/docs/core/changehistory/0.167.0.md b/docs/core/changehistory/0.167.0.md new file mode 100644 index 0000000..ecea1b3 --- /dev/null +++ b/docs/core/changehistory/0.167.0.md @@ -0,0 +1,8 @@ +--- +deltaDoc: true +version: '0.167.0' +--- +# 0.167.0 Change Notes + + +Minor changes. \ No newline at end of file diff --git a/docs/core/changehistory/0.168.0.md b/docs/core/changehistory/0.168.0.md new file mode 100644 index 0000000..65fb074 --- /dev/null +++ b/docs/core/changehistory/0.168.0.md @@ -0,0 +1,8 @@ +--- +deltaDoc: true +version: '0.168.0' +--- +# 0.168.0 Change Notes + + +Minor changes. \ No newline at end of file diff --git a/docs/core/changehistory/0.169.0.md b/docs/core/changehistory/0.169.0.md new file mode 100644 index 0000000..0f7901e --- /dev/null +++ b/docs/core/changehistory/0.169.0.md @@ -0,0 +1,9 @@ +--- +deltaDoc: true +version: '0.169.0' +--- +# 0.169.0 Change Notes + + +Minor changes. + diff --git a/docs/core/changehistory/0.170.0.md b/docs/core/changehistory/0.170.0.md new file mode 100644 index 0000000..0de246c --- /dev/null +++ b/docs/core/changehistory/0.170.0.md @@ -0,0 +1,66 @@ +--- +deltaDoc: true +version: '0.170.0' +--- +# 0.170.0 Change Notes + +## Breaking changes to Viewport.readPixels + +The display system underwent several performance optimizations targeted at increasing framerate and reducing memory consumption. These changes produced significant improvements in both of these metrics, but unfortunately necessitated a change to the way that [Viewport.readPixels]($frontend) works. The function no longer *returns* a [Pixel.Buffer]($frontend) object; instead it accepts a *callback function* to receive the pixel buffer. Once the callback returns, the data in the pixel buffer becomes invalid - so storing a reference to the buffer and attempting to use it outside of the callback will produce unexpected results. + +These changes also enable some enhancements and simplications to the `readPixels` function: + +* A [Pixel.Data]($frontend) object now supplies complete information about the [Feature]($common) which produced the pixel, including element ID, subcategory ID, and [GeometryClass]($frontend). Previously it could only supply the element ID. +* The [Pixel.Selector]($frontend) enumeration has been simplified to allow the caller to request the geometry and distance fraction of each pixel, the feature of each pixel, or both. + +As an example of how to adjust your code in response to these changes, consider the following example: + +` + /** Returns true if the specified element ID is drawn within the specified rectangular region of the viewport */ + function isElementInRect(elementId: Id64String, vp: Viewport, rect: ViewRect): Id64String { + const pixels: Pixel.Buffer = vp.readPixels(rect, Pixel.Selector.ElementId); + if (undefined === pixels) + return false; + + for (let x = rect.left; x < rect.right; x++) { + for (let y = rect.top; y < rect.bottom; y++) { + const pixel: Pixel.Data = pixels.getPixel(x, y); + if (undefined !== pixel.elementId && pixel.elementId === elementId) + return true; + } + } + + return false; + }` + +This function would be rewritten using a callback as follows: + +` + /** Returns true if the specified element ID is drawn within the specified rectangular region of the viewport */ + function isElementInRect(elementId: Id64String, vp: Viewport, rect: ViewRect): Id64String { + let isInRect = false; + vp.readPixels(rect, Pixel.Selector.Feature, (pixels?: Pixel.Buffer) => { + if (undefined === pixels) + return; + + for (let x = rect.left; x < rect.right; x++) { + for (let y = rect.top; y < rect.bottom; y++) { + const pixel: Pixel.Data = pixels.getPixel(x, y); + if (undefined !== pixel.elementId && pixel.elementId === elementId) { + isInRect = true; + return; + } + } + } + }); + + return isInRect; + }` + +## Breaking changes to IModelClient + +To be consistent with imodeljs code style, [IModelClient]($clients) methods have been changed to properties and start with lower case letter. + +For example, querying [Version]($clients)s with an [IModelHubClient]($clients) object `client` should be changed from `client.Versions().get(alctx, token, imodelId)` to `client.versions.get(alctx, token, imodelId)`. + + diff --git a/docs/core/changehistory/0.171.0.md b/docs/core/changehistory/0.171.0.md new file mode 100644 index 0000000..83ca275 --- /dev/null +++ b/docs/core/changehistory/0.171.0.md @@ -0,0 +1,8 @@ +--- +deltaDoc: true +version: '0.171.0' +--- +# 0.171.0 Change Notes + +Minor changes. + diff --git a/docs/core/changehistory/NextVersion.md b/docs/core/changehistory/NextVersion.md new file mode 100644 index 0000000..1ece2f8 --- /dev/null +++ b/docs/core/changehistory/NextVersion.md @@ -0,0 +1,5 @@ +--- +ignore: true +--- +# NextVersion + diff --git a/docs/core/changehistory/index.md b/docs/core/changehistory/index.md new file mode 100644 index 0000000..e6c5283 --- /dev/null +++ b/docs/core/changehistory/index.md @@ -0,0 +1,3 @@ +# 0.171.0 Change Notes + +Minor changes. diff --git a/docs/core/changehistory/leftNav.md b/docs/core/changehistory/leftNav.md new file mode 100644 index 0000000..bded87f --- /dev/null +++ b/docs/core/changehistory/leftNav.md @@ -0,0 +1,21 @@ +## Change History +--- + +### Versions +- [0.171.0](./0.171.0.md) + +- [0.170.0](./0.170.0.md) + +- [0.169.0](./0.169.0.md) + +- [0.168.0](./0.168.0.md) + +- [0.167.0](./0.167.0.md) + +- [0.166.0](./0.166.0.md) + +- [0.165.0](./0.165.0.md) + +- [0.164.0](./0.164.0.md) + +- [0.163.0](./0.163.0.md) diff --git a/docs/core/config/docSites.json b/docs/core/config/docSites.json index df442d7..94c90eb 100644 --- a/docs/core/config/docSites.json +++ b/docs/core/config/docSites.json @@ -19,9 +19,14 @@ "docSiteId": 5, "displayName": "Learning" }, + "changehistory": { + "docSiteId": 6, + "displayName": "Change History" + }, "metadata": { "home": "landing-page", "logo": "landing-page/assets/imodeljs-logo.svg", + "smallLogo": "landing-page/assets/imodeljs-icon.svg", "sponsoredByLogo": "landing-page/assets/bentley.svg", "favicon": "landing-page/assets/imodeljs_16.png", "docSiteOrder": [ @@ -29,21 +34,15 @@ "getting-started", "learning", "bis", - "reference" + "reference", + "changehistory" ], "headerTabs": { - "Getting Started": { - "docSite": "getting-started" - }, - "BIS": { - "docSite": "bis" - }, - "Learning": { - "docSite": "learning" - }, - "API Reference": { - "docSite": "reference" - } + "Getting Started": "getting-started", + "BIS": "bis", + "Learning": "learning", + "API Reference": "reference", + "0.171.0": "changehistory" }, "packageAliases": { "common": "imodeljs-common", @@ -58,4 +57,4 @@ "headerHTML": "", "ogShareImage": "landing-page/assets/twitter-share.png" } -} \ No newline at end of file +} diff --git a/docs/core/config/layouts/agentApplication.html b/docs/core/config/layouts/agentApplication.html index 9a3fe41..7dcb829 100644 --- a/docs/core/config/layouts/agentApplication.html +++ b/docs/core/config/layouts/agentApplication.html @@ -1,9 +1,7 @@ {{>header}}
    -
    - {{>table_of_contents}} -
    + {{>table_of_contents}}
    {{{editLink this}}} diff --git a/docs/core/config/layouts/browserApplication.html b/docs/core/config/layouts/browserApplication.html index ed49707..8000f90 100644 --- a/docs/core/config/layouts/browserApplication.html +++ b/docs/core/config/layouts/browserApplication.html @@ -1,9 +1,7 @@ {{>header}}
    -
    - {{>table_of_contents}} -
    + {{>table_of_contents}}
    {{{editLink this}}} diff --git a/docs/core/config/layouts/partials/landing_middle_content.html b/docs/core/config/layouts/partials/landing_middle_content.html index f0d62c1..52ddb02 100644 --- a/docs/core/config/layouts/partials/landing_middle_content.html +++ b/docs/core/config/layouts/partials/landing_middle_content.html @@ -73,4 +73,15 @@

    Try iModel.js now

    onclick="replaceViewer(event)">Open iModel.js Viewer Example
    -
    \ No newline at end of file +
    + + \ No newline at end of file diff --git a/docs/core/config/layouts/partials/landing_page_header.html b/docs/core/config/layouts/partials/landing_page_header.html index e1d47f4..7ed1706 100644 --- a/docs/core/config/layouts/partials/landing_page_header.html +++ b/docs/core/config/layouts/partials/landing_page_header.html @@ -49,6 +49,10 @@ {{#if this.docSiteMetadata.logo}} {{/if}} + {{#if this.docSiteMetadata.smallLogo}} + + {{/if}} + + + } + isActive={false} + key=".0/.$animationPause" + onClick={[Function]} + renderHistoryTo={[Function]} + renderPanelTo={[Function]} + title="tools.AnalysisAnimation.ToolSettings.pause" + > + + + + } + isActive={true} + key=".0/.$animationStop" + onClick={[Function]} + renderHistoryTo={[Function]} + renderPanelTo={[Function]} + title="tools.AnalysisAnimation.ToolSettings.stop" + > + + +
    + + + + + + +`; diff --git a/ui/framework/src/test/tools/AnalysisAnimationToolSettings.test.tsx b/ui/framework/src/test/tools/AnalysisAnimationToolSettings.test.tsx new file mode 100644 index 0000000..15cba8d --- /dev/null +++ b/ui/framework/src/test/tools/AnalysisAnimationToolSettings.test.tsx @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import * as React from "react"; +import { expect } from "chai"; +import { mount } from "enzyme"; + +import TestUtils from "../TestUtils"; +import { ConfigurableUiManager, ZoneState, WidgetState, FrontstageManager, AnalysisAnimationTool, FrontstageProvider, FrontstageProps, Frontstage, Zone, Widget, ToolWidget } from "../../index"; +import { AnalysisAnimationToolSettings } from "../../index"; + +describe("AnalysisAnimationToolUiProvider", () => { + + before(async () => { + await TestUtils.initializeUiFramework(); + + class Frontstage1 extends FrontstageProvider { + public get frontstage(): React.ReactElement { + return ( + } + />, + ]} + /> + } + /> + ); + } + } + + const frontstageProvider = new Frontstage1(); + ConfigurableUiManager.addFrontstageProvider(frontstageProvider); + }); + + it("starting a tool with tool settings", () => { + const frontstageDef = FrontstageManager.findFrontstageDef("ToolUiProvider-TestFrontstage"); + expect(frontstageDef).to.not.be.undefined; + + if (frontstageDef) { + FrontstageManager.setActiveFrontstageDef(frontstageDef); // tslint:disable-line:no-floating-promises + + FrontstageManager.setActiveToolId(AnalysisAnimationTool.toolId); + expect(FrontstageManager.activeToolId).to.eq(AnalysisAnimationTool.toolId); + + const toolInformation = FrontstageManager.activeToolInformation; + expect(toolInformation).to.not.be.undefined; + + if (toolInformation) { + const toolUiProvider = toolInformation.toolUiProvider; + expect(toolUiProvider).to.not.be.undefined; + + if (toolUiProvider) { + expect(toolUiProvider.toolSettingsNode).to.not.be.undefined; + } + } + + const toolSettingsNode = FrontstageManager.activeToolSettingsNode; + expect(toolSettingsNode).to.not.be.undefined; + } + }); + + it("AnalysisAnimationToolSettings will mount", () => { + const wrapper = mount(); + expect(wrapper).to.not.be.undefined; + + wrapper.should.matchSnapshot(); + + const durationItem = wrapper.find("#animationDuration"); + expect(durationItem.length).to.eq(1); + durationItem.simulate("change", { target: { value: "15" } }); + expect(wrapper.state("animationDuration")).to.eq(15000); + + const loopItem = wrapper.find("#animationLoop"); + expect(loopItem.length).to.eq(1); + loopItem.simulate("change", { target: { checked: false } }); + expect(wrapper.state("isLooping")).to.eq(false); + + // all the other items require an active content control + wrapper.unmount(); + }); + +}); diff --git a/ui/framework/src/test/tools/Tool1.ts b/ui/framework/src/test/tools/Tool1.ts new file mode 100644 index 0000000..e55f95c --- /dev/null +++ b/ui/framework/src/test/tools/Tool1.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { + IModelApp, PrimitiveTool, + BeButtonEvent, EventHandled, +} from "@bentley/imodeljs-frontend"; + +import { Point3d } from "@bentley/geometry-core"; + +export class Tool1 extends PrimitiveTool { + public static toolId = "Tool1"; + public readonly points: Point3d[] = []; + + public requireWriteableTarget(): boolean { return false; } + public onPostInstall() { super.onPostInstall(); this.setupAndPromptForNextAction(); } + + public setupAndPromptForNextAction(): void { + IModelApp.notifications.outputPromptByKey("SampleApp:tools.Tool1.Prompts.GetPoint"); + } + + public async onDataButtonDown(ev: BeButtonEvent): Promise { + this.points.push(ev.point.clone()); + this.setupAndPromptForNextAction(); + return EventHandled.No; + } + + public async onResetButtonUp(_ev: BeButtonEvent): Promise { + IModelApp.toolAdmin.startDefaultTool(); + return EventHandled.No; + } + + public onRestartTool(): void { + const tool = new Tool1(); + if (!tool.run()) + this.exitTool(); + } +} diff --git a/ui/framework/tests/utils/SyncUiEventDispatcher.test.ts b/ui/framework/src/test/utils/SyncUiEventDispatcher.test.ts similarity index 98% rename from ui/framework/tests/utils/SyncUiEventDispatcher.test.ts rename to ui/framework/src/test/utils/SyncUiEventDispatcher.test.ts index 1a201be..875f851 100644 --- a/ui/framework/tests/utils/SyncUiEventDispatcher.test.ts +++ b/ui/framework/src/test/utils/SyncUiEventDispatcher.test.ts @@ -2,7 +2,7 @@ * Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license terms. *--------------------------------------------------------------------------------------------*/ -import { SyncUiEventDispatcher, SyncUiEventArgs } from "../../src/index"; +import { SyncUiEventDispatcher, SyncUiEventArgs } from "../..//index"; import { expect } from "chai"; import * as sinon from "sinon"; diff --git a/ui/framework/tests/utils/ViewUtilities.test.tsx b/ui/framework/src/test/utils/ViewUtilities.test.tsx similarity index 94% rename from ui/framework/tests/utils/ViewUtilities.test.tsx rename to ui/framework/src/test/utils/ViewUtilities.test.tsx index 918d1ae..fabb442 100644 --- a/ui/framework/tests/utils/ViewUtilities.test.tsx +++ b/ui/framework/src/test/utils/ViewUtilities.test.tsx @@ -1,35 +1,35 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import { expect } from "chai"; -import TestUtils from "../TestUtils"; -import { ViewUtilities } from "../../src/utils"; - -describe("ViewUtilities", () => { - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - it("should get bis base class name", () => { - const bisBaseClass = ViewUtilities.getBisBaseClass("xyz:SheetViewDefinition"); - expect(bisBaseClass).to.eq("SheetViewDefinition"); - }); - - it("should recognize spatial view", () => { - expect(ViewUtilities.isSpatial("SpatialViewDefinition")).to.be.true; - expect(ViewUtilities.isSpatial("OrthographicViewDefinition")).to.be.true; - expect(ViewUtilities.isSpatial("")).to.be.false; - }); - - it("should recognize drawing view", () => { - expect(ViewUtilities.isDrawing("DrawingViewDefinition")).to.be.true; - expect(ViewUtilities.isDrawing("")).to.be.false; - }); - - it("should recognize sheet view", () => { - expect(ViewUtilities.isSheet("SheetViewDefinition")).to.be.true; - expect(ViewUtilities.isSheet("")).to.be.false; - }); - -}); +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import { expect } from "chai"; +import TestUtils from "../TestUtils"; +import { ViewUtilities } from "../..//utils"; + +describe("ViewUtilities", () => { + before(async () => { + await TestUtils.initializeUiFramework(); + }); + + it("should get bis base class name", () => { + const bisBaseClass = ViewUtilities.getBisBaseClass("xyz:SheetViewDefinition"); + expect(bisBaseClass).to.eq("SheetViewDefinition"); + }); + + it("should recognize spatial view", () => { + expect(ViewUtilities.isSpatial("SpatialViewDefinition")).to.be.true; + expect(ViewUtilities.isSpatial("OrthographicViewDefinition")).to.be.true; + expect(ViewUtilities.isSpatial("")).to.be.false; + }); + + it("should recognize drawing view", () => { + expect(ViewUtilities.isDrawing("DrawingViewDefinition")).to.be.true; + expect(ViewUtilities.isDrawing("")).to.be.false; + }); + + it("should recognize sheet view", () => { + expect(ViewUtilities.isSheet("SheetViewDefinition")).to.be.true; + expect(ViewUtilities.isSheet("")).to.be.false; + }); + +}); diff --git a/ui/framework/src/tools/AnalysisAnimation.ts b/ui/framework/src/tools/AnalysisAnimation.ts new file mode 100644 index 0000000..ada68a6 --- /dev/null +++ b/ui/framework/src/tools/AnalysisAnimation.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +import { + IModelApp, PrimitiveTool, + BeButtonEvent, EventHandled, Viewport, +} from "@bentley/imodeljs-frontend"; + +import { Point3d } from "@bentley/geometry-core"; +import { ConfigurableUiManager } from "../configurableui/ConfigurableUiManager"; +import { AnalysisAnimationToolSettingsProvider } from "./AnalysisAnimationToolSettings"; + +/** Tool that shows animation of Analysis information store as a 'special' property in the display style. */ +export class AnalysisAnimationTool extends PrimitiveTool { + public static toolId = "AnalysisAnimationTool"; + public readonly points: Point3d[] = []; + + /** Allow tool to run on ready only iModels. */ + public requireWriteableTarget(): boolean { return false; } + public onPostInstall() { super.onPostInstall(); this.setupAndPromptForNextAction(); } + + /** Show tool prompt. */ + public setupAndPromptForNextAction(): void { + IModelApp.notifications.outputPromptByKey("UiFramework:tools.AnalysisAnimation.Prompts.SelectView"); + } + + /** Handle user pressing left mouse button. */ + public async onDataButtonDown(ev: BeButtonEvent): Promise { + this.points.push(ev.point.clone()); + this.setupAndPromptForNextAction(); + return EventHandled.No; + } + + /** Handle user pressing right mouse button. */ + public async onResetButtonUp(_ev: BeButtonEvent): Promise { + IModelApp.toolAdmin.startDefaultTool(); + return EventHandled.No; + } + + /** Process request to restart the tool. */ + public onRestartTool(): void { + const tool = new AnalysisAnimationTool(); + if (!tool.run()) + this.exitTool(); + } + + /** Process selected viewport changes. */ + public onSelectedViewportChanged(_previous: Viewport | undefined, current: Viewport | undefined): void { + if (current && undefined !== current.view.AnalysisStyle) + return; + IModelApp.toolAdmin.startDefaultTool(); + } +} + +ConfigurableUiManager.registerControl(AnalysisAnimationTool.toolId, AnalysisAnimationToolSettingsProvider); diff --git a/ui/framework/src/tools/AnalysisAnimationToolSettings.scss b/ui/framework/src/tools/AnalysisAnimationToolSettings.scss new file mode 100644 index 0000000..e9e8925 --- /dev/null +++ b/ui/framework/src/tools/AnalysisAnimationToolSettings.scss @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +.toolSettingsRow { + display: flex; + align-items: center; + justify-content: left; + margin-top: 4px; + margin-bottom: 4px; +} + +.toolSettingsRow.toolSettings-toolbar { + justify-content: center; +} + +.toolSettingsRow.toolSettings-stretch { + justify-content: stretch; + width: 100% +} + +.toolSettings-sliderStretch { + width: 100% +} + +.toolSettings-animationDuration { + width: 4em; + margin-inline-start: 5px; + margin-inline-end: 5px; +} \ No newline at end of file diff --git a/ui/framework/src/tools/AnalysisAnimationToolSettings.tsx b/ui/framework/src/tools/AnalysisAnimationToolSettings.tsx new file mode 100644 index 0000000..ea23ae7 --- /dev/null +++ b/ui/framework/src/tools/AnalysisAnimationToolSettings.tsx @@ -0,0 +1,215 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +/** @module Tools */ + +import * as React from "react"; + +// cSpell:Ignore configurableui +import { ConfigurableCreateInfo } from "../configurableui/ConfigurableUiControl"; +import { ToolUiProvider } from "../configurableui/ToolUiProvider"; +import { ContentViewManager } from "../configurableui/ContentViewManager"; +import ToolbarIcon from "@bentley/ui-ninezone/lib/toolbar/item/Icon"; +import { Icon } from "../configurableui/IconComponent"; +import Toolbar from "@bentley/ui-ninezone/lib/toolbar/Toolbar"; +import Direction from "@bentley/ui-ninezone/lib/utilities/Direction"; +import { UiFramework } from "../UiFramework"; +import "./AnalysisAnimationToolSettings.scss"; + +/** State for [[AnalysisAnimationToolSettings]] */ +interface AnimationState { + animationDuration: number; + elapsedTime: number; + isAnimationPaused: boolean; + isAnimating: boolean; + isLooping: boolean; + animationSliderValue: string; +} + +/** ToolSetting for AnalysisAnimationTool */ +export class AnalysisAnimationToolSettings extends React.Component<{}, AnimationState> { + private _timeLastCycle = 0; + private _unmounted = false; + private _requestFrame = 0; + + constructor(props: {}) { + super(props); + this.state = { + animationDuration: 3000, // 3 seconds + elapsedTime: 0, + animationSliderValue: "0", + isAnimating: false, + isAnimationPaused: false, + isLooping: true, + }; + } + + public componentWillUnmount() { + const activeContentControl = ContentViewManager.getActiveContentControl(); + if (activeContentControl && activeContentControl.viewport) { + activeContentControl.viewport.animationFraction = 0; + window.cancelAnimationFrame(this._requestFrame); + } + this._unmounted = true; + } + + private _updateAnimation = () => { + if (this.state.isAnimationPaused && !this._unmounted) { + this._requestFrame = window.requestAnimationFrame(this._updateAnimation); + return; + } + + const activeContentControl = ContentViewManager.getActiveContentControl(); + if (activeContentControl && activeContentControl.viewport) { + const now = (new Date()).getTime(); + let elapsedTime = this.state.elapsedTime + (now - this._timeLastCycle); + this._timeLastCycle = now; + activeContentControl.viewport.animationFraction = elapsedTime / this.state.animationDuration; + const userHitStop = !this.state.isAnimating; + + if (elapsedTime >= this.state.animationDuration || userHitStop) { // stop the animation! + elapsedTime = 0; + activeContentControl.viewport.animationFraction = 0; + + if (!userHitStop && this.state.isLooping) { // only loop if user did not hit stop (naturally finished animation) + this._startAnimation(); + return; + } else { + this.setState({ elapsedTime, isAnimating: false, isAnimationPaused: false }); + activeContentControl.viewport.animationFraction = 0; + window.cancelAnimationFrame(this._requestFrame); + + return; + } + + } else { // continue the animation - request the next frame + this._requestFrame = window.requestAnimationFrame(this._updateAnimation); + } + + this.setState({ elapsedTime }); + } + } + + private _startAnimation = () => { + this._timeLastCycle = new Date().getTime(); + + if (this.state.isAnimationPaused) { // resume animation + this.setState({ isAnimationPaused: false, isAnimating: true }); + return; + } + + this.setState({ isAnimating: true, isAnimationPaused: false, elapsedTime: 0 }); + this._requestFrame = window.requestAnimationFrame(this._updateAnimation); + } + + private _pauseAnimation = () => { + if (!this.state.isAnimating) + return; // already not animating! + this.setState({ isAnimationPaused: true }); + } + + private _stopAnimation = () => { + if (!this.state.isAnimating) + return; // already not animating! + this.setState({ isAnimating: false, isAnimationPaused: false, elapsedTime: 0 }); + + const activeContentControl = ContentViewManager.getActiveContentControl(); + if (activeContentControl && activeContentControl.viewport) { + activeContentControl.viewport.animationFraction = 0; + } + window.cancelAnimationFrame(this._requestFrame); + } + + private _handleSliderChange = (event: React.ChangeEvent) => { + const activeContentControl = ContentViewManager.getActiveContentControl(); + if (activeContentControl && activeContentControl.viewport) { + const elapsedTime = parseInt(event.target.value, undefined); + if (elapsedTime === 0) { + this.setState({ elapsedTime }); + this._stopAnimation(); + return; + } + + activeContentControl.viewport.animationFraction = elapsedTime / this.state.animationDuration; + this.setState({ elapsedTime }); + } + } + + private _handleLoopChange = (event: React.ChangeEvent) => { + const target = event.target; + const value = target.checked; + this.setState({ isLooping: value }); + } + + private _handleDurationChange = (event: React.ChangeEvent) => { + const target = event.target; + const value = parseInt(target.value, undefined); + // min 1 sec, max 30 seconds + const animationDuration = (value <= 1) ? 1000 : (value >= 30) ? 30000 : value * 1000; + this.setState({ animationDuration }); + } + + public render(): React.ReactNode { + return ( +
    +
    + {UiFramework.i18n.translate("UiFramework:tools.AnalysisAnimation.ToolSettings.duration")} + + {UiFramework.i18n.translate("UiFramework:tools.AnalysisAnimation.ToolSettings.seconds")} +
    +
    + + {UiFramework.i18n.translate("UiFramework:tools.AnalysisAnimation.ToolSettings.loop")} +
    +
    + +
    +
    + + } + /> + } + /> + } + /> + + } + /> +
    +
    + ); + } +} + +/** ToolUiProvider class that informs ConfigurableUi that Tool Settings are provided for the specified tool. */ +export class AnalysisAnimationToolSettingsProvider extends ToolUiProvider { + constructor(info: ConfigurableCreateInfo, options: any) { + super(info, options); + + this.toolSettingsNode = ; + } + + public execute(): void { + } +} diff --git a/ui/framework/src/tools/index.ts b/ui/framework/src/tools/index.ts new file mode 100644 index 0000000..8d023b8 --- /dev/null +++ b/ui/framework/src/tools/index.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ + +export * from "./AnalysisAnimation"; +export * from "./AnalysisAnimationToolSettings"; diff --git a/ui/framework/tests/configurableui/ContentLayout.test.tsx b/ui/framework/tests/configurableui/ContentLayout.test.tsx deleted file mode 100644 index 5abb78f..0000000 --- a/ui/framework/tests/configurableui/ContentLayout.test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import { mount, shallow } from "enzyme"; -import * as React from "react"; -import { - ContentLayout, - ContentGroup, - ContentLayoutDef, - ContentControl, - ConfigurableCreateInfo, - ConfigurableUiManager, -} from "../../src/index"; -import TestUtils from "../TestUtils"; - -describe("ContentLayout", () => { - - class TestContentControl extends ContentControl { - constructor(info: ConfigurableCreateInfo, options: any) { - super(info, options); - - this.reactElement =
    ; - } - } - - before(async () => { - await TestUtils.initializeUiFramework(); - ConfigurableUiManager.registerControl("TestContentControl2", TestContentControl); - }); - - const myContentGroup: ContentGroup = new ContentGroup({ - contents: [{ classId: "TestContentControl2" }], - }); - - const myContentLayout: ContentLayoutDef = new ContentLayoutDef({ - id: "SingleContent", - descriptionKey: "UiFramework:tests.singleContent", - priority: 100, - }); - - it("SingleContent should render", () => { - mount(); - }); - - it("SingleContent renders correctly", () => { - shallow().should.matchSnapshot(); - }); - - const contentGroup2: ContentGroup = new ContentGroup({ - id: "contentGroup2", - contents: [ - { classId: "TestContentControl2", applicationData: "data1" }, - { classId: "TestContentControl2", applicationData: "data2" }, - ], - }); - - const contentLayout2: ContentLayoutDef = new ContentLayoutDef({ - id: "TwoHalvesVertical", - descriptionKey: "SampleApp:ContentLayoutDef.TwoHalvesVertical", - priority: 60, - verticalSplit: { id: "TwoHalvesVertical.VerticalSplit", percentage: 0.50, left: 0, right: 1 }, - }); - - // NEEDSWORK: Results in - // Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. - it.skip("TwoHalvesVertical should render", () => { - mount(); - }); - - it("TwoHalvesVertical renders correctly", () => { - shallow().should.matchSnapshot(); - }); - - const contentLayout3: ContentLayoutDef = new ContentLayoutDef({ - id: "TwoHalvesHorizontal", - descriptionKey: "SampleApp:ContentLayoutDef.TwoHalvesHorizontal", - priority: 60, - horizontalSplit: { id: "TwoHalvesHorizontal.HorizontalSplit", percentage: 0.50, top: 0, bottom: 1 }, - }); - - // NEEDSWORK: Results in - // Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. - it.skip("TwoHalvesHorizontal should render", () => { - mount(); - }); - - it("TwoHalvesHorizontal renders correctly", () => { - shallow().should.matchSnapshot(); - }); - -}); diff --git a/ui/framework/tests/configurableui/ContentLayout.test.tsx.snap b/ui/framework/tests/configurableui/ContentLayout.test.tsx.snap deleted file mode 100644 index 9a36d91..0000000 --- a/ui/framework/tests/configurableui/ContentLayout.test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ContentLayout SingleContent renders correctly 1`] = ` -
    - } - /> -
    -`; - -exports[`ContentLayout TwoHalvesHorizontal renders correctly 1`] = ` -
    - } - /> - } - contentB={ - } - /> - } - orientation={0} - percentage={0.5} - resizable={true} - splitterStateId="TwoHalvesHorizontal.HorizontalSplit" - /> -
    -`; - -exports[`ContentLayout TwoHalvesVertical renders correctly 1`] = ` -
    - } - /> - } - contentB={ - } - /> - } - orientation={1} - percentage={0.5} - resizable={true} - splitterStateId="TwoHalvesVertical.VerticalSplit" - /> -
    -`; diff --git a/ui/framework/tests/configurableui/Frontstage.test.tsx b/ui/framework/tests/configurableui/Frontstage.test.tsx deleted file mode 100644 index 779f071..0000000 --- a/ui/framework/tests/configurableui/Frontstage.test.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import { mount, shallow } from "enzyme"; -import * as sinon from "sinon"; -import { expect } from "chai"; - -import TestUtils from "../TestUtils"; -import { - Frontstage, - FrontstageProvider, - FrontstageProps, - ContentLayoutDef, - Zone, - Widget, - ContentGroup, - FrontstageManager, - ZoneState, - ContentControl, - ConfigurableCreateInfo, - WidgetState, - WidgetControl, -} from "../../src/index"; - -describe("Frontstage", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - it("should render", () => { - mount(); - }); - - it("renders correctly", () => { - shallow().should.matchSnapshot(); - }); - - it("FrontstageProvider", () => { - - class TestContentControl extends ContentControl { - constructor(info: ConfigurableCreateInfo, options: any) { - super(info, options); - - this.reactElement =
    ; - } - } - - class TestWidget extends WidgetControl { - constructor(info: ConfigurableCreateInfo, options: any) { - super(info, options); - - this.reactElement =
    ; - } - } - - class Frontstage1 extends FrontstageProvider { - - public get frontstage(): React.ReactElement { - const contentLayoutDef: ContentLayoutDef = new ContentLayoutDef( - { - id: "SingleContent", - descriptionKey: "App:ContentLayoutDef.SingleContent", - priority: 100, - }, - ); - - const myContentGroup: ContentGroup = new ContentGroup( - { - contents: [ - { - classId: TestContentControl, - applicationData: { label: "Content 1a", bgColor: "black" }, - }, - ], - }, - ); - - return ( - } />, - ]} - /> - } - topCenter={ - , - ]} - /> - } - bottomCenter={ - , - ]} - /> - } - bottomRight={ - } />, - } />, - ]} - /> - } - /> - ); - } - } - - const spyMethod = sinon.spy(); - const frontstageProvider = new Frontstage1(); - FrontstageManager.addFrontstageProvider(frontstageProvider); - FrontstageManager.setActiveFrontstageDef(frontstageProvider.frontstageDef).then(() => { - spyMethod(); - }); - setImmediate(() => { - spyMethod.calledOnce.should.true; - - const widgetDef = FrontstageManager.findWidget("widget1"); - expect(widgetDef).to.not.be.undefined; - - if (widgetDef) { - widgetDef.setWidgetState(WidgetState.Open); - expect(widgetDef.widgetState).to.eq(WidgetState.Open); - - FrontstageManager.setWidgetState("widget1", WidgetState.Off); - expect(widgetDef.widgetState).to.eq(WidgetState.Off); - } - }); - - }); - -}); diff --git a/ui/framework/tests/configurableui/GroupItem.test.tsx.snap b/ui/framework/tests/configurableui/GroupItem.test.tsx.snap deleted file mode 100644 index 97510d0..0000000 --- a/ui/framework/tests/configurableui/GroupItem.test.tsx.snap +++ /dev/null @@ -1,179 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GroupItem renders correctly 1`] = ` - ToolItemDef { - "_iconLabelSupport": IconLabelSupport { - "iconClass": "icon-placeholder", - "label": "buttons.item1", - "tooltip": "", - }, - "execute": [Function], - "featureId": "", - "isEnabled": true, - "isEnabledExpr": "", - "isPressed": false, - "isVisible": true, - "isVisibleExpr": "", - "itemSyncMsg": "", - "toolId": "item1", - }, - "item2" => ToolItemDef { - "_iconLabelSupport": IconLabelSupport { - "iconClass": "icon-placeholder", - "label": "buttons.item2", - "tooltip": "", - }, - "execute": [Function], - "featureId": "", - "isEnabled": true, - "isEnabledExpr": "", - "isPressed": false, - "isVisible": true, - "isVisibleExpr": "", - "itemSyncMsg": "", - "toolId": "item2", - }, - "item3" => ToolItemDef { - "_iconLabelSupport": IconLabelSupport { - "iconClass": "icon-placeholder", - "label": "buttons.item3", - "tooltip": "", - }, - "execute": [Function], - "featureId": "", - "isEnabled": true, - "isEnabledExpr": "", - "isPressed": false, - "isVisible": true, - "isVisibleExpr": "", - "itemSyncMsg": "", - "toolId": "item3", - }, - "item4" => ToolItemDef { - "_iconLabelSupport": IconLabelSupport { - "iconClass": "icon-placeholder", - "label": "buttons.item4", - "tooltip": "", - }, - "execute": [Function], - "featureId": "", - "isEnabled": true, - "isEnabledExpr": "", - "isPressed": false, - "isVisible": true, - "isVisibleExpr": "", - "itemSyncMsg": "", - "toolId": "item4", - }, - }, - "_items": Array [ - "item1", - "item2", - "item3", - "item4", - ], - "direction": 3, - "execute": [Function], - "featureId": "", - "groupId": "", - "isEnabled": true, - "isEnabledExpr": "", - "isPressed": false, - "isVisible": true, - "isVisibleExpr": "", - "itemSyncMsg": "", - "itemsInColumn": 4, - } - } - iconClass="icon-placeholder" - items={ - Array [ - "item1", - "item2", - "item3", - "item4", - ] - } - itemsInColumn={4} - key="" - labelKey="UiFramework:tests.label" -/> -`; diff --git a/ui/framework/tests/configurableui/Item.test.tsx b/ui/framework/tests/configurableui/Item.test.tsx deleted file mode 100644 index 3f7a5de..0000000 --- a/ui/framework/tests/configurableui/Item.test.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import { mount, shallow } from "enzyme"; -import { expect } from "chai"; -import * as sinon from "sinon"; - -import { - ToolButton, - CommandButton, - ToolItemDef, - ToolItemProps, - ItemList, - ItemPropsList, - CommandItemDef, - GroupItemDef, - FrontstageManager, -} from "../../src/index"; -import TestUtils from "../TestUtils"; -import Direction from "@bentley/ui-ninezone/lib/utilities/Direction"; - -describe("ToolButton", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - describe("", () => { - it("should render", () => { - mount(); - }); - - it("renders correctly", () => { - FrontstageManager.setActiveToolId("tool1"); - shallow().should.matchSnapshot(); - }); - - it("should execute a function", () => { - const spyMethod = sinon.spy(); - const wrapper = mount(); - wrapper.find(".nz-toolbar-item-item").simulate("click"); - spyMethod.should.have.been.called; - wrapper.unmount(); - }); - }); -}); - -describe("CommandButton", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - const commandHandler1 = { - execute: () => { - }, - }; - - describe("", () => { - it("should render", () => { - mount(); - }); - - it("renders correctly", () => { - shallow().should.matchSnapshot(); - }); - - it("should execute a function", () => { - const spyMethod = sinon.spy(); - commandHandler1.execute = spyMethod; - const wrapper = mount(); - wrapper.find(".nz-toolbar-item-item").simulate("click"); - spyMethod.should.have.been.called; - wrapper.unmount(); - }); - - }); - -}); - -describe("ToolItemDef", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - it("Defaults", () => { - const toolItemProps: ToolItemProps = { - toolId: "ToolTest", - }; - const toolItemDef = new ToolItemDef(toolItemProps); - - expect(toolItemDef.isVisible).to.be.true; - expect(toolItemDef.isEnabled).to.be.true; - expect(toolItemDef.trayId).to.be.undefined; - }); - - it("Optional properties set", () => { - const toolItemProps: ToolItemProps = { - toolId: "ToolTest", - isEnabled: false, - isVisible: false, - featureId: "FeatureId", - itemSyncMsg: "ItemSyncMsg", - applicationData: "AppData", - }; - const toolItemDef = new ToolItemDef(toolItemProps); - - expect(toolItemDef.isVisible).to.be.false; - expect(toolItemDef.isEnabled).to.be.false; - expect(toolItemDef.featureId).to.eq("FeatureId"); - expect(toolItemDef.itemSyncMsg).to.eq("ItemSyncMsg"); - expect(toolItemDef.applicationData).to.eq("AppData"); - }); - -}); - -describe("ItemList & ItemFactory", () => { - it("ItemList creates ItemDefs correctly", () => { - const itemsList: ItemPropsList = { - items: [ - { - toolId: "tool1", - iconClass: "icon-placeholder", - labelKey: "SampleApp:buttons.tool1", - }, - { - groupId: "my-group1", - labelKey: "SampleApp:buttons.toolGroup", - iconClass: "icon-placeholder", - items: ["item1", "item2", "item3", "item4"], - direction: Direction.Bottom, - itemsInColumn: 7, - }, - { - commandId: "command1", - commandHandler: { execute: () => { } }, - }, - ], - }; - - const itemList = new ItemList(itemsList); - expect(itemList.items.length).to.eq(3); - expect(itemList.items[0]).to.be.instanceof(ToolItemDef); - expect(itemList.items[1]).to.be.instanceof(GroupItemDef); - expect(itemList.items[2]).to.be.instanceof(CommandItemDef); - }); - -}); diff --git a/ui/framework/tests/configurableui/Item.test.tsx.snap b/ui/framework/tests/configurableui/Item.test.tsx.snap deleted file mode 100644 index 9f2c065..0000000 --- a/ui/framework/tests/configurableui/Item.test.tsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CommandButton renders correctly 1`] = ` - - } - isDisabled={false} - key="command1" - onClick={[Function]} - title="tests.label" -/> -`; - -exports[`ToolButton renders correctly 1`] = ` - - } - isActive={true} - isDisabled={false} - key="tool1" - onClick={[Function]} - title="tests.label" -/> -`; diff --git a/ui/framework/tests/configurableui/StackedWidget.test.tsx b/ui/framework/tests/configurableui/StackedWidget.test.tsx deleted file mode 100644 index 2b9dca2..0000000 --- a/ui/framework/tests/configurableui/StackedWidget.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import { mount } from "enzyme"; -import { expect } from "chai"; -import TestUtils from "../TestUtils"; -import { FrontstageDefProps, ZoneState, WidgetState, ConfigurableUiManager, WidgetControl, ConfigurableCreateInfo, FrontstageManager, FrontstageComposer, ContentGroup, ContentLayoutDef } from "../../src"; - -describe("StackedWidget", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - class TestWidget extends WidgetControl { - constructor(info: ConfigurableCreateInfo, options: any) { - super(info, options); - - this.reactElement = ( -
    - This is the Test Widget -
    - ); - } - } - - it("Producing a StackedWidget", async () => { - - const myContentGroup: ContentGroup = new ContentGroup({ - contents: [{ classId: "TestContentControl2" }], - }); - - const myContentLayout: ContentLayoutDef = new ContentLayoutDef({ - id: "SingleContent", - descriptionKey: "UiFramework:tests.singleContent", - priority: 100, - }); - - const frontstageProps: FrontstageDefProps = { - id: "StackedWidget-Frontstage", - defaultToolId: "PlaceLine", - defaultLayout: myContentLayout, - contentGroup: myContentGroup, - - centerRight: { - defaultState: ZoneState.Minimized, - allowsMerging: true, - widgetProps: [ - { - classId: "StackedWidgetTestWidget", - defaultState: WidgetState.Open, - iconClass: "icon-placeholder", - labelKey: "SampleApp:Test.my-label", - }, - { - classId: "StackedWidgetTestWidget", - defaultState: WidgetState.Open, - iconClass: "icon-placeholder", - labelKey: "SampleApp:Test.my-label", - }, - ], - }, - }; - - ConfigurableUiManager.registerControl("StackedWidgetTestWidget", TestWidget); - ConfigurableUiManager.loadFrontstage(frontstageProps); - - FrontstageManager.setActiveFrontstageDef(undefined); - - const wrapper = mount(); - - const frontstageDef = ConfigurableUiManager.findFrontstageDef("StackedWidget-Frontstage"); - expect(frontstageDef).to.not.be.undefined; - await FrontstageManager.setActiveFrontstageDef(frontstageDef); - wrapper.update(); - - const stackedWidget = wrapper.find("div.nz-widget-stacked"); - expect(stackedWidget.length).to.eq(1); - - const tabs = wrapper.find("div.nz-widget-rectangular-tab-tab"); - expect(tabs.length).to.eq(2); - - // NEEDSWORK - not working - tabs.at(0).simulate("click"); - wrapper.update(); - - // tslint:disable-next-line:no-console - // console.log(wrapper.debug()); - - // NEEDSWORK - not working - tabs.at(1).simulate("click"); - wrapper.update(); - - // tslint:disable-next-line:no-console - // console.log(wrapper.debug()); - - wrapper.unmount(); - }); - -}); diff --git a/ui/framework/tests/configurableui/ToolSettingsZone.test.tsx b/ui/framework/tests/configurableui/ToolSettingsZone.test.tsx deleted file mode 100644 index 7ec3178..0000000 --- a/ui/framework/tests/configurableui/ToolSettingsZone.test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import { expect } from "chai"; -import { mount } from "enzyme"; - -import TestUtils from "../TestUtils"; -import { - ZoneState, - WidgetState, - ToolSettingsZone, - ConfigurableUiManager, - FrontstageDefProps, - ToolUiProvider, - ConfigurableCreateInfo, - FrontstageManager, - ToolItemDef, - ItemPropsList, -} from "../../src"; - -import { RectangleProps } from "@bentley/ui-ninezone/lib/utilities/Rectangle"; -import ToolbarIcon from "@bentley/ui-ninezone/lib/toolbar/item/Icon"; - -describe("ToolSettingsZone", () => { - - const bounds: RectangleProps = { - left: 0, - top: 0, - right: 100, - bottom: 200, - }; - - class Tool2UiProvider extends ToolUiProvider { - constructor(info: ConfigurableCreateInfo, options: any) { - super(info, options); - - this.toolSettingsNode = ; - } - - public execute(): void { - } - } - - class Tool2Settings extends React.Component { - public render(): React.ReactNode { - return ( -
    - - - - - - - - - - - -
    TypeInput
    Month
    -
    - ); - } - } - - const testToolId = "ToolSettingsZone-TestTool"; - - before(async () => { - await TestUtils.initializeUiFramework(); - - const commonItemsList: ItemPropsList = { - items: [ - { - toolId: testToolId, - iconClass: "icon-home", - }, - ], - }; - - const frontstageProps: FrontstageDefProps = { - id: "ToolSettingsZone-TestFrontstage", - defaultToolId: "PlaceLine", - defaultLayout: "FourQuadrants", - contentGroup: "TestContentGroup4", - defaultContentId: "TestContent1", - - topCenter: { - defaultState: ZoneState.Open, - allowsMerging: false, - widgetProps: [ - { - defaultState: WidgetState.Open, - isFreeform: false, - iconClass: "icon-home", - labelKey: "SampleApp:Test.my-label", - isToolSettings: true, - }, - ], - }, - }; - - ConfigurableUiManager.loadCommonItems(commonItemsList); - ConfigurableUiManager.registerControl(testToolId, Tool2UiProvider); - ConfigurableUiManager.loadFrontstage(frontstageProps); - }); - - it("mount ToolSettingsZone with active Tool Settings", () => { - const frontstageDef = FrontstageManager.findFrontstageDef("ToolSettingsZone-TestFrontstage"); - expect(frontstageDef).to.not.be.undefined; - - if (frontstageDef) { - FrontstageManager.setActiveFrontstageDef(frontstageDef); - - const toolItemDef = ConfigurableUiManager.findItem(testToolId); - expect(toolItemDef).to.not.be.undefined; - expect(toolItemDef).to.be.instanceof(ToolItemDef); - - if (toolItemDef) { - FrontstageManager.setActiveToolId(testToolId); - expect(FrontstageManager.activeToolId).to.eq(testToolId); - - const wrapper = mount(); - - const toolbarIcon = wrapper.find(ToolbarIcon); - toolbarIcon.simulate("click"); - - wrapper.unmount(); - } - } - }); - -}); diff --git a/ui/framework/tests/configurableui/ToolWidget.test.tsx b/ui/framework/tests/configurableui/ToolWidget.test.tsx deleted file mode 100644 index f3e474b..0000000 --- a/ui/framework/tests/configurableui/ToolWidget.test.tsx +++ /dev/null @@ -1,148 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import { expect } from "chai"; -import * as sinon from "sinon"; -import { mount, shallow } from "enzyme"; -import TestUtils from "../TestUtils"; -import { - AnyWidgetProps, - WidgetState, - WidgetDefFactory, - ToolWidgetDef, - ConfigurableUiManager, - ItemPropsList, - ToolButton, - GroupButton, - ToolWidget, -} from "../../src/index"; -import Toolbar from "@bentley/ui-ninezone/lib/toolbar/Toolbar"; -import Direction from "@bentley/ui-ninezone/lib/utilities/Direction"; - -describe("ToolWidget", () => { - - const testCallback = sinon.stub(); - - before(async () => { - await TestUtils.initializeUiFramework(); - - const commonItemsList: ItemPropsList = { - items: [ - { - toolId: "SampleApp.BackstageToggle", - iconClass: "icon-home", - execute: testCallback, - }, - { - toolId: "tool1", - iconClass: "icon-placeholder", - }, - { - toolId: "tool2", - iconClass: "icon-placeholder", - }, - ], - }; - - ConfigurableUiManager.loadCommonItems(commonItemsList); - }); - - const widgetProps: AnyWidgetProps = { - classId: "ToolWidget", - defaultState: WidgetState.Open, - isFreeform: true, - iconClass: "icon-home", - labelKey: "SampleApp:Test.my-label", - appButtonId: "SampleApp.BackstageToggle", - horizontalIds: ["tool1"], - verticalIds: ["tool2"], - horizontalDirection: Direction.Top, - verticalDirection: Direction.Left, - }; - - it("ToolWidgetDef from WidgetProps", () => { - - const widgetDef = WidgetDefFactory.create(widgetProps); - expect(widgetDef).to.be.instanceof(ToolWidgetDef); - - const toolWidgetDef = widgetDef as ToolWidgetDef; - toolWidgetDef.executeAppButtonClick(); - expect(testCallback.calledOnce).to.be.true; - - const reactElement = toolWidgetDef.reactElement; - expect(reactElement).to.not.be.undefined; - - const reactNode = toolWidgetDef.renderCornerItem(); - expect(reactNode).to.not.be.undefined; - }); - - it("ToolWidget should mount with Ids", () => { - const wrapper = mount( - , - ); - wrapper.unmount(); - }); - - const horizontalToolbar = - - - - - - } - />; - - const verticalToolbar = - - - - - - } - />; - - it("ToolWidget should render", () => { - const wrapper = mount( - , - ); - wrapper.unmount(); - }); - - it("ToolWidget should render correctly", () => { - shallow( - , - ).should.matchSnapshot(); - }); - -}); diff --git a/ui/framework/tests/configurableui/ToolWidget.test.tsx.snap b/ui/framework/tests/configurableui/ToolWidget.test.tsx.snap deleted file mode 100644 index 6659f44..0000000 --- a/ui/framework/tests/configurableui/ToolWidget.test.tsx.snap +++ /dev/null @@ -1,114 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ToolWidget ToolWidget should render correctly 1`] = ` - - - - - - } - /> - } - toolWidgetDef={ - ToolWidgetDef { - "_appButtonId": "SampleApp.BackstageToggle", - "_horizontalIds": Array [], - "_iconLabelSupport": IconLabelSupport { - "label": "", - "tooltip": "", - }, - "_verticalIds": Array [], - "classId": undefined, - "executeAppButtonClick": [Function], - "featureId": "", - "horizontalDirection": 3, - "id": "toolWidget", - "isFloatingStateSupported": false, - "isFloatingStateWindowResizable": true, - "isFreeform": false, - "isStatusBar": false, - "isToolSettings": false, - "priority": 0, - "renderHorizontalToolbar": [Function], - "renderVerticalToolbar": [Function], - "stateChanged": false, - "verticalDirection": 2, - "widgetState": 1, - "widgetType": 0, - } - } - verticalToolbar={ - - - - - - } - /> - } -/> -`; diff --git a/ui/framework/tests/configurableui/ZoneDef.test.tsx b/ui/framework/tests/configurableui/ZoneDef.test.tsx deleted file mode 100644 index 21c5907..0000000 --- a/ui/framework/tests/configurableui/ZoneDef.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import TestUtils from "../TestUtils"; -import { ZoneDef, ZoneState, WidgetDef } from "../../src/index"; -import { expect } from "chai"; - -describe("ZoneDef", () => { - - before(async () => { - await TestUtils.initializeUiFramework(); - }); - - it("Defaults, widgetDefs & widgetCount", () => { - const zoneDef = new ZoneDef( - { - defaultState: ZoneState.Minimized, - allowsMerging: false, - widgetProps: [ - { - classId: "Test", - }, - ], - }, - ); - - expect(zoneDef.applicationData).to.be.undefined; - expect(zoneDef.widgetDefs).to.have.lengthOf(1); - expect(zoneDef.widgetCount).to.eq(1); - expect(zoneDef.getOnlyWidgetDef()).to.not.be.undefined; - expect(zoneDef.isToolSettings).to.be.false; - expect(zoneDef.isStatusBar).to.be.false; - }); - - it("applicationData", () => { - const zoneDef = new ZoneDef( - { - defaultState: ZoneState.Open, - allowsMerging: false, - applicationData: "AppData", - widgetProps: [ - { - classId: "Test", - }, - ], - }, - ); - - expect(zoneDef.applicationData).to.eq("AppData"); - }); - - it("addWidgetDef, widgetDefs & getOnlyWidgetDef", () => { - const zoneDef = new ZoneDef( - { - defaultState: ZoneState.Open, - allowsMerging: false, - applicationData: "AppData", - widgetProps: [ - { - classId: "Test", - }, - ], - }, - ); - - zoneDef.addWidgetDef(new WidgetDef({ - classId: "Test2", - })); - - expect(zoneDef.widgetDefs).to.have.lengthOf(2); - expect(zoneDef.getOnlyWidgetDef()).to.be.undefined; - expect(zoneDef.isToolSettings).to.be.false; - expect(zoneDef.isStatusBar).to.be.false; - }); - - it("findWidgetDef", () => { - const zoneDef = new ZoneDef( - { - defaultState: ZoneState.Open, - allowsMerging: false, - applicationData: "AppData", - widgetProps: [ - { - id: "IdTest", - classId: "Test", - }, - ], - }, - ); - - expect(zoneDef.findWidgetDef("IdTest")).to.not.be.undefined; - }); - - it("clearDefaultOpenUsed", () => { - const zoneDef = new ZoneDef( - { - defaultState: ZoneState.Open, - allowsMerging: false, - applicationData: "AppData", - widgetProps: [ - { - id: "IdTest", - classId: "Test", - }, - ], - }, - ); - - const widgetDef = zoneDef.findWidgetDef("IdTest"); - expect(widgetDef).to.not.be.undefined; - - }); - -}); diff --git a/ui/framework/tests/tsconfig.json b/ui/framework/tests/tsconfig.json deleted file mode 100644 index da73149..0000000 --- a/ui/framework/tests/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../node_modules/@bentley/build-tools/tsconfig-base.json", - "compilerOptions": { - "declaration": false, - "skipLibCheck": true, - "baseUrl": "../node_modules", - "outDir": "../lib", - "jsx": "react" - }, - "include": [ - "../src/**/*.ts*", - "./**/*.ts*" - ], - "exclude": [ - "../lib", - "../node_modules" - ] -} \ No newline at end of file diff --git a/ui/framework/tsconfig.json b/ui/framework/tsconfig.json index 8588e4b..53f963d 100644 --- a/ui/framework/tsconfig.json +++ b/ui/framework/tsconfig.json @@ -8,7 +8,7 @@ "jsx": "react" }, "include": [ - "./src/**/*.ts" + "./src/**/*.ts*" ], "exclude": [ "lib", diff --git a/ui/mocha.opts b/ui/mocha.opts index dbe7e86..2b1becf 100644 --- a/ui/mocha.opts +++ b/ui/mocha.opts @@ -1,9 +1,10 @@ --require ../scripts/copy-test-setup.js --require raf/polyfill ---require ts-node/register +--require ../../common/scripts/mocha-reporter-tweaks.js --require source-map-support/register --require jsdom-global/register --require ignore-styles --check-leaks --timeout 60000 ---file lib/setup-tests.js +--file lib/test/setup.js +--exclude lib/test/coverage/**/* diff --git a/ui/ninezone/CHANGELOG.json b/ui/ninezone/CHANGELOG.json index 78c3836..406a6e8 100644 --- a/ui/ninezone/CHANGELOG.json +++ b/ui/ninezone/CHANGELOG.json @@ -1,6 +1,90 @@ { "name": "@bentley/ui-ninezone", "entries": [ + { + "version": "0.171.0", + "tag": "@bentley/ui-ninezone_v0.171.0", + "date": "Mon, 03 Dec 2018 18:52:58 GMT", + "comments": { + "none": [ + { + "comment": "Changed Omit typedef source from ui/ninezone to ui/core" + } + ] + } + }, + { + "version": "0.170.0", + "tag": "@bentley/ui-ninezone_v0.170.0", + "date": "Mon, 26 Nov 2018 19:38:42 GMT", + "comments": { + "none": [ + { + "comment": "Changed Omit typedef source from ui/ninezone to ui/core" + } + ] + } + }, + { + "version": "0.169.0", + "tag": "@bentley/ui-ninezone_v0.169.0", + "date": "Tue, 20 Nov 2018 16:17:15 GMT", + "comments": {} + }, + { + "version": "0.168.0", + "tag": "@bentley/ui-ninezone_v0.168.0", + "date": "Sat, 17 Nov 2018 14:20:11 GMT", + "comments": {} + }, + { + "version": "0.167.0", + "tag": "@bentley/ui-ninezone_v0.167.0", + "date": "Fri, 16 Nov 2018 21:45:44 GMT", + "comments": { + "none": [ + { + "comment": "Fixed some content control sizing issues" + }, + { + "comment": "Moved most isHidden logic for toolbar items into ui-ninezone" + }, + { + "comment": "Hiding items by rendering them conditionally instead of using a CSS class." + }, + { + "comment": "ui-framework unit tests & docs" + } + ] + } + }, + { + "version": "0.166.0", + "tag": "@bentley/ui-ninezone_v0.166.0", + "date": "Mon, 12 Nov 2018 16:42:10 GMT", + "comments": {} + }, + { + "version": "0.165.0", + "tag": "@bentley/ui-ninezone_v0.165.0", + "date": "Mon, 12 Nov 2018 15:47:00 GMT", + "comments": {} + }, + { + "version": "0.164.0", + "tag": "@bentley/ui-ninezone_v0.164.0", + "date": "Thu, 08 Nov 2018 17:59:21 GMT", + "comments": { + "none": [ + { + "comment": "Updated to TypeScript 3.1" + }, + { + "comment": "fixed height issues with widget content" + } + ] + } + }, { "version": "0.163.0", "tag": "@bentley/ui-ninezone_v0.163.0", diff --git a/ui/ninezone/CHANGELOG.md b/ui/ninezone/CHANGELOG.md index 8dffaaa..f6cf155 100644 --- a/ui/ninezone/CHANGELOG.md +++ b/ui/ninezone/CHANGELOG.md @@ -1,6 +1,58 @@ # Change Log - @bentley/ui-ninezone -This log was last generated on Wed, 31 Oct 2018 20:55:37 GMT and should not be manually modified. +This log was last generated on Mon, 03 Dec 2018 18:52:58 GMT and should not be manually modified. + +## 0.171.0 +Mon, 03 Dec 2018 18:52:58 GMT + +### Updates + +- Changed Omit typedef source from ui/ninezone to ui/core + +## 0.170.0 +Mon, 26 Nov 2018 19:38:42 GMT + +### Updates + +- Changed Omit typedef source from ui/ninezone to ui/core + +## 0.169.0 +Tue, 20 Nov 2018 16:17:15 GMT + +*Version update only* + +## 0.168.0 +Sat, 17 Nov 2018 14:20:11 GMT + +*Version update only* + +## 0.167.0 +Fri, 16 Nov 2018 21:45:44 GMT + +### Updates + +- Fixed some content control sizing issues +- Moved most isHidden logic for toolbar items into ui-ninezone +- Hiding items by rendering them conditionally instead of using a CSS class. +- ui-framework unit tests & docs + +## 0.166.0 +Mon, 12 Nov 2018 16:42:10 GMT + +*Version update only* + +## 0.165.0 +Mon, 12 Nov 2018 15:47:00 GMT + +*Version update only* + +## 0.164.0 +Thu, 08 Nov 2018 17:59:21 GMT + +### Updates + +- Updated to TypeScript 3.1 +- fixed height issues with widget content ## 0.163.0 Wed, 31 Oct 2018 20:55:37 GMT diff --git a/ui/ninezone/demo/src/pages/Tools.tsx b/ui/ninezone/demo/src/pages/Tools.tsx index 3793b8b..05e2e44 100644 --- a/ui/ninezone/demo/src/pages/Tools.tsx +++ b/ui/ninezone/demo/src/pages/Tools.tsx @@ -18,11 +18,11 @@ import Icon from "@src/toolbar/item/Icon"; import Toolbar from "@src/toolbar/Toolbar"; import Scrollable from "@src/toolbar/Scrollable"; import { Direction } from "@src/utilities/Direction"; -import Button from "@src/toolbar/button/Button"; -import App from "@src/toolbar/button/App"; -import Back from "@src/toolbar/button/Back"; -import ExpandableButton from "@src/toolbar/button/Expandable"; -import IconButton from "@src/toolbar/button/Icon"; +import Button from "@src/widget/tools/button/Button"; +import App from "@src/widget/tools/button/App"; +import Back from "@src/widget/tools/button/Back"; +import ExpandableButton from "@src/widget/tools/button/Expandable"; +import IconButton from "@src/widget/tools/button/Icon"; export interface State { onBackCount: number; diff --git a/ui/ninezone/demo/src/pages/Zones.tsx b/ui/ninezone/demo/src/pages/Zones.tsx index 74fe917..ab07b3e 100644 --- a/ui/ninezone/demo/src/pages/Zones.tsx +++ b/ui/ninezone/demo/src/pages/Zones.tsx @@ -5,11 +5,11 @@ import "@bentley/icons-generic-webfont/dist/bentley-icons-generic-webfont.css"; import * as React from "react"; import * as ReactDOM from "react-dom"; -import { BlueButton, HollowButton } from "@bentley/bwc/lib/index"; +import { BlueButton, HollowButton } from "@bentley/bwc/lib/bwc"; import { Timer, withTimeout } from "@bentley/ui-core"; import App from "@src/app/App"; import Content from "@src/app/Content"; -import AppButton from "@src/toolbar/button/App"; +import AppButton from "@src/widget/tools/button/App"; import MouseTracker from "@src/context/MouseTracker"; import Footer from "@src/footer/Footer"; import MessageCenter, { MessageCenterButton } from "@src/footer/message-center/MessageCenter"; @@ -71,7 +71,7 @@ import TabSeparator from "@src/widget/rectangular/tab/Separator"; import WidgetTabGroup, { VisibilityMode } from "@src/widget/rectangular/tab/Group"; import { TabMode } from "@src/widget/rectangular/tab/Tab"; import StackedWidget, { HorizontalAnchor } from "@src/widget/Stacked"; -import ToolsWidget from "@src/widget/Tools"; +import ToolsWidget from "@src/widget/tools/Tools"; import FooterZone from "@src/zones/Footer"; import NineZone, { getDefaultProps as getDefaultNineZone, NineZoneProps, WidgetZoneIndex } from "@src/zones/state/NineZone"; import NineZoneManager from "@src/zones/state/Manager"; @@ -253,7 +253,7 @@ export default class ZonesExample extends React.Component<{}, State> { icon: "icon-placeholder", } as ToolGroup, "angle": { - icon: "icon-placeholder", + icon: "icon-app-1", } as SimpleTool, "attach": { icon: "icon-placeholder", @@ -1867,6 +1867,7 @@ export default class ZonesExample extends React.Component<{}, State> { <> {this.state.showAllItems && this.getToolbarItem("2d")} diff --git a/ui/ninezone/package.json b/ui/ninezone/package.json index 4575e4b..4d667bc 100644 --- a/ui/ninezone/package.json +++ b/ui/ninezone/package.json @@ -1,6 +1,6 @@ { "name": "@bentley/ui-ninezone", - "version": "0.163.0", + "version": "0.171.0", "description": "iModel.js Nine-zone React UI components", "license": "MIT", "repository": { @@ -14,10 +14,10 @@ "copy:scss": "cpx \"src/**/*.scss\" lib", "cover": "nyc npm test", "docs": "node ./node_modules/@bentley/build-tools/scripts/docs.js --source=./src --includes=../../generated-docs/extract --json=../../generated-docs/ui/ui-ninezone/file.json --tsIndexFile=./index.ts --onlyJson %TYPEDOC_THEME%", - "lint": "tslint --project . 1>&2 && tslint --project ./tests/tsconfig.json 1>&2 && tslint --project ./demo/tsconfig.json 1>&2", + "lint": "tslint --project . 1>&2 && tslint --project ./demo/tsconfig.json 1>&2", "pack": "npm run build && node ../../scripts/pack.js", "start": "webpack-dev-server --config ./demo/webpack.dev.config.js", - "test": "cross-env TS_NODE_PROJECT=./tests/tsconfig.json mocha --opts ../mocha.opts tests/**/*.test.{ts,tsx}", + "test": "mocha --opts ../mocha.opts lib/test/**/*.test.js", "test:watch": "npm test -- --reporter min --watch-extensions ts,tsx --watch" }, "keywords": [ @@ -30,17 +30,17 @@ "url": "http://www.bentley.com" }, "peerDependencies": { - "@bentley/ui-core": "0.163.0" + "@bentley/ui-core": "0.171.0" }, "//devDependencies": [ "NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install", "NOTE: All tools used by scripts in this package must be listed as devDependencies" ], "devDependencies": { - "@bentley/build-tools": "0.163.0", + "@bentley/build-tools": "0.171.0", "@bentley/bwc": "^7.0.1", - "@bentley/icons-generic-webfont": "^0.0.5", - "@bentley/ui-core": "0.163.0", + "@bentley/icons-generic-webfont": "^0.0.6", + "@bentley/ui-core": "0.171.0", "@types/chai": "^4.1.4", "@types/chai-as-promised": "^7", "@types/chai-jest-snapshot": "^1.3.0", @@ -51,7 +51,7 @@ "@types/react": "^16.4.14", "@types/react-dom": "16.0.7", "@types/react-router-dom": "^4.2.7", - "@types/sinon": "^5.0.1", + "@types/sinon": "^5.0.5", "chai": "^4.1.2", "chai-as-promised": "^7", "chai-jest-snapshot": "^2.0.0", @@ -74,7 +74,7 @@ "react-router-dom": "^4.2.2", "rimraf": "^2.6.2", "sass-loader": "^7.1.0", - "sinon": "^6.1.4", + "sinon": "^7.1.1", "sinon-chai": "^3.2.0", "style-loader": "^0.21.0", "svg-react-loader": "^0.4.5", @@ -86,7 +86,7 @@ "typedoc": "^0.11.1", "typedoc-plugin-external-module-name": "^1.1.1", "typemoq": "^2.1.0", - "typescript": "~3.0.0", + "typescript": "~3.1.0", "url-loader": "^1.0.1", "webpack": "^4.16.4", "webpack-cli": "^3.1.0", @@ -108,9 +108,6 @@ "jsdom-global/register", "source-map-support/register", "ts-node/register" - ], - "exclude": [ - "lib/**/*" ] } } diff --git a/ui/ninezone/src/backstage/Item.tsx b/ui/ninezone/src/backstage/Item.tsx index 0c11da2..0174aec 100644 --- a/ui/ninezone/src/backstage/Item.tsx +++ b/ui/ninezone/src/backstage/Item.tsx @@ -15,9 +15,9 @@ export interface BackstageItemProps extends CommonProps { icon?: React.ReactChild; /** Optional label. */ label?: string; - /** Describes if the items is active. */ + /** Describes if the item is active. */ isActive?: boolean; - /** Describes if the items is disabled. */ + /** Describes if the item is disabled. */ isDisabled?: boolean; /** Function called when item is clicked. */ onClick?: () => void; diff --git a/ui/ninezone/src/backstage/UserProfile.tsx b/ui/ninezone/src/backstage/UserProfile.tsx index 8ff5eb4..2b683da 100644 --- a/ui/ninezone/src/backstage/UserProfile.tsx +++ b/ui/ninezone/src/backstage/UserProfile.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import CommonProps, { NoChildrenProps } from "../utilities/Props"; -import { getUserColor } from "@bentley/bwc/lib/index"; +import { getUserColor } from "@bentley/bwc"; import "./UserProfile.scss"; /** Properties of [[UserProfile]] component. */ diff --git a/ui/ninezone/src/index.ts b/ui/ninezone/src/index.ts index 8c4d7fa..c5b99bb 100644 --- a/ui/ninezone/src/index.ts +++ b/ui/ninezone/src/index.ts @@ -67,9 +67,9 @@ export * from "./app/App"; */ /** * @docs-group-description Widget - * Classes for working a Widget + * Classes for working with a Widget */ /** * @docs-group-description Zone - * Classes for working a Zone + * Classes for working with a Zone */ diff --git a/ui/ninezone/tests/toolbar/button/App.test.tsx.snap b/ui/ninezone/src/test/appbutton/App.test.snap similarity index 100% rename from ui/ninezone/tests/toolbar/button/App.test.tsx.snap rename to ui/ninezone/src/test/appbutton/App.test.snap diff --git a/ui/ninezone/tests/toolbar/button/App.test.tsx b/ui/ninezone/src/test/appbutton/App.test.tsx similarity index 91% rename from ui/ninezone/tests/toolbar/button/App.test.tsx rename to ui/ninezone/src/test/appbutton/App.test.tsx index 8599c4d..c35bdfc 100644 --- a/ui/ninezone/tests/toolbar/button/App.test.tsx +++ b/ui/ninezone/src/test/appbutton/App.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import AppButton from "../../../src/toolbar/button/App"; +import AppButton from "../../widget/tools/button/App"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/toolbar/button/Back.test.tsx.snap b/ui/ninezone/src/test/appbutton/Back.test.snap similarity index 100% rename from ui/ninezone/tests/toolbar/button/Back.test.tsx.snap rename to ui/ninezone/src/test/appbutton/Back.test.snap diff --git a/ui/ninezone/tests/toolbar/button/Back.test.tsx b/ui/ninezone/src/test/appbutton/Back.test.tsx similarity index 91% rename from ui/ninezone/tests/toolbar/button/Back.test.tsx rename to ui/ninezone/src/test/appbutton/Back.test.tsx index 5f382f1..f112057 100644 --- a/ui/ninezone/tests/toolbar/button/Back.test.tsx +++ b/ui/ninezone/src/test/appbutton/Back.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import BackButton from "../../../src/toolbar/button/Back"; +import BackButton from "../../widget/tools/button/Back"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/backstage/Backstage.test.tsx.snap b/ui/ninezone/src/test/backstage/Backstage.test.snap similarity index 100% rename from ui/ninezone/tests/backstage/Backstage.test.tsx.snap rename to ui/ninezone/src/test/backstage/Backstage.test.snap diff --git a/ui/ninezone/tests/backstage/Backstage.test.tsx b/ui/ninezone/src/test/backstage/Backstage.test.tsx similarity index 96% rename from ui/ninezone/tests/backstage/Backstage.test.tsx rename to ui/ninezone/src/test/backstage/Backstage.test.tsx index bf5e9ce..79a6bfb 100644 --- a/ui/ninezone/tests/backstage/Backstage.test.tsx +++ b/ui/ninezone/src/test/backstage/Backstage.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; import * as sinon from "sinon"; -import Backstage from "../../src/backstage/Backstage"; +import Backstage from "../..//backstage/Backstage"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/backstage/Item.test.tsx.snap b/ui/ninezone/src/test/backstage/Item.test.snap similarity index 100% rename from ui/ninezone/tests/backstage/Item.test.tsx.snap rename to ui/ninezone/src/test/backstage/Item.test.snap diff --git a/ui/ninezone/tests/backstage/Item.test.tsx b/ui/ninezone/src/test/backstage/Item.test.tsx similarity index 94% rename from ui/ninezone/tests/backstage/Item.test.tsx rename to ui/ninezone/src/test/backstage/Item.test.tsx index 94a9c34..90e76d2 100644 --- a/ui/ninezone/tests/backstage/Item.test.tsx +++ b/ui/ninezone/src/test/backstage/Item.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import Item from "../../src/backstage/Item"; +import Item from "../..//backstage/Item"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/backstage/Separator.test.tsx.snap b/ui/ninezone/src/test/backstage/Separator.test.snap similarity index 100% rename from ui/ninezone/tests/backstage/Separator.test.tsx.snap rename to ui/ninezone/src/test/backstage/Separator.test.snap diff --git a/ui/ninezone/tests/backstage/Separator.test.tsx b/ui/ninezone/src/test/backstage/Separator.test.tsx similarity index 91% rename from ui/ninezone/tests/backstage/Separator.test.tsx rename to ui/ninezone/src/test/backstage/Separator.test.tsx index 6fd5dae..c90e326 100644 --- a/ui/ninezone/tests/backstage/Separator.test.tsx +++ b/ui/ninezone/src/test/backstage/Separator.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import Separator from "../../src/backstage/Separator"; +import Separator from "../..//backstage/Separator"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/base/PointerCaptor.test.tsx.snap b/ui/ninezone/src/test/base/PointerCaptor.test.snap similarity index 100% rename from ui/ninezone/tests/base/PointerCaptor.test.tsx.snap rename to ui/ninezone/src/test/base/PointerCaptor.test.snap diff --git a/ui/ninezone/tests/base/PointerCaptor.test.tsx b/ui/ninezone/src/test/base/PointerCaptor.test.tsx similarity index 95% rename from ui/ninezone/tests/base/PointerCaptor.test.tsx rename to ui/ninezone/src/test/base/PointerCaptor.test.tsx index fdedf5d..edd17ca 100644 --- a/ui/ninezone/tests/base/PointerCaptor.test.tsx +++ b/ui/ninezone/src/test/base/PointerCaptor.test.tsx @@ -6,7 +6,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; import * as sinon from "sinon"; -import PointerCaptor, { PointerCaptorState } from "../../src/base/PointerCaptor"; +import PointerCaptor, { PointerCaptorState } from "../..//base/PointerCaptor"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/base/SvgPath.test.tsx.snap b/ui/ninezone/src/test/base/SvgPath.test.snap similarity index 100% rename from ui/ninezone/tests/base/SvgPath.test.tsx.snap rename to ui/ninezone/src/test/base/SvgPath.test.snap diff --git a/ui/ninezone/tests/base/SvgPath.test.tsx b/ui/ninezone/src/test/base/SvgPath.test.tsx similarity index 98% rename from ui/ninezone/tests/base/SvgPath.test.tsx rename to ui/ninezone/src/test/base/SvgPath.test.tsx index d24fb2a..3e8eca6 100644 --- a/ui/ninezone/tests/base/SvgPath.test.tsx +++ b/ui/ninezone/src/test/base/SvgPath.test.tsx @@ -1,30 +1,30 @@ -/*--------------------------------------------------------------------------------------------- -* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. -* Licensed under the MIT License. See LICENSE.md in the project root for license terms. -*--------------------------------------------------------------------------------------------*/ -import { mount, shallow } from "enzyme"; -import * as React from "react"; - -import SvgPath from "../../src/base/SvgPath"; - -describe("", () => { - it("should render", () => { - mount( - ); - }); - - it("renders correctly", () => { - shallow( - ).should.matchSnapshot(); - }); -}); +/*--------------------------------------------------------------------------------------------- +* Copyright (c) 2018 Bentley Systems, Incorporated. All rights reserved. +* Licensed under the MIT License. See LICENSE.md in the project root for license terms. +*--------------------------------------------------------------------------------------------*/ +import { mount, shallow } from "enzyme"; +import * as React from "react"; + +import SvgPath from "../..//base/SvgPath"; + +describe("", () => { + it("should render", () => { + mount( + ); + }); + + it("renders correctly", () => { + shallow( + ).should.matchSnapshot(); + }); +}); diff --git a/ui/ninezone/tests/base/SvgSprite.test.tsx.snap b/ui/ninezone/src/test/base/SvgSprite.test.snap similarity index 100% rename from ui/ninezone/tests/base/SvgSprite.test.tsx.snap rename to ui/ninezone/src/test/base/SvgSprite.test.snap diff --git a/ui/ninezone/tests/base/SvgSprite.test.tsx b/ui/ninezone/src/test/base/SvgSprite.test.tsx similarity index 92% rename from ui/ninezone/tests/base/SvgSprite.test.tsx rename to ui/ninezone/src/test/base/SvgSprite.test.tsx index 7cf5144..6da869d 100644 --- a/ui/ninezone/tests/base/SvgSprite.test.tsx +++ b/ui/ninezone/src/test/base/SvgSprite.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import SvgSprite from "../../src/base/SvgSprite"; +import SvgSprite from "../..//base/SvgSprite"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/context/MouseTracker.test.tsx.snap b/ui/ninezone/src/test/context/MouseTracker.test.snap similarity index 100% rename from ui/ninezone/tests/context/MouseTracker.test.tsx.snap rename to ui/ninezone/src/test/context/MouseTracker.test.snap diff --git a/ui/ninezone/tests/context/MouseTracker.test.tsx b/ui/ninezone/src/test/context/MouseTracker.test.tsx similarity index 91% rename from ui/ninezone/tests/context/MouseTracker.test.tsx rename to ui/ninezone/src/test/context/MouseTracker.test.tsx index 27c53a7..4716073 100644 --- a/ui/ninezone/tests/context/MouseTracker.test.tsx +++ b/ui/ninezone/src/test/context/MouseTracker.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import MouseTracker from "../../src/context/MouseTracker"; +import MouseTracker from "../..//context/MouseTracker"; describe("", () => { it("should render", () => { diff --git a/ui/ninezone/tests/footer/Footer.test.tsx.snap b/ui/ninezone/src/test/footer/Footer.test.snap similarity index 100% rename from ui/ninezone/tests/footer/Footer.test.tsx.snap rename to ui/ninezone/src/test/footer/Footer.test.snap diff --git a/ui/ninezone/tests/footer/Footer.test.tsx b/ui/ninezone/src/test/footer/Footer.test.tsx similarity index 92% rename from ui/ninezone/tests/footer/Footer.test.tsx rename to ui/ninezone/src/test/footer/Footer.test.tsx index 7abfe30..5c0f30a 100644 --- a/ui/ninezone/tests/footer/Footer.test.tsx +++ b/ui/ninezone/src/test/footer/Footer.test.tsx @@ -5,7 +5,7 @@ import { mount, shallow } from "enzyme"; import * as React from "react"; -import Footer from "../../src/footer/Footer"; +import Footer from "../..//footer/Footer"; describe("