Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/builder/app/canvas/interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
$selectedPageHash,
} from "~/shared/nano-states";
import { $currentSystem, updateCurrentSystem } from "~/shared/system";
import { comparePatterns } from "./shared/routing-priority";

const isAbsoluteUrl = (href: string) => {
try {
Expand Down Expand Up @@ -55,7 +56,11 @@ const switchPageAndUpdateSystem = (href: string, formData?: FormData) => {
}
}
const pageHref = new URL(href, "https://any-valid.url");
for (const page of [pages.homePage, ...pages.pages]) {
// sort pages before matching to not depend on order of page creation
const sortedPages = [pages.homePage, ...pages.pages].toSorted(
(leftPage, rightPage) => comparePatterns(leftPage.path, rightPage.path)
);
for (const page of sortedPages) {
const pagePath = getPagePath(page.id, pages);
const params = matchPathnamePattern(pagePath, pageHref.pathname);
if (params) {
Expand Down
50 changes: 50 additions & 0 deletions apps/builder/app/canvas/shared/routing-priority.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { test, expect } from "vitest";
import { comparePatterns } from "./routing-priority";

test("sort top-level patterns", () => {
const patterns = ["/foo*", "/:foo", "/foo"];
const expected = ["/foo", "/:foo", "/foo*"];
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
});

test("sort static paths", () => {
const patterns = ["/a/z", "/a/b/c", "/a/c", "/a/b"];
const expected = ["/a/b", "/a/c", "/a/z", "/a/b/c"];
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
});

test("sort mixed static, dynamic, spread at multiple levels", () => {
const patterns = [
"/foo",
"/:id",
"/bar*",
"/foo/bar",
"/foo/:id",
"/foo/bar*",
];
const expected = [
// static first-segment
"/foo",
"/foo/bar",
"/foo/:id",
"/foo/bar*",
// dynamic then spread at top level
"/:id",
"/bar*",
];
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
});

test("sort deeply nested mixed segments", () => {
const patterns = ["/u/bar", "/u/:id", "/u/bar/b", "/u/:id/c", "/u/bar/*"];
const expected = [
// static second-segment
"/u/bar",
"/u/bar/b",
"/u/bar/*",
// dynamic second-segment
"/u/:id",
"/u/:id/c",
];
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
});
41 changes: 41 additions & 0 deletions apps/builder/app/canvas/shared/routing-priority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const STATIC = 1;
const DYNAMIC = 2;
const SPREAD = 3;

const getSegmentScore = (segment: string) => {
// give spread the least priority
if (segment.endsWith("*")) {
return SPREAD;
}
// sort dynamic segments before splat
if (segment.startsWith(":")) {
return DYNAMIC;
}
// sort static routes before dynamic routes
return STATIC;
};

export function comparePatterns(leftPattern: string, rightPattern: string) {

Check failure on line 18 in apps/builder/app/canvas/shared/routing-priority.ts

View workflow job for this annotation

GitHub Actions / checks (empty)

Expected a function expression
const leftSegments = leftPattern.split("/");
const rightSegments = rightPattern.split("/");
const commonLength = Math.min(leftSegments.length, rightSegments.length);

// compare each segment first
for (let index = 0; index < commonLength; index++) {
const leftScore = getSegmentScore(leftSegments[index]);
const rightScore = getSegmentScore(rightSegments[index]);
if (leftScore !== rightScore) {
return leftScore - rightScore;
}
}

// compare amount of segments
const leftLength = leftSegments.length;
const rightLength = rightSegments.length;
if (leftLength !== rightLength) {
return leftLength - rightLength;
}

// sort alphabetically
return leftPattern.localeCompare(rightPattern);
}
Loading