@arcgis/coding-components
Version:
ArcGIS Coding Components
307 lines (305 loc) • 9.77 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */
import { g as v, f as A, m as F, L as M, E as S } from "./language-defaults-base.js";
import { i as h, a as L, b as k, c as E, d as I, e as C, f as T } from "./utils.js";
import { supportedLocales as j, fetchT9nStringsBundle as $ } from "@arcgis/toolkit/intl";
import { g as P } from "./runtime.js";
import { CompletionItemKind as w, InsertTextFormat as u, InsertTextMode as f } from "vscode-languageserver-types";
import { getDeclaredPixelMembers as U } from "@arcgis/core/applications/Components/arcadeEditorUtils.js";
import "@arcgis/toolkit/type";
async function q(e) {
const { apiVersion: r, bundles: t, hiddenApiItems: s } = e.toEditorProfileDefinition(), o = await b(e.dictionaryVariables.filter(T));
return { apiVersion: r, bundles: t, variables: o, hiddenApiItems: s?.map((a) => a.toLowerCase()) };
}
async function b(e, r = w.Variable) {
return await Promise.all(
e.map(async (t) => {
switch (t.type) {
case "dictionary":
return await z(t, r);
case "pixel":
return await _(t, r);
// Voxel is equivalent to features in regards to the languageservice.
// They both have equivalent fields, but some voxel fields are different types (arrays)
// than what is reported in .fields, so in the sidebar we treat a bit differently.
// In autocomplete we don't differentiate types, so we can use the same logic as features.
case "voxel":
case "feature":
return await K(t, r);
default:
return B(t, r);
}
})
);
}
function B(e, r) {
const { name: t, type: s } = e, o = e.getDescription(), a = r === w.Field ? v(t, !1) : t;
return {
name: t,
description: o,
type: s,
completion: {
label: t,
detail: t,
insertText: a,
insertTextMode: f.asIs,
insertTextFormat: u.PlainText,
kind: r,
documentation: { kind: "markdown", value: o }
}
};
}
async function K(e, r) {
const t = await e.loadSource(), { name: s } = e, o = e.getDescription(), a = {
label: s,
detail: s,
insertText: s,
insertTextMode: f.asIs,
insertTextFormat: u.PlainText,
kind: r
}, n = {
name: s,
description: o,
type: "dictionary",
properties: [],
completion: a
};
if (!t)
return n;
const [i, c] = A(t.fields, o, {
includeAliases: !0
});
return n.properties = i, a.documentation = { kind: "markdown", value: c }, n;
}
async function _(e, r) {
const t = await e.loadSource(), s = e.toProfileVariableDefinition();
if (s.type !== "pixel")
throw new Error("Incorrect definition passed to pixel variable");
const { name: o } = e, a = e.getDescription(), n = {
label: o,
detail: o,
insertText: o,
insertTextMode: f.asIs,
insertTextFormat: u.PlainText,
kind: r
}, i = {
name: o,
description: a,
type: "dictionary",
properties: [],
completion: n
};
if (!t || !s.definition)
return i;
const c = await U(t), [d, l] = F(c, a);
return i.properties = d, n.documentation = { kind: "markdown", value: l }, i;
}
async function z(e, r) {
const { name: t, dictionaryVariables: s } = e, o = e.getDescription(), a = s.reduce((n, i) => {
n !== "" && (n += `
`), n += `**${i.name}**
${i.type}`;
const c = i.getDescription();
return c && (n += `
${c}`), n;
}, o);
return {
name: t,
description: o,
type: "dictionary",
// TODO(editor-profile): remove filter once we have a arcade-specific editor profile type that doesn't include SQL-layer-specific variables.
properties: await b(s.filter(T), w.Field),
completion: {
label: t,
detail: t,
insertText: t,
insertTextMode: f.asIs,
insertTextFormat: u.PlainText,
kind: r,
documentation: { kind: "markdown", value: a }
}
};
}
async function y(e = "en") {
const r = x.get(e);
if (r)
return r;
if (!j.has(e))
return await y("en");
try {
const t = await fetch(P(`./assets/arcade-language/profiles/arcade-profiles.t9n.${e}.json`));
return t.ok ? O(e, await t.json()) : e === "en" ? null : await y("en");
} catch {
return null;
}
}
const x = /* @__PURE__ */ new Map();
function O(e, r) {
const t = /* @__PURE__ */ new Map();
return r.forEach((s) => {
t.set(s.id, s);
}), x.set(e, t), t;
}
async function Q(e, r = "en") {
return (await y(r))?.get(e) ?? null;
}
function G(e) {
return !!e && typeof e == "object" && "additionalVariables" in e && Array.isArray(e.additionalVariables);
}
function H(e, r) {
return e?.map((t) => m(t, r));
}
function m(e, r) {
switch (e.type) {
case "dictionary":
return {
...e,
type: e.type,
properties: H(e.properties, r)
};
case "array":
return {
...e,
type: e.type,
// TODO(5.2): Remove conversion options from the function signatures, its a safety net for a last minute patch. See issue #17618
...r?.allowUserArrayElementTypes ? {} : { elementType: { type: "number", name: "number" } }
};
default:
return { ...e, type: e.type };
}
}
async function J(e, r = "en") {
if (!h(e))
return;
const t = await Q(e.id, r);
if (!t)
return;
const s = e.definitions, o = new Map(
Object.entries(s).map(([n, i]) => [n.toLowerCase(), i])
), a = {
bundles: [...t.bundles],
variables: [],
hiddenApiItems: e.hiddenApiItems?.map((n) => n.toLowerCase())
};
return t.variables.forEach((n) => {
if (e.disabledVariables?.includes(n.name))
return;
const i = o.get(n.name.toLowerCase());
switch (n.type) {
case "dictionary":
return a.variables.push(
m(
{
...n,
type: n.type,
properties: Array.isArray(i) ? [...i] : n.properties
},
{
allowUserArrayElementTypes: e.id === "data-pipelines"
}
)
);
case "feature":
return C(i) ? a.variables.push({ ...n, type: "feature", definition: i }) : void 0;
case "pixel":
return I(i) ? a.variables.push({ ...n, type: n.type, definition: i }) : void 0;
case "voxel":
return E(i) ? a.variables.push({ ...n, type: n.type, definition: i }) : void 0;
case "featureSet":
return k(i) ? a.variables.push({ ...n, type: n.type, definition: i }) : void 0;
case "featureSetCollection":
return L(i) ? a.variables.push({ ...n, type: n.type, definition: i }) : void 0;
default:
return a.variables.push(m(n));
}
}), G(e) && a.variables.push(...e.additionalVariables ?? []), a;
}
const N = [
"catalog-footprint",
"catalog",
"csv",
"feature",
"geojson",
"knowledge-graph-sublayer",
"oriented-imagery",
"subtype-group",
"subtype-sublayer",
"wfs",
"parquet"
];
function R(e) {
return !!e && typeof e == "object" && "type" in e && typeof e.type == "string" && N.includes(e.type);
}
const W = {
filterFsCollectionLayersFromMap: (e) => (
// only some layers are supported in arcade feature sets, one example: https://devtopia.esri.com/WebGIS/arcgis-web-components/issues/10715
R(e)
)
};
class X extends M {
/**
* Per-model update chaining to serialize profile updates.
*
* Why this is necessary:
* `setProfileForModel` does multiple async steps (t9n load, predefined profile conversion,
* profile -> API conversion, model disposal + re-init). If callers invoke it twice quickly,
* the *earlier* call can complete *after* the later call and overwrite the newer API context,
* because a new profile could convert faster than an old one (e.g. if the newer one is a simple
* editor profile but the older one is a complex predefined profile that takes a while to convert).
*
* We prevent that race by chaining a promise per model key so updates for the same model run
* strictly in call order.
*/
#e = /* @__PURE__ */ new Map();
constructor() {
super("arcade", {
apiPath: "./assets/arcade-language/api",
apiPrefix: "arcade-api.t9n."
});
}
/**
* Create an EditorProfile for the given model id using the given definition and locale.
* The EditorProfile is used by the the Arcade Language service as well by the coding components.
* The definition can be a pre-defined profile or an editor profile definition.
* If the locale is not provided then the 'en' locale is used.
* @param modelId The model id for which to create the context.
* @param definition The definition to use for the model context.
* @param locale The locale to use for the model context.
* @returns The EditorProfile for the model.
*/
async setProfileForModel(r, t, s = { locale: "en" }) {
const o = this._getApiKey(r), n = (this.#e.get(o) || Promise.resolve()).then(async () => {
const c = t, d = h(c) ? await J(c, s.locale) : c, l = await $(
s.locale,
P("./assets/editor-profile/t9n"),
"messages."
);
if (!l)
throw new Error(`Failed to load the language bundle for ${s.locale}`);
this.disposeForModel(r);
const g = new S(
d,
l,
s.locale,
W
);
this._modelToProfileMap.set(o, g);
const D = await q(g);
this.updateApiContextForModel(r, {
locale: s.locale,
profile: D,
snippets: s.snippets
});
}), i = n.finally(() => {
this.#e.get(o) === i && this.#e.delete(o);
});
return this.#e.set(o, i), await n;
}
}
const p = new X(), ie = {
setProfileForModel: p.setProfileForModel.bind(p),
getApiLibraryForModel: p.getApiLibraryForModel.bind(p)
};
export {
ie as a,
p as b
};