UNPKG

@arcgis/coding-components

Version:
307 lines (305 loc) • 9.77 kB
/* 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 };