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

Add Arcade Editor Sample #135

Merged
merged 9 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"file-loader": "^6.2.0",
"http-proxy-middleware": "^3.0.3",
"lint-staged": "^13.1.2",
"postcss": "^8.4.49",
"postcss-loader": "^8.1.1",
"prettier": "^2.8.4",
"raw-loader": "^4.0.2",
Expand All @@ -67,5 +68,5 @@
"sax": "^1.4.1",
"three-bmfont-text": "3.0.1"
},
"packageManager": "yarn@4.3.1"
"packageManager": "yarn@4.5.1"
}
7 changes: 7 additions & 0 deletions samples/arcade-editor/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
extends: [require.resolve("@vertigis/web-sdk/config/.eslintrc")],
parserOptions: {
tsconfigRootDir: __dirname,
},
rules: {},
};
4 changes: 4 additions & 0 deletions samples/arcade-editor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Arcade Editor

This sample demonstrates the integration of a [web component](https://developers.arcgis.com/javascript/latest/components/) based ArcGIS Maps SDK for JavaScript widget. The [Arcade Editor component](https://developers.arcgis.com/javascript/latest/references/coding-components/?path=/docs/component-reference-arcade-editor--docs) is initialized with a custom profile, and data is provided via an Identify operation.

66 changes: 66 additions & 0 deletions samples/arcade-editor/app/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"schemaVersion": "1.0",
"items": [
{
"id": "map-config-1",
"$type": "map-extension",
"webMap": "https://latitudegeo.maps.arcgis.com/home/item.html?id=12391f2d60ca49c189114a837e508ca2",
"layerExtensions": ["item://layer-extension/air-bnb-nyc"]
},
{
"id": "air-bnb-nyc",
"$type": "layer-extension",
"layer": {
"$ref": {
"id": "air-bnb-nyc-featurelayer",
"title": "AirBnB NYC",
"url": "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/WebChartTests_AirBnB_ChiCrime_MedicareCounty/FeatureServer/0"
},
"layerType": "ArcGISFeatureLayer"
}
},
{
"id": "arcade-editor-config-1",
"$type": "arcade-editor-model",
"layerName": "AirBnB NYC",
"title": "Arcade Editor"
},
{
"id": "panel-config-1",
"$type": "panel"
},
{
"id": "stack-config-1",
"$type": "stack"
},
{
"id": "text-config-1",
"text": "Use the Identify tool to select AirBnB locations in the New York City area. Your selection will be used to create a FeatureSet and open an Arcade Editor component where you can construct expressions using the data.",
"$type": "text"
},
{
"id": "button-config-1",
"iconId": "identify",
"title": "Identify",
"action": [
"highlights.clear",
{
"name": "sketching.capture-geometry",
"arguments": {
"geometryType": ["point", "extent"]
}
},
"tasks.identify",
"arcade-editor.load-data",
"highlights.add"
],
"$type": "menu-item"
},
{
"$type": "layout",
"id": "default",
"title": "Default",
"url": "./layout.xml"
}
]
}
12 changes: 12 additions & 0 deletions samples/arcade-editor/app/layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns="https://geocortex.com/layout/v1" xmlns:custom="your.custom.namespace">
<map id="map-1" config="map-config-1">
<panel id="panel-1" slot="top-start" config="panel-config-1" show-close-button="false" show-back-button="false" show-maximize-button="false" show-minimize-button="false">
<stack id="stack-1" config="stack-config-1" halign="center">
<text id="text-1" config="text-config-1" />
<button id="button-1" config="button-config-1" size="large" icon-position="above" foreground-color="#1a72c4" background-color="#ffffff" border="false" margin="0.7" />
</stack>
</panel>
<custom:arcade-editor slot="bottom-end" config="arcade-editor-config-1" />
</map>
</layout>
18 changes: 18 additions & 0 deletions samples/arcade-editor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "sample-arcade-editor",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "vertigis-web-sdk build",
"start": "vertigis-web-sdk start"
},
"dependencies": {
"@arcgis/coding-components-react": "4.30.7",
"@esri/calcite-components-react": "2.12.0"
},
"devDependencies": {
"@vertigis/web": "~5.33.0",
"@vertigis/web-sdk": "~1.11.0",
"typescript": "~5.3.3"
}
}
24 changes: 24 additions & 0 deletions samples/arcade-editor/src/components/ArcadeEditor/ArcadeEditor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@import "https://js.arcgis.com/calcite-components/2.8.6/calcite.css";
@import "https://js.arcgis.com/coding-components/4.30/arcgis-coding-components.css";

