git-truck 3.1.0 → 3.2.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.
Files changed (35) hide show
  1. package/build/client/assets/RepoTabs-36qUc-RT.js +1 -0
  2. package/build/client/assets/browse-B9wBPRaK.js +9 -0
  3. package/build/client/assets/browse-Bx9O5Mvl.js +1 -0
  4. package/build/client/assets/clear-cache-BJQQzFka.js +1 -0
  5. package/build/client/assets/clear-cache-DkN7P69Z.js +1 -0
  6. package/build/client/assets/dist-Bpk5WHYg.js +2 -0
  7. package/build/client/assets/{entry.client-lU2GATMj.js → entry.client-Ci1gLbyI.js} +1 -1
  8. package/build/client/assets/manifest-601d0239.js +1 -0
  9. package/build/client/assets/react-dom-B9ui1jR3.js +1 -0
  10. package/build/client/assets/root-BK0u4vgl.css +1 -0
  11. package/build/client/assets/root-CGqKHKfX.js +3 -0
  12. package/build/client/assets/ui-Bw635ygf.js +1 -0
  13. package/build/client/assets/view-mmd0Q4B8.js +1 -0
  14. package/build/client/assets/view.commits-b-k-A7BT.js +1 -0
  15. package/build/client/assets/view.details-B7LCPu46.js +1 -0
  16. package/build/server/assets/{server-build-DEt84pjT.js → server-build-voprGiDU.js} +661 -561
  17. package/build/server/index.js +1 -1
  18. package/cli.mjs +200 -37883
  19. package/package.json +3 -3
  20. package/build/client/assets/GitTruckInfo-CiYjIacT.js +0 -1
  21. package/build/client/assets/RepoTabs-CWt-EVgp.js +0 -1
  22. package/build/client/assets/browse-Ce7uXDK8.js +0 -1
  23. package/build/client/assets/browse-wwHpvs7Z.js +0 -9
  24. package/build/client/assets/chunk-JPUPSTYD-YBbsfvLw.js +0 -1
  25. package/build/client/assets/clear-cache-C5k9U711.js +0 -1
  26. package/build/client/assets/clear-cache-Xrw5kNtu.js +0 -1
  27. package/build/client/assets/compare-Br3z3FUS-CQWcjqXQ.js +0 -2
  28. package/build/client/assets/manifest-3c232792.js +0 -1
  29. package/build/client/assets/react-dom-BkDQo3BN.js +0 -1
  30. package/build/client/assets/root-CaiUVml-.css +0 -1
  31. package/build/client/assets/root-CpxyjKLd.js +0 -3
  32. package/build/client/assets/ui-EnssW_g_.js +0 -1
  33. package/build/client/assets/view-DhREUKDU.js +0 -1
  34. package/build/client/assets/view.commits-D8Z4b-x3.js +0 -1
  35. package/build/client/assets/view.details-Bsu_yyiE.js +0 -1
@@ -4,9 +4,9 @@ import { Await, Form, Link, Links, Meta, Outlet, Scripts, ScrollRestoration, Ser
4
4
  import { renderToPipeableStream } from "react-dom/server";
5
5
  import c from "ansi-colors";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- import * as React$1 from "react";
7
+ import * as React from "react";
8
8
  import { Activity, Fragment as Fragment$1, Suspense, createContext as createContext$1, createElement, createRef, memo, startTransition, use, useCallback, useContext, useDeferredValue, useEffect, useId, useMemo, useReducer, useRef, useState, useSyncExternalStore, useTransition } from "react";
9
- import { mdiAccount, mdiAccountGroup, mdiAccountMultiple, mdiAccountMultipleCheck, mdiAccountMultipleMinus, mdiAccountMultiplePlus, mdiArrowLeft, mdiArrowRight, mdiArrowUp, mdiArrowUpBoldCircleOutline, mdiChartBubble, mdiChartTree, mdiCheckboxBlank, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiCheckboxOutline, mdiChevronDown, mdiChevronRight, mdiCircle, mdiCircleSmall, mdiClockEdit, mdiClose, mdiCog, mdiContentCut, mdiDeleteForever, mdiDiceMultipleOutline, mdiEye, mdiEyeOff, mdiEyeOffOutline, mdiFile, mdiFileCodeOutline, mdiFileOutline, mdiFileTree, mdiFilter, mdiFlaskOutline, mdiFolder, mdiFullscreen, mdiFullscreenExit, mdiHelpCircle, mdiInformationOutline, mdiKnife, mdiLabel, mdiLink, mdiMagnify, mdiMagnifyMinus, mdiMenu, mdiOpenInNew, mdiPlus, mdiPlusMinusVariant, mdiPodiumGold, mdiPulse, mdiRefresh, mdiResize, mdiRestore, mdiScaleBalance, mdiSearchWeb, mdiSort, mdiSortAscending, mdiSortDescending, mdiSourceBranch, mdiSourceCommit, mdiSourceRepository, mdiTransition, mdiUndo } from "@mdi/js";
9
+ import { mdiAccount, mdiAccountGroup, mdiAccountMultiple, mdiAccountMultipleMinus, mdiAccountMultiplePlus, mdiArrowLeft, mdiArrowRight, mdiArrowUp, mdiArrowUpBoldCircleOutline, mdiChartBubble, mdiChartTree, mdiCheckboxBlank, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiCheckboxOutline, mdiChevronDown, mdiChevronRight, mdiCircle, mdiCircleSmall, mdiClockEdit, mdiClose, mdiCog, mdiContentCut, mdiDeleteForever, mdiDiceMultipleOutline, mdiEye, mdiEyeOff, mdiEyeOffOutline, mdiFile, mdiFileCodeOutline, mdiFileOutline, mdiFileTree, mdiFilter, mdiFlaskOutline, mdiFolder, mdiFullscreen, mdiFullscreenExit, mdiHelpCircle, mdiInformationOutline, mdiKnife, mdiLabel, mdiLink, mdiMagnify, mdiMagnifyMinus, mdiMenu, mdiOpenInNew, mdiPlus, mdiPlusMinusVariant, mdiPodiumGold, mdiPulse, mdiRefresh, mdiResize, mdiRestore, mdiScaleBalance, mdiSort, mdiSortAscending, mdiSortDescending, mdiSourceBranch, mdiSourceCommit, mdiSourceRepository, mdiTransition, mdiUndo } from "@mdi/js";
10
10
  import clsx$1, { clsx } from "clsx";
11
11
  import { ArrowContainer, Popover } from "react-tiny-popover";
12
12
  import { HexColorPicker } from "react-colorful";
@@ -30,11 +30,11 @@ import { existsSync as existsSync$1, promises as promises$1, readFileSync } from
30
30
  import os$1, { cpus, freemem, totalmem } from "node:os";
31
31
  import { inflateSync } from "node:zlib";
32
32
  import { readFile, readdir } from "node:fs/promises";
33
+ import { createSerializer, parseAsBoolean, parseAsInteger, parseAsNumberLiteral, parseAsString, parseAsStringLiteral, useQueryState, useQueryStates } from "nuqs";
33
34
  import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
34
35
  import randomstring from "randomstring";
35
36
  import { createPortal } from "react-dom";
36
- import { createSerializer, parseAsBoolean, parseAsInteger, parseAsNumberLiteral, parseAsStringLiteral, useQueryState, useQueryStates } from "nuqs";
37
- import { createLoader, createSerializer as createSerializer$1, parseAsInteger as parseAsInteger$1, parseAsString, parseAsStringLiteral as parseAsStringLiteral$1 } from "nuqs/server";
37
+ import { createLoader, createSerializer as createSerializer$1, parseAsInteger as parseAsInteger$1, parseAsString as parseAsString$1, parseAsStringLiteral as parseAsStringLiteral$1 } from "nuqs/server";
38
38
  import { hierarchy, pack, partition, treemap, treemapResquarify } from "d3-hierarchy";
39
39
  import ignore from "ignore";
40
40
  import { Field, Label, Radio, RadioGroup } from "@headlessui/react";
@@ -180,7 +180,7 @@ function handleBrowserRequest(request, responseStatusCode, responseHeaders, reac
180
180
  });
181
181
  }
182
182
  const name = "git-truck";
183
- const version = "3.1.0";
183
+ const version = "3.2.0";
184
184
  const description = "Visualizing a Git repository";
185
185
  const main = "./cli.mjs";
186
186
  const bin = "./cli.mjs";
