Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve the extension object to make it easier to extend Terra Draw base adapters and modes #349

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions guides/7.DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
18 changes: 18 additions & 0 deletions src/extend.ts
Original file line number Diff line number Diff line change
@@ -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,
};
220 changes: 220 additions & 0 deletions src/terra-draw.extensions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/* 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 { 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<string, unknown>;
} & 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<Project> {
return { x: lng, y: lat };
}
public unproject(x: number, y: number): ReturnType<Unproject> {
return { lng: x, lat: y };
}
public setCursor(
_:
| "move"
| "unset"
| "grab"
| "grabbing"
| "crosshair"
| "pointer"
| "wait",
): ReturnType<SetCursor> {
// pass
}
public getLngLatFromEvent(
_: PointerEvent | MouseEvent,
): ReturnType<GetLngLatFromEvent> {
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<T extends CustomStyling>
extends TerraDrawExtend.BaseModeOptions<T> {
customProperty: string;
}

interface TestModeStyling extends CustomStyling {
fillColor: TerraDrawExtend.HexColorStyling;
outlineWidth: TerraDrawExtend.NumericStyling;
}

class TerraDrawTestMode extends TerraDrawBaseDrawMode<TestModeStyling> {
private customProperty: string;

constructor(options?: TerraDrawTestModeOptions<TestModeStyling>) {
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.");
}
}
12 changes: 2 additions & 10 deletions src/terra-draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
SELECT_PROPERTIES,
OnFinishContext,
} from "./common";
import { TerraDrawBaseAdapter } from "./adapters/common/base.adapter";
import {
ModeTypes,
TerraDrawBaseDrawMode,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,

Expand All @@ -807,9 +799,9 @@ export {
TerraDrawMapLibreGLAdapter,
TerraDrawOpenLayersAdapter,
TerraDrawArcGISMapsSDKAdapter,
TerraDrawExtend,

// Types that are required for 3rd party developers to extend
TerraDrawExtend,

// TerraDrawBaseMode
BehaviorConfig,
Expand Down
Loading