react-router-dom#matchRoutes TypeScript Examples

The following examples show how to use react-router-dom#matchRoutes. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: RouteTracker.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
getExtensionContext = (
  pathname: string,
  routes: BackstageRouteObject[],
): CommonAnalyticsContext | {} => {
  try {
    // Find matching routes for the given path name.
    const matches = matchRoutes(routes, { pathname }) as
      | { route: BackstageRouteObject }[]
      | null;

    // Of the matching routes, get the last (e.g. most specific) instance of
    // the BackstageRouteObject.
    const routeObject = matches
      ?.filter(match => match?.route.routeRefs?.size > 0)
      .pop()?.route;

    // If there is no route object, then allow inheritance of default context.
    if (!routeObject) {
      return {};
    }

    // If there is a single route ref, return it.
    // todo: get routeRef of rendered gathered mount point(?)
    let routeRef: RouteRef | undefined;
    if (routeObject.routeRefs.size === 1) {
      routeRef = routeObject.routeRefs.values().next().value;
    }

    return {
      extension: 'App',
      pluginId: routeObject.plugin?.getId() || 'root',
      ...(routeRef ? { routeRef: (routeRef as { id?: string }).id } : {}),
    };
  } catch {
    return {};
  }
}
Example #2
Source File: RouteResolver.ts    From backstage with Apache License 2.0 5 votes vote down vote up
resolve<Params extends AnyParams>(
    anyRouteRef:
      | RouteRef<Params>
      | SubRouteRef<Params>
      | ExternalRouteRef<Params, any>,
    sourceLocation: Parameters<typeof matchRoutes>[1],
  ): RouteFunc<Params> | undefined {
    // First figure out what our target absolute ref is, as well as our target path.
    const [targetRef, targetPath] = resolveTargetRef(
      anyRouteRef,
      this.routePaths,
      this.routeBindings,
    );
    if (!targetRef) {
      return undefined;
    }

    // The location that we get passed in uses the full path, so start by trimming off
    // the app base path prefix in case we're running the app on a sub-path.
    let relativeSourceLocation: Parameters<typeof matchRoutes>[1];
    if (typeof sourceLocation === 'string') {
      relativeSourceLocation = this.trimPath(sourceLocation);
    } else if (sourceLocation.pathname) {
      relativeSourceLocation = {
        ...sourceLocation,
        pathname: this.trimPath(sourceLocation.pathname),
      };
    } else {
      relativeSourceLocation = sourceLocation;
    }

    // Next we figure out the base path, which is the combination of the common parent path
    // between our current location and our target location, as well as the additional path
    // that is the difference between the parent path and the base of our target location.
    const basePath =
      this.appBasePath +
      resolveBasePath(
        targetRef,
        relativeSourceLocation,
        this.routePaths,
        this.routeParents,
        this.routeObjects,
      );

    const routeFunc: RouteFunc<Params> = (...[params]) => {
      return basePath + generatePath(targetPath, params);
    };
    return routeFunc;
  }
Example #3
Source File: RouteResolver.ts    From backstage with Apache License 2.0 5 votes vote down vote up
/**
 * Resolves the complete base path for navigating to the target RouteRef.
 */
