This is an automated email from the ASF dual-hosted git repository. EnxDev pushed a commit to branch enxdev/chat-prototype in repository https://gitbox.apache.org/repos/asf/superset.git
commit d1aa512ec99034a3aefcbe4dfa9425885b67e172 Author: Enzo Martellucci <[email protected]> AuthorDate: Tue May 26 16:11:26 2026 +0200 feat(extensions): context sharing namespaces (navigation, explore, dashboard, dataset) Adds four stable namespaces to @apache-superset/core that give extensions host-managed access to page context without coupling to Redux internals: - navigation: getPageType(), onDidChangePage (routing signal only) - explore: getCurrentChart(), onDidChangeChart (ChartContext from Redux) - dashboard: getCurrentDashboard(), onDidChangeDashboard (DashboardContext with active native filter values from Redux) - dataset: getCurrentDataset(), onDidChangeDataset (push model) All four are wired into window.superset via ExtensionsStartup. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- .../packages/superset-core/package.json | 16 ++++ .../packages/superset-core/src/dashboard/index.ts | 84 ++++++++++++++++++++ .../packages/superset-core/src/dataset/index.ts | 73 ++++++++++++++++++ .../packages/superset-core/src/explore/index.ts | 75 ++++++++++++++++++ .../packages/superset-core/src/index.ts | 4 + .../packages/superset-core/src/navigation/index.ts | 71 +++++++++++++++++ superset-frontend/src/core/dashboard/index.ts | 90 ++++++++++++++++++++++ superset-frontend/src/core/dataset/index.ts | 62 +++++++++++++++ superset-frontend/src/core/explore/index.ts | 84 ++++++++++++++++++++ superset-frontend/src/core/index.ts | 4 + superset-frontend/src/core/navigation/index.ts | 69 +++++++++++++++++ 11 files changed, 632 insertions(+) diff --git a/superset-frontend/packages/superset-core/package.json b/superset-frontend/packages/superset-core/package.json index cf9d4a02665..0fd06f79a62 100644 --- a/superset-frontend/packages/superset-core/package.json +++ b/superset-frontend/packages/superset-core/package.json @@ -18,6 +18,22 @@ "types": "./lib/authentication/index.d.ts", "default": "./lib/authentication/index.js" }, + "./dashboard": { + "types": "./lib/dashboard/index.d.ts", + "default": "./lib/dashboard/index.js" + }, + "./dataset": { + "types": "./lib/dataset/index.d.ts", + "default": "./lib/dataset/index.js" + }, + "./explore": { + "types": "./lib/explore/index.d.ts", + "default": "./lib/explore/index.js" + }, + "./navigation": { + "types": "./lib/navigation/index.d.ts", + "default": "./lib/navigation/index.js" + }, "./commands": { "types": "./lib/commands/index.d.ts", "default": "./lib/commands/index.js" diff --git a/superset-frontend/packages/superset-core/src/dashboard/index.ts b/superset-frontend/packages/superset-core/src/dashboard/index.ts new file mode 100644 index 00000000000..4e78c7eef6c --- /dev/null +++ b/superset-frontend/packages/superset-core/src/dashboard/index.ts @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileoverview Dashboard namespace for Superset extensions (P3). + * + * Exposes dashboard identity and filter state as a stable semantic API. + * Extensions must not depend on the Redux dashboard slice structure directly. + */ + +import { Event } from '../common'; + +/** + * A single native filter's current selected value(s). + * The value type is intentionally kept as `unknown` because filter values + * are heterogeneous (date ranges, string lists, numbers, etc.). + */ +export interface FilterValue { + /** The filter's stable id. */ + filterId: string; + /** Display label of the filter. */ + label: string; + /** Currently applied value, or `null` when the filter is cleared. */ + value: unknown; +} + +/** + * Normalized dashboard context exposed to extensions on the Dashboard page. + */ +export interface DashboardContext { + /** Numeric dashboard id. */ + dashboardId: number; + /** Display title of the dashboard. */ + title: string; + /** + * Active native filter values keyed by filter id. + * Only includes filters that have a value applied. + */ + filters: FilterValue[]; +} + +/** + * Returns the normalized dashboard context for the page currently being viewed, + * or `undefined` when the user is not on a Dashboard page. + * + * @example + * ```typescript + * const dash = dashboard.getCurrentDashboard(); + * if (dash) { + * console.log(dash.title, dash.filters); + * } + * ``` + */ +export declare function getCurrentDashboard(): DashboardContext | undefined; + +/** + * Event fired when the dashboard identity or its active filter values change. + * Fired on native filter value changes and on navigation to a different dashboard. + * + * @example + * ```typescript + * const sub = dashboard.onDidChangeDashboard(dash => { + * chatbot.updateContext({ dashboard: dash }); + * }); + * sub.dispose(); + * ``` + */ +export declare const onDidChangeDashboard: Event<DashboardContext>; diff --git a/superset-frontend/packages/superset-core/src/dataset/index.ts b/superset-frontend/packages/superset-core/src/dataset/index.ts new file mode 100644 index 00000000000..ea3fafa4fdb --- /dev/null +++ b/superset-frontend/packages/superset-core/src/dataset/index.ts @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileoverview Dataset namespace for Superset extensions (P3). + * + * Exposes the dataset currently being viewed as a stable semantic API. + * Aligned with backend-enforced dataset visibility and column-access semantics. + */ + +import { Event } from '../common'; + +/** + * Normalized dataset context exposed to extensions on the Dataset page. + */ +export interface DatasetContext { + /** Numeric dataset id. */ + datasetId: number; + /** Display name (table name or virtual dataset name). */ + datasetName: string; + /** Schema the dataset belongs to, if applicable. */ + schema: string | null; + /** Catalog the dataset belongs to, if applicable. */ + catalog: string | null; + /** Database name backing this dataset. */ + databaseName: string | null; + /** Whether this is a virtual (SQL-defined) dataset. */ + isVirtual: boolean; +} + +/** + * Returns the normalized dataset context for the page currently being viewed, + * or `undefined` when the user is not on a Dataset page. + * + * @example + * ```typescript + * const ds = dataset.getCurrentDataset(); + * if (ds) { + * console.log(ds.datasetName, ds.schema); + * } + * ``` + */ +export declare function getCurrentDataset(): DatasetContext | undefined; + +/** + * Event fired when the focused dataset changes (e.g. the user navigates to a + * different dataset detail page). + * + * @example + * ```typescript + * const sub = dataset.onDidChangeDataset(ds => { + * chatbot.updateContext({ dataset: ds }); + * }); + * sub.dispose(); + * ``` + */ +export declare const onDidChangeDataset: Event<DatasetContext>; diff --git a/superset-frontend/packages/superset-core/src/explore/index.ts b/superset-frontend/packages/superset-core/src/explore/index.ts new file mode 100644 index 00000000000..162d1b2e6f7 --- /dev/null +++ b/superset-frontend/packages/superset-core/src/explore/index.ts @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileoverview Explore namespace for Superset extensions (P3). + * + * Exposes the current chart/explore context as a stable semantic API. + * Normalized over Explore Redux state — extensions must not depend on + * the Redux slice structure directly. + */ + +import { Event } from '../common'; + +/** + * Normalized chart context exposed to extensions during an Explore session. + * Covers saved chart identity and transient editing context; excludes raw + * form-data internals and datasource-implementation details. + */ +export interface ChartContext { + /** The saved chart id, or `null` when the chart has not been persisted. */ + chartId: number | null; + /** Display name of the saved chart, or `null` for a new/unsaved chart. */ + chartName: string | null; + /** The visualization type currently selected in the editor. */ + vizType: string; + /** Id of the datasource backing the chart (physical or virtual dataset). */ + datasourceId: number | null; + /** Human-readable datasource name. */ + datasourceName: string | null; +} + +/** + * Returns the normalized chart context for the active Explore session, or + * `undefined` when the user is not on the Explore page. + * + * @example + * ```typescript + * const chart = explore.getCurrentChart(); + * if (chart) { + * console.log(chart.vizType, chart.chartName); + * } + * ``` + */ +export declare function getCurrentChart(): ChartContext | undefined; + +/** + * Event fired when the chart context changes within the active Explore session + * (e.g. when the viz type, datasource, or saved name changes). + * Not fired during route changes — subscribe to `navigation.onDidChangePage` for those. + * + * @example + * ```typescript + * const sub = explore.onDidChangeChart(chart => { + * chatbot.updateContext({ chart }); + * }); + * sub.dispose(); + * ``` + */ +export declare const onDidChangeChart: Event<ChartContext>; diff --git a/superset-frontend/packages/superset-core/src/index.ts b/superset-frontend/packages/superset-core/src/index.ts index 75863372409..79c699caff4 100644 --- a/superset-frontend/packages/superset-core/src/index.ts +++ b/superset-frontend/packages/superset-core/src/index.ts @@ -19,9 +19,13 @@ export * as common from './common'; export * as authentication from './authentication'; export * as commands from './commands'; +export * as dashboard from './dashboard'; +export * as dataset from './dataset'; export * as editors from './editors'; +export * as explore from './explore'; export * as extensions from './extensions'; export * as menus from './menus'; +export * as navigation from './navigation'; export * as sqlLab from './sqlLab'; export * as views from './views'; export * as contributions from './contributions'; diff --git a/superset-frontend/packages/superset-core/src/navigation/index.ts b/superset-frontend/packages/superset-core/src/navigation/index.ts new file mode 100644 index 00000000000..b1aecf6375f --- /dev/null +++ b/superset-frontend/packages/superset-core/src/navigation/index.ts @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileoverview Navigation namespace for Superset extensions (P3). + * + * Exposes the current application surface so extensions can react to route + * changes without polling. Entity-level context (chart, dashboard, dataset) + * is intentionally not included here — use the surface-specific namespace + * (`explore`, `dashboard`, `dataset`) to retrieve entity payloads. + */ + +import { Event } from '../common'; + +/** + * The set of top-level application surfaces. + * `'other'` covers any route not explicitly enumerated. + */ +export type PageType = + | 'dashboard' + | 'explore' + | 'sqllab' + | 'dataset' + | 'home' + | 'other'; + +/** + * Returns the current page surface type. + * + * @example + * ```typescript + * const pageType = navigation.getPageType(); + * if (pageType === 'dashboard') { + * const ctx = dashboard.getCurrentDashboard(); + * } + * ``` + */ +export declare function getPageType(): PageType; + +/** + * Event fired whenever the user navigates to a different surface. + * Use the surface-specific namespace to read entity context after the event. + * + * @example + * ```typescript + * const sub = navigation.onDidChangePage(pageType => { + * if (pageType === 'dashboard') { + * const ctx = dashboard.getCurrentDashboard(); + * } + * }); + * // later: + * sub.dispose(); + * ``` + */ +export declare const onDidChangePage: Event<PageType>; diff --git a/superset-frontend/src/core/dashboard/index.ts b/superset-frontend/src/core/dashboard/index.ts new file mode 100644 index 00000000000..fbf17f3fa62 --- /dev/null +++ b/superset-frontend/src/core/dashboard/index.ts @@ -0,0 +1,90 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Host-internal implementation of the `dashboard` namespace. + * + * Wraps Redux dashboardInfo and dataMask state and normalizes them into the + * stable `DashboardContext` contract. Extensions must not depend on the Redux + * slice structure directly. + */ + +import type { dashboard as dashboardApi } from '@apache-superset/core'; +import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate'; +import { + UPDATE_DATA_MASK, + SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE, +} from 'src/dataMask/actions'; +import { store, RootState } from 'src/views/store'; +import { AnyListenerPredicate } from '@reduxjs/toolkit'; +import { createActionListener } from '../utils'; + +type DashboardContext = dashboardApi.DashboardContext; +type FilterValue = dashboardApi.FilterValue; + +function buildDashboardContext(): DashboardContext | undefined { + const state = store.getState(); + const info = (state as any).dashboardInfo; + if (!info?.id) return undefined; + + const nativeFilters = (state as any).nativeFilters?.filters ?? {}; + const dataMask = (state as any).dataMask ?? {}; + + const filters: FilterValue[] = Object.entries(dataMask) + .filter(([id, mask]: [string, any]) => { + if (!(id in nativeFilters)) return false; + const value = mask?.filterState?.value; + return value !== null && value !== undefined; + }) + .map(([id, mask]: [string, any]) => ({ + filterId: id, + label: nativeFilters[id]?.name ?? id, + value: mask.filterState.value, + })); + + return { + dashboardId: info.id as number, + title: info.dashboard_title ?? info.slug ?? String(info.id), + filters, + }; +} + +const dashboardChangePredicate: AnyListenerPredicate<RootState> = action => + action.type === HYDRATE_DASHBOARD || + action.type === UPDATE_DATA_MASK || + action.type === SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE; + +const getCurrentDashboard: typeof dashboardApi.getCurrentDashboard = () => + buildDashboardContext(); + +const onDidChangeDashboard: typeof dashboardApi.onDidChangeDashboard = ( + listener: (ctx: DashboardContext) => void, + thisArgs?: any, +) => + createActionListener<DashboardContext>( + dashboardChangePredicate, + listener, + () => buildDashboardContext() ?? null, + thisArgs, + ); + +export const dashboard: typeof dashboardApi = { + getCurrentDashboard, + onDidChangeDashboard, +}; diff --git a/superset-frontend/src/core/dataset/index.ts b/superset-frontend/src/core/dataset/index.ts new file mode 100644 index 00000000000..3c98d79454b --- /dev/null +++ b/superset-frontend/src/core/dataset/index.ts @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Host-internal implementation of the `dataset` namespace. + * + * Dataset page components call `setCurrentDataset` to publish context as they + * load. Extensions consume the stable `DatasetContext` contract; they are + * isolated from the page's internal data-fetching implementation. + */ + +import type { dataset as datasetApi } from '@apache-superset/core'; +import { Disposable } from '../models'; + +type DatasetContext = datasetApi.DatasetContext; + +let currentDataset: DatasetContext | undefined; +const listeners = new Set<(ctx: DatasetContext) => void>(); + +/** + * Host-internal: called by the Dataset page when its entity loads or changes. + * Not part of the public `@apache-superset/core` API. + */ +export const setCurrentDataset = (ctx: DatasetContext | undefined): void => { + currentDataset = ctx; + if (ctx) { + listeners.forEach(fn => fn(ctx)); + } +}; + +const getCurrentDataset: typeof datasetApi.getCurrentDataset = () => + currentDataset ? { ...currentDataset } : undefined; + +const onDidChangeDataset: typeof datasetApi.onDidChangeDataset = ( + listener: (ctx: DatasetContext) => void, + thisArgs?: any, +): Disposable => { + const bound = thisArgs ? listener.bind(thisArgs) : listener; + listeners.add(bound); + return new Disposable(() => listeners.delete(bound)); +}; + +export const dataset: typeof datasetApi = { + getCurrentDataset, + onDidChangeDataset, +}; diff --git a/superset-frontend/src/core/explore/index.ts b/superset-frontend/src/core/explore/index.ts new file mode 100644 index 00000000000..46b6b2ef84e --- /dev/null +++ b/superset-frontend/src/core/explore/index.ts @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Host-internal implementation of the `explore` namespace. + * + * Wraps Redux explore state and normalizes it into the stable `ChartContext` + * contract. Extensions must not depend on the Redux slice structure directly. + */ + +import type { explore as exploreApi } from '@apache-superset/core'; +import { HYDRATE_EXPLORE } from 'src/explore/actions/hydrateExplore'; +import { + SET_FORM_DATA, + UPDATE_CHART_TITLE, +} from 'src/explore/actions/exploreActions'; +import { SET_DATASOURCE } from 'src/explore/actions/datasourcesActions'; +import { store, RootState } from 'src/views/store'; +import { AnyListenerPredicate } from '@reduxjs/toolkit'; +import { createActionListener } from '../utils'; + +type ChartContext = exploreApi.ChartContext; + +function buildChartContext(): ChartContext | undefined { + const state = store.getState(); + const exploreState = (state as any).explore; + if (!exploreState) return undefined; + + const { slice, datasource, controls } = exploreState; + const vizType: string = + (controls?.viz_type?.value as string) ?? + exploreState.form_data?.viz_type ?? + ''; + + return { + chartId: slice?.slice_id ?? null, + chartName: exploreState.sliceName ?? slice?.slice_name ?? null, + vizType, + datasourceId: datasource?.id ?? null, + datasourceName: + datasource?.table_name ?? datasource?.datasource_name ?? null, + }; +} + +const exploreChangePredicate: AnyListenerPredicate<RootState> = action => + action.type === HYDRATE_EXPLORE || + action.type === SET_FORM_DATA || + action.type === UPDATE_CHART_TITLE || + action.type === SET_DATASOURCE; + +const getCurrentChart: typeof exploreApi.getCurrentChart = () => + buildChartContext(); + +const onDidChangeChart: typeof exploreApi.onDidChangeChart = ( + listener: (ctx: ChartContext) => void, + thisArgs?: any, +) => + createActionListener<ChartContext>( + exploreChangePredicate, + listener, + () => buildChartContext() ?? null, + thisArgs, + ); + +export const explore: typeof exploreApi = { + getCurrentChart, + onDidChangeChart, +}; diff --git a/superset-frontend/src/core/index.ts b/superset-frontend/src/core/index.ts index 6a106ebe87a..d259597457c 100644 --- a/superset-frontend/src/core/index.ts +++ b/superset-frontend/src/core/index.ts @@ -28,10 +28,14 @@ export const core: typeof coreType = { export * from './authentication'; export * from './commands'; +export * from './dashboard'; +export * from './dataset'; export * from './editors'; +export * from './explore'; export * from './extensions'; export * from './menus'; export * from './models'; +export * from './navigation'; export * from './sqlLab'; export * from './utils'; export * from './views'; diff --git a/superset-frontend/src/core/navigation/index.ts b/superset-frontend/src/core/navigation/index.ts new file mode 100644 index 00000000000..96e87754f9e --- /dev/null +++ b/superset-frontend/src/core/navigation/index.ts @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Host-internal implementation of the `navigation` namespace. + * + * Backed by browser location — no Redux dependency. + * The app shell calls `notifyPageChange(pathname)` whenever the route changes. + */ + +import type { navigation as navigationApi } from '@apache-superset/core'; +import { Disposable } from '../models'; + +type PageType = navigationApi.PageType; + +const listeners = new Set<(pageType: PageType) => void>(); + +function derivePageType(pathname: string): PageType { + if (pathname.startsWith('/superset/dashboard/')) return 'dashboard'; + if (pathname.startsWith('/explore/')) return 'explore'; + if (pathname.startsWith('/superset/explore/')) return 'explore'; + if (pathname.startsWith('/chart/add')) return 'explore'; + if (pathname.startsWith('/sqllab/')) return 'sqllab'; + if (pathname.startsWith('/dataset/')) return 'dataset'; + if (pathname.startsWith('/superset/welcome/')) return 'home'; + return 'other'; +} + +let currentPageType: PageType = derivePageType(window.location.pathname); + +/** Called by ExtensionsStartup whenever the React Router location changes. */ +export const notifyPageChange = (pathname: string): void => { + const next = derivePageType(pathname); + if (next === currentPageType) return; + currentPageType = next; + listeners.forEach(fn => fn(next)); +}; + +const getPageType: typeof navigationApi.getPageType = () => currentPageType; + +const onDidChangePage: typeof navigationApi.onDidChangePage = ( + listener: (pageType: PageType) => void, + thisArgs?: any, +): Disposable => { + const bound = thisArgs ? listener.bind(thisArgs) : listener; + listeners.add(bound); + return new Disposable(() => listeners.delete(bound)); +}; + +export const navigation: typeof navigationApi = { + getPageType, + onDidChangePage, +};
