mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Navigation refactoring, handling extension page params (#1651)
* decentralizing page url-params management -- PoC / tsc 4.1 random fixes Signed-off-by: Roman <ixrock@gmail.com> * fixes, tweak example-extension for demo Signed-off-by: Roman <ixrock@gmail.com> * lint fixes, revert tests Signed-off-by: Roman <ixrock@gmail.com> * removed occasional changes related to typescript 4.1 Signed-off-by: Roman <ixrock@gmail.com> * updated example with 2 menu-items targeting same page with different params Signed-off-by: Roman <ixrock@gmail.com> * fix: merge page url chunks with native URL()-api, simplified default page-params registration Signed-off-by: Roman <ixrock@gmail.com> * fix: make lint happy Signed-off-by: Roman <ixrock@gmail.com> * fix: unit-tests Signed-off-by: Roman <ixrock@gmail.com> * renaming by jim's request: UrlParam => PageParam (type), createUrlParam => createPageParam (helper) Signed-off-by: Roman <ixrock@gmail.com> * fix: reverting NamespaceStore public-api breaking changes Signed-off-by: Roman <ixrock@gmail.com> * lint fix Signed-off-by: Roman <ixrock@gmail.com> * fine-tuning Signed-off-by: Roman <ixrock@gmail.com> * yes, lint always unhappy Signed-off-by: Roman <ixrock@gmail.com> * fix build Signed-off-by: Roman <ixrock@gmail.com> * small fixes Signed-off-by: Roman <ixrock@gmail.com> * fix merge-conflicts Signed-off-by: Roman <ixrock@gmail.com> * removed `isSystem` page-param's init field exposed to extensions-api Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
1 parent
11c611dabe
commit
be4e1aa15c
64 files changed
+751
-518
No files matched your search
@@ -0,0 +1,31 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import { reaction } from "mobx";
|
||||
import { getMatchedClusterId, navigate } from "./helpers";
|
||||
import { broadcastMessage, subscribeToBroadcast } from "../../common/ipc";
|
||||
import logger from "../../main/logger";
|
||||
|
||||
export function bindEvents() {
|
||||
if (!ipcRenderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.isMainFrame) {
|
||||
// Keep track of active cluster-id for handling IPC/menus/etc.
|
||||
reaction(() => getMatchedClusterId(), clusterId => {
|
||||
broadcastMessage("cluster-view:current-id", clusterId);
|
||||
}, {
|
||||
fireImmediately: true
|
||||
});
|
||||
}
|
||||
|
||||
// Handle navigation via IPC (e.g. from top menu)
|
||||
subscribeToBroadcast("renderer:navigate", (event, url: string) => {
|
||||
logger.info(`[IPC]: ${event.type} ${JSON.stringify(url)}`, event);
|
||||
navigate(url);
|
||||
});
|
||||
|
||||
// Reload dashboard window
|
||||
subscribeToBroadcast("renderer:reload", () => {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { LocationDescriptor } from "history";
|
||||
import { matchPath, RouteProps } from "react-router";
|
||||
import { PageParam, PageSystemParamInit } from "./page-param";
|
||||
import { clusterViewRoute, IClusterViewRouteParams } from "../components/cluster-manager/cluster-view.route";
|
||||
import { navigation } from "./history";
|
||||
|
||||
export function navigate(location: LocationDescriptor) {
|
||||
const currentLocation = navigation.getPath();
|
||||
|
||||
navigation.push(location);
|
||||
|
||||
if (currentLocation === navigation.getPath()) {
|
||||
navigation.goBack(); // prevent sequences of same url in history
|
||||
}
|
||||
}
|
||||
|
||||
export function createPageParam<V = string>(init: PageSystemParamInit<V>) {
|
||||
return new PageParam<V>(init, navigation);
|
||||
}
|
||||
|
||||
export function matchRoute<P>(route: string | string[] | RouteProps) {
|
||||
return matchPath<P>(navigation.location.pathname, route);
|
||||
}
|
||||
|
||||
export function isActiveRoute(route: string | string[] | RouteProps): boolean {
|
||||
return !!matchRoute(route);
|
||||
}
|
||||
|
||||
export function getMatchedClusterId(): string {
|
||||
const matched = matchPath<IClusterViewRouteParams>(navigation.location.pathname, {
|
||||
exact: true,
|
||||
path: clusterViewRoute.path
|
||||
});
|
||||
|
||||
return matched?.params.clusterId;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import { createBrowserHistory, createMemoryHistory } from "history";
|
||||
import { createObservableHistory } from "mobx-observable-history";
|
||||
|
||||
export const history = ipcRenderer ? createBrowserHistory() : createMemoryHistory();
|
||||
export const navigation = createObservableHistory(history);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Navigation (renderer)
|
||||
|
||||
import { bindEvents } from "./events";
|
||||
|
||||
export * from "./history";
|
||||
export * from "./helpers";
|
||||
|
||||
bindEvents();
|
||||
@@ -0,0 +1,135 @@
|
||||
// Manage observable URL-param from document.location.search
|
||||
import { IObservableHistory } from "mobx-observable-history";
|
||||
|
||||
export interface PageParamInit<V = any> {
|
||||
name: string;
|
||||
defaultValue?: V;
|
||||
defaultValueStringified?: string | string[]; // serialized version of "defaultValue"
|
||||
multiValues?: boolean; // false == by default
|
||||
multiValueSep?: string; // joining multiple values with separator, default: ","
|
||||
skipEmpty?: boolean; // skip empty value(s), e.g. "?param=", default: true
|
||||
parse?(value: string[]): V; // deserialize from URL
|
||||
stringify?(value: V): string | string[]; // serialize params to URL
|
||||
}
|
||||
|
||||
export interface PageSystemParamInit<V = any> extends PageParamInit<V> {
|
||||
isSystem?: boolean;
|
||||
}
|
||||
|
||||
export class PageParam<V = any> {
|
||||
static SYSTEM_PREFIX = "lens-";
|
||||
|
||||
readonly name: string;
|
||||
protected urlName: string;
|
||||
|
||||
constructor(readonly init: PageParamInit<V> | PageSystemParamInit<V>, protected history: IObservableHistory) {
|
||||
const { isSystem, name } = init as PageSystemParamInit;
|
||||
|
||||
this.name = name;
|
||||
this.init.skipEmpty ??= true;
|
||||
this.init.multiValueSep ??= ",";
|
||||
|
||||
// prefixing to avoid collisions with extensions
|
||||
this.urlName = `${isSystem ? PageParam.SYSTEM_PREFIX : ""}${name}`;
|
||||
}
|
||||
|
||||
isEmpty(value: V | any) {
|
||||
return [value].flat().every(value => value == "" || value == null);
|
||||
}
|
||||
|
||||
parse(values: string[]): V {
|
||||
const { parse, multiValues } = this.init;
|
||||
|
||||
if (!multiValues) values.splice(1); // reduce values to single item
|
||||
const parsedValues = [parse ? parse(values) : values].flat();
|
||||
|
||||
return multiValues ? parsedValues : parsedValues[0] as any;
|
||||
}
|
||||
|
||||
stringify(value: V = this.get()): string {
|
||||
const { stringify, multiValues, multiValueSep, skipEmpty } = this.init;
|
||||
|
||||
if (skipEmpty && this.isEmpty(value)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (multiValues) {
|
||||
const values = [value].flat();
|
||||
const stringValues = [stringify ? stringify(value) : values.map(String)].flat();
|
||||
|
||||
return stringValues.join(multiValueSep);
|
||||
}
|
||||
|
||||
return [stringify ? stringify(value) : String(value)].flat()[0];
|
||||
}
|
||||
|
||||
get(): V {
|
||||
const value = this.parse(this.getRaw());
|
||||
|
||||
if (this.init.skipEmpty && this.isEmpty(value)) {
|
||||
return this.getDefaultValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
set(value: V, { mergeGlobals = true, replaceHistory = false } = {}) {
|
||||
const search = this.toSearchString({ mergeGlobals, value });
|
||||
|
||||
this.history.merge({ search }, replaceHistory);
|
||||
}
|
||||
|
||||
setRaw(value: string | string[]) {
|
||||
const { history, urlName } = this;
|
||||
const { multiValues, multiValueSep, skipEmpty } = this.init;
|
||||
const paramValue = multiValues ? [value].flat().join(multiValueSep) : String(value);
|
||||
|
||||
if (skipEmpty && this.isEmpty(paramValue)) {
|
||||
history.searchParams.delete(urlName);
|
||||
} else {
|
||||
history.searchParams.set(urlName, paramValue);
|
||||
}
|
||||
}
|
||||
|
||||
getRaw(): string[] {
|
||||
const { history, urlName } = this;
|
||||
const { multiValueSep } = this.init;
|
||||
|
||||
return history.searchParams.getAsArray(urlName, multiValueSep);
|
||||
}
|
||||
|
||||
getDefaultValue() {
|
||||
const { defaultValue, defaultValueStringified } = this.init;
|
||||
|
||||
return defaultValueStringified ? this.parse([defaultValueStringified].flat()) : defaultValue;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.history.searchParams.delete(this.urlName);
|
||||
}
|
||||
|
||||
toSearchString({ withPrefix = true, mergeGlobals = true, value = this.get() } = {}): string {
|
||||
const { history, urlName, init: { skipEmpty } } = this;
|
||||
const searchParams = new URLSearchParams(mergeGlobals ? history.location.search : "");
|
||||
|
||||
searchParams.set(urlName, this.stringify(value));
|
||||
|
||||
if (skipEmpty) {
|
||||
searchParams.forEach((value: any, paramName) => {
|
||||
if (this.isEmpty(value)) searchParams.delete(paramName);
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.from(searchParams).length > 0) {
|
||||
return `${withPrefix ? "?" : ""}${searchParams}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
toObjectParam(value = this.get()): Record<string, V> {
|
||||
return {
|
||||
[this.urlName]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user