/* Normally a light or dark calcite theme would also be imported here, but Web
will take care of these styles itself. */

.editor-wrapper {
display: flex;
flex-direction: column;
min-height: 500px;
min-width: 50vw;
max-height: 50vh;
max-width: 50vw !important;
box-sizing: border-box;
padding: 1rem;
}

arcgis-arcade-editor {
/* Font sizing issues inside the webcomponent can be adressed by redeclaring
* the css variables on the webcomponent host */
--calcite-font-size-0: 1.6rem;
--calcite-font-size--1: 1.4rem;
--calcite-font-size--2: 1.2rem;
}
118 changes: 118 additions & 0 deletions samples/arcade-editor/src/components/ArcadeEditor/ArcadeEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { ArcgisArcadeEditor } from "@arcgis/coding-components-react";
import { CalciteScrim } from "@esri/calcite-components-react";
import type { LayoutElementProperties } from "@vertigis/web/components";
import { LayoutElement } from "@vertigis/web/components";
import { useWatchAndRerender } from "@vertigis/web/ui";
import Paper from "@vertigis/web/ui/Paper";
import { type ReactElement } from "react";

import type ArcadeEditorModel from "./ArcadeEditorModel";

import "./ArcadeEditor.css";

type ArcadeEditorProps = LayoutElementProperties<ArcadeEditorModel>;

const ArcadeEditor = (props: ArcadeEditorProps): ReactElement => {
const { model } = props;
const { data } = model;

useWatchAndRerender(model, "data");

return (
<LayoutElement
{...props}
stretch
className="arcade-editor-webcomponent"
>
<Paper className="editor-wrapper">
{data ? (
<ArcgisArcadeEditor
// Set the script on the editor
script="$featureSet"
// Log script change events
onArcgisScriptChange={async (e) => {
console.log("script:", e.detail);
// console.log("outputType on script:", await arcadeEditorElt.getOutputType());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these console logs could be cleaned up

}}
// Log editor diagnostics
onArcgisDiagnosticsChange={async (e) => {
console.log("diagnostics:", e.detail);
}}
// Tells the Arcade editor to use a custom profile with
// the defined variables and bundles loaded.
profile={{
label: "My Custom Arcade Profile",
variables: [
{
name: "$feature",
description:
"Provide a single feature from a featureSet. Use the same definition as you would use for a featureSet.",
type: "feature",
definition: {
fields: data.featureLayer.fields,
},
},
{
name: "$featureSet",
description:
"Use a field collection as the definition for a featureSet to provide arbitrary data that isn't linked to a layer.",
type: "featureSet",
definition: {
fields: data.featureLayer.fields,
},
},
{
name: "$layer",
description:
"Use the portal id of a layer as the definition for a featureSet to provide data originating from that layer.",
type: "featureSet",
definition: {
portalItem: {
id: data.featureLayer.id,
},
},
},
{
name: "$map",
description:
"Use the id of a webmap as the definition for a featureSetCollection to access data from that webmap.",
type: "featureSetCollection",
definition: {
portalItem: {
id: "12391f2d60ca49c189114a837e508ca2",
},
},
},
{
name: "$datastore",
description:
"Use the url of a feature service as the definition for a featureSetCollection to access data from that service.",
type: "featureSetCollection",
definition: { url: data.featureLayer.url },
},
],
bundles: ["core", "data-access", "geometry"],
}}
// Tells Arcade editor to use the following test data.
// The data provided must match the expected data for
// the variables defined in the profile.
testData={{
profileVariableInstances: {
$feature: data.featureSet.features[0],
$featureSet: data.featureSet,
$layer: data.featureLayer,
$map: data.webMap,
$datastore: data.featureLayer.url,
},
spatialReference: data.featureSet.spatialReference,
}}
/>
) : (
<CalciteScrim loading />
)}
</Paper>
</LayoutElement>
);
};