function resolveBasePath(
  targetRef: RouteRef,
  sourceLocation: Parameters<typeof matchRoutes>[1],
  routePaths: Map<RouteRef, string>,
  routeParents: Map<RouteRef, RouteRef | undefined>,
  routeObjects: BackstageRouteObject[],
) {
  // While traversing the app element tree we build up the routeObjects structure
  // used here. It is the same kind of structure that react-router creates, with the
  // addition that associated route refs are stored throughout the tree. This lets
  // us look up all route refs that can be reached from our source location.
  // Because of the similar route object structure, we can use `matchRoutes` from
  // react-router to do the lookup of our current location.
  const match = matchRoutes(routeObjects, sourceLocation) ?? [];

  // While we search for a common routing root between our current location and
  // the target route, we build a list of all route refs we find that we need
  // to traverse to reach the target.
  const refDiffList = Array<RouteRef>();

  let matchIndex = -1;
  for (
    let targetSearchRef: RouteRef | undefined = targetRef;
    targetSearchRef;
    targetSearchRef = routeParents.get(targetSearchRef)
  ) {
    // The match contains a list of all ancestral route refs present at our current location
    // Starting at the desired target ref and traversing back through its parents, we search
    // for a target ref that is present in the match for our current location. When a match
    // is found it means we have found a common base to resolve the route from.
    matchIndex = match.findIndex(m =>
      (m.route as BackstageRouteObject).routeRefs.has(targetSearchRef!),
    );
    if (matchIndex !== -1) {
      break;
    }

    // Every time we move a step up in the ancestry of the target ref, we add the current ref
    // to the diff list, which ends up being the list of route refs to traverse form the common base
    // in order to reach our target.
    refDiffList.unshift(targetSearchRef);
  }

  // If our target route is present in the initial match we need to construct the final path
  // from the parent of the matched route segment. That's to allow the caller of the route
  // function to supply their own params.
  if (refDiffList.length === 0) {
    matchIndex -= 1;
  }

  // This is the part of the route tree that the target and source locations have in common.
  // We re-use the existing pathname directly along with all params.
  const parentPath = matchIndex === -1 ? '' : match[matchIndex].pathname;

  // This constructs the mid section of the path using paths resolved from all route refs
  // we need to traverse to reach our target except for the very last one. None of these
  // paths are allowed to require any parameters, as the caller would have no way of knowing
  // what parameters those are.
  const diffPath = joinPaths(
    ...refDiffList.slice(0, -1).map(ref => {
      const path = routePaths.get(ref);
      if (!path) {
        throw new Error(`No path for ${ref}`);
      }
      if (path.includes(':')) {
        throw new Error(
          `Cannot route to ${targetRef} with parent ${ref} as it has parameters`,
        );
      }
      return path;
    }),
  );

  return parentPath + diffPath;
}
Example #4
Source File: index.tsx    From mui-toolpad with MIT License 4 votes vote down vote up
export default function HierarchyExplorer({ appId, className }: HierarchyExplorerProps) {
  const dom = useDom();
  const domApi = useDomApi();

  const app = appDom.getApp(dom);
  const {
    apis = [],
    codeComponents = [],
    pages = [],
    connections = [],
  } = appDom.getChildNodes(dom, app);

  const [expanded, setExpanded] = useLocalStorageState<string[]>(
    `editor/${app.id}/hierarchy-expansion`,
    [':connections', ':pages', ':codeComponents'],
  );

  const location = useLocation();
  const match =
    matchRoutes(
      [
        { path: `/app/:appId/editor/pages/:activeNodeId` },
        { path: `/app/:appId/editor/apis/:activeNodeId` },
        { path: `/app/:appId/editor/codeComponents/:activeNodeId` },
        { path: `/app/:appId/editor/connections/:activeNodeId` },
      ],
      location,
    ) || [];

  const selected: NodeId[] = match.map((route) => route.params.activeNodeId as NodeId);

  const handleToggle = (event: React.SyntheticEvent, nodeIds: string[]) => {
    setExpanded(nodeIds as NodeId[]);
  };

  const navigate = useNavigate();

  const handleSelect = (event: React.SyntheticEvent, nodeIds: string[]) => {
    if (nodeIds.length <= 0) {
      return;
    }

    const rawNodeId = nodeIds[0];
    if (rawNodeId.startsWith(':')) {
      return;
    }

    const selectedNodeId: NodeId = rawNodeId as NodeId;
    const node = appDom.getNode(dom, selectedNodeId);
    if (appDom.isElement(node)) {
      // TODO: sort out in-page selection
      const page = appDom.getPageAncestor(dom, node);
      if (page) {
        navigate(`/app/${appId}/editor/pages/${page.id}`);
      }
    }

    if (appDom.isPage(node)) {
      navigate(`/app/${appId}/editor/pages/${node.id}`);
    }

    if (appDom.isApi(node)) {
      navigate(`/app/${appId}/editor/apis/${node.id}`);
    }

    if (appDom.isCodeComponent(node)) {
      navigate(`/app/${appId}/editor/codeComponents/${node.id}`);
    }

    if (appDom.isConnection(node)) {
      navigate(`/app/${appId}/editor/connections/${node.id}`);
    }
  };

  const [createConnectionDialogOpen, setCreateConnectionDialogOpen] = React.useState(0);
  const handleCreateConnectionDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreateConnectionDialogOpen(Math.random());
  }, []);
  const handleCreateConnectionDialogClose = React.useCallback(
    () => setCreateConnectionDialogOpen(0),
    [],
  );

  const [createPageDialogOpen, setCreatePageDialogOpen] = React.useState(0);
  const handleCreatePageDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreatePageDialogOpen(Math.random());
  }, []);
  const handleCreatepageDialogClose = React.useCallback(() => setCreatePageDialogOpen(0), []);

  const [createCodeComponentDialogOpen, setCreateCodeComponentDialogOpen] = React.useState(0);
  const handleCreateCodeComponentDialogOpen = React.useCallback((event: React.MouseEvent) => {
    event.stopPropagation();
    setCreateCodeComponentDialogOpen(Math.random());
  }, []);
  const handleCreateCodeComponentDialogClose = React.useCallback(
    () => setCreateCodeComponentDialogOpen(0),
    [],
  );

  const [deletedNodeId, setDeletedNodeId] = React.useState<NodeId | null>(null);
  const handleDeleteNodeDialogOpen = React.useCallback(
    (nodeId: NodeId) => (event: React.MouseEvent) => {
      event.stopPropagation();
      setDeletedNodeId(nodeId);
    },
    [],
  );
  const handledeleteNodeDialogClose = React.useCallback(() => setDeletedNodeId(null), []);

  const handleDeleteNode = React.useCallback(() => {
    if (deletedNodeId) {
      domApi.removeNode(deletedNodeId);
      navigate(`/app/${appId}/editor/`);
      handledeleteNodeDialogClose();
    }
  }, [deletedNodeId, domApi, navigate, appId, handledeleteNodeDialogClose]);

  const deletedNode = deletedNodeId && appDom.getMaybeNode(dom, deletedNodeId);
  const latestDeletedNode = useLatest(deletedNode);

  return (
    <HierarchyExplorerRoot className={className}>
      <TreeView
        aria-label="hierarchy explorer"
        selected={selected}
        onNodeSelect={handleSelect}
        expanded={expanded}
        onNodeToggle={handleToggle}
        multiSelect
        defaultCollapseIcon={<ExpandMoreIcon />}
        defaultExpandIcon={<ChevronRightIcon />}
      >
        <HierarchyTreeItem
          nodeId=":connections"
          labelText="Connections"
          onCreate={handleCreateConnectionDialogOpen}
        >
          {connections.map((connectionNode) => (
            <HierarchyTreeItem
              key={connectionNode.id}
              nodeId={connectionNode.id}
              labelText={connectionNode.name}
              onDelete={handleDeleteNodeDialogOpen(connectionNode.id)}
            />
          ))}
        </HierarchyTreeItem>
        {apis.length > 0 ? (
          <HierarchyTreeItem nodeId=":apis" labelText="Apis">
            {apis.map((apiNode) => (
              <HierarchyTreeItem
                key={apiNode.id}
                nodeId={apiNode.id}
                labelText={apiNode.name}
                onDelete={handleDeleteNodeDialogOpen(apiNode.id)}
              />
            ))}
          </HierarchyTreeItem>
        ) : null}
        <HierarchyTreeItem
          nodeId=":codeComponents"
          labelText="Components"
          onCreate={handleCreateCodeComponentDialogOpen}
        >
          {codeComponents.map((codeComponent) => (
            <HierarchyTreeItem
              key={codeComponent.id}
              nodeId={codeComponent.id}
              labelText={codeComponent.name}
              onDelete={handleDeleteNodeDialogOpen(codeComponent.id)}
            />
          ))}
        </HierarchyTreeItem>
        <HierarchyTreeItem nodeId=":pages" labelText="Pages" onCreate={handleCreatePageDialogOpen}>
          {pages.map((page) => (
            <HierarchyTreeItem
              key={page.id}
              nodeId={page.id}
              labelText={page.name}
              onDelete={handleDeleteNodeDialogOpen(page.id)}
            />
          ))}
        </HierarchyTreeItem>
      </TreeView>

      <CreateConnectionNodeDialog
        key={createConnectionDialogOpen || undefined}
        appId={appId}
        open={!!createConnectionDialogOpen}
        onClose={handleCreateConnectionDialogClose}
      />
      <CreatePageNodeDialog
        key={createPageDialogOpen || undefined}
        appId={appId}
        open={!!createPageDialogOpen}
        onClose={handleCreatepageDialogClose}
      />
      <CreateCodeComponentNodeDialog
        key={createCodeComponentDialogOpen || undefined}
        appId={appId}
        open={!!createCodeComponentDialogOpen}
        onClose={handleCreateCodeComponentDialogClose}
      />
      <Dialog open={!!deletedNode} onClose={handledeleteNodeDialogClose}>
        <DialogTitle>
          Delete {latestDeletedNode?.type} &quot;{latestDeletedNode?.name}&quot;?
        </DialogTitle>
        <DialogActions>
          <Button
            type="submit"
            color="inherit"
            variant="text"
            onClick={handledeleteNodeDialogClose}
          >
            Cancel
          </Button>
          <Button type="submit" onClick={handleDeleteNode}>
            Delete
          </Button>
        </DialogActions>
      </Dialog>
    </HierarchyExplorerRoot>
  );
}