nebulosity 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +11 -11
- package/.next/diagnostics/route-bundle-stats.json +10 -10
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/server/app/_not-found/page/next-font-manifest.json +2 -3
- package/.next/server/app/_not-found/page.js +3 -2
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +27 -20
- package/.next/server/app/_not-found.segments/_full.segment.rsc +27 -20
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +34 -26
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +29 -23
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +33 -24
- package/.next/server/app/index.segments/_full.segment.rsc +29 -23
- package/.next/server/app/index.segments/_tree.segment.rsc +4 -5
- package/.next/server/app/page/build-manifest.json +3 -3
- package/.next/server/app/page/next-font-manifest.json +2 -3
- package/.next/server/app/page.js +4 -4
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__06blyow8irfw3._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0bmqko-d8fd5k._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0cmmlmvta4i0g._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0jgr1dt1j95ku._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0m0n163465-zb._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0p7epsgp2b5m3._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1i5e5sqg45ens._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1vjt0neva-819._.js +3 -3
- package/.next/server/chunks/_0uqus0ehnijt-._.js +1 -1
- package/.next/server/chunks/lib_background_start_ts_11u_1uu8b9yuy._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_3l5tdzqy-td._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10ockmlli-lun._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0dyligeswbb47._.js → [root-of-the-server]__16-gclvix9mjd._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1auodc3c0rppy._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1cf6qt9agg_9-._.js +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1i9r59yk4i69p._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1l3qp8u9yc_ig._.js +3 -0
- package/.next/server/chunks/ssr/_1p-0wsx9qfm68._.js +13 -0
- package/.next/server/chunks/ssr/app_map_18iu6j19jsqmu._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1r3iqtbm45lqp._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892nagajq.js +3 -0
- package/.next/server/middleware-build-manifest.js +11 -11
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +4 -6
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{1rljem7o1jtdo.js → 18kfexnrqek07.js} +1 -1
- package/.next/static/chunks/1hwbgd4nvvhg8.js +1 -0
- package/.next/static/chunks/25r4lkkf1shfo.css +3 -0
- package/.next/static/chunks/35ubvk2ivdjgs.js +1 -0
- package/.next/static/chunks/3ieqvmcg3hofj.js +31 -0
- package/.next/static/chunks/3w00e1w2125pp.js +11 -0
- package/.next/static/media/733da6bd8dac84c8-s.3hlisc-pwq13z.woff2 +0 -0
- package/.next/static/media/784318cea1dfcfa4-s.0-oo9_1x_xmvg.woff2 +0 -0
- package/.next/static/media/7e114457660b62ed-s.36fqqi66-3tjb.woff2 +0 -0
- package/.next/static/media/85e86a88f68a624a-s.357dd_6abeqge.woff2 +0 -0
- package/.next/static/media/9a321a2c78eff0b5-s.p.1bkzbscqrt8rl.woff2 +0 -0
- package/.next/static/media/9fecbd3e5ea9bccb-s.3_z45zcoc-xoz.woff2 +0 -0
- package/.next/static/media/c64053b510252f55-s.1kd-r_s9smv5m.woff2 +0 -0
- package/.next/static/media/c6a5c2fdc12da45c-s.p.17-aodiw50953.woff2 +0 -0
- package/.next/static/media/f263782c6a4bddee-s.1gyfqm5yhhzs7.woff2 +0 -0
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/README.md +13 -4
- package/app/globals.css +91 -54
- package/app/layout.tsx +20 -11
- package/app/map/BackButton.tsx +5 -5
- package/app/map/BoxActions.tsx +20 -12
- package/app/map/Breadcrumb.tsx +132 -46
- package/app/map/DetailPanel.tsx +408 -297
- package/app/map/Explorer.tsx +268 -0
- package/app/map/Header.tsx +105 -0
- package/app/map/HelpDialog.tsx +37 -34
- package/app/map/Legend.tsx +19 -75
- package/app/map/LineCard.tsx +187 -0
- package/app/map/MapBox.tsx +73 -38
- package/app/map/MapCanvas.tsx +344 -115
- package/app/map/MapToolbar.tsx +377 -0
- package/app/map/MapView.tsx +217 -213
- package/app/map/RepoMenu.tsx +13 -12
- package/app/map/RoutedEdge.tsx +48 -16
- package/app/map/SearchPalette.tsx +6 -6
- package/app/map/SourceViewer.tsx +19 -17
- package/app/map/StatusBar.tsx +166 -0
- package/app/map/embed.ts +22 -2
- package/app/map/emphasis.ts +94 -8
- package/app/map/explorerRows.ts +85 -0
- package/app/map/flowNodes.ts +12 -7
- package/app/map/format.ts +2 -2
- package/app/map/history.ts +4 -3
- package/app/map/inspector.ts +113 -0
- package/app/map/labels.ts +66 -7
- package/app/map/narrow.ts +165 -85
- package/app/map/opening.ts +16 -0
- package/app/map/portals.ts +2 -2
- package/app/map/routes.ts +1060 -55
- package/app/map/runs.ts +108 -0
- package/app/map/shell.ts +91 -0
- package/app/map/shortcuts.ts +2 -1
- package/app/map/summaries.ts +41 -5
- package/app/map/text.ts +9 -1
- package/app/map/theme.ts +197 -0
- package/app/map/useDismiss.ts +20 -6
- package/app/map/useEditorBridge.ts +12 -1
- package/app/map/useThemeKind.ts +28 -0
- package/app/map/useWindowSize.ts +37 -0
- package/app/map/viewport.ts +134 -16
- package/app/map/weights.ts +80 -0
- package/app/page.tsx +3 -5
- package/lib/background/child.ts +2 -0
- package/lib/background/service.ts +26 -10
- package/lib/contracts.ts +25 -2
- package/lib/db/migrations/0011_plain_summaries_prompt_version.sql +1 -0
- package/lib/db/migrations/meta/0011_snapshot.json +1498 -0
- package/lib/db/migrations/meta/_journal.json +7 -0
- package/lib/db/schema.ts +2 -1
- package/lib/db/store/map.ts +6 -0
- package/lib/db/store/summaries.ts +14 -8
- package/lib/engine/describe.ts +2 -0
- package/lib/index/summarize/budget.ts +5 -2
- package/lib/index/summarize/claude.ts +19 -4
- package/lib/index/summarize/compare.ts +2 -2
- package/lib/index/summarize/plan.ts +73 -13
- package/lib/index/summarize/preview.ts +12 -9
- package/lib/index/summarize/prompts.ts +122 -25
- package/lib/index/summarize/run.ts +48 -18
- package/lib/index/summarize/snapshot.ts +7 -2
- package/lib/index/summarize/status.ts +3 -2
- package/lib/tools/text.ts +3 -2
- package/mcp/server.ts +1 -1
- package/package.json +4 -2
- package/scripts/commands/summarize-command.ts +27 -9
- package/.next/server/chunks/ssr/[root-of-the-server]__0ekw8p2ramybv._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0fwbsqt9e2tr-._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__17_rqmx62cz59._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1zfp6sltse4hf._.js +0 -3
- package/.next/server/chunks/ssr/_1d2woi6toj_0_._.js +0 -3
- package/.next/server/chunks/ssr/_1wfc1zz26g5ji._.js +0 -9
- package/.next/server/chunks/ssr/app_0gw6epmxyea3q._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_1ztp13a5szri_._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_1c5t_vsn6u8fr._.js +0 -3
- package/.next/static/chunks/02x9pxyl2d8om.css +0 -3
- package/.next/static/chunks/0p-0bv2dzer18.js +0 -7
- package/.next/static/chunks/10u7p2gj02qay.js +0 -1
- package/.next/static/chunks/31qp8jfkw0x2h.js +0 -31
- package/.next/static/media/123d516c9baeb953-s.1qc6dx89jd2qv.woff2 +0 -0
- package/.next/static/media/41ba59f553543ecf-s.3cb5lpr2l6xud.woff2 +0 -0
- package/.next/static/media/44ed10da57ebfb0e-s.2y65deuc30pmy.woff2 +0 -0
- package/.next/static/media/48eea41d8317494f-s.p.0wiir8udbzjvx.woff2 +0 -0
- package/.next/static/media/49b1d53681a91a99-s.1yd4_i3e35931.woff2 +0 -0
- package/.next/static/media/67bba87670b402d1-s.1u30mmyq6lnsd.woff2 +0 -0
- package/.next/static/media/6fca7e74603dd412-s.02z5rrbg5nt92.woff2 +0 -0
- package/.next/static/media/73263b2957b73100-s.2qt3tdwt-y9x_.woff2 +0 -0
- package/.next/static/media/7ac0b1cd3e695dd0-s.1i-sca43-qwng.woff2 +0 -0
- package/.next/static/media/916cd8a3bde3edf8-s.p.0oace-s_gkfks.woff2 +0 -0
- package/.next/static/media/a45aa1001bf9176f-s.2vemzm0itqzfy.woff2 +0 -0
- package/.next/static/media/a74d2542cc4c775f-s.29yke99gs9qpn.woff2 +0 -0
- package/.next/static/media/ab664c0e419cc841-s.3cvcemwuzoywn.woff2 +0 -0
- package/.next/static/media/c1b11b726b36a5e5-s.2rixgazldzomk.woff2 +0 -0
- package/.next/static/media/d6c1ff7e86974212-s.p.40sczeszzbjw1.woff2 +0 -0
- package/.next/static/media/faab88e4be272d29-s.0rgnxg2b64rzs.woff2 +0 -0
- package/app/map/ActivityBar.tsx +0 -81
- package/app/map/StatusPill.tsx +0 -93
- package/app/map/TopBar.tsx +0 -84
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_buildManifest.js +0 -0
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_ssgManifest.js +0 -0
package/app/map/Breadcrumb.tsx
CHANGED
|
@@ -1,83 +1,169 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
|
4
4
|
import { trailStart } from "./narrow";
|
|
5
5
|
import { nameOf } from "./paths";
|
|
6
|
+
import { placePopover, usePlaceOnResize } from "./popover";
|
|
7
|
+
import { useDismiss } from "./useDismiss";
|
|
6
8
|
|
|
7
|
-
type BreadcrumbProps = {
|
|
9
|
+
type BreadcrumbProps = {
|
|
10
|
+
/** The repo's name, which goes to the top level, with its picker when several repos are indexed (RepoMenu). */
|
|
11
|
+
repo: ReactNode;
|
|
12
|
+
/** The repo's root path, for the steps' titles. */
|
|
13
|
+
repoRoot: string;
|
|
14
|
+
/** Folder paths from the root down to the one on screen: ["", "src", "src/vs"]. */
|
|
15
|
+
trail: readonly string[];
|
|
16
|
+
onOpen: (folder: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* A window under 360 px: the repo's name folds into the "…" with every folder but the one on screen, which keeps the
|
|
19
|
+
* room. At the top level the repo's name is the whole trail.
|
|
20
|
+
*/
|
|
21
|
+
compact: boolean;
|
|
22
|
+
};
|
|
8
23
|
|
|
9
|
-
/** The space between steps (gap-1). */
|
|
24
|
+
/** The space between steps (gap-1), and the folded folders' menu width. */
|
|
10
25
|
const STEP_GAP = 4;
|
|
26
|
+
const MENU_WIDTH = 260;
|
|
11
27
|
|
|
12
28
|
/**
|
|
13
|
-
* Where the Map is: "vscode / src / vs" (
|
|
29
|
+
* Where the Map is, in mono: "microsoft/vscode / src / vs / workbench" (the Workbench redesign, ADR-070). The repo
|
|
30
|
+
* comes first and every step but the last goes back up to it; at the top level it reads "microsoft/vscode / root".
|
|
14
31
|
*
|
|
15
|
-
* When the trail doesn't fit (a deep folder, a narrow window), it
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
32
|
+
* When the trail doesn't fit (a deep folder, a narrow window), it keeps the repo and the folder on screen and folds the
|
|
33
|
+
* folders between into a "…" that opens a list of them to jump to (the owner, 2026-10-27). It never scrolls sideways.
|
|
34
|
+
* When even that is too wide, the repo's owner goes (whole or not at all, never a sliver of it), then the repo's name
|
|
35
|
+
* truncates, then the folder on screen, each whole in its title. The steps' natural widths come from a hidden copy of
|
|
36
|
+
* the trail, measured whenever it or the room for it changes. In a window under 360 px the repo's name folds in too,
|
|
37
|
+
* first in the list (it goes to the top level, where the repo's name and its picker are the whole trail).
|
|
19
38
|
*/
|
|
20
|
-
export default function Breadcrumb({ repo, trail, onOpen }: BreadcrumbProps){
|
|
39
|
+
export default function Breadcrumb({ repo, repoRoot, trail, onOpen, compact }: BreadcrumbProps){
|
|
40
|
+
const steps = trail.slice(1);
|
|
21
41
|
const nav = useRef<HTMLElement>(null);
|
|
42
|
+
const repoBox = useRef<HTMLSpanElement>(null);
|
|
22
43
|
const ruler = useRef<HTMLDivElement>(null);
|
|
23
|
-
// how many
|
|
24
|
-
const [fold, setFold] = useState<{ trail: readonly string[]; count: number }>({ trail, count: 0 });
|
|
25
|
-
const
|
|
44
|
+
// how many folders after the repo are folded into "…", and whether the repo's owner shows, for the trail measured
|
|
45
|
+
const [fold, setFold] = useState<{ trail: readonly string[]; count: number; owner: boolean }>({ trail, count: 0, owner: true });
|
|
46
|
+
const repoFolded = compact && steps.length > 0;
|
|
47
|
+
const folded = repoFolded ? steps.length - 1 : fold.trail === trail ? fold.count : 0;
|
|
48
|
+
const ownerShown = fold.trail !== trail || fold.owner;
|
|
49
|
+
// the "…" list: the repo's top level first when its name folded too, then the folders between
|
|
50
|
+
const foldedSteps = repoFolded ? ["", ...steps.slice(0, folded)] : steps.slice(0, folded);
|
|
26
51
|
|
|
27
52
|
// measured before the browser paints the new trail, so a deep folder's whole trail never shows for a frame over its
|
|
28
|
-
// neighbours
|
|
53
|
+
// neighbours, then again whenever the room or a step's width changes (the window resized, the font arrived)
|
|
29
54
|
useLayoutEffect(() => {
|
|
30
|
-
const navEl = nav.current, rulerEl = ruler.current;
|
|
31
|
-
if (!navEl || !rulerEl) return;
|
|
55
|
+
const navEl = nav.current, rulerEl = ruler.current, repoEl = repoBox.current;
|
|
56
|
+
if (!navEl || !rulerEl || !repoEl) return;
|
|
57
|
+
// the header's search field gives way before the trail does (Header), down to its least width: what it can still
|
|
58
|
+
// give is the trail's too. Measured from the nav alone, a trail folded for a narrower window, or a font not yet
|
|
59
|
+
// loaded, would stay folded, its nav only as wide as what it shows.
|
|
60
|
+
const search = navEl.closest("header")?.querySelector<HTMLElement>('[data-testid="search-button"]') ?? null;
|
|
61
|
+
const slack = (): number => {
|
|
62
|
+
if (search === null) return 0;
|
|
63
|
+
const style = getComputedStyle(search);
|
|
64
|
+
return style.flexShrink === "0" ? 0 : Math.max(0, search.getBoundingClientRect().width - (parseFloat(style.minWidth) || 0));
|
|
65
|
+
};
|
|
32
66
|
const measure = (): void => {
|
|
33
67
|
const widths = [...rulerEl.children].map((step) => step.getBoundingClientRect().width);
|
|
34
68
|
const ellipsis = widths.pop() ?? 0; // the ruler ends with the "…" step
|
|
35
|
-
// the
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
// the repo at its natural width, what it takes now plus what its truncated names gave up (the owner too, which
|
|
70
|
+
// is only narrowed to nothing when it goes): squeezed first, it would otherwise make room for the folders
|
|
71
|
+
// between rather than have them fold
|
|
72
|
+
const given = [...repoEl.querySelectorAll<HTMLElement>(".truncate")].reduce((sum, el) => sum + Math.max(0, el.scrollWidth - el.clientWidth), 0);
|
|
73
|
+
const room = navEl.getBoundingClientRect().width + slack() - repoEl.getBoundingClientRect().width - given - STEP_GAP;
|
|
74
|
+
// the steps fold from the one after the repo onward; the last always stays (narrow.ts's trailStart)
|
|
75
|
+
const start = (space: number): number => widths.length === 0 ? 0 : trailStart(widths, space, ellipsis, STEP_GAP);
|
|
76
|
+
// what the trail after the repo takes from `from` on: the "…" for what's folded, the steps, or the top level's "/ root"
|
|
77
|
+
const needs = (from: number): number => widths.length === 0
|
|
78
|
+
? navEl.querySelector("[data-crumb-root]")?.getBoundingClientRect().width ?? 0
|
|
79
|
+
: widths.slice(from).reduce((sum, w) => sum + w + STEP_GAP, from > 0 ? ellipsis + STEP_GAP : 0) - STEP_GAP;
|
|
80
|
+
// the owner stays while the trail fits with it whole; otherwise it goes and the trail folds against the rest
|
|
81
|
+
const owned = start(room);
|
|
82
|
+
const owner = needs(owned) <= room;
|
|
83
|
+
const ownerWidth = repoEl.querySelector("[data-repo-owner]")?.scrollWidth ?? 0;
|
|
84
|
+
const count = owner ? owned : start(room + ownerWidth);
|
|
85
|
+
setFold((last) => last.trail === trail && last.count === count && last.owner === owner ? last : { trail, count, owner });
|
|
38
86
|
};
|
|
39
87
|
measure();
|
|
40
88
|
const observer = new ResizeObserver(measure);
|
|
41
89
|
observer.observe(navEl);
|
|
42
|
-
|
|
90
|
+
observer.observe(repoEl);
|
|
91
|
+
if (search !== null) observer.observe(search);
|
|
43
92
|
for (const step of rulerEl.children) observer.observe(step);
|
|
44
93
|
return () => observer.disconnect();
|
|
45
94
|
}, [trail]);
|
|
46
95
|
|
|
47
|
-
const
|
|
48
|
-
? <span aria-current="page" title={step || nameOf(step, repo)} className="truncate px-1 font-medium text-text">{nameOf(step, repo)}</span>
|
|
49
|
-
: <button type="button" onClick={() => onOpen(step)} title={step || nameOf(step, repo)} className="truncate rounded-md px-1 hover:bg-surface hover:text-text">{nameOf(step, repo)}</button>;
|
|
50
|
-
|
|
96
|
+
const here = steps.length === 0 ? "root" : nameOf(steps[steps.length - 1], repoRoot);
|
|
51
97
|
return (
|
|
52
|
-
<nav ref={nav} aria-label="Where you are" className="relative flex min-w-0 flex-1 items-center gap-1 text-[
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
…
|
|
63
|
-
</button>
|
|
98
|
+
<nav ref={nav} aria-label="Where you are" data-owner={ownerShown ? undefined : "hidden"} className="group/crumb relative flex min-w-0 flex-1 items-center gap-1 font-mono text-[12.5px] text-text-2">
|
|
99
|
+
{/* Short of room it gives way before the folder on screen (shrink 1000 to the folder's 1), down to a floor
|
|
100
|
+
that keeps a few letters of its name and its ▾. Hidden rather than gone while it's folded, so an open
|
|
101
|
+
repo list keeps its place for a wider window. */}
|
|
102
|
+
<span ref={repoBox} className={`${repoFolded ? "hidden" : "flex"} min-w-[30px] shrink-[1000] items-center has-[[data-testid=repo-menu]]:min-w-[50px]`}>{repo}</span>
|
|
103
|
+
{foldedSteps.length > 0 && (
|
|
104
|
+
<>
|
|
105
|
+
{!repoFolded && <span aria-hidden className="shrink-0">/</span>}
|
|
106
|
+
<FoldedSteps steps={foldedSteps} repoRoot={repoRoot} onOpen={onOpen} />
|
|
107
|
+
</>
|
|
64
108
|
)}
|
|
65
|
-
{
|
|
66
|
-
<span key={step
|
|
67
|
-
|
|
68
|
-
{
|
|
109
|
+
{steps.map((step, i) => i < folded ? null : (
|
|
110
|
+
<span key={step} className={`flex items-center gap-1 ${i === steps.length - 1 ? "min-w-0" : "shrink-0"}`}>
|
|
111
|
+
<span aria-hidden>/</span>
|
|
112
|
+
{i === steps.length - 1
|
|
113
|
+
? <span aria-current="page" title={step} className="truncate text-text">{nameOf(step, repoRoot)}</span>
|
|
114
|
+
: <button type="button" onClick={() => onOpen(step)} title={step} className="truncate rounded-[4px] px-0.5 hover:bg-node hover:text-text">{nameOf(step, repoRoot)}</button>}
|
|
69
115
|
</span>
|
|
70
116
|
))}
|
|
71
|
-
{
|
|
117
|
+
{steps.length === 0 && !compact && (
|
|
118
|
+
<span data-crumb-root className="flex shrink-0 items-center gap-1"><span aria-hidden>/</span><span aria-current="page" className="text-text-2">{here}</span></span>
|
|
119
|
+
)}
|
|
120
|
+
{/* the steps at their natural width, unseen and unclickable, for measuring; the "…" step last */}
|
|
72
121
|
<div ref={ruler} aria-hidden className="pointer-events-none invisible absolute inset-x-0 top-0 flex h-0 gap-1 overflow-hidden whitespace-nowrap">
|
|
73
|
-
{
|
|
74
|
-
<span key={step
|
|
75
|
-
{i > 0 && <span>/</span>}
|
|
76
|
-
<span className={`px-1 ${i === trail.length - 1 ? "font-medium" : ""}`}>{nameOf(step, repo)}</span>
|
|
77
|
-
</span>
|
|
122
|
+
{steps.map((step) => (
|
|
123
|
+
<span key={step} className="flex shrink-0 items-center gap-1"><span>/</span><span className="px-0.5">{nameOf(step, repoRoot)}</span></span>
|
|
78
124
|
))}
|
|
79
|
-
<span className="shrink-0 px-1">…</span>
|
|
125
|
+
<span className="flex shrink-0 items-center gap-1"><span>/</span><span className="px-1.5">…</span></span>
|
|
80
126
|
</div>
|
|
81
127
|
</nav>
|
|
82
128
|
);
|
|
83
129
|
}
|
|
130
|
+
|
|
131
|
+
/** The "…" for the folders folded out of the trail, and the list of them it opens, each one a way up to it. */
|
|
132
|
+
function FoldedSteps({ steps, repoRoot, onOpen }: { steps: readonly string[]; repoRoot: string; onOpen: (folder: string) => void }){
|
|
133
|
+
const [open, setOpen] = useState(false);
|
|
134
|
+
const [place, setPlace] = useState<CSSProperties>({ left: 0, width: MENU_WIDTH });
|
|
135
|
+
const ref = useDismiss<HTMLSpanElement>(open, () => setOpen(false));
|
|
136
|
+
const placeMenu = useCallback(() => {
|
|
137
|
+
if (ref.current) setPlace(placePopover(ref.current, MENU_WIDTH, "start"));
|
|
138
|
+
}, [ref]);
|
|
139
|
+
usePlaceOnResize(open, placeMenu);
|
|
140
|
+
return (
|
|
141
|
+
<span ref={ref} className="relative shrink-0">
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
data-testid="breadcrumb-folded"
|
|
145
|
+
aria-expanded={open}
|
|
146
|
+
aria-label={`${steps.length} more ${steps.length === 1 ? "folder" : "folders"} on the way here`}
|
|
147
|
+
title={steps.map((step) => nameOf(step, repoRoot)).join(" / ")}
|
|
148
|
+
onClick={() => {
|
|
149
|
+
if (!open) placeMenu();
|
|
150
|
+
setOpen((o) => !o);
|
|
151
|
+
}}
|
|
152
|
+
className="rounded-[4px] border border-border px-1.5 leading-[18px] hover:bg-node hover:text-text"
|
|
153
|
+
>
|
|
154
|
+
…
|
|
155
|
+
</button>
|
|
156
|
+
{open && (
|
|
157
|
+
<ul data-testid="breadcrumb-menu" data-covers style={place} className="absolute top-7 z-30 overflow-y-auto rounded-md border border-border bg-panel py-1 font-sans text-[13px]">
|
|
158
|
+
{steps.map((step) => (
|
|
159
|
+
<li key={step}>
|
|
160
|
+
<button type="button" onClick={() => { setOpen(false); onOpen(step); }} title={step} className="block w-full truncate px-3 py-1.5 text-left font-mono text-[12.5px] text-text hover:bg-node">
|
|
161
|
+
{nameOf(step, repoRoot)}
|
|
162
|
+
</button>
|
|
163
|
+
</li>
|
|
164
|
+
))}
|
|
165
|
+
</ul>
|
|
166
|
+
)}
|
|
167
|
+
</span>
|
|
168
|
+
);
|
|
169
|
+
}
|