export default ArcadeEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type WebMap from "@arcgis/core/WebMap";
import type FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import FeatureSet from "@arcgis/core/rest/support/FeatureSet";
import { isLayerExtension } from "@vertigis/arcgis-extensions/ItemType";
import type { FeatureLayerExtension } from "@vertigis/arcgis-extensions/mapping/FeatureLayerExtension";
import type { MapModel } from "@vertigis/web/mapping/MapModel";
import type { HasFeatures } from "@vertigis/web/messaging";
import { command } from "@vertigis/web/messaging";
import { toFeatureArray } from "@vertigis/web/messaging/featureConversion";
import { toLayerExtension } from "@vertigis/web/messaging/mapConversion";
import type {
ComponentModelProperties,
PropertyDefs,
} from "@vertigis/web/models";
import {
ComponentModelBase,
importModel,
serializable,
} from "@vertigis/web/models";

export interface ArcadeEditorData {
webMap: WebMap;
featureLayer: FeatureLayer;
featureSet: FeatureSet;
}

interface ArcadeEditorModelProperties extends ComponentModelProperties {
layerName?: string;
}

@serializable
export default class ArcadeEditorModel extends ComponentModelBase<ArcadeEditorModelProperties> {
@importModel("map-extension")
map: MapModel;

data: ArcadeEditorData;
layerName: string;
featureLayer: __esri.FeatureLayer;

constructor(props: ArcadeEditorModelProperties) {
super(props);
this.layerName = props.layerName;
}

@command("arcade-editor.load-data")
protected async _executeArcadeEditorLoadData(
args: HasFeatures
): Promise<void> {
const featureSet = new FeatureSet({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the arcadeeditor be initially hidden and then shown when we load data?

features: (await toFeatureArray(args.features)).map((feature) =>
feature.toGraphic()
),
fields: this.featureLayer.fields,
geometryType: "point",
spatialReference: this.featureLayer.spatialReference,
});
this.data = {
webMap: this.map.webMap as unknown as __esri.WebMap,
featureLayer: this.featureLayer,
featureSet,
};
}

protected override async _onInitialize(): Promise<void> {
const watchHandle = this.watch("map", async () => {
if (!this.map) {
return undefined;
}
const extension = toLayerExtension(this.layerName, this.map);
if (
isLayerExtension(extension) &&
(extension as FeatureLayerExtension).layer.type === "feature"
) {
this.featureLayer = (extension as FeatureLayerExtension).layer;
}
watchHandle.remove();
await this.messages
.command<HasFeatures>("arcade-editor.load-data")
.execute({ features: [] });
});
}

protected override _getSerializableProperties(): PropertyDefs<ComponentModelProperties> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably can be removed.

const props = super._getSerializableProperties();
return {
...props,
};
}
}
2 changes: 2 additions & 0 deletions samples/arcade-editor/src/components/ArcadeEditor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./ArcadeEditor";
export { default as ArcadeEditorModel } from "./ArcadeEditorModel";
32 changes: 32 additions & 0 deletions samples/arcade-editor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { setAssetPath as setCalciteComponentsAssetPath } from "@esri/calcite-components/dist/components";
import type { LibraryRegistry } from "@vertigis/web/config";

import ArcadeEditor from "./components/ArcadeEditor";
import ArcadeEditorModel from "./components/ArcadeEditor/ArcadeEditorModel";

// Individual imports for each component used in this sample
import "@arcgis/coding-components/dist/components/arcgis-arcade-editor";
import "@esri/calcite-components/dist/components/calcite-scrim";

// Set the asset path for calcite components
setCalciteComponentsAssetPath(
"https://js.arcgis.com/calcite-components/2.8.6/assets"
);

export default function registerLibrary(registry: LibraryRegistry): void {
registry.registerComponent({
name: "arcade-editor",
namespace: "your.custom.namespace",
getComponentType: () => ArcadeEditor,
itemType: "arcade-editor-model",
title: "language-web-incubator-arcade-editor-title",
});
registry.registerModel({
getModel: (config) => new ArcadeEditorModel(config),
itemType: "arcade-editor-model",
});
registry.registerCommand({
name: "arcade-editor.load-data",
itemType: "arcade-editor-model",
});
}
Loading