From 7eeac9e73b13b7a9b542408effbc429c0074f9ff Mon Sep 17 00:00:00 2001 From: James Milner Date: Mon, 14 Oct 2024 21:53:05 +0100 Subject: [PATCH 1/2] fix: improve the extension object to make it easier to extend Terra Draw base adapters and modes --- src/extend.ts | 18 +++ src/terra-draw.extensions.spec.ts | 221 ++++++++++++++++++++++++++++++ src/terra-draw.ts | 12 +- 3 files changed, 241 insertions(+), 10 deletions(-) create mode 100644 src/extend.ts create mode 100644 src/terra-draw.extensions.spec.ts diff --git a/src/extend.ts b/src/extend.ts new file mode 100644 index 0000000..8ff4be9 --- /dev/null +++ b/src/extend.ts @@ -0,0 +1,18 @@ +import { + TerraDrawBaseAdapter, + BaseAdapterConfig, +} from "./adapters/common/base.adapter"; +import { HexColorStyling, NumericStyling } from "./common"; +import { BaseModeOptions, TerraDrawBaseDrawMode } from "./modes/base.mode"; + +// This object allows 3rd party developers to +// extend these abstract classes and create there +// own modes and adapters +export { + TerraDrawBaseDrawMode, + TerraDrawBaseAdapter, + BaseAdapterConfig, + NumericStyling, + HexColorStyling, + BaseModeOptions, +}; diff --git a/src/terra-draw.extensions.spec.ts b/src/terra-draw.extensions.spec.ts new file mode 100644 index 0000000..081ccbb --- /dev/null +++ b/src/terra-draw.extensions.spec.ts @@ -0,0 +1,221 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +/** + * For this specific test suite we should only ever import from the Terra Draw + * public API, and not from the internal implementation details. This is to ensure that + * a developer can write a custom adapter exclusively from the publicly exposed base classes and tpyes + * of the library. + */ + +import { exp } from "protomaps-leaflet"; +import { CustomStyling } from "./modes/base.mode"; +import { + GeoJSONStoreFeatures, + GetLngLatFromEvent, + Project, + SetCursor, + TerraDraw, + TerraDrawAdapterStyling, + TerraDrawChanges, + TerraDrawExtend, + TerraDrawPointMode, + TerraDrawStylingFunction, + Unproject, + BehaviorConfig, +} from "./terra-draw"; + +// Frustratingly required to keep the tests working and avoiding SyntaxError: Cannot use import statement outside a module +jest.mock("ol/style/Circle", () => jest.fn()); +jest.mock("ol/style/Fill", () => jest.fn()); +jest.mock("ol/style/Stroke", () => jest.fn()); +jest.mock("ol/style/Style", () => jest.fn()); +jest.mock("ol/proj", () => jest.fn()); +jest.mock("ol/geom/Geometry", () => jest.fn()); + +const { TerraDrawBaseAdapter, TerraDrawBaseDrawMode } = TerraDrawExtend; + +describe("Terra Draw", () => { + describe("can support a custom adapter from the public interface where the", () => { + describe("constructor", () => { + it("initialises correctly", () => { + const draw = new TerraDraw({ + adapter: new TerraDrawTestAdapter({ + lib: {}, + coordinatePrecision: 9, + }), + modes: [new TerraDrawPointMode()], + }); + + expect(draw).toBeDefined(); + }); + }); + + describe("start and stop methods", () => { + it("work as expected", () => { + const draw = new TerraDraw({ + adapter: new TerraDrawTestAdapter({ + lib: {}, + coordinatePrecision: 9, + }), + modes: [new TerraDrawPointMode()], + }); + + draw.start(); + + expect(draw.enabled).toEqual(true); + + draw.stop(); + + expect(draw.enabled).toEqual(false); + }); + }); + }); + + describe("can support a custom draw mode from the public interface where the", () => { + describe("constructor", () => { + it("initialises correctly", () => { + const mode = new TerraDrawTestMode({ + customProperty: "custom", + }); + + expect(mode).toBeDefined(); + }); + }); + + describe("start and stop methods", () => { + it("work as expected", () => { + const draw = new TerraDraw({ + adapter: new TerraDrawTestAdapter({ + lib: {}, + coordinatePrecision: 9, + }), + modes: [new TerraDrawTestMode()], + }); + + draw.start(); + + expect(draw.enabled).toEqual(true); + + draw.stop(); + + expect(draw.enabled).toEqual(false); + }); + }); + }); +}); + +/** + * A mock implementation of a custom adapter - this is to ensure that it is possible to write + * custom adapters for Terra Draw exclusively relying on the public API of the library. + */ +class TerraDrawTestAdapter extends TerraDrawBaseAdapter { + constructor( + config: { + lib: Record; + } & TerraDrawExtend.BaseAdapterConfig, + ) { + super(config); + } + + public getMapEventElement(): HTMLElement { + return { + addEventListener: () => { + // + }, + removeEventListener: () => { + // + }, + } as unknown as HTMLElement; + } + public clear(): void { + // pass + } + + public project(lng: number, lat: number): ReturnType { + return { x: lng, y: lat }; + } + public unproject(x: number, y: number): ReturnType { + return { lng: x, lat: y }; + } + public setCursor( + _: + | "move" + | "unset" + | "grab" + | "grabbing" + | "crosshair" + | "pointer" + | "wait", + ): ReturnType { + // pass + } + public getLngLatFromEvent( + _: PointerEvent | MouseEvent, + ): ReturnType { + return { lng: 0, lat: 0 }; + } + public setDraggability(_: boolean): void { + // pass + } + public setDoubleClickToZoom(_: boolean): void { + // pass + } + public render(_1: TerraDrawChanges, _2: TerraDrawStylingFunction): void { + // pass + } +} + +/** + * A mock implementation for a custom draw mode - this is to ensure that it is possible to write + * custom draw modes for Terra Draw exclusively relying on the public API of the library. + */ + +interface TerraDrawTestModeOptions + extends TerraDrawExtend.BaseModeOptions { + customProperty: string; +} + +interface TestModeStyling extends CustomStyling { + fillColor: TerraDrawExtend.HexColorStyling; + outlineWidth: TerraDrawExtend.NumericStyling; +} + +class TerraDrawTestMode extends TerraDrawBaseDrawMode { + private customProperty: string; + + constructor(options?: TerraDrawTestModeOptions) { + super(options); + + this.customProperty = options?.customProperty ?? "default"; + } + + styleFeature(_: GeoJSONStoreFeatures): TerraDrawAdapterStyling { + return { + polygonFillColor: "#3f97e0", + polygonOutlineColor: "#3f97e0", + polygonOutlineWidth: 4, + polygonFillOpacity: 0.3, + pointColor: "#B90E0A", + pointOutlineColor: "#ffffff", + pointOutlineWidth: 2, + pointWidth: 5, + lineStringColor: "#B90E0A", + lineStringWidth: 4, + zIndex: 0, + }; + } + + registerBehaviors(_: BehaviorConfig): void { + // pass + } + + start(): void { + throw new Error("Method not implemented."); + } + stop(): void { + throw new Error("Method not implemented."); + } + cleanUp(): void { + throw new Error("Method not implemented."); + } +} diff --git a/src/terra-draw.ts b/src/terra-draw.ts index d6d7f15..25e841f 100644 --- a/src/terra-draw.ts +++ b/src/terra-draw.ts @@ -19,7 +19,6 @@ import { SELECT_PROPERTIES, OnFinishContext, } from "./common"; -import { TerraDrawBaseAdapter } from "./adapters/common/base.adapter"; import { ModeTypes, TerraDrawBaseDrawMode, @@ -54,6 +53,7 @@ import { ValidateNotSelfIntersecting } from "./validations/not-self-intersecting import { TerraDrawAngledRectangleMode } from "./modes/angled-rectangle/angled-rectangle.mode"; import { TerraDrawSectorMode } from "./modes/sector/sector.mode"; import { TerraDrawSensorMode } from "./modes/sensor/sensor.mode"; +import * as TerraDrawExtend from "./extend"; type FinishListener = (id: FeatureId, context: OnFinishContext) => void; type ChangeListener = (ids: FeatureId[], type: string) => void; @@ -776,14 +776,6 @@ class TerraDraw { } } -// This object allows 3rd party developers to -// extend these abstract classes and create there -// own modes and adapters -const TerraDrawExtend = { - TerraDrawBaseDrawMode, - TerraDrawBaseAdapter, -}; - export { TerraDraw, @@ -807,9 +799,9 @@ export { TerraDrawMapLibreGLAdapter, TerraDrawOpenLayersAdapter, TerraDrawArcGISMapsSDKAdapter, - TerraDrawExtend, // Types that are required for 3rd party developers to extend + TerraDrawExtend, // TerraDrawBaseMode BehaviorConfig, From 133cb3fc9a6c52dfbff058ba0d901540b7173534 Mon Sep 17 00:00:00 2001 From: James Milner Date: Mon, 14 Oct 2024 21:56:46 +0100 Subject: [PATCH 2/2] fix: reference the test adapter/modes in terra-draw.extensions.spec.ts --- guides/7.DEVELOPMENT.md | 4 ++++ src/terra-draw.extensions.spec.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/guides/7.DEVELOPMENT.md b/guides/7.DEVELOPMENT.md index 3b685a3..18f7af1 100644 --- a/guides/7.DEVELOPMENT.md +++ b/guides/7.DEVELOPMENT.md @@ -120,6 +120,8 @@ public render( ): void; ``` +You can see a very basic example adapter in the `terra-draw.extensions.spec.ts` file. It shows how you can create your own adapter from the publicly exposed library imports. + ## Mode Anatomy Modes are not just limited to drawing features, for example the built-in `TerraDrawSelectMode` allows for selection and editing of geometries that have previously been drawn. The `TerraDrawRenderMode` is a "view only" Mode useful for showing non-editable data alongside editable data in your application. @@ -163,6 +165,8 @@ onDragEnd(event: TerraDrawMouseEvent) {} styleFeature(feature: GeoJSONStoreFeatures): TerraDrawAdapterStyling {} ``` +You can see a very basic example mode in the `terra-draw.extensions.spec.ts` file. It shows how you can create your own mode from the publicly exposed library imports. + ## Precommit Hooks It is probably useful to be aware of the precommit hooks you will face when trying to run a git commit on the project. There are two currently in use, namely: diff --git a/src/terra-draw.extensions.spec.ts b/src/terra-draw.extensions.spec.ts index 081ccbb..ac9d1f1 100644 --- a/src/terra-draw.extensions.spec.ts +++ b/src/terra-draw.extensions.spec.ts @@ -7,7 +7,6 @@ * of the library. */ -import { exp } from "protomaps-leaflet"; import { CustomStyling } from "./modes/base.mode"; import { GeoJSONStoreFeatures,