@@ -239,6 +239,7 @@ const dependencies = {
239
239
  "clsx": "^2.1.1",
240
240
  "color-convert": "^3.1.3",
241
241
  "d3": "^7.9.0",
242
+ "express": "^5.2.1",
242
243
  "ignore": "^7.0.5",
243
244
  "nanospinner": "^1.2.2",
244
245
  "nuqs": "^2.8.8",
@@ -279,7 +280,6 @@ const devDependencies = {
279
280
  "eslint-plugin-jsx-a11y": "^6.10.2",
280
281
  "eslint-plugin-react": "^7.37.5",
281
282
  "eslint-plugin-react-hooks": "^7.0.1",
282
- "express": "^5.2.1",
283
283
  "get-port": "^7.1.0",
284
284
  "globals": "^17.3.0",
285
285
  "husky": "^9.1.7",
@@ -302,7 +302,7 @@ const devDependencies = {
302
302
  "typescript": "^5.9.3",
303
303
  "typescript-eslint": "^8.55.0",
304
304
  "vite": "npm:rolldown-vite@7.3.1",
305
- "vite-tsconfig-paths": "^5.1.4",
305
+ "vite-tsconfig-paths": "^6.1.1",
306
306
  "vitest": "^4.0.18"
307
307
  };
308
308
  const tsdown = {
@@ -444,6 +444,32 @@ function useDataNullable() {
444
444
  repo: context.repo
445
445
  };
446
446
  }
447
+ function useComponentSize$1() {
448
+ const [size, setSize] = React.useState({
449
+ height: 0,
450
+ width: 0
451
+ });
452
+ const ref = React.useRef(void 0);
453
+ const onResize = React.useCallback(() => {
454
+ if (!ref.current) return;
455
+ const newHeight = ref.current.offsetHeight;
456
+ const newWidth = ref.current.offsetWidth;
457
+ if (newHeight !== size.height || newWidth !== size.width) setSize({
458
+ height: newHeight,
459
+ width: newWidth
460
+ });
461
+ }, [size.height, size.width]);
462
+ React.useLayoutEffect(() => {
463
+ if (!ref || !ref.current) return;
464
+ const resizeObserver = new ResizeObserver(onResize);
465
+ resizeObserver.observe(ref.current);
466
+ return () => resizeObserver.disconnect();
467
+ }, [ref, onResize]);
468
+ return {
469
+ ref,
470
+ ...size
471
+ };
472
+ }
447
473
  function dateFormatLong(epochTime) {
448
474
  if (!epochTime) return "Invalid date";
449
475
  return (/* @__PURE__ */ new Date(epochTime * 1e3)).toLocaleString("en-gb", {
@@ -502,13 +528,6 @@ function getSeparator(path$1) {
502
528
  if (path$1.includes("\\")) return "\\";
503
529
  return "/";
504
530
  }
505
- const getPathFromRepoAndHead = (params, segments = ["view"]) => {
506
- const searchParams = new URLSearchParams();
507
- Object.entries(params).forEach(([key, value]) => {
508
- if (value) searchParams.set(key, value);
509
- });
510
- return `/${segments.map(encodeURIComponent).join("/")}?${searchParams.toString()}`;
511
- };
512
531
  const branchCompare = (a, b) => {
513
532
  const defaultBranchNames = ["main", "master"];
514
533
  if (defaultBranchNames.includes(a)) return -1;
@@ -728,11 +747,6 @@ function expandIntervalToRange(timestamp, commitCountPerTimeIntervalUnit) {
728
747
  }
729
748
  }
730
749
  const getSep = (path$1) => path$1.includes("\\") ? "\\" : "/";
731
- const comparePaths = (a, b) => {
732
- const sepA = getSep(a);
733
- const sepB = getSep(b);
734
- return a.split(sepA).join("/") === b.split(sepB).join("/");
735
- };
736
750
  function iconToURL(icon) {
737
751
  return `url("data:image/svg+xml;utf8,${encodeURIComponent("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' d='" + icon + "'/></svg>")}")`;
738
752
  }
@@ -744,31 +758,32 @@ function normalizePath(p) {
744
758
  if (p.split("/").length > 2) return trimmed;
745
759
  return p;
746
760
  }
747
- function useComponentSize$1() {
748
- const [size, setSize] = React$1.useState({
749
- height: 0,
750
- width: 0
751
- });
752
- const ref = React$1.useRef(void 0);
753
- const onResize = React$1.useCallback(() => {
754
- if (!ref.current) return;
755
- const newHeight = ref.current.offsetHeight;
756
- const newWidth = ref.current.offsetWidth;
757
- if (newHeight !== size.height || newWidth !== size.width) setSize({
758
- height: newHeight,
759
- width: newWidth
760
- });
761
- }, [size.height, size.width]);
762
- React$1.useLayoutEffect(() => {
763
- if (!ref || !ref.current) return;
764
- const resizeObserver = new ResizeObserver(onResize);
765
- resizeObserver.observe(ref.current);
766
- return () => resizeObserver.disconnect();
767
- }, [ref, onResize]);
768
- return {
769
- ref,
770
- ...size
771
- };
761
+ function findSubTree(tree, path$1) {
762
+ if (!path$1) return tree;
763
+ if (!(path$1 === tree.name || path$1.startsWith(`${tree.name}/`))) return tree;
764
+ let relativePath = path$1.length === tree.name.length ? "" : path$1.substring(tree.name.length + 1);
765
+ if (!relativePath) return tree;
766
+ relativePath = normalizePath(relativePath);
767
+ let currentTree = tree;
768
+ const separator = getSep(relativePath);
769
+ const steps = relativePath.split(separator);
770
+ for (let i = 0; i < steps.length; i++) {
771
+ let foundStep = false;
772
+ for (const child of currentTree.children) if (child.type === "tree") {
773
+ const childSteps = child.name.split(separator);
774
+ if (childSteps[0] === steps[i]) {
775
+ currentTree = child;
776
+ i += childSteps.length - 1;
777
+ foundStep = true;
778
+ break;
779
+ }
780
+ }
781
+ if (!foundStep) {
782
+ console.warn(`Could not find step ${steps[i]} in subtree ${currentTree.name}`);
783
+ return tree;
784
+ }
785
+ }
786
+ return currentTree;
772
787
  }
773
788
  function useComponentSize() {
774
789
  const { ref, width, height } = useComponentSize$1();
@@ -838,8 +853,8 @@ function useFullscreen(getElement) {
838
853
  const toggleFullscreen = () => {
839
854
  if (!document.fullscreenElement) {
840
855
  const element = getElement();
841
- if (element instanceof Element) promiseHelper(document.documentElement.requestFullscreen());
842
- else promiseHelper(element.current.requestFullscreen());
856
+ if (element instanceof Element) promiseHelper(element.requestFullscreen());
857
+ else if (element.current) promiseHelper(element.current.requestFullscreen());
843
858
  } else if (document.exitFullscreen) document.exitFullscreen();
844
859
  };
845
860
  return {
@@ -847,6 +862,20 @@ function useFullscreen(getElement) {
847
862
  toggleFullscreen
848
863
  };
849
864
  }
865
+ function useViewAction() {
866
+ const location = useLocation();
867
+ return href("/view") + location.search;
868
+ }
869
+ function useViewSubmit() {
870
+ const submit = useSubmit();
871
+ const location = useLocation();
872
+ return (target, options) => {
873
+ submit(target, {
874
+ ...options,
875
+ action: href("/view") + location.search
876
+ });
877
+ };
878
+ }
850
879
  const cn = (...args) => twMerge(clsx(args));
851
880
  function usePrefersLightMode() {
852
881
  return useMediaQuery("(prefers-color-scheme: light)");
@@ -8737,7 +8766,7 @@ var defaultOptions = {
8737
8766
  commitSearch: "",
8738
8767
  transitionsEnabled: true,
8739
8768
  labelsVisible: true,
8740
- renderCutoff: 2,
8769
+ renderCutOff: 2,
8741
8770
  showFilesWithoutChanges: true,
8742
8771
  dominantAuthorCutoff: 0,
8743
8772
  linkMetricAndSizeMetric: false,
@@ -8757,8 +8786,7 @@ const LegendDot = ({ className = "", dotColor, authorColorToChange = void 0 }) =
8757
8786
  const [color, setColor] = useState(dotColor);
8758
8787
  const { databaseInfo } = useData();
8759
8788
  const { chartType } = useOptions();
8760
- const submit = useSubmit();
8761
- const [searchParams] = useSearchParams();
8789
+ const submit = useViewSubmit();
8762
8790
  if (!authorColorToChange) return /* @__PURE__ */ jsx(Dot, {
8763
8791
  className,
8764
8792
  chartType,
@@ -8768,13 +8796,7 @@ const LegendDot = ({ className = "", dotColor, authorColorToChange = void 0 }) =
8768
8796
  const form = new FormData();
8769
8797
  form.append("authorname", author);
8770
8798
  form.append("authorcolor", color$1);
8771
- submit(form, {
8772
- action: getPathFromRepoAndHead({
8773
- path: searchParams.get("path"),
8774
- branch: databaseInfo.branch
8775
- }),
8776
- method: "post"
8777
- });
8799
+ submit(form, { method: "post" });
8778
8800
  }
8779
8801
  return /* @__PURE__ */ jsxs(Popover$1, {
8780
8802
  triggerClassName: "flex gap-1 items-center",
@@ -8926,9 +8948,13 @@ var DB = class DB {
8926
8948
  );
8927
8949
  CREATE TABLE IF NOT EXISTS files (
8928
8950
  path VARCHAR,
8929
- hash VARCHAR,
8930
- type VARCHAR
8931
8951
  );
8952
+
8953
+ -- Migrations
8954
+ CREATE SEQUENCE IF NOT EXISTS hiddenfiles_id_sequence START 1;
8955
+ ALTER TABLE hiddenfiles ADD COLUMN IF NOT EXISTS id INTEGER DEFAULT nextval('hiddenfiles_id_sequence');
8956
+ ALTER TABLE files ADD COLUMN IF NOT EXISTS hash VARCHAR;
8957
+ ALTER TABLE files ADD COLUMN IF NOT EXISTS type VARCHAR;
8932
8958
  `);
8933
8959
  }
8934
8960
  async getDbConnection() {
@@ -9084,19 +9110,33 @@ var DB = class DB {
9084
9110
  }
9085
9111
  async getHiddenFiles() {
9086
9112
  return (await this.query(`
9087
- SELECT path FROM hiddenfiles ORDER BY path ASC;
9113
+ SELECT path, id FROM hiddenfiles ORDER BY id DESC;
9088
9114
  `)).map((row) => row["path"]);
9089
9115
  }
9090
- async replaceHiddenFiles(hiddenFiles) {
9116
+ async addHiddenFile(path$1) {
9117
+ const statement = await this.prepare(`
9118
+ INSERT INTO hiddenfiles (id, path)
9119
+ SELECT nextval('hiddenfiles_id_sequence'), ?
9120
+ WHERE NOT EXISTS (
9121
+ SELECT 1 FROM hiddenfiles WHERE path = ?
9122
+ );
9123
+ `);
9124
+ statement.bindVarchar(1, path$1);
9125
+ statement.bindVarchar(2, path$1);
9126
+ await statement.run();
9127
+ }
9128
+ async removeHiddenFile(path$1) {
9129
+ const statement = await this.prepare(`
9130
+ DELETE FROM hiddenfiles
9131
+ WHERE path = ?;
9132
+ `);
9133
+ statement.bindVarchar(1, path$1);
9134
+ await statement.run();
9135
+ }
9136
+ async clearHiddenFiles() {
9091
9137
  await this.run(`
9092
9138
  DELETE FROM hiddenfiles;
9093
9139
  `);
9094
- await this.usingTableAppender("hiddenfiles", async (appender) => {
9095
- for (const path$1 of hiddenFiles) {
9096
- appender.appendVarchar(path$1);
9097
- appender.endRow();
9098
- }
9099
- });
9100
9140
  }
9101
9141
  async getCommits(path$1, count) {
9102
9142
  return (await this.query(`
@@ -9250,10 +9290,7 @@ var DB = class DB {
9250
9290
  async getObjectType(objectPath) {
9251
9291
  const statement = await (await this.connectionPromise).prepare(`SELECT type FROM files WHERE path = ?`);
9252
9292
  statement.bindVarchar(1, objectPath);
9253
- const results = (await statement.runAndReadAll()).getRowObjectsJS();
9254
- if (results.length === 0) return null;
9255
- const { type: type$1 } = results[0];
9256
- return type$1;
9293
+ return (await statement.runAndReadAll()).getRowObjectsJS()[0]?.type ?? null;
9257
9294
  }
9258
9295
  async commitTableEmpty() {
9259
9296
  return (await this.query(`
@@ -10376,29 +10413,29 @@ function GitTruckInfo({ className = "", installedVersion, latestVersion: latestV
10376
10413
  }
10377
10414
  var clear_cache_exports = /* @__PURE__ */ __export({
10378
10415
  ClearCacheForm: () => ClearCacheForm,
10379
- action: () => action$2,
10416
+ action: () => action$1,
10380
10417
  default: () => clear_cache_default,
10381
10418
  loader: () => loader$11
10382
10419
  }, 1);
10383
10420
  const loader$11 = async ({ context }) => {
10384
10421
  return { versionInfo: context.get(versionContext) };
10385
10422
  };
10386
- const action$2 = async ({ request }) => {
10423
+ const action$1 = async ({ request }) => {
10387
10424
  const redirectPath = new URL(request.url).searchParams.get("redirect");
10425
+ if (!redirectPath) throw new Error("Missing redirect path");
10388
10426
  await InstanceManager.closeAllDBConnections();
10389
10427
  await DB.clearCache();
10390
- throw redirect(redirectPath ?? "/");
10428
+ throw redirect(redirectPath);
10391
10429
  };
10392
10430
  function ClearCacheForm({ redirectPath, className = "" } = {}) {
10393
- const location$1 = useLocation();
10394
- const fetcher = useFetcher({ key: "clear-cache-form" });
10395
- const formAction = `/clear-cache?${new URLSearchParams({ redirect: redirectPath ?? location$1.pathname + location$1.search }).toString()}`;
10431
+ const location = useLocation();
10432
+ const formAction = href("/clear-cache") + "?" + new URLSearchParams({ redirect: redirectPath ?? location.pathname + location.search }).toString();
10433
+ const fetcher = useFetcher();
10396
10434
  const isTransitioning = fetcher.state !== "idle";
10397
10435
  return /* @__PURE__ */ jsx(fetcher.Form, {
10398
10436
  action: formAction,
10399
10437
  method: "post",
10400
10438
  children: /* @__PURE__ */ jsxs("button", {
10401
- type: "submit",
10402
10439
  disabled: isTransitioning,
10403
10440
  className: cn("btn btn--danger", className),
10404
10441
  title: "Click here if you are experiencing issues",
@@ -10407,10 +10444,11 @@ function ClearCacheForm({ redirectPath, className = "" } = {}) {
10407
10444
  className: "hover-swap inline-block h-full"
10408
10445
  }), isTransitioning ? "Clearing..." : "Clear all data"]
10409
10446
  })
10410
- }, fetcher.state);
10447
+ });
10411
10448
  }
10412
10449
  var clear_cache_default = UNSAFE_withComponentProps(function ClearCache() {
10413
10450
  const { versionInfo } = useLoaderData();
10451
+ const [redirect$1] = useQueryState("redirect", parseAsString.withDefault("/"));
10414
10452
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
10415
10453
  className: "app-container flex flex-col gap-2 p-2",
10416
10454
  children: [/* @__PURE__ */ jsx("div", {
@@ -10435,7 +10473,7 @@ var clear_cache_default = UNSAFE_withComponentProps(function ClearCache() {
10435
10473
  }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
10436
10474
  className: "font-bold",
10437
10475
  children: "Warning: "
10438
- }), /* @__PURE__ */ jsx("span", { children: "Merged authors and hidden files will be reset." })] })]
10476
+ }), /* @__PURE__ */ jsx("span", { children: "Grouped authors and Hidden files will be reset." })] })]
10439
10477
  }),
10440
10478
  /* @__PURE__ */ jsxs("div", {
10441
10479
  className: "flex justify-end gap-2",
@@ -10443,7 +10481,7 @@ var clear_cache_default = UNSAFE_withComponentProps(function ClearCache() {
10443
10481
  to: "/",
10444
10482
  className: "btn btn--text",
10445
10483
  children: "Go back"
10446
- }), /* @__PURE__ */ jsx(ClearCacheForm, { redirectPath: "/" })]
10484
+ }), /* @__PURE__ */ jsx(ClearCacheForm, { redirectPath: redirect$1 })]
10447
10485
  })
10448
10486
  ]
10449
10487
  })]
@@ -10570,7 +10608,7 @@ var refreshPolicy = {
10570
10608
  "commitCountPerDay",
10571
10609
  "loadRepoData"
10572
10610
  ],
10573
- unignore: [
10611
+ show: [
10574
10612
  "cache",
10575
10613
  "commitCount",
10576
10614
  "dominantAuthor",
@@ -10584,7 +10622,7 @@ var refreshPolicy = {
10584
10622
  "authors",
10585
10623
  "hiddenfiles"
10586
10624
  ],
10587
- ignore: [
10625
+ hide: [
10588
10626
  "cache",
10589
10627
  "commitCount",
10590
10628
  "maxMinCommitCount",
@@ -10722,7 +10760,7 @@ var COUNT_OPTIONS = [
10722
10760
  100
10723
10761
  ];
10724
10762
  const browseSearchParamsConfig = {
10725
- path: parseAsString.withOptions({ shallow: false }),
10763
+ path: parseAsString$1.withOptions({ shallow: false }),
10726
10764
  count: parseAsNumberLiteral(COUNT_OPTIONS).withOptions({ shallow: false }).withDefault(DEFAULT_COUNT),
10727
10765
  offset: parseAsInteger$1.withOptions({ shallow: false }).withDefault(DEFAULT_OFFSET),
10728
10766
  sort: parseAsStringLiteral$1([
@@ -10731,7 +10769,7 @@ const browseSearchParamsConfig = {
10731
10769
  "asc",
10732
10770
  "desc"
10733
10771
  ]).withOptions({ shallow: false }).withDefault("most-recent"),
10734
- search: parseAsString.withOptions({
10772
+ search: parseAsString$1.withOptions({
10735
10773
  shallow: false,
10736
10774
  limitUrlUpdates: {
10737
10775
  method: "throttle",
@@ -10864,7 +10902,7 @@ const loader$9 = async ({ context, request }) => {
10864
10902
  };
10865
10903
  var browse_default = UNSAFE_withComponentProps(function Index() {
10866
10904
  const { error: error$1, versionInfo, directories, parentDirectoryPath, analyzedReposPromise, totalCount } = useLoaderData();
10867
- const location$1 = useLocation();
10905
+ const location = useLocation();
10868
10906
  const navigation = useNavigation();
10869
10907
  const [{ path: path$1, "include-dirs": includeDirs, sort: sortMethod, search: searchQuery, count }, setSearchParams] = useQueryStates(browseSearchParamsConfig);
10870
10908
  const placeholderCount = Math.max(0, count - directories.length);
@@ -10990,7 +11028,7 @@ var browse_default = UNSAFE_withComponentProps(function Index() {
10990
11028
  /* @__PURE__ */ jsx("div", {}),
10991
11029
  /* @__PURE__ */ jsx("div", {}),
10992
11030
  /* @__PURE__ */ jsx(Link, {
10993
- to: `/clear-cache?redirect=${encodeURIComponent(location$1.pathname + location$1.search)}`,
11031
+ to: `/clear-cache?redirect=${encodeURIComponent(location.pathname + location.search)}`,
10994
11032
  className: "btn btn--danger max-w-min justify-self-end",
10995
11033
  children: "Clear cache"
10996
11034
  })
@@ -11261,9 +11299,9 @@ function SearchField({ searchQuery, includeDirs, onSearch, ref }) {
11261
11299
  }
11262
11300
  function Breadcrumb({ className = "", zoom = false }) {
11263
11301
  const [browseParams] = useQueryStates(browseSearchParamsConfig);
11264
- const [{ path: path$1, zoomPath }, setSearchParams] = useQueryStates(viewSearchParamsConfig);
11302
+ const [viewParams, setViewParams] = useQueryStates(viewSearchParamsConfig);
11303
+ const { path: path$1, zoomPath } = viewParams;
11265
11304
  const data = useDataNullable();
11266
- const navigate = useNavigate();
11267
11305
  const breadcrumbSegments = useMemo(() => {
11268
11306
  if (!path$1) return [];
11269
11307
  const pathSegments = path$1.split(getSep(path$1)).map((segment, i, segments$1) => {
@@ -11272,28 +11310,29 @@ function Breadcrumb({ className = "", zoom = false }) {
11272
11310
  type: "browse",
11273
11311
  segment: segment.length === 0 && i === 0 ? "/" : segment,
11274
11312
  fullPath: i === 0 ? fullPath + getSep(path$1) : fullPath,
11275
- isRepo: i === segments$1.length - 1
11313
+ showAnalysisInfo: false
11276
11314
  };
11277
11315
  }).filter((segment) => segment.segment.length > 0);
11278
- const zoomSegments = [
11316
+ const zoomSegments = !data ? [] : [
11279
11317
  {
11280
11318
  type: "browse",
11281
- segment: data?.repo.parentDirName ?? "",
11282
- fullPath: data?.repo.parentDirPath ?? "",
11283
- isRepo: false
11319
+ segment: data.repo.parentDirName ?? "",
11320
+ fullPath: data.repo.parentDirPath ?? "",
11321
+ showAnalysisInfo: false
11284
11322
  },
11285
11323
  {
11286
- type: "browse",
11287
- segment: data?.repo.repositoryName ?? "",
11288
- fullPath: data?.repo.repositoryPath ?? "",
11289
- isRepo: true
11324
+ type: "zoom",
11325
+ segment: data.repo.repositoryName ?? "",
11326
+ fullPath: data.repo.repositoryName ?? "",
11327
+ showAnalysisInfo: true
11290
11328
  },
11291
- ...(zoomPath?.split(getSep(zoomPath)) ?? []).slice(1).map((segment, index, segments$1) => {
11329
+ ...(zoomPath?.split(getSep(zoomPath)) ?? []).flatMap((segment, index, segments$1) => {
11330
+ if (segment === data?.repo.repositoryName) return [];
11292
11331
  return {
11293
11332
  type: "zoom",
11294
11333
  segment,
11295
11334
  fullPath: segments$1.slice(0, index + 1).join("/"),
11296
- isRepo: index === 0
11335
+ showAnalysisInfo: false
11297
11336
  };
11298
11337
  })
11299
11338
  ];
@@ -11305,52 +11344,49 @@ function Breadcrumb({ className = "", zoom = false }) {
11305
11344
  type: "filler",
11306
11345
  segment: "...",
11307
11346
  fullPath: "",
11308
- isRepo: false
11347
+ showAnalysisInfo: false
11309
11348
  },
11310
11349
  segments[segments.length - 2],
11311
11350
  segments[segments.length - 1]
11312
11351
  ];
11313
11352
  return segments;
11314
11353
  }, [
11315
- data?.repo.parentDirName,
11316
- data?.repo.parentDirPath,
11317
- data?.repo.repositoryName,
11318
- data?.repo.repositoryPath,
11354
+ data,
11319
11355
  path$1,
11320
11356
  zoom,
11321
11357
  zoomPath
11322
11358
  ]);
11323
11359
  return /* @__PURE__ */ jsx("div", {
11324
11360
  className: cn("text-secondary-text dark:text-secondary-text-dark flex items-center gap-1 overflow-x-auto", className),
11325
- children: breadcrumbSegments.map(({ type: type$1, segment, fullPath }, i) => {
11326
- const isRepo = data && comparePaths(fullPath, data.repo.repositoryPath);
11361
+ children: breadcrumbSegments.map(({ type: type$1, segment, fullPath, showAnalysisInfo: isRepo }, i) => {
11327
11362
  const title = isRepo ? "Reset zoom to repository root" : type$1 === "browse" ? `Browse ${segment} directory` : `Zoom to ${segment} directory`;
11328
11363
  const isFirst = i === 0;
11329
11364
  const isLast = breadcrumbSegments.length - 1 === i;
11330
- const onClick = () => {
11331
- if (type$1 === "browse") {
11332
- navigate(href("/browse") + browseSerializer({
11333
- ...browseParams,
11334
- path: fullPath,
11335
- search: null
11336
- }));
11337
- return;
11338
- }
11339
- if (!data) throw Error("Attempting to access data when none is loaded");
11340
- setSearchParams((prev) => ({
11341
- ...prev,
11342
- zoomPath: fullPath
11343
- }));
11344
- };
11345
11365
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [isRepo ? /* @__PURE__ */ jsx(Icon, { path: mdiSourceRepository }) : null, segment] });
11346
11366
  const button = isLast || type$1 === "filler" ? /* @__PURE__ */ jsx("span", {
11347
11367
  className: "flex items-center gap-1 truncate font-bold",
11348
11368
  title: fullPath,
11349
11369
  children: content
11370
+ }) : type$1 === "browse" ? /* @__PURE__ */ jsx(Link, {
11371
+ to: href("/browse") + browseSerializer({
11372
+ ...browseParams,
11373
+ offset: 0,
11374
+ search: null,
11375
+ path: fullPath
11376
+ }),
11377
+ title,
11378
+ className: "btn btn--primary truncate",
11379
+ children: content
11350
11380
  }) : /* @__PURE__ */ jsx("button", {
11351
11381
  title,
11352
11382
  className: "btn btn--primary truncate",
11353
- onClick,
11383
+ onClick: () => {
11384
+ if (!data) throw Error("Attempting to access data when none is loaded");
11385
+ setViewParams((prev) => ({
11386
+ ...prev,
11387
+ zoomPath: fullPath
11388
+ }));
11389
+ },
11354
11390
  children: content
11355
11391
  });
11356
11392
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [!isFirst ? /* @__PURE__ */ jsx(Icon, { path: mdiChevronRight }) : null, isRepo ? /* @__PURE__ */ jsx(AnalysisInfo, { trigger: button }) : button] }, fullPath);
@@ -11358,9 +11394,9 @@ function Breadcrumb({ className = "", zoom = false }) {
11358
11394
  });
11359
11395
  }
11360
11396
  function useClickedObject() {
11361
- const location$1 = useLocation();
11397
+ const location = useLocation();
11362
11398
  const [params] = useQueryStates(viewSearchParamsConfig);
11363
- const clickedObject = params.objectPath ? location$1.state?.clickedObject ?? null : null;
11399
+ const clickedObject = params.objectPath ? location.state?.clickedObject ?? null : null;
11364
11400
  const navigate = useNavigate();
11365
11401
  const tabURL = useMatch(href("/view/commits")) ? "/view/commits" : "/view/details";
11366
11402
  return {
@@ -11395,7 +11431,7 @@ const Chart = memo(function Chart$2({ hoveredObject, setHoveredObject }) {
11395
11431
  const { searchResults, hasSearchResults } = useSearch();
11396
11432
  const size = useDeferredValue(rawSize);
11397
11433
  const { databaseInfo } = useData();
11398
- const { chartType, sizeMetric, hierarchyType, labelsVisible, renderCutoff } = useOptions();
11434
+ const { chartType, sizeMetric, hierarchyType, labelsVisible, renderCutOff } = useOptions();
11399
11435
  const [params, setParams] = useQueryStates(viewSearchParamsConfig);
11400
11436
  const { zoomPath } = params;
11401
11437
  const setZoomPath = (zoomPathUpdate) => setParams((prev) => {
@@ -11449,8 +11485,7 @@ const Chart = memo(function Chart$2({ hoveredObject, setHoveredObject }) {
11449
11485
  size,
11450
11486
  chartType,
11451
11487
  sizeMetricType: sizeMetric,
11452
- renderCutoff,
11453
- zoomPath: zoomPath ?? void 0
11488
+ renderCutOff
11454
11489
  }).descendants();
11455
11490
  if (process.env.NODE_ENV === "development") console.timeEnd("Create and pack hiearchy");
11456
11491
  return res;
@@ -11458,8 +11493,7 @@ const Chart = memo(function Chart$2({ hoveredObject, setHoveredObject }) {
11458
11493
  size,
11459
11494
  chartType,
11460
11495
  sizeMetric,
11461
- zoomPath,
11462
- renderCutoff,
11496
+ renderCutOff,
11463
11497
  databaseInfo,
11464
11498
  filetree
11465
11499
  ]);
@@ -11745,20 +11779,8 @@ function NodeText({ d, isSearchMatch, children = null }) {
11745
11779
  function isCircularNode(d) {
11746
11780
  return typeof d.r === "number";
11747
11781
  }
11748
- function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMetricType, renderCutoff, zoomPath: objectPath }) {
11749
- let currentTree = tree;
11750
- if (objectPath) {
11751
- const steps = objectPath.substring(tree.name.length + 1).split("/");
11752
- for (let i = 0; i < steps.length; i++) for (const child of currentTree.children) if (child.type === "tree") {
11753
- const childSteps = child.name.split("/");
11754
- if (childSteps[0] === steps[i]) {
11755
- currentTree = child;
11756
- i += childSteps.length - 1;
11757
- break;
11758
- }
11759
- }
11760
- }
11761
- const hiearchy = hierarchy(currentTree).sum((d) => {
11782
+ function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMetricType, renderCutOff }) {
11783
+ const hiearchy = hierarchy(tree).sum((d) => {
11762
11784
  const blob = d;
11763
11785
  switch (sizeMetricType) {
11764
11786
  case "FILE_SIZE": return blob.sizeInBytes ?? 1;
@@ -11768,7 +11790,7 @@ function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMe
11768
11790
  case "MOST_CONTRIBUTIONS": return databaseInfo.contribSumPerFile[blob.path] ?? 1;
11769
11791
  }
11770
11792
  }).sort((a, b) => (b.value ?? 1) - (a.value ?? 1));
11771
- const cutOff = Number.isNaN(renderCutoff) ? 2 : renderCutoff;
11793
+ const cutOff = Number.isNaN(renderCutOff) ? 2 : renderCutOff;
11772
11794
  if (chartType === "TREE_MAP" || chartType === "PARTITION") {
11773
11795
  const tmPartition = (chartType === "TREE_MAP" ? treemap().size([size.width, size.height]).round(true).tile(treemapResquarify).paddingInner(2).paddingOuter(4).paddingTop(21) : partition().size([size.width, size.height]).padding(2))(hiearchy);
11774
11796
  filterVisualization(tmPartition, (child) => {
@@ -11801,94 +11823,43 @@ function flatten(tree) {
11801
11823
  else flattened.push(...flatten(child));
11802
11824
  return flattened;
11803
11825
  }
11804
- function hiddenFileFormat(ignored) {
11805
- if (!ignored.includes("/")) return ignored;
11806
- const split = ignored.split("/");
11807
- return split[split.length - 1];
11808
- }
11809
- const HiddenFiles = memo(function HiddenFiles$1() {
11810
- const navigationState = useNavigation();
11811
- const { databaseInfo } = useData();
11812
- const expandHiddenFilesButtonId = useId();
11813
- const action$3 = href("/view/details");
11814
- return /* @__PURE__ */ jsx(Popover$1, {
11815
- popoverTitle: `Hidden files (${databaseInfo.hiddenFiles.length})`,
11816
- className: "min-w-10",
11817
- trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
11818
- className: "btn",
11819
- title: "Hidden files",
11820
- "aria-label": "Hidden files",
11821
- "aria-controls": expandHiddenFilesButtonId,
11822
- onClick,
11823
- children: /* @__PURE__ */ jsx(Icon, { path: mdiEyeOff })
11824
- }),
11825
- children: /* @__PURE__ */ jsx("div", {
11826
- className: "flex flex-col gap-2",
11827
- children: databaseInfo.hiddenFiles.length > 0 ? databaseInfo.hiddenFiles.map((hidden) => /* @__PURE__ */ jsxs("div", {
11828
- className: "grid grid-cols-[auto_1fr] items-center gap-2",
11829
- title: hidden,
11830
- children: [/* @__PURE__ */ jsxs(Form, {
11831
- className: "w-4",
11832
- method: "post",
11833
- action: action$3,
11834
- children: [/* @__PURE__ */ jsx("input", {
11835
- type: "hidden",
11836
- name: "unignore",
11837
- value: hidden
11838
- }), /* @__PURE__ */ jsxs("button", {
11839
- className: "btn btn--hover-swap h-4",
11840
- title: "Show file",
11841
- disabled: navigationState.state !== "idle",
11842
- children: [/* @__PURE__ */ jsx(Icon, {
11843
- path: mdiEyeOff,
11844
- className: "inline-block h-full"
11845
- }), /* @__PURE__ */ jsx(Icon, {
11846
- path: mdiEye,
11847
- className: "hover-swap inline-block h-full"
11848
- })]
11849
- })]
11850
- }), /* @__PURE__ */ jsx("span", {
11851
- className: "text-sm opacity-70",
11852
- children: hiddenFileFormat(hidden)
11853
- })]
11854
- }, hidden)) : /* @__PURE__ */ jsx("div", {
11855
- className: "text-sm opacity-70",
11856
- children: "No hidden files"
11857
- })
11858
- })
11859
- });
11860
- });
11861
11826
  function CheckboxWithLabel({ children, checked, onChange, className = "", checkedIcon = mdiCheckboxOutline, uncheckedIcon = mdiCheckboxBlankOutline, ...props }) {
11827
+ const [value, setValue] = useState(checked);
11862
11828
  const [isTransitioning, startTransition$1] = useTransition();
11863
11829
  return /* @__PURE__ */ jsxs("label", {
11864
11830
  className: `label group flex w-full items-center justify-start gap-2 hover:text-blue-500 ${className}`,
11865
11831
  ...props,
11866
11832
  children: [
11867
- /* @__PURE__ */ jsxs("span", {
11868
- className: "flex grow items-center gap-2",
11869
- children: [children, isTransitioning ? /* @__PURE__ */ jsx("img", {
11870
- src: truck_default$1,
11871
- alt: "...",
11872
- className: "h-5"
11873
- }) : ""]
11833
+ /* @__PURE__ */ jsx("div", {
11834
+ className: "flex flex-1 items-center gap-2",
11835
+ children
11836
+ }),
11837
+ /* @__PURE__ */ jsx("img", {
11838
+ src: truck_default$1,
11839
+ alt: "",
11840
+ "aria-hidden": "true",
11841
+ className: cn("h-5", { "opacity-0": !isTransitioning })
11874
11842
  }),
11875
11843
  /* @__PURE__ */ jsx(Icon, {
11876
11844
  className: "place-self-end group-hover:text-blue-500",
11877
- path: checked ? checkedIcon : uncheckedIcon,
11845
+ path: value ? checkedIcon : uncheckedIcon,
11878
11846
  size: 1
11879
11847
  }),
11880
11848
  /* @__PURE__ */ jsx("input", {
11881
11849
  type: "checkbox",
11882
- defaultChecked: checked,
11850
+ checked: value,
11883
11851
  className: "hidden",
11884
- onChange: (e) => startTransition$1(() => onChange(e))
11852
+ onChange: (e) => {
11853
+ setValue(e.target.checked);
11854
+ startTransition$1(() => onChange(e));
11855
+ }
11885
11856
  })
11886
11857
  ]
11887
11858
  });
11888
11859
  }
11889
- function UnionAuthorsModal() {
11860
+ function GroupAuthorsModal() {
11890
11861
  const { databaseInfo } = useData();
11891
- const submit = useSubmit();
11862
+ const submit = useViewSubmit();
11892
11863
  const { authors } = databaseInfo;
11893
11864
  const authorUnions = databaseInfo.authorUnions;
11894
11865
  const [selectedAuthors, setSelectedAuthors] = useState([]);
@@ -11896,7 +11867,7 @@ function UnionAuthorsModal() {
11896
11867
  const navigationData = useNavigation();
11897
11868
  const [, authorColors] = useMetrics();
11898
11869
  const [, startTransition$1] = useTransition();
11899
- const location$1 = useLocation();
11870
+ const viewAction = useViewAction();
11900
11871
  const flattedUnionedAuthors = authorUnions.reduce((acc, union) => {
11901
11872
  return [...acc, ...union];
11902
11873
  }, []).sort(stringSorter);
@@ -11904,27 +11875,18 @@ function UnionAuthorsModal() {
11904
11875
  const newAuthorUnions = authorUnions.filter((_, i) => i !== groupToUnGroup);
11905
11876
  const form = new FormData();
11906
11877
  form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11907
- submit(form, {
11908
- action: href("/view") + location$1.search,
11909
- method: "post"
11910
- });
11878
+ submit(form, { method: "post" });
11911
11879
  }
11912
11880
  function ungroupAll() {
11913
11881
  const form = new FormData();
11914
11882
  form.append("unionedAuthors", JSON.stringify([]));
11915
- submit(form, {
11916
- action: href("/view") + location$1.search,
11917
- method: "post"
11918
- });
11883
+ submit(form, { method: "post" });
11919
11884
  }
11920
11885
  function groupSelectedAuthors() {
11921
11886
  if (selectedAuthors.length < 2) return;
11922
11887
  const form = new FormData();
11923
11888
  form.append("unionedAuthors", JSON.stringify([...authorUnions, selectedAuthors]));
11924
- submit(form, {
11925
- action: href("/view") + location$1.search,
11926
- method: "post"
11927
- });
11889
+ submit(form, { method: "post" });
11928
11890
  setSelectedAuthors([]);
11929
11891
  }
11930
11892
  function makePrimaryAlias(alias, groupIndex) {
@@ -11934,31 +11896,31 @@ function UnionAuthorsModal() {
11934
11896
  });
11935
11897
  const form = new FormData();
11936
11898
  form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11937
- submit(form, {
11938
- action: href("/view") + location$1.search,
11939
- method: "post"
11940
- });
11899
+ submit(form, { method: "post" });
11941
11900
  }
11942
11901
  const disabled = navigationData.state !== "idle";
11943
11902
  const ungroupedAuthorsSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
11944
11903
  const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
11945
11904
  const ungroupedAuthorsFiltered = ungroupedAuthorsSorted.filter((author) => author.toLowerCase().includes(filter.toLowerCase()));
11946
- const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => /* @__PURE__ */ jsx(CheckboxWithLabel, {
11947
- className: "hover:opacity-70",
11948
- checked: selectedAuthors.includes(author),
11949
- onChange: (e) => {
11950
- setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
11951
- },
11952
- children: /* @__PURE__ */ jsxs("div", {
11953
- className: "inline-flex flex-row place-items-center gap-2",
11954
- children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
11955
- })
11956
- }, author));
11905
+ const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => {
11906
+ const isAuthorSelected = selectedAuthors.includes(author);
11907
+ return /* @__PURE__ */ jsx(CheckboxWithLabel, {
11908
+ className: "hover:opacity-70",
11909
+ checked: isAuthorSelected,
11910
+ onChange: (e) => {
11911
+ setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
11912
+ },
11913
+ children: /* @__PURE__ */ jsxs("div", {
11914
+ className: "inline-flex flex-row place-items-center gap-2",
11915
+ children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
11916
+ })
11917
+ }, author + isAuthorSelected);
11918
+ });
11957
11919
  const groupedAuthorsEntries = authorUnions.map((aliasGroup, aliasGroupIndex) => {
11958
11920
  const displayName = aliasGroup[0];
11959
11921
  const disabled$1 = navigationData.state !== "idle";
11960
11922
  return /* @__PURE__ */ jsxs("div", {
11961
- className: "card group m-0 flex h-full flex-col p-2",
11923
+ className: "card bg-tertiary-bg dark:bg-tertiary-bg-dark group m-0 flex h-full flex-col p-2",
11962
11924
  children: [
11963
11925
  /* @__PURE__ */ jsxs("div", {
11964
11926
  className: "inline-flex flex-row place-items-center gap-2",
@@ -11977,8 +11939,8 @@ function UnionAuthorsModal() {
11977
11939
  /* @__PURE__ */ jsxs("div", {
11978
11940
  className: "flex items-end justify-end gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
11979
11941
  children: [/* @__PURE__ */ jsxs(Form, {
11980
- action: href("/view") + location$1.search,
11981
11942
  method: "post",
11943
+ action: viewAction,
11982
11944
  children: [/* @__PURE__ */ jsx("input", {
11983
11945
  type: "hidden",
11984
11946
  name: "unionedAuthors",
@@ -11998,10 +11960,7 @@ function UnionAuthorsModal() {
11998
11960
  });
11999
11961
  const form = new FormData();
12000
11962
  form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12001
- submit(form, {
12002
- action: href("/view") + location$1.search,
12003
- method: "post"
12004
- });
11963
+ submit(form, { method: "post" });
12005
11964
  setSelectedAuthors([]);
12006
11965
  },
12007
11966
  children: "Add selected"
@@ -12018,61 +11977,32 @@ function UnionAuthorsModal() {
12018
11977
  }, aliasGroupIndex);
12019
11978
  });
12020
11979
  return /* @__PURE__ */ jsxs("div", {
12021
- className: "m-auto grid h-full max-h-200 w-full max-w-(--breakpoint-2xl) grid-cols-[1fr_1fr] grid-rows-[max-content_1fr] gap-2 overflow-hidden bg-gray-100 p-4",
11980
+ className: "flex min-h-0 w-auto max-w-(--breakpoint-lg) min-w-0 flex-col gap-2 p-4",
12022
11981
  children: [
12023
11982
  /* @__PURE__ */ jsxs("div", {
12024
- className: "flex justify-between",
11983
+ className: "grid grid-cols-[1fr_1fr] gap-2",
12025
11984
  children: [/* @__PURE__ */ jsxs("h3", {
12026
- className: "grow text-center text-lg font-bold",
11985
+ className: "text-center text-lg font-bold",
12027
11986
  children: [
12028
11987
  "Ungrouped Authors (",
12029
11988
  ungroupedAuthorsSorted.length,
12030
11989
  ")"
12031
11990
  ]
12032
- }), /* @__PURE__ */ jsx("div", {
12033
- className: "flex justify-end gap-2",
12034
- children: /* @__PURE__ */ jsxs("button", {
12035
- className: "btn btn--primary justify-self-end",
12036
- title: "Group the selected authors",
12037
- disabled: disabled || selectedAuthors.length < 2,
12038
- onClick: groupSelectedAuthors,
12039
- children: [/* @__PURE__ */ jsx(Icon, {
12040
- path: mdiAccountMultiplePlus,
12041
- size: 1
12042
- }), "Create group"]
12043
- })
12044
- })]
12045
- }),
12046
- /* @__PURE__ */ jsxs("div", {
12047
- className: "flex justify-between",
12048
- children: [/* @__PURE__ */ jsxs("h3", {
12049
- className: "grow text-center text-lg font-bold",
11991
+ }), /* @__PURE__ */ jsxs("h3", {
11992
+ className: "text-center text-lg font-bold",
12050
11993
  children: [
12051
11994
  "Author Groups (",
12052
11995
  groupedAuthorsEntries.length,
12053
11996
  ")"
12054
11997
  ]
12055
- }), /* @__PURE__ */ jsx("div", {
12056
- className: "flex justify-end gap-4",
12057
- children: /* @__PURE__ */ jsxs("button", {
12058
- className: "btn btn--danger",
12059
- disabled: disabled || authorUnions.length === 0,
12060
- onClick: () => {
12061
- if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
12062
- },
12063
- children: [/* @__PURE__ */ jsx(Icon, {
12064
- path: mdiAccountMultipleMinus,
12065
- size: 1
12066
- }), "Ungroup all"]
12067
- })
12068
11998
  })]
12069
11999
  }),
12070
- /* @__PURE__ */ jsx("div", {
12071
- className: "max-h-full overflow-y-scroll",
12072
- children: /* @__PURE__ */ jsxs("div", {
12073
- className: "h-fill flex min-h-0 flex-col rounded-md dark:bg-gray-700",
12000
+ /* @__PURE__ */ jsxs("div", {
12001
+ className: "grid min-h-0 flex-1 grid-cols-[1fr_1fr] gap-2",
12002
+ children: [/* @__PURE__ */ jsxs("div", {
12003
+ className: "flex min-h-0 flex-col rounded-md",
12074
12004
  children: [/* @__PURE__ */ jsxs("div", {
12075
- className: "sticky top-0 z-10 flex gap-2 bg-gray-100 p-2 dark:bg-gray-700",
12005
+ className: "flex gap-2 p-2",
12076
12006
  children: [
12077
12007
  /* @__PURE__ */ jsx("input", {
12078
12008
  className: "input min-w-0",
@@ -12097,23 +12027,45 @@ function UnionAuthorsModal() {
12097
12027
  })
12098
12028
  ]
12099
12029
  }), /* @__PURE__ */ jsx("div", {
12100
- className: "min-h-fill max-h-full overflow-y-auto p-2",
12030
+ className: "min-h-0 flex-1 overflow-y-auto p-2",
12101
12031
  children: ungroupedAuthorsEntries.length > 0 ? ungroupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12102
- className: "place-self-center",
12032
+ className: "place-self-center text-sm",
12103
12033
  children: filter.length > 0 ? "No authors found" : "All authors have been grouped"
12104
12034
  })
12105
12035
  })]
12106
- })
12107
- }),
12108
- /* @__PURE__ */ jsx("div", {
12109
- className: "min-h-0 overflow-y-auto",
12110
- children: /* @__PURE__ */ jsx("div", {
12111
- className: "grid h-min min-h-0 grid-cols-1 gap-4 rounded-md bg-white p-4 shadow-sm lg:grid-cols-2 xl:grid-cols-3 dark:bg-gray-700",
12112
- children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12113
- className: "place-self-center",
12114
- children: "No authors have been grouped yet"
12036
+ }), /* @__PURE__ */ jsx("div", {
12037
+ className: "min-h-0 overflow-y-auto",
12038
+ children: /* @__PURE__ */ jsx("div", {
12039
+ className: "grid h-min min-h-0 grid-cols-1 gap-4 rounded-md p-2 lg:grid-cols-2 xl:grid-cols-3",
12040
+ children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12041
+ className: "col-span-2 text-center text-sm",
12042
+ children: "No authors have been grouped yet"
12043
+ })
12115
12044
  })
12116
- })
12045
+ })]
12046
+ }),
12047
+ /* @__PURE__ */ jsxs("div", {
12048
+ className: "grid grid-cols-[1fr_1fr] gap-2",
12049
+ children: [/* @__PURE__ */ jsxs("button", {
12050
+ className: "btn btn--primary mx-auto w-fit",
12051
+ title: disabled || selectedAuthors.length < 2 ? "Select at least 2 authors to group them" : "Group the selected authors",
12052
+ disabled: disabled || selectedAuthors.length < 2,
12053
+ onClick: groupSelectedAuthors,
12054
+ children: [/* @__PURE__ */ jsx(Icon, {
12055
+ path: mdiAccountMultiplePlus,
12056
+ size: 1
12057
+ }), "Create group"]
12058
+ }), /* @__PURE__ */ jsxs("button", {
12059
+ className: "btn btn--danger mx-auto w-fit",
12060
+ disabled: disabled || authorUnions.length === 0,
12061
+ onClick: () => {
12062
+ if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
12063
+ },
12064
+ children: [/* @__PURE__ */ jsx(Icon, {
12065
+ path: mdiAccountMultipleMinus,
12066
+ size: 1
12067
+ }), "Ungroup all"]
12068
+ })]
12117
12069
  })
12118
12070
  ]
12119
12071
  });
@@ -12135,17 +12087,138 @@ function UnionAuthorsModal() {
12135
12087
  }
12136
12088
  }
12137
12089
  var stringSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
12138
- function IconRadioGroup({ group, titleMap, defaultValue, className, onChange, large, iconMap }) {
12090
+ function HideFilesModal() {
12091
+ const navigationState = useNavigation();
12092
+ const { databaseInfo } = useData();
12093
+ const inputRef = useRef(null);
12094
+ const viewAction = useViewAction();
12095
+ const optimisticHide = navigationState.state !== "idle" && navigationState.formData ? navigationState.formData.get("hide") : null;
12096
+ const optimisticUnhide = navigationState.state !== "idle" && navigationState.formData ? navigationState.formData.get("show") : null;
12097
+ const displayedHiddenFiles = [...databaseInfo.hiddenFiles];
12098
+ if (optimisticHide && !databaseInfo.hiddenFiles.includes(optimisticHide)) displayedHiddenFiles.unshift(optimisticHide);
12099
+ const filteredHiddenFiles = displayedHiddenFiles.filter((path$1) => path$1 !== optimisticUnhide);
12100
+ return /* @__PURE__ */ jsxs("div", {
12101
+ className: "flex min-h-0 max-w-[40ch] flex-col items-start justify-center gap-3 overflow-y-auto p-2 pl-0",
12102
+ children: [
12103
+ /* @__PURE__ */ jsx("p", {
12104
+ className: "text-sm",
12105
+ children: "Pattern-matched files are hidden from the file tree and excluded from metric computations."
12106
+ }),
12107
+ /* @__PURE__ */ jsxs("p", {
12108
+ className: "text-sm",
12109
+ children: [
12110
+ "Hidden files behave like entries in a ",
12111
+ /* @__PURE__ */ jsx(Code, {
12112
+ inline: true,
12113
+ children: ".gitignore"
12114
+ }),
12115
+ " file."
12116
+ ]
12117
+ }),
12118
+ /* @__PURE__ */ jsxs(Form, {
12119
+ action: viewAction,
12120
+ method: "post",
12121
+ className: "flex w-full flex-wrap items-end gap-2",
12122
+ onSubmit: () => {
12123
+ setTimeout(() => {
12124
+ if (inputRef.current) inputRef.current.value = "";
12125
+ }, 0);
12126
+ },
12127
+ children: [/* @__PURE__ */ jsxs("label", {
12128
+ className: "label flex min-w-0 flex-1 flex-col gap-2",
12129
+ children: ["Path or glob", /* @__PURE__ */ jsx("input", {
12130
+ ref: inputRef,
12131
+ type: "text",
12132
+ className: "input",
12133
+ name: "hide",
12134
+ placeholder: "Enter pattern...",
12135
+ onChange: (e) => {
12136
+ const exists = databaseInfo.hiddenFiles.includes(e.target.value);
12137
+ e.target.setCustomValidity(exists ? "Pattern already exists" : "");
12138
+ }
12139
+ })]
12140
+ }), /* @__PURE__ */ jsx("button", {
12141
+ className: "btn btn--primary whitespace-nowrap",
12142
+ children: "Hide"
12143
+ })]
12144
+ }),
12145
+ /* @__PURE__ */ jsxs("div", {
12146
+ className: "flex w-full items-center justify-between",
12147
+ children: [/* @__PURE__ */ jsx("h3", {
12148
+ className: "text-tertiary-text dark:text-tertiary-text-dark text-sm font-bold tracking-wide uppercase",
12149
+ children: "Hidden files/patterns"
12150
+ }), /* @__PURE__ */ jsxs(Form, {
12151
+ action: viewAction,
12152
+ method: "post",
12153
+ className: cn({ hidden: databaseInfo.hiddenFiles.length === 0 }),
12154
+ children: [/* @__PURE__ */ jsx("input", {
12155
+ type: "hidden",
12156
+ name: "unhideAll",
12157
+ value: "true"
12158
+ }), /* @__PURE__ */ jsx("button", {
12159
+ className: "btn btn--danger btn--text",
12160
+ children: "Reset"
12161
+ })]
12162
+ })]
12163
+ }),
12164
+ /* @__PURE__ */ jsx("div", {
12165
+ className: "flex max-h-96 min-h-0 w-full flex-col gap-2 overflow-y-auto",
12166
+ children: filteredHiddenFiles.length > 0 ? filteredHiddenFiles.map((hidden) => {
12167
+ const isOptimistic = hidden === optimisticHide;
12168
+ return /* @__PURE__ */ jsxs("div", {
12169
+ className: "primary grid grid-cols-[auto_1fr] items-center gap-2 rounded-md px-2 py-1 text-sm",
12170
+ style: { opacity: isOptimistic ? .6 : hidden === optimisticUnhide ? .4 : 1 },
12171
+ title: hidden,
12172
+ children: [/* @__PURE__ */ jsxs(Form, {
12173
+ action: viewAction,
12174
+ className: "w-4",
12175
+ method: "post",
12176
+ children: [/* @__PURE__ */ jsx("input", {
12177
+ type: "hidden",
12178
+ name: "show",
12179
+ value: hidden
12180
+ }), /* @__PURE__ */ jsxs("button", {
12181
+ className: "btn btn--text btn--hover-swap h-4",
12182
+ title: "Show file",
12183
+ disabled: navigationState.state !== "idle" || isOptimistic,
12184
+ children: [/* @__PURE__ */ jsx(Icon, {
12185
+ path: mdiEyeOff,
12186
+ className: "inline-block h-full"
12187
+ }), /* @__PURE__ */ jsx(Icon, {
12188
+ path: mdiEye,
12189
+ className: "hover-swap inline-block h-full"
12190
+ })]
12191
+ })]
12192
+ }), /* @__PURE__ */ jsx("span", {
12193
+ className: "truncate text-sm",
12194
+ title: hidden,
12195
+ children: hiddenFileFormat(hidden)
12196
+ })]
12197
+ }, hidden);
12198
+ }) : /* @__PURE__ */ jsx("div", {
12199
+ className: "text-secondary-text dark:text-secondary-text-dark text-sm opacity-70",
12200
+ children: "No hidden files"
12201
+ })
12202
+ })
12203
+ ]
12204
+ });
12205
+ }
12206
+ function hiddenFileFormat(ignored) {
12207
+ if (!ignored.includes(getSep(ignored))) return ignored;
12208
+ const split = ignored.split(getSep(ignored));
12209
+ return split[split.length - 1];
12210
+ }
12211
+ function IconRadioGroup({ group, titleMap, defaultValue, className, onChange, large, iconMap, ariaLabel }) {
12139
12212
  const enumEntries = Object.entries(group);
12140
12213
  return /* @__PURE__ */ jsx(RadioGroup, {
12141
12214
  value: defaultValue,
12142
12215
  className: cn("flex flex-wrap gap-0", { "gap-2": large }, className),
12143
- "aria-label": "Select branch or revision",
12216
+ "aria-label": ariaLabel,
12144
12217
  onChange,
12145
12218
  children: enumEntries.map(([key, value]) => /* @__PURE__ */ jsx(Field, { children: /* @__PURE__ */ jsx(Radio, {
12146
12219
  value: key,
12147
12220
  className: "group",
12148
- title: titleMap ? titleMap[key] : value,
12221
+ title: titleMap?.[key] ?? value,
12149
12222
  children: /* @__PURE__ */ jsxs("div", {
12150
12223
  className: clsx$1("group btn btn--text cursor-pointer gap-2 text-xs transition-all duration-200 hover:opacity-100! focus-visible:opacity-100!", {
12151
12224
  "group-data-checked:bg-blue-primary group-data-checked:fill-primary-text-dark group-data-checked:text-primary-text-dark group-data-checked:border-blue-primary hover:fill-blue-primary hover:text-blue-primary hover:border-blue-primary h-auto flex-col place-items-center items-center rounded border border-transparent p-1": large,
@@ -12185,6 +12258,7 @@ const Options = memo(function Options$1() {
12185
12258
  TREE_MAP: mdiChartTree,
12186
12259
  PARTITION: mdiKnife
12187
12260
  },
12261
+ ariaLabel: "Select layout",
12188
12262
  onChange: (chartType$1) => setChartType(chartType$1)
12189
12263
  })] }),
12190
12264
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
@@ -12202,6 +12276,7 @@ const Options = memo(function Options$1() {
12202
12276
  LAST_CHANGED: mdiPulse,
12203
12277
  MOST_CONTRIBUTIONS: mdiPlusMinusVariant
12204
12278
  },
12279
+ ariaLabel: "Select size metric",
12205
12280
  onChange: (sizeMetric$1) => {
12206
12281
  setSizeMetricType(sizeMetric$1);
12207
12282
  }
@@ -12221,6 +12296,7 @@ const Options = memo(function Options$1() {
12221
12296
  TOP_CONTRIBUTOR: mdiPodiumGold,
12222
12297
  MOST_CONTRIBUTIONS: mdiPlusMinusVariant
12223
12298
  },
12299
+ ariaLabel: "Select color metric",
12224
12300
  onChange: (metric) => {
12225
12301
  setMetricType(metric);
12226
12302
  if (!linkMetricAndSizeMetric) return;
@@ -12231,10 +12307,9 @@ const Options = memo(function Options$1() {
12231
12307
  ] });
12232
12308
  });
12233
12309
  function SettingsModal() {
12234
- const { metricType, hierarchyType, transitionsEnabled, renderCutoff, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setRenderCutoff, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
12235
- const [isTransitioning, startTransition$1] = useTransition();
12310
+ const { metricType, hierarchyType, transitionsEnabled, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
12236
12311
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
12237
- className: "flex min-w-120 flex-col items-start justify-center gap-4 p-2 pl-0",
12312
+ className: "flex min-h-0 max-w-max flex-col items-start justify-center gap-3 overflow-y-auto p-2 pl-0",
12238
12313
  children: [
12239
12314
  /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12240
12315
  className: "group text-sm hover:text-blue-500 hover:opacity-100",
@@ -12308,48 +12383,64 @@ function SettingsModal() {
12308
12383
  size: "1.25em"
12309
12384
  }), "Show only search matches"]
12310
12385
  }),
12311
- /* @__PURE__ */ jsxs("label", {
12312
- className: "label group flex w-full items-center justify-start gap-2 text-sm hover:text-blue-500 hover:opacity-100",
12313
- title: "Increase this to improve render performance, decrease it to get higher level of detail",
12314
- children: [/* @__PURE__ */ jsxs("span", {
12315
- className: "group flex grow items-center gap-2",
12316
- children: [
12317
- /* @__PURE__ */ jsx(Icon, {
12318
- className: "ml-1.5",
12319
- path: mdiContentCut,
12320
- size: "1.25em"
12321
- }),
12322
- "Pixel render cut-off ",
12323
- isTransitioning ? /* @__PURE__ */ jsx("img", {
12324
- src: truck_default$1,
12325
- alt: "...",
12326
- className: "h-5"
12327
- }) : ""
12328
- ]
12329
- }), /* @__PURE__ */ jsx("input", {
12330
- type: "number",
12331
- min: 0,
12332
- defaultValue: renderCutoff,
12333
- className: "mr-1 w-12 place-self-end border-b-2",
12334
- onChange: (x) => startTransition$1(() => setRenderCutoff(x.target.valueAsNumber))
12335
- })]
12336
- })
12386
+ /* @__PURE__ */ jsx(RenderCutOff, {})
12337
12387
  ]
12338
12388
  }) });
12339
12389
  }
12390
+ function RenderCutOff() {
12391
+ const { renderCutOff, setRenderCutOff } = useOptions();
12392
+ const [value, setValue] = useState(renderCutOff);
12393
+ const [isTransitioning, startTransition$1] = useTransition();
12394
+ return /* @__PURE__ */ jsxs("label", {
12395
+ className: "label group flex w-full items-center justify-start gap-2 text-sm hover:text-blue-500 hover:opacity-100",
12396
+ title: "Increase to improve render performance, decrease it to get higher level of detail",
12397
+ children: [/* @__PURE__ */ jsxs("span", {
12398
+ className: "group flex grow items-center gap-2",
12399
+ children: [
12400
+ /* @__PURE__ */ jsx(Icon, {
12401
+ className: "ml-1.5",
12402
+ path: mdiContentCut,
12403
+ size: "1.25em"
12404
+ }),
12405
+ "Pixel render cut-off ",
12406
+ isTransitioning ? /* @__PURE__ */ jsx("img", {
12407
+ src: truck_default$1,
12408
+ alt: "",
12409
+ "aria-hidden": "true",
12410
+ className: "h-5"
12411
+ }) : ""
12412
+ ]
12413
+ }), /* @__PURE__ */ jsx("input", {
12414
+ type: "number",
12415
+ min: 0,
12416
+ value,
12417
+ className: "mr-1 w-12 place-self-end border-b-2",
12418
+ onChange: (x) => {
12419
+ const v = Number.isNaN(x.target.valueAsNumber) ? getDefaultOptionsContextValue().renderCutOff : x.target.valueAsNumber;
12420
+ setValue(v);
12421
+ startTransition$1(() => setRenderCutOff(v));
12422
+ }
12423
+ })]
12424
+ });
12425
+ }
12340
12426
  var modals = {
12341
- "group-authors": {
12342
- content: /* @__PURE__ */ jsx(UnionAuthorsModal, {}),
12343
- title: "Group Authors",
12344
- icon: mdiAccountMultipleCheck
12345
- },
12346
12427
  "app-settings": {
12347
12428
  content: /* @__PURE__ */ jsx(SettingsModal, {}),
12348
12429
  title: "Settings",
12349
12430
  icon: mdiCog
12431
+ },
12432
+ "group-authors": {
12433
+ content: /* @__PURE__ */ jsx(GroupAuthorsModal, {}),
12434
+ title: "Group Authors",
12435
+ icon: mdiAccountMultiple
12436
+ },
12437
+ "ignore-files": {
12438
+ content: /* @__PURE__ */ jsx(HideFilesModal, {}),
12439
+ title: "Hide files",
12440
+ icon: mdiEyeOff
12350
12441
  }
12351
12442
  };
12352
- var modalSearchParamConfig = parseAsStringLiteral(Object.keys(modals));
12443
+ var modalSearchParamConfig = parseAsStringLiteral(Object.keys(modals)).withOptions({ shallow: false });
12353
12444
  function useModal(modalKey = null) {
12354
12445
  const [modal, setModal] = useQueryState("modal", modalSearchParamConfig);
12355
12446
  const openModal = (modal$1 = modalKey) => void setModal(modal$1);
@@ -12363,16 +12454,22 @@ function useModal(modalKey = null) {
12363
12454
  function ModalManager() {
12364
12455
  const [modalKey, setModal] = useQueryState("modal", modalSearchParamConfig);
12365
12456
  const dialogRef = useRef(null);
12366
- const onClose = () => setModal(null);
12457
+ const onClose = () => {
12458
+ return setModal(null);
12459
+ };
12367
12460
  useEffect(() => {
12368
- if (!dialogRef.current) return;
12369
12461
  const dialog = dialogRef.current;
12462
+ if (!dialog) return;
12370
12463
  if (modalKey) {
12371
- dialogRef.current.showModal();
12372
- return;
12464
+ document.body.style.setProperty("overflow", "hidden");
12465
+ if (!dialog.open) dialog.showModal();
12466
+ } else {
12467
+ document.body.style.setProperty("overflow", null);
12468
+ if (dialog.open) dialog.close();
12373
12469
  }
12374
- dialog.close();
12375
- return () => dialog.close();
12470
+ return () => {
12471
+ document.body.style.setProperty("overflow", null);
12472
+ };
12376
12473
  }, [modalKey]);
12377
12474
  if (!modalKey) return null;
12378
12475
  const modal = modals[modalKey];
@@ -12381,10 +12478,10 @@ function ModalManager() {
12381
12478
  "aria-modal": true,
12382
12479
  open: false,
12383
12480
  closedby: "any",
12384
- className: "z-10 m-auto flex flex-col items-start justify-stretch bg-transparent text-inherit backdrop:bg-gray-500/75 backdrop:p-0",
12481
+ className: "z-10 m-auto flex flex-col bg-transparent text-inherit backdrop:bg-gray-400/75 backdrop:backdrop-blur-xs dark:backdrop:bg-gray-800/75",
12385
12482
  onClose,
12386
12483
  children: /* @__PURE__ */ jsxs("div", {
12387
- className: "card m-auto h-full w-full max-w-(--breakpoint-2xl) gap-2 overflow-hidden rounded-xl bg-gray-100 p-4 shadow-sm",
12484
+ className: "card flex min-h-0 max-w-(--breakpoint-2xl) gap-2 rounded-xl p-4 shadow-sm",
12388
12485
  children: [
12389
12486
  /* @__PURE__ */ jsxs("div", {
12390
12487
  className: "flex justify-between gap-2",
@@ -12409,29 +12506,29 @@ function ModalManager() {
12409
12506
  })
12410
12507
  });
12411
12508
  }
12412
- function AuthorOptions() {
12509
+ function HideFilesButton() {
12510
+ const { openModal } = useModal("ignore-files");
12511
+ return /* @__PURE__ */ jsx("button", {
12512
+ className: "btn btn--icon",
12513
+ title: "Hidden files",
12514
+ "aria-label": "Hidden files",
12515
+ onClick: () => openModal(),
12516
+ children: /* @__PURE__ */ jsx(Icon, { path: mdiEyeOff })
12517
+ });
12518
+ }
12519
+ function ShuffleColorsButton() {
12413
12520
  const transitionState = useNavigation();
12414
- const action$3 = href("/view");
12415
- const { openModal } = useModal("group-authors");
12416
- return /* @__PURE__ */ jsxs("div", {
12417
- className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
12418
- children: [/* @__PURE__ */ jsxs("button", {
12419
- className: "btn",
12420
- onClick: () => openModal(),
12421
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
12422
- }), /* @__PURE__ */ jsxs(Form, {
12423
- method: "post",
12424
- action: action$3,
12425
- children: [/* @__PURE__ */ jsx("input", {
12426
- type: "hidden",
12427
- name: "rerollColors",
12428
- value: ""
12429
- }), /* @__PURE__ */ jsxs("button", {
12430
- className: "btn w-full",
12431
- type: "submit",
12432
- disabled: transitionState.state !== "idle",
12433
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
12434
- })]
12521
+ return /* @__PURE__ */ jsxs(Form, {
12522
+ method: "post",
12523
+ action: useViewAction(),
12524
+ children: [/* @__PURE__ */ jsx("input", {
12525
+ type: "hidden",
12526
+ name: "rerollColors",
12527
+ value: ""
12528
+ }), /* @__PURE__ */ jsxs("button", {
12529
+ className: "btn w-full",
12530
+ disabled: transitionState.state !== "idle",
12531
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
12435
12532
  })]
12436
12533
  });
12437
12534
  }
@@ -12729,6 +12826,16 @@ function PercentageSlider({ className = "" }) {
12729
12826
  })]
12730
12827
  });
12731
12828
  }
12829
+ function GroupAuthorsButton({ compact = false }) {
12830
+ const { openModal } = useModal("group-authors");
12831
+ return /* @__PURE__ */ jsxs("button", {
12832
+ className: cn("btn", { "btn--icon": compact }),
12833
+ title: "Group authors",
12834
+ "aria-label": "Group authors",
12835
+ onClick: () => openModal(),
12836
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), compact ? null : /* @__PURE__ */ jsx("span", { children: "Group Authors" })]
12837
+ });
12838
+ }
12732
12839
  function Legend({ hoveredObject }) {
12733
12840
  const { sizeMetric, metricType } = useOptions();
12734
12841
  const [metricsData] = useMetrics();
@@ -12766,7 +12873,10 @@ function Legend({ hoveredObject }) {
12766
12873
  }),
12767
12874
  metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(PercentageSlider, { className: "my-4" }) : null,
12768
12875
  legend,
12769
- metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(AuthorOptions, {}) : null
12876
+ metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsxs("div", {
12877
+ className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
12878
+ children: [/* @__PURE__ */ jsx(GroupAuthorsButton, {}), /* @__PURE__ */ jsx(ShuffleColorsButton, {})]
12879
+ }) : null
12770
12880
  ] });
12771
12881
  }
12772
12882
  function LoadingIndicator({ className = "", showProgress = false, fetchProgress = true, loadingText }) {
@@ -12831,10 +12941,19 @@ function Providers({ children, data }) {
12831
12941
  const [searchResults, setSearchResults] = useState({});
12832
12942
  const hasSearchResults = useMemo(() => Object.values(searchResults).length > 0, [searchResults]);
12833
12943
  const prefersLight = usePrefersLightMode();
12944
+ const [zoomPath] = useQueryState("zoomPath");
12945
+ const databaseInfo = useMemo(() => ({
12946
+ ...data.databaseInfo,
12947
+ fileTree: findSubTree(data.databaseInfo.fileTree, zoomPath ?? void 0)
12948
+ }), [data.databaseInfo, zoomPath]);
12834
12949
  const metricsData = useMemo(() => {
12835
- return createMetricData(data, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12950
+ return createMetricData({
12951
+ ...data,
12952
+ databaseInfo
12953
+ }, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12836
12954
  }, [
12837
12955
  data,
12956
+ databaseInfo,
12838
12957
  options?.dominantAuthorCutoff,
12839
12958
  prefersLight
12840
12959
  ]);
@@ -12872,9 +12991,9 @@ function Providers({ children, data }) {
12872
12991
  ...prevOptions,
12873
12992
  labelsVisible: visible
12874
12993
  })),
12875
- setRenderCutoff: (renderCutoff) => setOptions((prevOptions) => ({
12994
+ setRenderCutOff: (renderCutOff) => setOptions((prevOptions) => ({
12876
12995
  ...prevOptions,
12877
- renderCutoff
12996
+ renderCutOff
12878
12997
  })),
12879
12998
  setShowFilesWithoutChanges: (showFilesWithoutChanges) => setOptions((prevOptions) => ({
12880
12999
  ...prevOptions,
@@ -12905,7 +13024,10 @@ function Providers({ children, data }) {
12905
13024
  };
12906
13025
  }, [options]);
12907
13026
  return /* @__PURE__ */ jsx(DataContext.Provider, {
12908
- value: data,
13027
+ value: {
13028
+ ...data,
13029
+ databaseInfo
13030
+ },
12909
13031
  children: /* @__PURE__ */ jsx(MetricsContext.Provider, {
12910
13032
  value: metricsData,
12911
13033
  children: /* @__PURE__ */ jsx(OptionsContext.Provider, {
@@ -12935,6 +13057,7 @@ function findSearchResults(tree, searchString) {
12935
13057
  }
12936
13058
  const SearchCard = memo(function SearchCard$1() {
12937
13059
  const searchFieldRef = useRef(null);
13060
+ const [zoomPath] = useQueryState("zoomPath");
12938
13061
  const [isTransitioning, startTransition$1] = useTransition();
12939
13062
  const [searchText, setSearchText] = useState("");
12940
13063
  const { clickedObject, setClickedObject } = useClickedObject();
@@ -12942,9 +13065,26 @@ const SearchCard = memo(function SearchCard$1() {
12942
13065
  const searchResultsArray = useMemo(() => Object.values(searchResults), [searchResults]);
12943
13066
  const id = useId();
12944
13067
  const { databaseInfo } = useData();
13068
+ useEffect(() => {
13069
+ setSearchResults(searchText.length > 0 ? findSearchResults(databaseInfo.fileTree, searchText) : {});
13070
+ }, [
13071
+ searchText,
13072
+ setSearchResults,
13073
+ databaseInfo.fileTree
13074
+ ]);
12945
13075
  const options = useOptions();
12946
13076
  const [metrics] = useMetrics();
12947
13077
  const resultRefs = Object.keys(searchResults).map(() => createRef());
13078
+ useEffect(() => {
13079
+ startTransition$1(() => {
13080
+ if (searchText.trim() === "") setSearchResults({});
13081
+ else setSearchResults(findSearchResults(databaseInfo.fileTree, searchText));
13082
+ });
13083
+ }, [
13084
+ searchText,
13085
+ databaseInfo.fileTree,
13086
+ setSearchResults
13087
+ ]);
12948
13088
  useKey({
12949
13089
  key: "f",
12950
13090
  ctrlOrMeta: true
@@ -12952,15 +13092,13 @@ const SearchCard = memo(function SearchCard$1() {
12952
13092
  event.preventDefault();
12953
13093
  searchFieldRef.current?.focus();
12954
13094
  });
12955
- function onClickObject(object) {
12956
- setClickedObject(object);
12957
- }
12958
13095
  function focusResultAtIndex(nextIndex) {
12959
13096
  resultRefs[nextIndex].current?.focus();
12960
13097
  }
12961
13098
  const items = Object.values(searchResults);
13099
+ const helpText = `Search within ${zoomPath ? zoomPath.split(getSeparator(zoomPath)).at(-1) : databaseInfo.repo}`;
12962
13100
  return /* @__PURE__ */ jsxs("form", {
12963
- className: "w-sidepanel absolute top-2 bottom-0 left-1/2 z-10 flex -translate-x-1/2 flex-col gap-2 transition-[width,translate] not-focus-within:has-placeholder-shown:static not-focus-within:has-placeholder-shown:w-min not-focus-within:has-placeholder-shown:translate-x-0",
13101
+ className: "w-sidepanel not-focus-within:has-placeholder-shown:w-button pointer-events-none absolute right-0 z-10 flex flex-col gap-2 transition-[left,width,translate] duration-75 **:pointer-events-auto not-focus-within:has-placeholder-shown:static not-focus-within:has-placeholder-shown:translate-x-0",
12964
13102
  onSubmit: (event) => {
12965
13103
  event.preventDefault();
12966
13104
  setSearchText("");
@@ -12969,23 +13107,21 @@ const SearchCard = memo(function SearchCard$1() {
12969
13107
  children: [
12970
13108
  /* @__PURE__ */ jsx("button", {
12971
13109
  className: "hidden min-w-max cursor-pointer peer-placeholder-shown:hidden peer-focus:inline",
12972
- type: "submit",
12973
13110
  children: /* @__PURE__ */ jsx(Icon, {
12974
13111
  path: mdiClose,
12975
- size: "1em",
12976
- className: ""
13112
+ size: "1em"
12977
13113
  })
12978
13114
  }),
12979
13115
  /* @__PURE__ */ jsxs("label", {
12980
13116
  className: "input min-h-button h-button max-h-button relative flex w-full min-w-0 cursor-pointer flex-row-reverse items-center gap-2 overflow-hidden not-focus-within:has-placeholder-shown:grow-0 not-focus-within:has-placeholder-shown:gap-0",
12981
- title: "Search for a file or folder",
13117
+ title: helpText,
12982
13118
  children: [
12983
13119
  /* @__PURE__ */ jsx("input", {
12984
13120
  ref: searchFieldRef,
12985
13121
  className: "peer w-full grow placeholder-shown:not-focus:w-0 placeholder-shown:not-focus:min-w-0",
12986
13122
  id,
12987
13123
  type: "search",
12988
- placeholder: "Search for a file or folder...",
13124
+ placeholder: helpText,
12989
13125
  value: searchText,
12990
13126
  onKeyDown: (event) => {
12991
13127
  if (event.key === "Enter") event.preventDefault();
@@ -13010,17 +13146,11 @@ const SearchCard = memo(function SearchCard$1() {
13010
13146
  }
13011
13147
  },
13012
13148
  onChange: (event) => {
13013
- const value = event.target.value;
13014
- setSearchText(value);
13015
- startTransition$1(() => {
13016
- if (value.trim() === "") setSearchResults({});
13017
- setSearchResults(findSearchResults(databaseInfo.fileTree, value));
13018
- });
13149
+ setSearchText(event.target.value);
13019
13150
  }
13020
13151
  }),
13021
13152
  /* @__PURE__ */ jsx(Icon, {
13022
13153
  path: mdiMagnify,
13023
- size: "1em",
13024
13154
  className: "hidden min-w-max peer-placeholder-shown:inline peer-focus:hidden"
13025
13155
  }),
13026
13156
  /* @__PURE__ */ jsx("span", {
@@ -13034,7 +13164,7 @@ const SearchCard = memo(function SearchCard$1() {
13034
13164
  ]
13035
13165
  }),
13036
13166
  searchResultsArray.length > 0 ? /* @__PURE__ */ jsx("div", {
13037
- className: "card bg-tertiary-bg/10 dark:bg-tertiary-bg-dark/50 w-sidepanel relative max-h-1/5 min-h-0 overflow-auto backdrop-blur-lg",
13167
+ className: "card bg-tertiary-bg/10 dark:bg-tertiary-bg-dark/50 w-sidepanel max-h-sidepanel relative min-h-0 overflow-auto backdrop-blur-lg",
13038
13168
  children: items.map((object, i) => /* @__PURE__ */ jsxs("button", {
13039
13169
  ref: resultRefs[i],
13040
13170
  className: "flex cursor-pointer items-center justify-start gap-2 text-sm font-bold",
@@ -13064,7 +13194,7 @@ const SearchCard = memo(function SearchCard$1() {
13064
13194
  focusResultAtIndex(prevIndex);
13065
13195
  }
13066
13196
  },
13067
- onClick: () => onClickObject(object),
13197
+ onClick: () => setClickedObject(object),
13068
13198
  children: [object.type === "tree" ? /* @__PURE__ */ jsx(Icon, {
13069
13199
  path: object.type === "tree" ? mdiFolder : mdiFileOutline,
13070
13200
  size: .75,
@@ -13094,18 +13224,11 @@ var BarChart = () => {
13094
13224
  const width = size.width;
13095
13225
  const xScale = d3.scaleBand().domain(data.map((d) => d.date.toString())).range([0, width]);
13096
13226
  const yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count) || 0]).range([height, 0]);
13097
- const [searchParams] = useSearchParams();
13098
- const submit = useSubmit();
13227
+ const submit = useViewSubmit();
13099
13228
  function updateTimeseries(e) {
13100
13229
  const form = new FormData();
13101
13230
  form.append("timeseries", `${e[0]}-${e[1]}`);
13102
- submit(form, {
13103
- action: getPathFromRepoAndHead({
13104
- path: searchParams.get("path"),
13105
- branch: databaseInfo.branch
13106
- }),
13107
- method: "post"
13108
- });
13231
+ submit(form, { method: "post" });
13109
13232
  }
13110
13233
  return /* @__PURE__ */ jsx("div", {
13111
13234
  ref,
@@ -13152,20 +13275,13 @@ function Timeline({ className }) {
13152
13275
  const { timerange, selectedRange } = databaseInfo;
13153
13276
  const newestChangeDate = timerange[1];
13154
13277
  const oldestChangeDate = timerange[0];
13155
- const submit = useSubmit();
13278
+ const submit = useViewSubmit();
13156
13279
  const [range, setRange] = useState(selectedRange[0] === 0 ? timerange : selectedRange);
13157
13280
  const disabled = useNavigation().state !== "idle";
13158
- const [searchParams] = useSearchParams();
13159
13281
  function updateTimeseries(e) {
13160
13282
  const form = new FormData();
13161
13283
  form.append("timeseries", `${e[0]}-${e[1]}`);
13162
- submit(form, {
13163
- action: getPathFromRepoAndHead({
13164
- path: searchParams.get("path"),
13165
- branch: databaseInfo.branch
13166
- }),
13167
- method: "post"
13168
- });
13284
+ submit(form, { method: "post" });
13169
13285
  }
13170
13286
  const selectedStartDate = range[0] * 1e3;
13171
13287
  const selectedEndDate = range[1] * 1e3;
@@ -13290,20 +13406,20 @@ function TimePicker({ range, setRange, timerange, setsBeginning, updateTimeserie
13290
13406
  });
13291
13407
  }
13292
13408
  function RefreshButton() {
13293
- const navigation = useNavigation();
13294
- const isRefreshing = navigation.formData?.get("refresh") === "true";
13409
+ const isRefreshing = useNavigation().formData?.get("refresh") === "true";
13295
13410
  return /* @__PURE__ */ jsxs(Form, {
13296
13411
  method: "post",
13297
- action: navigation.location?.pathname,
13412
+ action: useViewAction(),
13413
+ className: "contents",
13298
13414
  children: [/* @__PURE__ */ jsx("input", {
13299
13415
  type: "hidden",
13300
13416
  name: "refresh",
13301
13417
  value: "true"
13302
13418
  }), /* @__PURE__ */ jsx("button", {
13303
- type: "submit",
13304
- className: "btn",
13419
+ className: "btn btn--icon",
13305
13420
  disabled: isRefreshing,
13306
13421
  title: "Refresh analysis",
13422
+ "aria-label": "Refresh analysis",
13307
13423
  children: /* @__PURE__ */ jsx(Icon, {
13308
13424
  path: mdiRefresh,
13309
13425
  size: "1.25em",
@@ -13396,7 +13512,7 @@ function ColorMetricDependentInfo(props) {
13396
13512
  break;
13397
13513
  }
13398
13514
  if (!contribSum) {
13399
- content = `${icon}${dominant.author}`;
13515
+ content = dominant.author;
13400
13516
  break;
13401
13517
  }
13402
13518
  const authorPercentage = Math.round(dominant.contribcount / contribSum * 100);
@@ -13432,7 +13548,6 @@ function SizeMetricDependentInfo({ sizeMetric, hoveredBlob, databaseInfo }) {
13432
13548
  let icon = mdiCircleSmall;
13433
13549
  let content = null;
13434
13550
  if (!hoveredBlob) return null;
13435
- icon = mdiCircleSmall;
13436
13551
  switch (sizeMetric) {
13437
13552
  case "FILE_SIZE": {
13438
13553
  icon = mdiResize;
@@ -13489,8 +13604,9 @@ function ChartTooltip({ hoveredObject }) {
13489
13604
  function FullscreenButton() {
13490
13605
  const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
13491
13606
  return /* @__PURE__ */ jsx("button", {
13492
- className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
13607
+ className: cn("btn btn--icon", { "btn--primary": isFullscreen }),
13493
13608
  title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
13609
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
13494
13610
  onClick: toggleFullscreen,
13495
13611
  children: /* @__PURE__ */ jsx(Icon, {
13496
13612
  path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
@@ -13555,8 +13671,9 @@ function RevisionSelect({ headGroups, disabled, className = "", analyzedBranches
13555
13671
  function SettingsButton() {
13556
13672
  const { openModal } = useModal("app-settings");
13557
13673
  return /* @__PURE__ */ jsx("button", {
13558
- className: "btn hover:text-primary-text dark:hover:text-primary-text-dark relative flex cursor-pointer justify-between gap-2",
13559
- title: "Visualization settings",
13674
+ className: "btn btn--icon",
13675
+ title: "Settings",
13676
+ "aria-label": "Settings",
13560
13677
  onClick: () => openModal(),
13561
13678
  children: /* @__PURE__ */ jsx(Icon, { path: mdiCog })
13562
13679
  });
@@ -13567,8 +13684,25 @@ function BrowseParentFolder() {
13567
13684
  if (!data) return null;
13568
13685
  return /* @__PURE__ */ jsx("div", { children: data.repo.parentDirPath });
13569
13686
  }
13687
+ function ResetTimeIntervalButton() {
13688
+ const data = useData();
13689
+ const viewAction = useViewAction();
13690
+ return /* @__PURE__ */ jsxs(Form, {
13691
+ className: cn({ invisible: data.databaseInfo.timerange[0] === data.databaseInfo.selectedRange[0] && data.databaseInfo.timerange[1] === data.databaseInfo.selectedRange[1] }),
13692
+ method: "post",
13693
+ action: viewAction,
13694
+ children: [/* @__PURE__ */ jsx("input", {
13695
+ type: "hidden",
13696
+ name: "timeseries",
13697
+ value: `${data.databaseInfo.timerange[0]}-${data.databaseInfo.timerange[1]}`
13698
+ }), /* @__PURE__ */ jsxs("button", {
13699
+ className: cn("btn btn--text", {}),
13700
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiRestore }), "Reset time interval"]
13701
+ })]
13702
+ });
13703
+ }
13570
13704
  var view_exports = /* @__PURE__ */ __export({
13571
- action: () => action$1,
13705
+ action: () => action,
13572
13706
  currentRepositoryContext: () => currentRepositoryContext,
13573
13707
  default: () => view_default,
13574
13708
  loadViewSearchParams: () => loadViewSearchParams,
@@ -13580,10 +13714,10 @@ var view_exports = /* @__PURE__ */ __export({
13580
13714
  }, 1);
13581
13715
  const currentRepositoryContext = createContext();
13582
13716
  const viewSearchParamsConfig = {
13583
- path: parseAsString,
13584
- objectPath: parseAsString,
13585
- zoomPath: parseAsString,
13586
- branch: parseAsString
13717
+ path: parseAsString$1,
13718
+ objectPath: parseAsString$1,
13719
+ zoomPath: parseAsString$1,
13720
+ branch: parseAsString$1
13587
13721
  };
13588
13722
  const viewSerializer = createSerializer$1(viewSearchParamsConfig);
13589
13723
  const loadViewSearchParams = createLoader(viewSearchParamsConfig);
@@ -13642,9 +13776,10 @@ const loader$8 = async ({ request, context }) => {
13642
13776
  return params$1;
13643
13777
  }, viewSearchParams);
13644
13778
  if (shouldRedirect) {
13645
- const redirectUrl = href("/view") + viewSerializer(params);
13779
+ const redirectUrl = new URL(request.url);
13780
+ redirectUrl.search = viewSerializer(params);
13646
13781
  log.warn(`At least one required parameter is missing, redirecting to ${redirectUrl}`);
13647
- throw redirect(redirectUrl);
13782
+ throw redirect(redirectUrl.toString());
13648
13783
  }
13649
13784
  return {
13650
13785
  dataPromise: analyze({
@@ -13656,8 +13791,8 @@ const loader$8 = async ({ request, context }) => {
13656
13791
  versionInfo
13657
13792
  };
13658
13793
  };
13659
- const action$1 = async ({ request, context }) => {
13660
- const { instance } = context.get(currentRepositoryContext);
13794
+ const action = async ({ request, context }) => {
13795
+ const { instance, repositoryPath } = context.get(currentRepositoryContext);
13661
13796
  const formData = await request.formData();
13662
13797
  const refresh = formData.get("refresh");
13663
13798
  const unionedAuthors = formData.get("unionedAuthors");
@@ -13665,11 +13800,36 @@ const action$1 = async ({ request, context }) => {
13665
13800
  const timeseries = formData.get("timeseries");
13666
13801
  const authorname = formData.get("authorname");
13667
13802
  const authorcolor = formData.get("authorcolor");
13803
+ const hidePath = formData.get("hide");
13804
+ const unhidePath = formData.get("show");
13805
+ const unhideAll = formData.get("unhideAll");
13806
+ const openPath = formData.get("open");
13668
13807
  instance.prevInvokeReason = "unknown";
13669
13808
  if (refresh) {
13670
13809
  instance.prevInvokeReason = "refresh";
13671
13810
  return null;
13672
13811
  }
13812
+ if (hidePath && typeof hidePath === "string") {
13813
+ log.info("Ignoring path: " + hidePath);
13814
+ instance.prevInvokeReason = "hide";
13815
+ await instance.db.addHiddenFile(hidePath);
13816
+ return null;
13817
+ }
13818
+ if (unhidePath && typeof unhidePath === "string") {
13819
+ instance.prevInvokeReason = "show";
13820
+ await instance.db.removeHiddenFile(unhidePath);
13821
+ return null;
13822
+ }
13823
+ if (unhideAll && typeof unhideAll === "string") {
13824
+ instance.prevInvokeReason = "hide";
13825
+ await instance.db.clearHiddenFiles();
13826
+ return null;
13827
+ }
13828
+ if (typeof openPath === "string") {
13829
+ instance.prevInvokeReason = "open";
13830
+ openFile(repositoryPath, openPath);
13831
+ return null;
13832
+ }
13673
13833
  if (typeof unionedAuthors === "string") {
13674
13834
  instance.prevInvokeReason = "unionedAuthors";
13675
13835
  const json = JSON.parse(unionedAuthors);
@@ -13795,17 +13955,17 @@ async function analyze({ instance, path: path$1, branch }) {
13795
13955
  }
13796
13956
  var view_default = UNSAFE_withComponentProps(function Repo() {
13797
13957
  const { versionInfo, dataPromise } = useLoaderData();
13798
- const [{ leftExpanded }, dispatch] = useReducer((prevState, action$3) => {
13799
- switch (action$3) {
13958
+ const [{ leftExpanded }, dispatch] = useReducer((prevState, action$2) => {
13959
+ switch (action$2) {
13800
13960
  case "toggleLeft": return { leftExpanded: !prevState.leftExpanded };
13801
13961
  }
13802
13962
  }, { leftExpanded: true });
13803
13963
  const toggleLeft = () => dispatch("toggleLeft");
13804
13964
  const [hoveredObject, setHoveredObject] = useState(null);
13805
- const location$1 = useLocation();
13965
+ const location = useLocation();
13806
13966
  const navigate = useNavigate();
13807
13967
  const [objectPath] = useQueryState("objectPath", viewSearchParamsConfig.objectPath);
13808
- const clearCacheUrl = `/clear-cache?${new URLSearchParams({ redirect: location$1.pathname + location$1.search }).toString()}`;
13968
+ const clearCacheUrl = `/clear-cache?${new URLSearchParams({ redirect: location.pathname + location.search }).toString()}`;
13809
13969
  const objectPathIsFile = objectPath?.split("/").pop()?.includes(".");
13810
13970
  return /* @__PURE__ */ jsx(Suspense, {
13811
13971
  fallback: /* @__PURE__ */ jsx("div", {
@@ -13827,11 +13987,11 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13827
13987
  children: (data) => /* @__PURE__ */ jsxs(Providers, {
13828
13988
  data,
13829
13989
  children: [/* @__PURE__ */ jsxs("div", {
13830
- className: cn(`grid grid-cols-1 transition-all [grid-template-areas:"main"_"left"] lg:h-screen lg:grid-cols-[0_1fr] lg:grid-rows-[1fr] lg:overflow-hidden lg:[grid-template-areas:"left_main"]`, { "gap-2 lg:grid-cols-[var(--spacing-sidepanel)_1fr]": leftExpanded }),
13990
+ className: cn(`grid grid-cols-1 transition-all [grid-template-areas:"main"_"left"] lg:h-screen lg:grid-cols-[0_1fr] lg:grid-rows-[1fr] lg:overflow-hidden lg:[grid-template-areas:"left_main"]`, { "lg:grid-cols-[var(--spacing-sidepanel)_1fr]": leftExpanded }),
13831
13991
  children: [/* @__PURE__ */ jsx(Activity, {
13832
13992
  mode: leftExpanded ? "visible" : "hidden",
13833
13993
  children: /* @__PURE__ */ jsxs("aside", {
13834
- className: clsx$1("*:not-first:m-2 lg:pr-0 lg:transition-transform", leftExpanded ? "overflow-y-auto [grid-area:left]" : "lg:-translate-x-sidepanel"),
13994
+ className: clsx$1("*:not-first:m-2 lg:transition-transform", leftExpanded ? "overflow-y-auto [grid-area:left]" : "lg:-translate-x-sidepanel"),
13835
13995
  children: [
13836
13996
  /* @__PURE__ */ jsxs("div", {
13837
13997
  className: "bg-primary-bg dark:bg-primary-bg-dark sticky top-0 z-10 flex justify-between p-2",
@@ -13881,10 +14041,10 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13881
14041
  ]
13882
14042
  })
13883
14043
  }), /* @__PURE__ */ jsxs("main", {
13884
- className: cn("relative grid h-full min-w-25 grid-rows-[auto_1fr_auto] gap-2 [grid-area:main] lg:transition-transform"),
14044
+ className: cn("relative grid h-full min-h-screen min-w-25 grid-rows-[auto_1fr_auto] gap-2 p-2 [grid-area:main] lg:transition-transform"),
13885
14045
  children: [
13886
14046
  /* @__PURE__ */ jsxs("header", {
13887
- className: "from-primary-bg dark:from-primary-bg-dark to-primary-bg dark:to-primary-bg-dark top-0 right-0 left-0 z-10 grid grid-flow-col items-center justify-between gap-2 bg-linear-to-r via-transparent p-2",
14047
+ className: "grid grid-flow-col items-center justify-between gap-2",
13888
14048
  children: [
13889
14049
  /* @__PURE__ */ jsxs("div", {
13890
14050
  className: "flex gap-2",
@@ -13897,7 +14057,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13897
14057
  path: mdiMenu,
13898
14058
  size: 1
13899
14059
  })
13900
- }) : /* @__PURE__ */ jsx("div", {}),
14060
+ }) : null,
13901
14061
  /* @__PURE__ */ jsx(BrowseParentFolder, {}),
13902
14062
  /* @__PURE__ */ jsx(Breadcrumb, { zoom: true })
13903
14063
  ]
@@ -13906,20 +14066,24 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13906
14066
  /* @__PURE__ */ jsxs("div", {
13907
14067
  className: "flex gap-2",
13908
14068
  children: [
14069
+ /* @__PURE__ */ jsx("div", {
14070
+ className: "relative",
14071
+ children: /* @__PURE__ */ jsx(SearchCard, {})
14072
+ }),
13909
14073
  data.repo.status === "Success" ? /* @__PURE__ */ jsx(RevisionSelect, {
13910
- className: "max-w-3xs",
14074
+ className: "max-w-32",
13911
14075
  title: "Select branch",
13912
14076
  defaultValue: data.databaseInfo.branch,
13913
14077
  headGroups: data.repo.refs,
13914
14078
  analyzedBranches: data.databaseInfo.analyzedRepos.filter((rep) => rep.repo === data.databaseInfo.repo),
13915
- onChange: (e) => navigate(getPathFromRepoAndHead({
14079
+ onChange: (e) => navigate(href("/view") + viewSerializer({
13916
14080
  path: data.repo.repositoryPath,
13917
14081
  branch: e.target.value
13918
14082
  }))
13919
- }, data.databaseInfo.branch) : null,
13920
- /* @__PURE__ */ jsx(SearchCard, {}),
14083
+ }, data.databaseInfo.branch) : /* @__PURE__ */ jsx("div", {}),
13921
14084
  /* @__PURE__ */ jsx(RefreshButton, {}),
13922
- /* @__PURE__ */ jsx(HiddenFiles, {}),
14085
+ /* @__PURE__ */ jsx(HideFilesButton, {}),
14086
+ /* @__PURE__ */ jsx(GroupAuthorsButton, { compact: true }),
13923
14087
  /* @__PURE__ */ jsx(SettingsButton, {}),
13924
14088
  /* @__PURE__ */ jsx(FullscreenButton, {})
13925
14089
  ]
@@ -13943,24 +14107,13 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13943
14107
  }), createPortal(/* @__PURE__ */ jsx(ChartTooltip, { hoveredObject }), document.body)] }) })
13944
14108
  }),
13945
14109
  /* @__PURE__ */ jsxs("div", {
13946
- className: "flex flex-col gap-1 px-2 text-center select-none",
14110
+ className: "flex flex-col text-center select-none",
13947
14111
  children: [/* @__PURE__ */ jsxs("div", {
13948
14112
  className: "flex items-start justify-between gap-2",
13949
14113
  children: [/* @__PURE__ */ jsxs("h2", {
13950
14114
  className: "card__title",
13951
14115
  children: ["Commits per ", data.databaseInfo.commitCountPerTimeIntervalUnit]
13952
- }), /* @__PURE__ */ jsxs(Form, {
13953
- className: cn({ invisible: data.databaseInfo.timerange[0] === data.databaseInfo.selectedRange[0] && data.databaseInfo.timerange[1] === data.databaseInfo.selectedRange[1] }),
13954
- method: "post",
13955
- children: [/* @__PURE__ */ jsx("input", {
13956
- type: "hidden",
13957
- name: "timeseries",
13958
- value: `${data.databaseInfo.timerange[0]}-${data.databaseInfo.timerange[1]}`
13959
- }), /* @__PURE__ */ jsxs("button", {
13960
- className: cn("btn btn--text", {}),
13961
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiRestore }), "Reset time interval"]
13962
- })]
13963
- })]
14116
+ }), /* @__PURE__ */ jsx(ResetTimeIntervalButton, {})]
13964
14117
  }), /* @__PURE__ */ jsx(Timeline, {}, `${data.databaseInfo.selectedRange[0]}-${data.databaseInfo.selectedRange[1]}`)]
13965
14118
  })
13966
14119
  ]
@@ -14139,7 +14292,7 @@ function CommitListEntry(props) {
14139
14292
  }
14140
14293
  function CommitHistory({ commits, commitCount }) {
14141
14294
  const navigation = useNavigation();
14142
- const location$1 = useLocation();
14295
+ const location = useLocation();
14143
14296
  const [searchParams, setSearchParams] = useSearchParams();
14144
14297
  const commitShowCount = Number(searchParams.get("count") ?? String(10));
14145
14298
  const { clickedObject } = useClickedObject();
@@ -14157,13 +14310,13 @@ function CommitHistory({ commits, commitCount }) {
14157
14310
  onClick: () => setSearchParams((prev) => {
14158
14311
  prev.set("count", String(Number(prev.get("count") ?? String(10)) + 10));
14159
14312
  return prev;
14160
- }, { state: location$1.state }),
14313
+ }, { state: location.state }),
14161
14314
  children: "Show more commits"
14162
14315
  }) : null : /* @__PURE__ */ jsx("h3", { children: "Loading commits..." })] }) });
14163
14316
  }
14164
14317
  function RepoTabs() {
14165
14318
  const navigate = useNavigate();
14166
- const location$1 = useLocation();
14319
+ const location = useLocation();
14167
14320
  const isCommits = useMatch(href("/view/commits"));
14168
14321
  return /* @__PURE__ */ jsx(IconRadioGroup, {
14169
14322
  large: true,
@@ -14176,8 +14329,9 @@ function RepoTabs() {
14176
14329
  "/commits": mdiSourceCommit
14177
14330
  },
14178
14331
  defaultValue: isCommits ? "/commits" : "/details",
14332
+ ariaLabel: "Select details tab",
14179
14333
  onChange: (v) => {
14180
- navigate(href(`/view${v}`) + location$1.search, { state: location$1.state });
14334
+ navigate(href(`/view${v}`) + location.search, { state: location.state });
14181
14335
  }
14182
14336
  });
14183
14337
  }
@@ -14262,7 +14416,6 @@ function usePath() {
14262
14416
  }
14263
14417
  var view_details_exports = /* @__PURE__ */ __export({
14264
14418
  HydrateFallback: () => HydrateFallback,
14265
- action: () => action,
14266
14419
  default: () => view_details_default,
14267
14420
  loader: () => loader$1
14268
14421
  }, 1);
@@ -14284,46 +14437,20 @@ const loader$1 = async ({ request, context }) => {
14284
14437
  authorDistributionPromise: instance.db.getAuthorContribsForPath(objectPath)
14285
14438
  };
14286
14439
  };
14287
- const action = async ({ request, context }) => {
14288
- const { instance } = context.get(currentRepositoryContext);
14289
- const formData = await request.formData();
14290
- const ignorePath = formData.get("ignore");
14291
- const unignorePath = formData.get("unignore");
14292
- const openPath = formData.get("open");
14293
- if (ignorePath && typeof ignorePath === "string") {
14294
- instance.prevInvokeReason = "ignore";
14295
- const hidden = await instance.db.getHiddenFiles();
14296
- hidden.push(ignorePath);
14297
- await instance.db.replaceHiddenFiles(hidden);
14298
- return null;
14299
- }
14300
- if (unignorePath && typeof unignorePath === "string") {
14301
- instance.prevInvokeReason = "unignore";
14302
- const hidden = await instance.db.getHiddenFiles();
14303
- await instance.db.replaceHiddenFiles(hidden.filter((path$1) => path$1 !== unignorePath));
14304
- return null;
14305
- }
14306
- if (typeof openPath === "string") {
14307
- instance.prevInvokeReason = "open";
14308
- openFile(instance.repositoryPath, openPath);
14309
- return null;
14310
- }
14311
- return null;
14312
- };
14313
14440
  var view_details_default = UNSAFE_withComponentProps(function Details() {
14314
14441
  const { setPath } = usePath();
14315
14442
  const { path: path$1, authorDistributionPromise } = useLoaderData();
14316
14443
  const data = useData();
14317
14444
  const { state } = useNavigation();
14318
- const location$1 = useLocation();
14319
- const clickedObject = location$1.state?.clickedObject;
14445
+ const location = useLocation();
14446
+ const viewAction = useViewAction();
14447
+ const clickedObject = location.state?.clickedObject;
14320
14448
  const setOpen = useSetOpenCollapsibleHeader();
14321
14449
  const [viewSearchParams] = useQueryStates(viewSearchParamsConfig);
14322
- const zoomLink = location$1.pathname + viewSerializer({
14450
+ const zoomLink = location.pathname + viewSerializer({
14323
14451
  ...viewSearchParams,
14324
14452
  zoomPath: path$1
14325
14453
  });
14326
- const { openModal } = useModal("group-authors");
14327
14454
  useEffect(() => {
14328
14455
  setOpen(!!clickedObject);
14329
14456
  }, [clickedObject, setOpen]);
@@ -14360,18 +14487,14 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14360
14487
  })
14361
14488
  })]
14362
14489
  }),
14363
- /* @__PURE__ */ jsxs("button", {
14364
- className: "btn",
14365
- onClick: () => openModal(),
14366
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
14367
- }),
14490
+ /* @__PURE__ */ jsx(GroupAuthorsButton, {}),
14368
14491
  /* @__PURE__ */ jsxs("div", {
14369
14492
  className: "mt-2 flex flex-wrap gap-2",
14370
14493
  children: [/* @__PURE__ */ jsxs(Link, {
14371
14494
  className: "btn",
14372
14495
  to: zoomLink,
14373
14496
  children: [
14374
- /* @__PURE__ */ jsx(Icon, { path: mdiSearchWeb }),
14497
+ /* @__PURE__ */ jsx(Icon, { path: mdiMagnify }),
14375
14498
  "Zoom to this ",
14376
14499
  isBlob$1 ? "file" : "folder"
14377
14500
  ]
@@ -14380,11 +14503,10 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14380
14503
  method: "post",
14381
14504
  children: [/* @__PURE__ */ jsx("input", {
14382
14505
  type: "hidden",
14383
- name: "ignore",
14506
+ name: "hide",
14384
14507
  value: clickedObject.path
14385
14508
  }), /* @__PURE__ */ jsxs("button", {
14386
14509
  className: "btn",
14387
- type: "submit",
14388
14510
  disabled: state !== "idle",
14389
14511
  title: "Hide this file",
14390
14512
  children: [/* @__PURE__ */ jsx(Icon, { path: mdiEyeOffOutline }), "Hide"]
@@ -14394,25 +14516,23 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14394
14516
  method: "post",
14395
14517
  children: [/* @__PURE__ */ jsx("input", {
14396
14518
  type: "hidden",
14397
- name: "ignore",
14519
+ name: "hide",
14398
14520
  value: `*.${extension}`
14399
14521
  }), /* @__PURE__ */ jsxs("button", {
14400
14522
  className: "btn",
14401
- type: "submit",
14402
14523
  disabled: state !== "idle",
14403
14524
  title: `Hide all files with .${extension} extension`,
14404
14525
  children: [/* @__PURE__ */ jsx(Icon, { path: mdiEyeOffOutline }), /* @__PURE__ */ jsxs("span", { children: ["Hide *.", extension] })]
14405
14526
  })]
14406
14527
  }) : null] }) : /* @__PURE__ */ jsxs(Form, {
14407
14528
  method: "post",
14408
- action: location$1.pathname,
14529
+ action: viewAction,
14409
14530
  children: [/* @__PURE__ */ jsx("input", {
14410
14531
  type: "hidden",
14411
- name: "ignore",
14532
+ name: "hide",
14412
14533
  value: clickedObject.path
14413
14534
  }), /* @__PURE__ */ jsxs("button", {
14414
14535
  className: "btn",
14415
- type: "submit",
14416
14536
  disabled: state !== "idle",
14417
14537
  onClick: () => {
14418
14538
  setPath(resolveParentFolder(path$1));
@@ -14505,6 +14625,7 @@ function LastchangedEntry(props) {
14505
14625
  function PathEntry(props) {
14506
14626
  const { state } = useNavigation();
14507
14627
  const { clickedObject } = useClickedObject();
14628
+ const viewAction = useViewAction();
14508
14629
  if (!clickedObject) return null;
14509
14630
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
14510
14631
  className: "flex grow items-center overflow-hidden text-sm font-semibold text-ellipsis whitespace-pre",
@@ -14517,7 +14638,7 @@ function PathEntry(props) {
14517
14638
  children: props.path
14518
14639
  }), /* @__PURE__ */ jsxs(Form, {
14519
14640
  method: "post",
14520
- action: location.pathname,
14641
+ action: viewAction,
14521
14642
  children: [/* @__PURE__ */ jsx("input", {
14522
14643
  type: "hidden",
14523
14644
  name: "open",
@@ -14984,13 +15105,6 @@ var ui_default = UNSAFE_withComponentProps(function UI() {
14984
15105
  latestVersion: "1.0.1"
14985
15106
  })
14986
15107
  }),
14987
- /* @__PURE__ */ jsxs("div", {
14988
- className: "card",
14989
- children: [/* @__PURE__ */ jsx("h2", {
14990
- className: "card__title",
14991
- children: "AuthorOptions"
14992
- }), /* @__PURE__ */ jsx(AuthorOptions, {})]
14993
- }),
14994
15108
  /* @__PURE__ */ jsxs("div", {
14995
15109
  className: "card",
14996
15110
  children: [
@@ -15028,6 +15142,7 @@ var ui_default = UNSAFE_withComponentProps(function UI() {
15028
15142
  }),
15029
15143
  (() => {
15030
15144
  return /* @__PURE__ */ jsx(IconRadioGroup, {
15145
+ ariaLabel: "Select demo option",
15031
15146
  group: {
15032
15147
  A: "Alpha",
15033
15148
  B: "Beta",
@@ -15045,6 +15160,7 @@ var ui_default = UNSAFE_withComponentProps(function UI() {
15045
15160
  (() => {
15046
15161
  return /* @__PURE__ */ jsx(IconRadioGroup, {
15047
15162
  large: true,
15163
+ ariaLabel: "Select demo option large",
15048
15164
  group: {
15049
15165
  A: "Alpha",
15050
15166
  B: "Beta",
@@ -15111,12 +15227,8 @@ async function loader({ request, params }) {
15111
15227
  }
15112
15228
  var server_manifest_default = {
15113
15229
  "entry": {
15114
- "module": "/assets/entry.client-lU2GATMj.js",
15115
- "imports": [
15116
- "/assets/jsx-runtime-s_YwNCZb.js",
15117
- "/assets/react-dom-BkDQo3BN.js",
15118
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15119
- ],
15230
+ "module": "/assets/entry.client-Ci1gLbyI.js",
15231
+ "imports": ["/assets/jsx-runtime-s_YwNCZb.js", "/assets/react-dom-B9ui1jR3.js"],
15120
15232
  "css": []
15121
15233
  },
15122
15234
  "routes": {
@@ -15133,16 +15245,14 @@ var server_manifest_default = {
15133
15245
  "hasClientMiddleware": false,
15134
15246
  "hasDefaultExport": true,
15135
15247
  "hasErrorBoundary": true,
15136
- "module": "/assets/root-CpxyjKLd.js",
15248
+ "module": "/assets/root-CGqKHKfX.js",
15137
15249
  "imports": [
15138
15250
  "/assets/jsx-runtime-s_YwNCZb.js",
15139
- "/assets/react-dom-BkDQo3BN.js",
15140
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js",
15141
- "/assets/GitTruckInfo-CiYjIacT.js",
15142
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15143
- "/assets/clear-cache-C5k9U711.js"
15251
+ "/assets/react-dom-B9ui1jR3.js",
15252
+ "/assets/dist-Bpk5WHYg.js",
15253
+ "/assets/clear-cache-BJQQzFka.js"
15144
15254
  ],
15145
- "css": ["/assets/root-CaiUVml-.css"],
15255
+ "css": ["/assets/root-BK0u4vgl.css"],
15146
15256
  "clientActionModule": void 0,
15147
15257
  "clientLoaderModule": void 0,
15148
15258
  "clientMiddlewareModule": void 0,
@@ -15182,12 +15292,12 @@ var server_manifest_default = {
15182
15292
  "hasClientMiddleware": false,
15183
15293
  "hasDefaultExport": true,
15184
15294
  "hasErrorBoundary": false,
15185
- "module": "/assets/clear-cache-Xrw5kNtu.js",
15295
+ "module": "/assets/clear-cache-DkN7P69Z.js",
15186
15296
  "imports": [
15187
15297
  "/assets/jsx-runtime-s_YwNCZb.js",
15188
- "/assets/GitTruckInfo-CiYjIacT.js",
15189
- "/assets/react-dom-BkDQo3BN.js",
15190
- "/assets/clear-cache-C5k9U711.js"
15298
+ "/assets/dist-Bpk5WHYg.js",
15299
+ "/assets/react-dom-B9ui1jR3.js",
15300
+ "/assets/clear-cache-BJQQzFka.js"
15191
15301
  ],
15192
15302
  "css": [],
15193
15303
  "clientActionModule": void 0,
@@ -15271,14 +15381,12 @@ var server_manifest_default = {
15271
15381
  "hasClientMiddleware": false,
15272
15382
  "hasDefaultExport": true,
15273
15383
  "hasErrorBoundary": false,
15274
- "module": "/assets/browse-Ce7uXDK8.js",
15384
+ "module": "/assets/browse-Bx9O5Mvl.js",
15275
15385
  "imports": [
15276
15386
  "/assets/jsx-runtime-s_YwNCZb.js",
15277
- "/assets/browse-wwHpvs7Z.js",
15278
- "/assets/GitTruckInfo-CiYjIacT.js",
15279
- "/assets/react-dom-BkDQo3BN.js",
15280
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15281
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15387
+ "/assets/browse-B9wBPRaK.js",
15388
+ "/assets/dist-Bpk5WHYg.js",
15389
+ "/assets/react-dom-B9ui1jR3.js"
15282
15390
  ],
15283
15391
  "css": [],
15284
15392
  "clientActionModule": void 0,
@@ -15320,14 +15428,12 @@ var server_manifest_default = {
15320
15428
  "hasClientMiddleware": false,
15321
15429
  "hasDefaultExport": true,
15322
15430
  "hasErrorBoundary": false,
15323
- "module": "/assets/view-DhREUKDU.js",
15431
+ "module": "/assets/view-mmd0Q4B8.js",
15324
15432
  "imports": [
15325
15433
  "/assets/jsx-runtime-s_YwNCZb.js",
15326
- "/assets/browse-wwHpvs7Z.js",
15327
- "/assets/GitTruckInfo-CiYjIacT.js",
15328
- "/assets/react-dom-BkDQo3BN.js",
15329
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15330
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15434
+ "/assets/browse-B9wBPRaK.js",
15435
+ "/assets/dist-Bpk5WHYg.js",
15436
+ "/assets/react-dom-B9ui1jR3.js"
15331
15437
  ],
15332
15438
  "css": [],
15333
15439
  "clientActionModule": void 0,
@@ -15369,15 +15475,13 @@ var server_manifest_default = {
15369
15475
  "hasClientMiddleware": false,
15370
15476
  "hasDefaultExport": true,
15371
15477
  "hasErrorBoundary": false,
15372
- "module": "/assets/view.commits-D8Z4b-x3.js",
15478
+ "module": "/assets/view.commits-b-k-A7BT.js",
15373
15479
  "imports": [
15374
15480
  "/assets/jsx-runtime-s_YwNCZb.js",
15375
- "/assets/browse-wwHpvs7Z.js",
15376
- "/assets/GitTruckInfo-CiYjIacT.js",
15377
- "/assets/react-dom-BkDQo3BN.js",
15378
- "/assets/RepoTabs-CWt-EVgp.js",
15379
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15380
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15481
+ "/assets/browse-B9wBPRaK.js",
15482
+ "/assets/dist-Bpk5WHYg.js",
15483
+ "/assets/react-dom-B9ui1jR3.js",
15484
+ "/assets/RepoTabs-36qUc-RT.js"
15381
15485
  ],
15382
15486
  "css": [],
15383
15487
  "clientActionModule": void 0,
@@ -15391,22 +15495,20 @@ var server_manifest_default = {
15391
15495
  "path": "details",
15392
15496
  "index": void 0,
15393
15497
  "caseSensitive": void 0,
15394
- "hasAction": true,
15498
+ "hasAction": false,
15395
15499
  "hasLoader": true,
15396
15500
  "hasClientAction": false,
15397
15501
  "hasClientLoader": false,
15398
15502
  "hasClientMiddleware": false,
15399
15503
  "hasDefaultExport": true,
15400
15504
  "hasErrorBoundary": false,
15401
- "module": "/assets/view.details-Bsu_yyiE.js",
15505
+ "module": "/assets/view.details-B7LCPu46.js",
15402
15506
  "imports": [
15403
15507
  "/assets/jsx-runtime-s_YwNCZb.js",
15404
- "/assets/browse-wwHpvs7Z.js",
15405
- "/assets/GitTruckInfo-CiYjIacT.js",
15406
- "/assets/react-dom-BkDQo3BN.js",
15407
- "/assets/RepoTabs-CWt-EVgp.js",
15408
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15409
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15508
+ "/assets/browse-B9wBPRaK.js",
15509
+ "/assets/dist-Bpk5WHYg.js",
15510
+ "/assets/react-dom-B9ui1jR3.js",
15511
+ "/assets/RepoTabs-36qUc-RT.js"
15410
15512
  ],
15411
15513
  "css": [],
15412
15514
  "clientActionModule": void 0,
@@ -15448,14 +15550,12 @@ var server_manifest_default = {
15448
15550
  "hasClientMiddleware": false,
15449
15551
  "hasDefaultExport": true,
15450
15552
  "hasErrorBoundary": false,
15451
- "module": "/assets/ui-EnssW_g_.js",
15553
+ "module": "/assets/ui-Bw635ygf.js",
15452
15554
  "imports": [
15453
15555
  "/assets/jsx-runtime-s_YwNCZb.js",
15454
- "/assets/browse-wwHpvs7Z.js",
15455
- "/assets/GitTruckInfo-CiYjIacT.js",
15456
- "/assets/react-dom-BkDQo3BN.js",
15457
- "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15458
- "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15556
+ "/assets/browse-B9wBPRaK.js",
15557
+ "/assets/dist-Bpk5WHYg.js",
15558
+ "/assets/react-dom-B9ui1jR3.js"
15459
15559
  ],
15460
15560
  "css": [],
15461
15561
  "clientActionModule": void 0,
@@ -15485,8 +15585,8 @@ var server_manifest_default = {
15485
15585
  "hydrateFallbackModule": void 0
15486
15586
  }
15487
15587
  },
15488
- "url": "/assets/manifest-3c232792.js",
15489
- "version": "3c232792",
15588
+ "url": "/assets/manifest-601d0239.js",
15589
+ "version": "601d0239",
15490
15590
  "sri": void 0
15491
15591
  };
15492
15592
  const assetsBuildDirectory = "build/client";