git-truck 3.0.2 → 3.1.1

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 (51) hide show
  1. package/build/client/assets/GitTruckInfo-Bu3InKk0.js +1 -0
  2. package/build/client/assets/RepoTabs-B_gVZfyQ.js +1 -0
  3. package/build/client/assets/browse-BVbfiHLu.js +1 -0
  4. package/build/client/assets/browse-DMoBg6Vh.js +9 -0
  5. package/build/client/assets/chunk-JPUPSTYD-YBbsfvLw.js +1 -0
  6. package/build/client/assets/clear-cache-CAdofhUa.js +1 -0
  7. package/build/client/assets/clear-cache-Ciosxv25.js +1 -0
  8. package/build/client/assets/{compare-Br3z3FUS-OODPzXmX.js → compare-Br3z3FUS-Bm1os1Jk.js} +1 -1
  9. package/build/client/assets/{entry.client-NklqTk8N.js → entry.client-lU2GATMj.js} +2 -2
  10. package/build/client/assets/jsx-runtime-s_YwNCZb.js +26 -0
  11. package/build/client/assets/manifest-bbde1677.js +1 -0
  12. package/build/client/assets/{react-dom-_FKEaPb7.js → react-dom-BkDQo3BN.js} +1 -1
  13. package/build/client/assets/{root-Djh_4EO9.js → root-CMX9SEB-.js} +2 -2
  14. package/build/client/assets/root-DEstG3hL.css +1 -0
  15. package/build/client/assets/ui-C21Ckl3a.js +1 -0
  16. package/build/client/assets/view-JlAiRFiK.js +1 -0
  17. package/build/client/assets/view._index-jmCCN9mA.js +1 -0
  18. package/build/client/assets/view.commits-CIgkE9N1.js +1 -0
  19. package/build/client/assets/view.details-DAXK445Q.js +1 -0
  20. package/build/server/assets/{server-build-D47uxE8F.js → server-build-BqWgrywa.js} +1307 -1356
  21. package/build/server/index.js +1 -1
  22. package/cli.mjs +2 -3
  23. package/package.json +2 -2
  24. package/build/client/assets/FullscreenButton-CFqdBqf-.js +0 -1
  25. package/build/client/assets/GitTruckInfo-C1VOiqSW.js +0 -1
  26. package/build/client/assets/RepoTabs-xQ7LCUn1.js +0 -1
  27. package/build/client/assets/UnionAuthorsModal-D-H775E5.js +0 -1
  28. package/build/client/assets/browse-CPxPbHtx.js +0 -1
  29. package/build/client/assets/browse-CuAoODR1.js +0 -9
  30. package/build/client/assets/chunk-JPUPSTYD-XO4UPun8.js +0 -1
  31. package/build/client/assets/chunk-LFPYN7LY-CPTP6242.js +0 -26
  32. package/build/client/assets/clear-cache-82hexQD7.js +0 -1
  33. package/build/client/assets/clear-cache-CrL0weu5.js +0 -1
  34. package/build/client/assets/jsx-runtime-BVRj4wYA.js +0 -1
  35. package/build/client/assets/manifest-b5d81016.js +0 -1
  36. package/build/client/assets/root-qwQAVU-u.css +0 -1
  37. package/build/client/assets/styling-B8OEge8x.js +0 -1
  38. package/build/client/assets/ui-nIuMvo3S.js +0 -1
  39. package/build/client/assets/util-1G1Ez1rt.js +0 -1
  40. package/build/client/assets/view-ESMBoxIY.js +0 -1
  41. package/build/client/assets/view._index-CjsEuBV6.js +0 -1
  42. package/build/client/assets/view.commits-DP_ltd6_.js +0 -1
  43. package/build/client/assets/view.details-C6kuGyhb.js +0 -1
  44. package/build/client/assets/view.merge-authors-VwPHWL-d.js +0 -1
  45. /package/build/client/assets/{_-RagYKtT_.js → _-CT17qCl3.js} +0 -0
  46. /package/build/client/assets/{_._well-known._-DIwl00oI.js → _._well-known._-8QUD84tm.js} +0 -0
  47. /package/build/client/assets/{_index-J0jvF8An.js → _index-VLoUE117.js} +0 -0
  48. /package/build/client/assets/{_path-bRAhzWvL.js → _path-DzSNgPJP.js} +0 -0
  49. /package/build/client/assets/{commitcount-eFguKtSp.js → commitcount-B_3CeIvr.js} +0 -0
  50. /package/build/client/assets/{commits-DPfZp-l3.js → commits-DDIAHyHU.js} +0 -0
  51. /package/build/client/assets/{view.progress-B7Os3EHt.js → view.progress-CEmcNaLe.js} +0 -0
@@ -1,12 +1,12 @@
1
1
  import { PassThrough } from "stream";
2
2
  import { createReadableStreamFromReadable } from "@react-router/node";
3
- import { Await, Form, Link, Links, Meta, Outlet, Scripts, ScrollRestoration, ServerRouter, UNSAFE_withComponentProps, UNSAFE_withErrorBoundaryProps, UNSAFE_withHydrateFallbackProps, createContext, href, redirect, useFetcher, useLoaderData, useLocation, useMatch, useNavigate, useNavigation, useOutletContext, useRouteError, useRouteLoaderData, useSearchParams, useSubmit } from "react-router";
3
+ import { Await, Form, Link, Links, Meta, Outlet, Scripts, ScrollRestoration, ServerRouter, UNSAFE_withComponentProps, UNSAFE_withErrorBoundaryProps, UNSAFE_withHydrateFallbackProps, createContext, href, redirect, useFetcher, useLoaderData, useLocation, useMatch, useNavigate, useNavigation, useRouteError, useRouteLoaderData, useSearchParams, useSubmit } from "react-router";
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
7
  import * as React$1 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, 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, 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";
10
10
  import clsx$1, { clsx } from "clsx";
11
11
  import { ArrowContainer, Popover } from "react-tiny-popover";
12
12
  import { HexColorPicker } from "react-colorful";
@@ -33,12 +33,12 @@ import { readFile, readdir } from "node:fs/promises";
33
33
  import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
34
34
  import randomstring from "randomstring";
35
35
  import { createPortal } from "react-dom";
36
- import { createSerializer, parseAsBoolean, parseAsInteger, parseAsNumberLiteral, useQueryState, useQueryStates } from "nuqs";
37
- import { createLoader, createSerializer as createSerializer$1, parseAsInteger as parseAsInteger$1, parseAsString, parseAsStringLiteral } from "nuqs/server";
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";
38
38
  import { hierarchy, pack, partition, treemap, treemapResquarify } from "d3-hierarchy";
39
39
  import ignore from "ignore";
40
- import { Handles, Rail, Slider, Ticks, Tracks } from "react-compound-slider";
41
40
  import { Field, Label, Radio, RadioGroup } from "@headlessui/react";
41
+ import { Handles, Rail, Slider, Ticks, Tracks } from "react-compound-slider";
42
42
  import DatePicker from "react-datepicker";
43
43
  import byteSize from "byte-size";
44
44
  var __defProp = Object.defineProperty;
@@ -180,7 +180,7 @@ function handleBrowserRequest(request, responseStatusCode, responseHeaders, reac
180
180
  });
181
181
  }
182
182
  const name = "git-truck";
183
- const version = "3.0.2";
183
+ const version = "3.1.1";
184
184
  const description = "Visualizing a Git repository";
185
185
  const main = "./cli.mjs";
186
186
  const bin = "./cli.mjs";
@@ -281,7 +281,7 @@ const devDependencies = {
281
281
  "eslint-plugin-react-hooks": "^7.0.1",
282
282
  "express": "^5.2.1",
283
283
  "get-port": "^7.1.0",
284
- "globals": "^16.5.0",
284
+ "globals": "^17.3.0",
285
285
  "husky": "^9.1.7",
286
286
  "isbot": "^5.1.35",
287
287
  "knip": "^5.83.1",
@@ -375,7 +375,6 @@ function Icon({ path: path$1, size = "1rem", color = "currentColor", style = {},
375
375
  children: /* @__PURE__ */ jsx("path", { d: path$1 })
376
376
  });
377
377
  }
378
- var truck_default = "/assets/truck-BgAoc4Gr.gif";
379
378
  function Popover$1({ popoverTitle, positions = [
380
379
  "top",
381
380
  "bottom",
@@ -729,11 +728,6 @@ function expandIntervalToRange(timestamp, commitCountPerTimeIntervalUnit) {
729
728
  }
730
729
  }
731
730
  const getSep = (path$1) => path$1.includes("\\") ? "\\" : "/";
732
- const comparePaths = (a, b) => {
733
- const sepA = getSep(a);
734
- const sepB = getSep(b);
735
- return a.split(sepA).join("/") === b.split(sepB).join("/");
736
- };
737
731
  function iconToURL(icon) {
738
732
  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>")}")`;
739
733
  }
@@ -745,6 +739,33 @@ function normalizePath(p) {
745
739
  if (p.split("/").length > 2) return trimmed;
746
740
  return p;
747
741
  }
742
+ function findSubTree(tree, path$1) {
743
+ if (!path$1) return tree;
744
+ if (!(path$1 === tree.name || path$1.startsWith(`${tree.name}/`))) return tree;
745
+ let relativePath = path$1.length === tree.name.length ? "" : path$1.substring(tree.name.length + 1);
746
+ if (!relativePath) return tree;
747
+ relativePath = normalizePath(relativePath);
748
+ let currentTree = tree;
749
+ const separator = getSep(relativePath);
750
+ const steps = relativePath.split(separator);
751
+ for (let i = 0; i < steps.length; i++) {
752
+ let foundStep = false;
753
+ for (const child of currentTree.children) if (child.type === "tree") {
754
+ const childSteps = child.name.split(separator);
755
+ if (childSteps[0] === steps[i]) {
756
+ currentTree = child;
757
+ i += childSteps.length - 1;
758
+ foundStep = true;
759
+ break;
760
+ }
761
+ }
762
+ if (!foundStep) {
763
+ console.warn(`Could not find step ${steps[i]} in subtree ${currentTree.name}`);
764
+ return tree;
765
+ }
766
+ }
767
+ return currentTree;
768
+ }
748
769
  function useComponentSize$1() {
749
770
  const [size, setSize] = React$1.useState({
750
771
  height: 0,
@@ -8746,12 +8767,12 @@ var defaultOptions = {
8746
8767
  };
8747
8768
  const getDefaultOptionsContextValue = () => defaultOptions;
8748
8769
  const CloseButton = ({ className = "", absolute = true, ...props }) => /* @__PURE__ */ jsx("button", {
8749
- className: clsx$1(className, "btn btn--text inline-grid bg-transparent text-lg leading-none hover:opacity-80", { "absolute top-2 right-2 z-10": absolute }),
8770
+ className: clsx$1(className, "btn btn--text inline-grid bg-transparent text-lg leading-none hover:text-blue-500", { "absolute top-2 right-2 z-10": absolute }),
8750
8771
  title: "Close",
8751
8772
  ...props,
8752
8773
  children: /* @__PURE__ */ jsx(Icon, {
8753
8774
  path: mdiClose,
8754
- size: 1
8775
+ size: 1.25
8755
8776
  })
8756
8777
  });
8757
8778
  const LegendDot = ({ className = "", dotColor, authorColorToChange = void 0 }) => {
@@ -8821,34 +8842,6 @@ const Code = ({ inline = false, className = "", ...props }) => /* @__PURE__ */ j
8821
8842
  className: cn("primary rounded border px-2 py-1 font-mono text-sm", inline ? "inline-block" : "block", { "select-all": inline }, className),
8822
8843
  ...props
8823
8844
  });
8824
- function CheckboxWithLabel({ children, checked, onChange, className = "", checkedIcon = mdiCheckboxOutline, uncheckedIcon = mdiCheckboxBlankOutline, ...props }) {
8825
- const [isTransitioning, startTransition$1] = useTransition();
8826
- return /* @__PURE__ */ jsxs("label", {
8827
- className: `label flex w-full items-center justify-start gap-2 ${className}`,
8828
- ...props,
8829
- children: [
8830
- /* @__PURE__ */ jsxs("span", {
8831
- className: "flex grow items-center gap-2",
8832
- children: [children, isTransitioning ? /* @__PURE__ */ jsx("img", {
8833
- src: truck_default,
8834
- alt: "...",
8835
- className: "h-5"
8836
- }) : ""]
8837
- }),
8838
- /* @__PURE__ */ jsx(Icon, {
8839
- className: "place-self-end",
8840
- path: checked ? checkedIcon : uncheckedIcon,
8841
- size: 1
8842
- }),
8843
- /* @__PURE__ */ jsx("input", {
8844
- type: "checkbox",
8845
- defaultChecked: checked,
8846
- className: "hidden",
8847
- onChange: (e) => startTransition$1(() => onChange(e))
8848
- })
8849
- ]
8850
- });
8851
- }
8852
8845
  const LegendBarIndicator = ({ visible, offset }) => {
8853
8846
  return /* @__PURE__ */ jsx("div", {
8854
8847
  className: clsx$1("absolute top-1/2 w-min -translate-x-1/2 -translate-y-1/2 transition-all", { "opacity-0": !visible }),
@@ -8864,18 +8857,6 @@ function ClientOnly({ children, fallback = null }) {
8864
8857
  if (!useIsClient()) return fallback;
8865
8858
  return children();
8866
8859
  }
8867
- function FullscreenButton() {
8868
- const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
8869
- return /* @__PURE__ */ jsx("button", {
8870
- className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
8871
- title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
8872
- onClick: toggleFullscreen,
8873
- children: /* @__PURE__ */ jsx(Icon, {
8874
- path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
8875
- size: 1
8876
- })
8877
- });
8878
- }
8879
8860
  var DB = class DB {
8880
8861
  connectionPromise;
8881
8862
  repoSanitized;
@@ -10332,7 +10313,8 @@ var InstanceManager = class {
10332
10313
  this.instances = /* @__PURE__ */ new Map();
10333
10314
  }
10334
10315
  };
10335
- var truck_default$1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAHuElEQVR4Xu2dsW8cVRCHL30sBEoqQKK1EI3/gasc6oBEQ41k5ILO3VV2eugiRRE9Tf6Ao3FL4QbRWCJCMjVVJEqj5CwB0nt3nt/N7s7sfCmdN2/nfb/5vOuN7TxY8AcChQk8KHx2jg6BBQIwBKUJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4ObybAJeXl7fgjEdguVy6ZRzvdPt35AYHAfYPY4gdEGA7VQQYYuoC7YkACBBoHMdvBQEQYPypC3RFBECAQOM4fisIMLEAR0dH46de8IpXV1fNU/cE6L20qCbM4F8EI8A4NiKAxhkBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqqwC3Nz8bfrerdevfzG9ZQoHqNMQAmRJakefCKAFiQAat3BVCKBFggAat3BVCKBFggAat3BVCKBFggAat3BVCKBF4ibAarVqvlU4OzvTOqPKRGBoAXrNZH87hACmMYu7GAG0bBBA4xauCgG0SBBA4xauCgG0SBBA4xauCgG0SBBA4xauCgG0SNwE6P2EET8QowVjrbIK8NXzP03fC2Ttp7f+p5OP3GbOoye3ZhDAIw59DwTQ2CGAxi1cFQJokSCAxi1cFQJokSCAxi1cFQJokSCAxi1cFQJokSCAxi1clZcAv/5wbDrbZ9+tm+t7+/TWT/V2CAFMccddjABaNgigcQtXhQBaJAigcQtXhQBaJAigcQtXhQBaJAigcQtXhQBaJAigcQtXlUWAHrip3g4hQLhR1hpCAI0bAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlVlF2Cqt0MIEG6UtYYQQOOGABq3cFUIoEWCABq3cFUIoEWCABq3cFUIoEWCABq3cFUIoEXiJgC/HVoLwKsKATSSCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokbgLwy3G1ALyqvAT46+a3ZksffPypV6vNfbyua/39QggwaKzjbY4AG9YIMN7MhboSAiBAqIEcuxkEQICxZy7U9RAAAUIN5NjNIAACjD1zoa5XTYDTw9+b/JfLpenFjmnxtsR5DTqtDwiw4Y8A087hZFdHAASYbPgiXBgBECDCHE7WAwIgwGTDF+HCCBBUgAjDUbmHi4uL5vHf//LH5set35PTW99j3vueIut1X379XvMSBwcHphc7psXKW6DKwxfh7AiwPQUEiDClA/aAAAgw4HjF3xoBECD+lA7YIQIgwIDjFX9rBBhJgNPT01vLOFxfX1uWm9eu1+3/wby30fGx7X9I7+3z4uFDc68eBd+8eWPaxvoWqLf5z88+b/7V4eFh8+MffvG9qU/rT6JN9gMxCLDJFQE2HBBgh+fcAUyfCHcu5g6wQcQd4G5UeATa7gyPQBs+bv8OwCMQj0D/VY5HIB6Bdj62eC7gEWjiR6BemLeLRfPt0JPOW5f1em29KzX3//bkpNnSo8ePmx8/Pz/vHcHaT3OfP54+Nb0ls8rxyatXLn1uuW4KzlZuQ0N7O/0IsFgsEGAzmkN/okGAOwLcAayjsHM9d4CdiBoLuANsoHAH4A7wPz34GkD5dNKv4WsAjSdfA9xxG/rZlDtA0TuA5qWpqvlsulqtTJsMLYCpmZiLZ8l58DvACFnOMpgRuFkvMUvOCDDSI5B12gKuR4CAobxtaZbBBGQ9S87cAbgD3Nc1BLgvqZHXzTKYkRne53Kz5DyHO0AvPOv33syZxX0GXF2TmvOcQ08djDqNE9Sl5owA/07MnFkM6QUCDEl3j71TB7PHuccuTc15zp/1Ugcz9hTvcb3UnBGAR6A9Zv9dKQLsS5B6CExFYM53gKmYct1EBBAgUVi06k8AAfyZsmMiAgiQKCxa9SeAAP5M2TERAQRIFBat+hNAAH+m7JiIAAIkCotW/QkggD9TdkxEAAEShUWr/gQQwJ8pOyYigACJwqJVfwII4M+UHRMRQIBEYdGqPwEE8GfKjokIIECisGjVnwAC+DNlx0QEECBRWLTqTwAB/JmyYyICCJAoLFr1J4AA/kzZMREBBEgUFq36E0AAf6bsmIgAAiQKi1b9CSCAP1N2TEQAARKFRav+BBDAnyk7JiKAAInColV/Agjgz5QdExFAgERh0ao/AQTwZ8qOiQggQKKwaNWfAAL4M2XHRAQQIFFYtOpPAAH8mbJjIgIIkCgsWvUngAD+TNkxEQEESBQWrfoTQAB/puyYiAACJAqLVv0JIIA/U3ZMRAABEoVFq/4EEMCfKTsmIoAAicKiVX8CCODPlB0TEUCARGHRqj8BBPBnyo6JCPwDAEEjDFGnv/IAAAAASUVORK5CYIIA";
10316
+ var truck_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAHuElEQVR4Xu2dsW8cVRCHL30sBEoqQKK1EI3/gasc6oBEQ41k5ILO3VV2eugiRRE9Tf6Ao3FL4QbRWCJCMjVVJEqj5CwB0nt3nt/N7s7sfCmdN2/nfb/5vOuN7TxY8AcChQk8KHx2jg6BBQIwBKUJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4OTwCMAOlCSBA6fg5PAIwA6UJIEDp+Dk8AjADpQkgQOn4ObybAJeXl7fgjEdguVy6ZRzvdPt35AYHAfYPY4gdEGA7VQQYYuoC7YkACBBoHMdvBQEQYPypC3RFBECAQOM4fisIMLEAR0dH46de8IpXV1fNU/cE6L20qCbM4F8EI8A4NiKAxhkBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqhBAiwQBNG7hqqwC3Nz8bfrerdevfzG9ZQoHqNMQAmRJakefCKAFiQAat3BVCKBFggAat3BVCKBFggAat3BVCKBFggAat3BVCKBF4ibAarVqvlU4OzvTOqPKRGBoAXrNZH87hACmMYu7GAG0bBBA4xauCgG0SBBA4xauCgG0SBBA4xauCgG0SBBA4xauCgG0SNwE6P2EET8QowVjrbIK8NXzP03fC2Ttp7f+p5OP3GbOoye3ZhDAIw59DwTQ2CGAxi1cFQJokSCAxi1cFQJokSCAxi1cFQJokSCAxi1cFQJokSCAxi1clZcAv/5wbDrbZ9+tm+t7+/TWT/V2CAFMccddjABaNgigcQtXhQBaJAigcQtXhQBaJAigcQtXhQBaJAigcQtXhQBaJAigcQtXlUWAHrip3g4hQLhR1hpCAI0bAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlUhgBYJAmjcwlVlF2Cqt0MIEG6UtYYQQOOGABq3cFUIoEWCABq3cFUIoEWCABq3cFUIoEWCABq3cFUIoEXiJgC/HVoLwKsKATSSCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokCKBxC1eFAFokbgLwy3G1ALyqvAT46+a3ZksffPypV6vNfbyua/39QggwaKzjbY4AG9YIMN7MhboSAiBAqIEcuxkEQICxZy7U9RAAAUIN5NjNIAACjD1zoa5XTYDTw9+b/JfLpenFjmnxtsR5DTqtDwiw4Y8A087hZFdHAASYbPgiXBgBECDCHE7WAwIgwGTDF+HCCBBUgAjDUbmHi4uL5vHf//LH5set35PTW99j3vueIut1X379XvMSBwcHphc7psXKW6DKwxfh7AiwPQUEiDClA/aAAAgw4HjF3xoBECD+lA7YIQIgwIDjFX9rBBhJgNPT01vLOFxfX1uWm9eu1+3/wby30fGx7X9I7+3z4uFDc68eBd+8eWPaxvoWqLf5z88+b/7V4eFh8+MffvG9qU/rT6JN9gMxCLDJFQE2HBBgh+fcAUyfCHcu5g6wQcQd4G5UeATa7gyPQBs+bv8OwCMQj0D/VY5HIB6Bdj62eC7gEWjiR6BemLeLRfPt0JPOW5f1em29KzX3//bkpNnSo8ePmx8/Pz/vHcHaT3OfP54+Nb0ls8rxyatXLn1uuW4KzlZuQ0N7O/0IsFgsEGAzmkN/okGAOwLcAayjsHM9d4CdiBoLuANsoHAH4A7wPz34GkD5dNKv4WsAjSdfA9xxG/rZlDtA0TuA5qWpqvlsulqtTJsMLYCpmZiLZ8l58DvACFnOMpgRuFkvMUvOCDDSI5B12gKuR4CAobxtaZbBBGQ9S87cAbgD3Nc1BLgvqZHXzTKYkRne53Kz5DyHO0AvPOv33syZxX0GXF2TmvOcQ08djDqNE9Sl5owA/07MnFkM6QUCDEl3j71TB7PHuccuTc15zp/1Ugcz9hTvcb3UnBGAR6A9Zv9dKQLsS5B6CExFYM53gKmYct1EBBAgUVi06k8AAfyZsmMiAgiQKCxa9SeAAP5M2TERAQRIFBat+hNAAH+m7JiIAAIkCotW/QkggD9TdkxEAAEShUWr/gQQwJ8pOyYigACJwqJVfwII4M+UHRMRQIBEYdGqPwEE8GfKjokIIECisGjVnwAC+DNlx0QEECBRWLTqTwAB/JmyYyICCJAoLFr1J4AA/kzZMREBBEgUFq36E0AAf6bsmIgAAiQKi1b9CSCAP1N2TEQAARKFRav+BBDAnyk7JiKAAInColV/Agjgz5QdExFAgERh0ao/AQTwZ8qOiQggQKKwaNWfAAL4M2XHRAQQIFFYtOpPAAH8mbJjIgIIkCgsWvUngAD+TNkxEQEESBQWrfoTQAB/puyYiAACJAqLVv0JIIA/U3ZMRAABEoVFq/4EEMCfKTsmIoAAicKiVX8CCODPlB0TEUCARGHRqj8BBPBnyo6JCPwDAEEjDFGnv/IAAAAASUVORK5CYIIA";
10317
+ var truck_default$1 = "/assets/truck-BgAoc4Gr.gif";
10336
10318
  function UpdateNotifier({ installedVersion, latestVersion: latestVersion$1 }) {
10337
10319
  const isExperimental = installedVersion.includes("0.0.0");
10338
10320
  return /* @__PURE__ */ jsxs(Popover$1, {
@@ -10390,7 +10372,7 @@ function GitTruckInfo({ className = "", installedVersion, latestVersion: latestV
10390
10372
  installedVersion,
10391
10373
  latestVersion: latestVersion$1
10392
10374
  }) : /* @__PURE__ */ jsx("img", {
10393
- src: loading ? truck_default : truck_default$1,
10375
+ src: loading ? truck_default$1 : truck_default,
10394
10376
  alt: "Git Truck Logo",
10395
10377
  className: "tertiary size-10 rounded-full border p-1"
10396
10378
  }), /* @__PURE__ */ jsxs("div", {
@@ -10498,7 +10480,7 @@ function ErrorPage({ className, message, noTruck = false, children }) {
10498
10480
  className: "flex max-w-2xl flex-col items-center gap-3 text-center",
10499
10481
  children: [
10500
10482
  !noTruck ? /* @__PURE__ */ jsx("img", {
10501
- src: truck_default$1,
10483
+ src: truck_default,
10502
10484
  alt: "Git Truck",
10503
10485
  className: "w-full max-w-sm min-w-0"
10504
10486
  }) : null,
@@ -10582,19 +10564,6 @@ const ErrorBoundary = UNSAFE_withErrorBoundaryProps(() => {
10582
10564
  })
10583
10565
  }) : null] });
10584
10566
  });
10585
- var FullscreenButton_exports = /* @__PURE__ */ __export({ FullscreenButton: () => FullscreenButton$1 }, 1);
10586
- function FullscreenButton$1() {
10587
- const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
10588
- return /* @__PURE__ */ jsx("button", {
10589
- className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
10590
- title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
10591
- onClick: toggleFullscreen,
10592
- children: /* @__PURE__ */ jsx(Icon, {
10593
- path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
10594
- size: 1
10595
- })
10596
- });
10597
- }
10598
10567
  var ____well_known_$_exports = /* @__PURE__ */ __export({ loader: () => loader$10 }, 1);
10599
10568
  function loader$10() {
10600
10569
  return "";
@@ -10778,7 +10747,7 @@ const browseSearchParamsConfig = {
10778
10747
  path: parseAsString.withOptions({ shallow: false }),
10779
10748
  count: parseAsNumberLiteral(COUNT_OPTIONS).withOptions({ shallow: false }).withDefault(DEFAULT_COUNT),
10780
10749
  offset: parseAsInteger$1.withOptions({ shallow: false }).withDefault(DEFAULT_OFFSET),
10781
- sort: parseAsStringLiteral([
10750
+ sort: parseAsStringLiteral$1([
10782
10751
  "most-recent",
10783
10752
  "least-recent",
10784
10753
  "asc",
@@ -11314,9 +11283,9 @@ function SearchField({ searchQuery, includeDirs, onSearch, ref }) {
11314
11283
  }
11315
11284
  function Breadcrumb({ className = "", zoom = false }) {
11316
11285
  const [browseParams] = useQueryStates(browseSearchParamsConfig);
11317
- const [{ path: path$1, zoomPath }, setSearchParams] = useQueryStates(viewSearchParamsConfig);
11286
+ const [viewParams, setViewParams] = useQueryStates(viewSearchParamsConfig);
11287
+ const { path: path$1, zoomPath } = viewParams;
11318
11288
  const data = useDataNullable();
11319
- const navigate = useNavigate();
11320
11289
  const breadcrumbSegments = useMemo(() => {
11321
11290
  if (!path$1) return [];
11322
11291
  const pathSegments = path$1.split(getSep(path$1)).map((segment, i, segments$1) => {
@@ -11325,28 +11294,29 @@ function Breadcrumb({ className = "", zoom = false }) {
11325
11294
  type: "browse",
11326
11295
  segment: segment.length === 0 && i === 0 ? "/" : segment,
11327
11296
  fullPath: i === 0 ? fullPath + getSep(path$1) : fullPath,
11328
- isRepo: i === segments$1.length - 1
11297
+ showAnalysisInfo: false
11329
11298
  };
11330
11299
  }).filter((segment) => segment.segment.length > 0);
11331
- const zoomSegments = [
11300
+ const zoomSegments = !data ? [] : [
11332
11301
  {
11333
11302
  type: "browse",
11334
- segment: data?.repo.parentDirName ?? "",
11335
- fullPath: data?.repo.parentDirPath ?? "",
11336
- isRepo: false
11303
+ segment: data.repo.parentDirName ?? "",
11304
+ fullPath: data.repo.parentDirPath ?? "",
11305
+ showAnalysisInfo: false
11337
11306
  },
11338
11307
  {
11339
- type: "browse",
11340
- segment: data?.repo.repositoryName ?? "",
11341
- fullPath: data?.repo.repositoryPath ?? "",
11342
- isRepo: true
11308
+ type: "zoom",
11309
+ segment: data.repo.repositoryName ?? "",
11310
+ fullPath: data.repo.repositoryName ?? "",
11311
+ showAnalysisInfo: true
11343
11312
  },
11344
- ...(zoomPath?.split(getSep(zoomPath)) ?? []).slice(1).map((segment, index, segments$1) => {
11313
+ ...(zoomPath?.split(getSep(zoomPath)) ?? []).flatMap((segment, index, segments$1) => {
11314
+ if (segment === data?.repo.repositoryName) return [];
11345
11315
  return {
11346
11316
  type: "zoom",
11347
11317
  segment,
11348
11318
  fullPath: segments$1.slice(0, index + 1).join("/"),
11349
- isRepo: index === 0
11319
+ showAnalysisInfo: false
11350
11320
  };
11351
11321
  })
11352
11322
  ];
@@ -11358,52 +11328,49 @@ function Breadcrumb({ className = "", zoom = false }) {
11358
11328
  type: "filler",
11359
11329
  segment: "...",
11360
11330
  fullPath: "",
11361
- isRepo: false
11331
+ showAnalysisInfo: false
11362
11332
  },
11363
11333
  segments[segments.length - 2],
11364
11334
  segments[segments.length - 1]
11365
11335
  ];
11366
11336
  return segments;
11367
11337
  }, [
11368
- data?.repo.parentDirName,
11369
- data?.repo.parentDirPath,
11370
- data?.repo.repositoryName,
11371
- data?.repo.repositoryPath,
11338
+ data,
11372
11339
  path$1,
11373
11340
  zoom,
11374
11341
  zoomPath
11375
11342
  ]);
11376
11343
  return /* @__PURE__ */ jsx("div", {
11377
11344
  className: cn("text-secondary-text dark:text-secondary-text-dark flex items-center gap-1 overflow-x-auto", className),
11378
- children: breadcrumbSegments.map(({ type: type$1, segment, fullPath }, i) => {
11379
- const isRepo = data && comparePaths(fullPath, data.repo.repositoryPath);
11345
+ children: breadcrumbSegments.map(({ type: type$1, segment, fullPath, showAnalysisInfo: isRepo }, i) => {
11380
11346
  const title = isRepo ? "Reset zoom to repository root" : type$1 === "browse" ? `Browse ${segment} directory` : `Zoom to ${segment} directory`;
11381
11347
  const isFirst = i === 0;
11382
11348
  const isLast = breadcrumbSegments.length - 1 === i;
11383
- const onClick = () => {
11384
- if (type$1 === "browse") {
11385
- navigate(href("/browse") + browseSerializer({
11386
- ...browseParams,
11387
- path: fullPath,
11388
- search: null
11389
- }));
11390
- return;
11391
- }
11392
- if (!data) throw Error("Attempting to access data when none is loaded");
11393
- setSearchParams((prev) => ({
11394
- ...prev,
11395
- zoomPath: fullPath
11396
- }));
11397
- };
11398
11349
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [isRepo ? /* @__PURE__ */ jsx(Icon, { path: mdiSourceRepository }) : null, segment] });
11399
11350
  const button = isLast || type$1 === "filler" ? /* @__PURE__ */ jsx("span", {
11400
11351
  className: "flex items-center gap-1 truncate font-bold",
11401
11352
  title: fullPath,
11402
11353
  children: content
11354
+ }) : type$1 === "browse" ? /* @__PURE__ */ jsx(Link, {
11355
+ to: href("/browse") + browseSerializer({
11356
+ ...browseParams,
11357
+ offset: 0,
11358
+ search: null,
11359
+ path: fullPath
11360
+ }),
11361
+ title,
11362
+ className: "btn btn--primary truncate",
11363
+ children: content
11403
11364
  }) : /* @__PURE__ */ jsx("button", {
11404
11365
  title,
11405
11366
  className: "btn btn--primary truncate",
11406
- onClick,
11367
+ onClick: () => {
11368
+ if (!data) throw Error("Attempting to access data when none is loaded");
11369
+ setViewParams((prev) => ({
11370
+ ...prev,
11371
+ zoomPath: fullPath
11372
+ }));
11373
+ },
11407
11374
  children: content
11408
11375
  });
11409
11376
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [!isFirst ? /* @__PURE__ */ jsx(Icon, { path: mdiChevronRight }) : null, isRepo ? /* @__PURE__ */ jsx(AnalysisInfo, { trigger: button }) : button] }, fullPath);
@@ -11502,8 +11469,7 @@ const Chart = memo(function Chart$2({ hoveredObject, setHoveredObject }) {
11502
11469
  size,
11503
11470
  chartType,
11504
11471
  sizeMetricType: sizeMetric,
11505
- renderCutoff,
11506
- zoomPath: zoomPath ?? void 0
11472
+ renderCutoff
11507
11473
  }).descendants();
11508
11474
  if (process.env.NODE_ENV === "development") console.timeEnd("Create and pack hiearchy");
11509
11475
  return res;
@@ -11511,7 +11477,6 @@ const Chart = memo(function Chart$2({ hoveredObject, setHoveredObject }) {
11511
11477
  size,
11512
11478
  chartType,
11513
11479
  sizeMetric,
11514
- zoomPath,
11515
11480
  renderCutoff,
11516
11481
  databaseInfo,
11517
11482
  filetree
@@ -11798,20 +11763,8 @@ function NodeText({ d, isSearchMatch, children = null }) {
11798
11763
  function isCircularNode(d) {
11799
11764
  return typeof d.r === "number";
11800
11765
  }
11801
- function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMetricType, renderCutoff, zoomPath: objectPath }) {
11802
- let currentTree = tree;
11803
- if (objectPath) {
11804
- const steps = objectPath.substring(tree.name.length + 1).split("/");
11805
- for (let i = 0; i < steps.length; i++) for (const child of currentTree.children) if (child.type === "tree") {
11806
- const childSteps = child.name.split("/");
11807
- if (childSteps[0] === steps[i]) {
11808
- currentTree = child;
11809
- i += childSteps.length - 1;
11810
- break;
11811
- }
11812
- }
11813
- }
11814
- const hiearchy = hierarchy(currentTree).sum((d) => {
11766
+ function createPartitionedHiearchy({ databaseInfo, tree, size, chartType, sizeMetricType, renderCutoff }) {
11767
+ const hiearchy = hierarchy(tree).sum((d) => {
11815
11768
  const blob = d;
11816
11769
  switch (sizeMetricType) {
11817
11770
  case "FILE_SIZE": return blob.sizeInBytes ?? 1;
@@ -11911,415 +11864,283 @@ const HiddenFiles = memo(function HiddenFiles$1() {
11911
11864
  })
11912
11865
  });
11913
11866
  });
11914
- function AuthorOptions({ showUnionAuthorsModal }) {
11915
- const transitionState = useNavigation();
11916
- const action$3 = href("/view");
11917
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
11918
- className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
11919
- children: [/* @__PURE__ */ jsxs("button", {
11920
- className: "btn",
11921
- onClick: showUnionAuthorsModal,
11922
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
11923
- }), /* @__PURE__ */ jsxs(Form, {
11924
- method: "post",
11925
- action: action$3,
11926
- children: [/* @__PURE__ */ jsx("input", {
11927
- type: "hidden",
11928
- name: "rerollColors",
11929
- value: ""
11930
- }), /* @__PURE__ */ jsxs("button", {
11931
- className: "btn w-full",
11932
- type: "submit",
11933
- disabled: transitionState.state !== "idle",
11934
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
11935
- })]
11936
- })]
11937
- }) });
11938
- }
11939
- function GradientLegend({ hoveredObject }) {
11940
- const { metricType } = useOptions();
11941
- const [metricsData] = useMetrics();
11942
- const metricCache = metricsData.get(metricType);
11943
- if (metricCache === void 0) throw new Error("Metric cache is undefined");
11944
- const { minValue, maxValue, minColor, maxColor } = metricCache.legend;
11945
- const { clickedObject } = useClickedObject();
11946
- const path$1 = clickedObject?.path ?? hoveredObject?.path ?? null;
11947
- const color = path$1 ? metricCache.colormap.get(path$1) : null;
11948
- let blobLightness = color ? getLightness(color) : -1;
11949
- if (color === "#c0c0c0") blobLightness = -1;
11950
- const offset = useMemo(() => {
11951
- const min = getLightness(minColor);
11952
- const diff = getLightness(maxColor) - min;
11953
- if (diff === 0) return 1;
11954
- return (blobLightness - min) / diff;
11955
- }, [
11956
- blobLightness,
11957
- maxColor,
11958
- minColor
11959
- ]);
11960
- const visible = path$1 !== null;
11961
- const midValue = Math.round((maxValue - minValue) / 2);
11962
- return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
11963
- className: "relative h-4 rounded-sm",
11964
- style: { backgroundImage: `linear-gradient(to right, ${minColor}, ${maxColor})` },
11965
- children: /* @__PURE__ */ jsx(LegendBarIndicator, {
11966
- offset: offset * 100,
11967
- visible
11968
- })
11969
- }), /* @__PURE__ */ jsxs("div", {
11970
- className: "flex justify-between",
11867
+ function CheckboxWithLabel({ children, checked, onChange, className = "", checkedIcon = mdiCheckboxOutline, uncheckedIcon = mdiCheckboxBlankOutline, ...props }) {
11868
+ const [isTransitioning, startTransition$1] = useTransition();
11869
+ return /* @__PURE__ */ jsxs("label", {
11870
+ className: `label group flex w-full items-center justify-start gap-2 hover:text-blue-500 ${className}`,
11871
+ ...props,
11971
11872
  children: [
11972
- /* @__PURE__ */ jsx("span", {
11973
- className: "font-bold",
11974
- title: minValue.toLocaleString(),
11975
- children: numToFriendlyString(minValue)
11873
+ /* @__PURE__ */ jsxs("span", {
11874
+ className: "flex grow items-center gap-2",
11875
+ children: [children, isTransitioning ? /* @__PURE__ */ jsx("img", {
11876
+ src: truck_default$1,
11877
+ alt: "...",
11878
+ className: "h-5"
11879
+ }) : ""]
11976
11880
  }),
11977
- /* @__PURE__ */ jsx("span", {
11978
- className: "absolute left-1/2 -translate-x-1/2 font-bold",
11979
- title: midValue.toLocaleString(),
11980
- children: numToFriendlyString(midValue)
11881
+ /* @__PURE__ */ jsx(Icon, {
11882
+ className: "place-self-end group-hover:text-blue-500",
11883
+ path: checked ? checkedIcon : uncheckedIcon,
11884
+ size: 1
11981
11885
  }),
11982
- /* @__PURE__ */ jsx("span", {
11983
- className: "font-bold",
11984
- title: maxValue.toLocaleString(),
11985
- children: numToFriendlyString(maxValue)
11886
+ /* @__PURE__ */ jsx("input", {
11887
+ type: "checkbox",
11888
+ defaultChecked: checked,
11889
+ className: "hidden",
11890
+ onChange: (e) => startTransition$1(() => onChange(e))
11986
11891
  })
11987
11892
  ]
11988
- })] });
11989
- }
11990
- function Handle({ domain: [min, max], children, disabled, handle, handleType = "round", title, getHandleProps, className, onClick }) {
11991
- return /* @__PURE__ */ jsx("button", {
11992
- className: cn("absolute z-10 flex size-5 -translate-x-1/2 -translate-y-1.5 place-content-center disabled:grayscale", { "size-5 rounded-full": handleType === "round" }, disabled ? "cursor-progress" : "cursor-col-resize", className),
11993
- role: "slider",
11994
- "aria-disabled": disabled,
11995
- "aria-valuemin": min,
11996
- "aria-valuemax": max,
11997
- "aria-valuenow": handle.value,
11998
- title,
11999
- style: { left: `${handle.percent}%` },
12000
- onClick,
12001
- ...getHandleProps(handle.id),
12002
- children: /* @__PURE__ */ jsx("div", {
12003
- className: cn("btn--primary", {
12004
- "size-5 rounded-full": handleType === "round",
12005
- "h-5 w-0.5": handleType === "square"
12006
- }),
12007
- children
12008
- })
12009
11893
  });
12010
11894
  }
12011
- function SliderRail({ getRailProps, className = "", children }) {
12012
- return /* @__PURE__ */ jsx("div", {
12013
- className: cn("bg-blue-secondary/20 absolute h-2 w-full cursor-pointer", {}, className),
12014
- ...getRailProps(),
12015
- children
12016
- });
12017
- }
12018
- function Track({ source, target, trackType = "round", getTrackProps, disabled }) {
12019
- return /* @__PURE__ */ jsx("div", {
12020
- className: cn("btn btn--primary absolute h-2 cursor-pointer p-0", disabled ? "bg-primary-text-dark dark:bg-primary-text" : "bg-blue-primary", {
12021
- "rounded-full": trackType === "round",
12022
- "rounded-none": trackType === "square"
12023
- }),
12024
- style: {
12025
- left: `${source.percent}%`,
12026
- width: `${target.percent - source.percent}%`
12027
- },
12028
- ...getTrackProps()
12029
- });
12030
- }
12031
- var alignToJustify = {
12032
- left: "start",
12033
- center: "center",
12034
- right: "end"
12035
- };
12036
- function LabeledTicks({ valueMap, titleMap = valueMap, onTop = false }) {
12037
- const count = Object.entries(valueMap).length;
12038
- return /* @__PURE__ */ jsx(Ticks, {
12039
- count,
12040
- children: ({ ticks }) => /* @__PURE__ */ jsx("div", {
12041
- className: "grid grid-flow-col grid-cols-3 grid-rows-[1fr_auto] pt-4",
12042
- children: ticks.map((tick) => {
12043
- const tickLabelInfo = valueMap[tick.value * 100];
12044
- const align = tick.value === 0 ? "left" : tick.value === 1 ? "right" : "center";
12045
- const Tick$1 = /* @__PURE__ */ jsx("div", {
12046
- className: `flex justify-${tickLabelInfo ? alignToJustify[align] : "center"}`,
12047
- children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12048
- });
12049
- const Label$1 = /* @__PURE__ */ jsx("div", {
12050
- title: titleMap[tick.value * 100],
12051
- className: "truncate text-xs",
12052
- style: { textAlign: align },
12053
- children: tickLabelInfo
12054
- });
12055
- return /* @__PURE__ */ jsxs(Fragment, { children: [onTop ? Label$1 : Tick$1, onTop ? Tick$1 : Label$1] }, tick.id);
12056
- })
12057
- })
12058
- });
12059
- }
12060
- function TicksByCount({ className = "", count, tickToLabel, onTop = true, align, below = false }) {
12061
- return /* @__PURE__ */ jsx("div", {
12062
- className: cn("grid grid-flow-col grid-cols-(--cols) gap-1 text-xs", {
12063
- "grid-rows-[auto]": !below && !onTop,
12064
- "grid-rows-[auto_auto]": below !== onTop,
12065
- "grid-rows-[auto_auto_auto]": below && onTop
12066
- }, className),
12067
- style: { "--cols": `repeat(${count}, minmax(0, 1fr))` },
12068
- children: Array.from({ length: count }).map((_, i) => {
12069
- const tick = i / (count - 1);
12070
- const tickLabelInfo = tickToLabel(tick, i);
12071
- const alignment = align ?? (tick === 0 ? "left" : tick === 1 ? "right" : "center");
12072
- const justification = tickLabelInfo ? alignToJustify[alignment] : "center";
12073
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12074
- onTop ? /* @__PURE__ */ jsx(Tick, { justification }) : null,
12075
- /* @__PURE__ */ jsx("div", {
12076
- className: cn(`text-${alignment}`),
12077
- children: tickLabelInfo === 0 ? "" : tickLabelInfo
12078
- }),
12079
- below ? /* @__PURE__ */ jsx(Tick, { justification }) : null
12080
- ] }, i);
12081
- })
12082
- });
12083
- }
12084
- const Tick = ({ className = "", justification = "start" }) => /* @__PURE__ */ jsx("div", {
12085
- className: cn("flex", `justify-${justification}`, className),
12086
- children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12087
- });
12088
- function SegmentLegend({ hoveredObject }) {
12089
- const { metricType } = useOptions();
12090
- const [metricsData] = useMetrics();
12091
- const { steps, textGenerator, colorGenerator, offsetStepCalc } = metricsData.get(metricType).legend;
12092
- const width = 100 / steps;
12093
- let arrowVisible = false;
12094
- let arrowOffset = 0;
12095
- const clickedObject = useClickedObject().clickedObject ?? hoveredObject ?? null;
12096
- if (isBlob(clickedObject)) {
12097
- arrowVisible = true;
12098
- arrowOffset = width / 2 + width * offsetStepCalc(clickedObject);
11895
+ function UnionAuthorsModal() {
11896
+ const { databaseInfo } = useData();
11897
+ const submit = useSubmit();
11898
+ const { authors } = databaseInfo;
11899
+ const authorUnions = databaseInfo.authorUnions;
11900
+ const [selectedAuthors, setSelectedAuthors] = useState([]);
11901
+ const [filter, setFilter] = useState("");
11902
+ const navigationData = useNavigation();
11903
+ const [, authorColors] = useMetrics();
11904
+ const [, startTransition$1] = useTransition();
11905
+ const location$1 = useLocation();
11906
+ const flattedUnionedAuthors = authorUnions.reduce((acc, union) => {
11907
+ return [...acc, ...union];
11908
+ }, []).sort(stringSorter);
11909
+ function ungroup(groupToUnGroup) {
11910
+ const newAuthorUnions = authorUnions.filter((_, i) => i !== groupToUnGroup);
11911
+ const form = new FormData();
11912
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11913
+ submit(form, {
11914
+ action: href("/view") + location$1.search,
11915
+ method: "post"
11916
+ });
12099
11917
  }
12100
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
12101
- className: "relative",
11918
+ function ungroupAll() {
11919
+ const form = new FormData();
11920
+ form.append("unionedAuthors", JSON.stringify([]));
11921
+ submit(form, {
11922
+ action: href("/view") + location$1.search,
11923
+ method: "post"
11924
+ });
11925
+ }
11926
+ function groupSelectedAuthors() {
11927
+ if (selectedAuthors.length < 2) return;
11928
+ const form = new FormData();
11929
+ form.append("unionedAuthors", JSON.stringify([...authorUnions, selectedAuthors]));
11930
+ submit(form, {
11931
+ action: href("/view") + location$1.search,
11932
+ method: "post"
11933
+ });
11934
+ setSelectedAuthors([]);
11935
+ }
11936
+ function makePrimaryAlias(alias, groupIndex) {
11937
+ const newAuthorUnions = authorUnions.map((group, i) => {
11938
+ if (i === groupIndex) return [alias, ...group.filter((a) => a !== alias)];
11939
+ return group;
11940
+ });
11941
+ const form = new FormData();
11942
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11943
+ submit(form, {
11944
+ action: href("/view") + location$1.search,
11945
+ method: "post"
11946
+ });
11947
+ }
11948
+ const disabled = navigationData.state !== "idle";
11949
+ const ungroupedAuthorsSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
11950
+ const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
11951
+ const ungroupedAuthorsFiltered = ungroupedAuthorsSorted.filter((author) => author.toLowerCase().includes(filter.toLowerCase()));
11952
+ const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => /* @__PURE__ */ jsx(CheckboxWithLabel, {
11953
+ className: "hover:opacity-70",
11954
+ checked: selectedAuthors.includes(author),
11955
+ onChange: (e) => {
11956
+ setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
11957
+ },
12102
11958
  children: /* @__PURE__ */ jsxs("div", {
12103
- className: "relative flex text-xs whitespace-nowrap",
12104
- children: [[...Array(steps)].map((_, i) => {
12105
- return steps >= 4 ? /* @__PURE__ */ jsx(MetricSegment, {
12106
- className: i === 0 ? "rounded-l-sm" : i === steps - 1 ? "rounded-r-sm" : "",
12107
- width,
12108
- color: colorGenerator(i),
12109
- text: textGenerator(i),
12110
- top: i % 2 === 0
12111
- }, i) : /* @__PURE__ */ jsx(TopMetricSegment, {
12112
- width,
12113
- color: colorGenerator(i),
12114
- text: textGenerator(i)
12115
- }, i);
12116
- }), /* @__PURE__ */ jsx(LegendBarIndicator, {
12117
- offset: arrowOffset,
12118
- visible: arrowVisible
12119
- })]
11959
+ className: "inline-flex flex-row place-items-center gap-2",
11960
+ children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
12120
11961
  })
12121
- }) });
12122
- }
12123
- function MetricSegment({ className = "", width, color, text, top }) {
12124
- if (top) return /* @__PURE__ */ jsxs("div", {
12125
- className: "flex flex-col",
12126
- style: { width: `${width}%` },
12127
- children: [
12128
- /* @__PURE__ */ jsx("div", {
12129
- className: "h-5 truncate text-left",
12130
- title: text,
12131
- children: text
12132
- }),
12133
- /* @__PURE__ */ jsx(Tick, { className: "ml-1" }),
12134
- /* @__PURE__ */ jsx("div", {
12135
- className: cn("h-4", className),
12136
- style: { backgroundColor: color }
12137
- }),
12138
- /* @__PURE__ */ jsx(Tick, { className: "invisible" })
12139
- ]
11962
+ }, author));
11963
+ const groupedAuthorsEntries = authorUnions.map((aliasGroup, aliasGroupIndex) => {
11964
+ const displayName = aliasGroup[0];
11965
+ const disabled$1 = navigationData.state !== "idle";
11966
+ return /* @__PURE__ */ jsxs("div", {
11967
+ className: "card group m-0 flex h-full flex-col p-2",
11968
+ children: [
11969
+ /* @__PURE__ */ jsxs("div", {
11970
+ className: "inline-flex flex-row place-items-center gap-2",
11971
+ children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(displayName) }), /* @__PURE__ */ jsx("b", {
11972
+ className: "truncate",
11973
+ title: displayName,
11974
+ children: displayName
11975
+ })]
11976
+ }),
11977
+ aliasGroup.slice(1).sort(stringSorter).map((alias) => /* @__PURE__ */ jsx(AliasEntry, {
11978
+ alias,
11979
+ disabled: disabled$1,
11980
+ onClick: () => makePrimaryAlias(alias, aliasGroupIndex)
11981
+ }, alias)),
11982
+ /* @__PURE__ */ jsx("div", { className: "grow" }),
11983
+ /* @__PURE__ */ jsxs("div", {
11984
+ className: "flex items-end justify-end gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
11985
+ children: [/* @__PURE__ */ jsxs(Form, {
11986
+ action: href("/view") + location$1.search,
11987
+ method: "post",
11988
+ children: [/* @__PURE__ */ jsx("input", {
11989
+ type: "hidden",
11990
+ name: "unionedAuthors",
11991
+ value: JSON.stringify(authorUnions)
11992
+ }), /* @__PURE__ */ jsx("button", {
11993
+ className: "btn",
11994
+ title: "Add selected authors to this group",
11995
+ disabled: disabled$1 || selectedAuthors.length === 0,
11996
+ onClick: () => {
11997
+ const newAuthorUnions = authorUnions.map(([displayName$1, ...group], i) => {
11998
+ if (i === aliasGroupIndex) return [
11999
+ displayName$1,
12000
+ ...group,
12001
+ ...selectedAuthors
12002
+ ];
12003
+ return [displayName$1, ...group];
12004
+ });
12005
+ const form = new FormData();
12006
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12007
+ submit(form, {
12008
+ action: href("/view") + location$1.search,
12009
+ method: "post"
12010
+ });
12011
+ setSelectedAuthors([]);
12012
+ },
12013
+ children: "Add selected"
12014
+ })]
12015
+ }), /* @__PURE__ */ jsx("button", {
12016
+ className: "btn",
12017
+ title: "Ungroup",
12018
+ disabled: disabled$1,
12019
+ onClick: () => ungroup(aliasGroupIndex),
12020
+ children: "Ungroup"
12021
+ })]
12022
+ })
12023
+ ]
12024
+ }, aliasGroupIndex);
12140
12025
  });
12141
- else return /* @__PURE__ */ jsxs("div", {
12142
- className: "flex flex-col",
12143
- style: { width: `${width}%` },
12026
+ return /* @__PURE__ */ jsxs("div", {
12027
+ 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",
12144
12028
  children: [
12145
- /* @__PURE__ */ jsx("div", {
12146
- className: "invisible h-5 text-left",
12147
- children: text
12029
+ /* @__PURE__ */ jsxs("div", {
12030
+ className: "flex justify-between",
12031
+ children: [/* @__PURE__ */ jsxs("h3", {
12032
+ className: "grow text-center text-lg font-bold",
12033
+ children: [
12034
+ "Ungrouped Authors (",
12035
+ ungroupedAuthorsSorted.length,
12036
+ ")"
12037
+ ]
12038
+ }), /* @__PURE__ */ jsx("div", {
12039
+ className: "flex justify-end gap-2",
12040
+ children: /* @__PURE__ */ jsxs("button", {
12041
+ className: "btn btn--primary justify-self-end",
12042
+ title: "Group the selected authors",
12043
+ disabled: disabled || selectedAuthors.length < 2,
12044
+ onClick: groupSelectedAuthors,
12045
+ children: [/* @__PURE__ */ jsx(Icon, {
12046
+ path: mdiAccountMultiplePlus,
12047
+ size: 1
12048
+ }), "Create group"]
12049
+ })
12050
+ })]
12051
+ }),
12052
+ /* @__PURE__ */ jsxs("div", {
12053
+ className: "flex justify-between",
12054
+ children: [/* @__PURE__ */ jsxs("h3", {
12055
+ className: "grow text-center text-lg font-bold",
12056
+ children: [
12057
+ "Author Groups (",
12058
+ groupedAuthorsEntries.length,
12059
+ ")"
12060
+ ]
12061
+ }), /* @__PURE__ */ jsx("div", {
12062
+ className: "flex justify-end gap-4",
12063
+ children: /* @__PURE__ */ jsxs("button", {
12064
+ className: "btn btn--danger",
12065
+ disabled: disabled || authorUnions.length === 0,
12066
+ onClick: () => {
12067
+ if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
12068
+ },
12069
+ children: [/* @__PURE__ */ jsx(Icon, {
12070
+ path: mdiAccountMultipleMinus,
12071
+ size: 1
12072
+ }), "Ungroup all"]
12073
+ })
12074
+ })]
12148
12075
  }),
12149
- /* @__PURE__ */ jsx(Tick, { className: "invisible" }),
12150
12076
  /* @__PURE__ */ jsx("div", {
12151
- className: cn("h-4", className),
12152
- style: { backgroundColor: color }
12077
+ className: "max-h-full overflow-y-scroll",
12078
+ children: /* @__PURE__ */ jsxs("div", {
12079
+ className: "h-fill flex min-h-0 flex-col rounded-md dark:bg-gray-700",
12080
+ children: [/* @__PURE__ */ jsxs("div", {
12081
+ className: "sticky top-0 z-10 flex gap-2 bg-gray-100 p-2 dark:bg-gray-700",
12082
+ children: [
12083
+ /* @__PURE__ */ jsx("input", {
12084
+ className: "input min-w-0",
12085
+ type: "search",
12086
+ placeholder: "Filter...",
12087
+ disabled: ungroupedAuthorsSorted.length === 0,
12088
+ onChange: (e) => startTransition$1(() => setFilter(e.target.value))
12089
+ }),
12090
+ /* @__PURE__ */ jsx("button", {
12091
+ disabled: disabled || selectedAuthors.length === 0,
12092
+ className: "btn btn--outlined w-max grow",
12093
+ title: "Clear selection",
12094
+ onClick: () => setSelectedAuthors([]),
12095
+ children: "Clear"
12096
+ }),
12097
+ /* @__PURE__ */ jsx("button", {
12098
+ disabled: ungroupedAuthorsSorted.length === 0,
12099
+ className: "btn btn--outlined w-max grow",
12100
+ title: "Clear selection",
12101
+ onClick: () => selectedAuthors.length === ungroupedAuthorsFiltered.length ? setSelectedAuthors([]) : setSelectedAuthors((selected) => Array.from(new Set([...selected, ...ungroupedAuthorsFiltered]))),
12102
+ children: selectedAuthors.length === ungroupedAuthorsFiltered.length ? "Deselect all" : "Select all"
12103
+ })
12104
+ ]
12105
+ }), /* @__PURE__ */ jsx("div", {
12106
+ className: "min-h-fill max-h-full overflow-y-auto p-2",
12107
+ children: ungroupedAuthorsEntries.length > 0 ? ungroupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12108
+ className: "place-self-center",
12109
+ children: filter.length > 0 ? "No authors found" : "All authors have been grouped"
12110
+ })
12111
+ })]
12112
+ })
12153
12113
  }),
12154
- /* @__PURE__ */ jsx(Tick, {}),
12155
12114
  /* @__PURE__ */ jsx("div", {
12156
- className: "h-5 truncate text-left",
12157
- children: text
12115
+ className: "min-h-0 overflow-y-auto",
12116
+ children: /* @__PURE__ */ jsx("div", {
12117
+ 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",
12118
+ children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12119
+ className: "place-self-center",
12120
+ children: "No authors have been grouped yet"
12121
+ })
12122
+ })
12158
12123
  })
12159
12124
  ]
12160
12125
  });
12126
+ function AliasEntry({ alias, onClick, disabled: disabled$1 }) {
12127
+ return /* @__PURE__ */ jsxs("button", {
12128
+ className: "btn flex grid-flow-col gap-2 text-sm [&:hover>svg]:opacity-50 [&>svg]:opacity-0",
12129
+ disabled: disabled$1,
12130
+ title: "Make display name for this grouping",
12131
+ onClick,
12132
+ children: [/* @__PURE__ */ jsx(Icon, {
12133
+ path: mdiArrowUp,
12134
+ size: .75
12135
+ }), /* @__PURE__ */ jsx("label", {
12136
+ title: alias,
12137
+ className: "label truncate",
12138
+ children: alias
12139
+ })]
12140
+ }, alias);
12141
+ }
12161
12142
  }
12162
- function TopMetricSegment({ width, color, text }) {
12163
- return /* @__PURE__ */ jsxs("div", {
12164
- className: "flex flex-col",
12165
- style: { width: `${width}%` },
12166
- children: [
12167
- /* @__PURE__ */ jsx("div", {
12168
- className: "h-5 truncate text-left",
12169
- children: text
12170
- }),
12171
- /* @__PURE__ */ jsx(Tick, {}),
12172
- /* @__PURE__ */ jsx("div", {
12173
- className: "h-5",
12174
- style: { backgroundColor: color }
12175
- })
12176
- ]
12177
- });
12178
- }
12179
- function PercentageSlider({ className = "" }) {
12180
- const { dominantAuthorCutoff, setDominantAuthorCutoff } = useOptions();
12181
- const [displayPercentage, setDisplayPercentage] = useState(dominantAuthorCutoff);
12182
- const domain = [0, 100];
12183
- return /* @__PURE__ */ jsxs("div", {
12184
- className: cn("grid grid-cols-[calc(11*var(--spacing))_1fr] px-3", className),
12185
- children: [/* @__PURE__ */ jsxs("p", {
12186
- title: "Min percentage of changes a file's top author must account for to be colored.",
12187
- children: [displayPercentage, "%"]
12188
- }), /* @__PURE__ */ jsx("div", {
12189
- className: "relative",
12190
- title: "Adjust the dominant author percentage cutoff",
12191
- children: /* @__PURE__ */ jsxs(Slider, {
12192
- mode: 1,
12193
- step: 1,
12194
- domain,
12195
- values: [displayPercentage],
12196
- onChange: (e) => {
12197
- setDominantAuthorCutoff(e[0]);
12198
- startTransition(() => {
12199
- setDisplayPercentage(e[0]);
12200
- });
12201
- },
12202
- onUpdate: (e) => {
12203
- startTransition(() => {
12204
- setDisplayPercentage(e[0]);
12205
- });
12206
- },
12207
- children: [
12208
- /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12209
- /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: handles.map((handle) => /* @__PURE__ */ jsx(Handle, {
12210
- handle,
12211
- domain,
12212
- getHandleProps
12213
- }, handle.id)) }) }),
12214
- /* @__PURE__ */ jsx(Tracks, {
12215
- right: false,
12216
- children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: tracks.map(({ id, ...props }) => /* @__PURE__ */ createElement(Track, {
12217
- ...props,
12218
- key: id,
12219
- backgroundColor: noEntryColor,
12220
- getTrackProps
12221
- })) })
12222
- }),
12223
- /* @__PURE__ */ jsx(LabeledTicks, { valueMap: {
12224
- 0: "Top author",
12225
- 50: "Majority author",
12226
- 100: "Single author"
12227
- } })
12228
- ]
12229
- })
12230
- })]
12231
- });
12232
- }
12233
- function Legend({ hoveredObject, showUnionAuthorsModal }) {
12234
- const { sizeMetric, metricType } = useOptions();
12235
- const [metricsData] = useMetrics();
12236
- const deferredHoveredObject = useDeferredValue(hoveredObject);
12237
- const metricCache = metricsData.get(metricType) ?? void 0;
12238
- const legend = useMemo(() => {
12239
- if (metricCache === void 0) return null;
12240
- switch (getMetricLegendType(metricType)) {
12241
- case "POINT": return /* @__PURE__ */ jsx(PointLegend, {});
12242
- case "GRADIENT": return /* @__PURE__ */ jsx(GradientLegend, { hoveredObject: deferredHoveredObject });
12243
- case "SEGMENTS": return /* @__PURE__ */ jsx(SegmentLegend, { hoveredObject: deferredHoveredObject });
12244
- }
12245
- }, [
12246
- metricCache,
12247
- deferredHoveredObject,
12248
- metricType
12249
- ]);
12250
- if (legend === null) return null;
12251
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12252
- /* @__PURE__ */ jsx("h3", {
12253
- className: "card__subtitle",
12254
- children: "Size legend"
12255
- }),
12256
- /* @__PURE__ */ jsx("p", {
12257
- className: "mb-2 text-sm",
12258
- children: sizeMetricLegendDescriptions[sizeMetric]
12259
- }),
12260
- /* @__PURE__ */ jsx("h3", {
12261
- className: "card__subtitle",
12262
- children: "Color legend"
12263
- }),
12264
- /* @__PURE__ */ jsx("p", {
12265
- className: "mb-4 text-sm",
12266
- children: colorMetricDescriptions[metricType]
12267
- }),
12268
- metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(PercentageSlider, { className: "my-4" }) : null,
12269
- legend,
12270
- metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(AuthorOptions, { showUnionAuthorsModal }) : null
12271
- ] });
12272
- }
12273
- function LoadingIndicator({ className = "", showProgress = false, fetchProgress = true, loadingText }) {
12274
- const [path$1] = useQueryState("path");
12275
- const fetcher = useFetcher();
12276
- useEffect(() => {
12277
- if (fetcher.state === "idle" && showProgress && fetchProgress) fetcher.load(href("/view/progress") + viewSerializer({ path: path$1 }));
12278
- }, [
12279
- fetchProgress,
12280
- fetcher,
12281
- fetcher.state,
12282
- path$1,
12283
- showProgress
12284
- ]);
12285
- const [progressText, progress] = useMemo(() => {
12286
- if (!fetcher.data) return ["Loading truck...", 0];
12287
- const { progress: progress$1, analyzationStatus } = fetcher.data;
12288
- switch (analyzationStatus) {
12289
- case "Starting": return ["Loading truck...", 0];
12290
- case "GeneratingChart": return ["Unloading truck...", 100];
12291
- default: return [progress$1 < 100 ? "Driving to destination..." : "Parking truck...", progress$1];
12292
- }
12293
- }, [fetcher.data]);
12294
- return /* @__PURE__ */ jsx("div", {
12295
- className: clsx$1("grid h-full w-full place-items-center px-4", className),
12296
- children: /* @__PURE__ */ jsxs("div", {
12297
- className: "flex w-full max-w-[clamp(16rem,80vw,36rem)] flex-col gap-4 px-2 py-2",
12298
- children: [
12299
- /* @__PURE__ */ jsx("img", {
12300
- src: !showProgress || progress > 0 && progress < 100 ? truck_default : truck_default$1,
12301
- alt: "🚛",
12302
- className: "pixelated aspect-square w-full"
12303
- }),
12304
- showProgress ? /* @__PURE__ */ jsx("div", {
12305
- className: "text-center text-3xl font-bold",
12306
- children: progressText
12307
- }) : null,
12308
- showProgress ? /* @__PURE__ */ jsx("div", {
12309
- className: "h-6 w-3/4 self-center rounded-2xl bg-gray-300",
12310
- children: /* @__PURE__ */ jsx("div", {
12311
- className: cn("bg-blue-primary h-[calc(100%-4px)] min-w-[calc(var(--spacing)*6-4px)] translate-x-0.5 translate-y-0.5 rounded-2xl transition-[width] ease-in-out", { "animate-skeet": !progress }),
12312
- style: { width: progress ? `calc(${Math.min(progress, 100)}% - 4px)` : "40px" }
12313
- })
12314
- }) : null,
12315
- loadingText ? /* @__PURE__ */ jsx("div", {
12316
- className: "text-center font-bold",
12317
- children: loadingText
12318
- }) : null
12319
- ]
12320
- })
12321
- });
12322
- }
12143
+ var stringSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
12323
12144
  function IconRadioGroup({ group, titleMap, defaultValue, className, onChange, large, iconMap }) {
12324
12145
  const enumEntries = Object.entries(group);
12325
12146
  return /* @__PURE__ */ jsx(RadioGroup, {
@@ -12415,47 +12236,645 @@ const Options = memo(function Options$1() {
12415
12236
  })] })
12416
12237
  ] });
12417
12238
  });
12418
- function Providers({ children, data }) {
12419
- const [options, setOptions] = useState(() => {
12420
- const savedOptions = typeof document !== "undefined" ? localStorage.getItem(OPTIONS_LOCAL_STORAGE_KEY) : null;
12421
- return {
12422
- ...getDefaultOptionsContextValue(),
12423
- ...savedOptions ? JSON.parse(savedOptions) : {},
12424
- hasLoadedSavedOptions: !!savedOptions
12425
- };
12239
+ function SettingsModal() {
12240
+ const { metricType, hierarchyType, transitionsEnabled, renderCutoff, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setRenderCutoff, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
12241
+ const [isTransitioning, startTransition$1] = useTransition();
12242
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
12243
+ className: "flex min-w-120 flex-col items-start justify-center gap-4 p-2 pl-0",
12244
+ children: [
12245
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12246
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12247
+ checked: Boolean(linkMetricAndSizeMetric),
12248
+ title: "Enable to sync size metric with color metric",
12249
+ onChange: (e) => {
12250
+ setLinkMetricAndSizeMetric(e.target.checked);
12251
+ if (e.target.checked) setSizeMetricType(relatedSizeMetric[metricType]);
12252
+ },
12253
+ children: [/* @__PURE__ */ jsx(Icon, {
12254
+ className: "ml-1.5",
12255
+ path: mdiLink,
12256
+ size: "1.25em"
12257
+ }), /* @__PURE__ */ jsx("span", { children: "Link size and color option" })]
12258
+ }),
12259
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12260
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12261
+ checked: transitionsEnabled,
12262
+ title: "Disable to improve performance when zooming",
12263
+ onChange: (e) => setTransitionsEnabled(e.target.checked),
12264
+ children: [/* @__PURE__ */ jsx(Icon, {
12265
+ className: "ml-1.5",
12266
+ path: mdiTransition,
12267
+ size: "1.25em"
12268
+ }), "Transitions"]
12269
+ }),
12270
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12271
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12272
+ checked: labelsVisible,
12273
+ title: "Disable to improve performance",
12274
+ onChange: (e) => setLabelsVisible(e.target.checked),
12275
+ children: [/* @__PURE__ */ jsx(Icon, {
12276
+ className: "ml-1.5",
12277
+ path: mdiLabel,
12278
+ size: "1.25em"
12279
+ }), "Labels"]
12280
+ }),
12281
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12282
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12283
+ checked: showFilesWithoutChanges,
12284
+ title: "Show files that have had no changes in the selected time range",
12285
+ onChange: (e) => setShowFilesWithoutChanges(e.target.checked),
12286
+ children: [/* @__PURE__ */ jsx(Icon, {
12287
+ className: "ml-1.5",
12288
+ path: mdiClockEdit,
12289
+ size: "1.25em"
12290
+ }), "Show files with no activity"]
12291
+ }),
12292
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12293
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12294
+ checked: hierarchyType === "FLAT",
12295
+ title: "Show all files on the same level, instead of as a file tree",
12296
+ onChange: () => {
12297
+ if (hierarchyType === "FLAT") setHierarchyType("NESTED");
12298
+ else setHierarchyType("FLAT");
12299
+ },
12300
+ children: [/* @__PURE__ */ jsx(Icon, {
12301
+ className: "ml-1.5",
12302
+ path: mdiFileTree,
12303
+ size: "1.25em"
12304
+ }), "Flatten file tree"]
12305
+ }),
12306
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12307
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12308
+ checked: showOnlySearchMatches,
12309
+ title: "When searching, hide files that do not match the search query",
12310
+ onChange: (e) => setShowOnlySearchMatches(e.target.checked),
12311
+ children: [/* @__PURE__ */ jsx(Icon, {
12312
+ className: "ml-1.5",
12313
+ path: mdiFilter,
12314
+ size: "1.25em"
12315
+ }), "Show only search matches"]
12316
+ }),
12317
+ /* @__PURE__ */ jsxs("label", {
12318
+ className: "label group flex w-full items-center justify-start gap-2 text-sm hover:text-blue-500 hover:opacity-100",
12319
+ title: "Increase this to improve render performance, decrease it to get higher level of detail",
12320
+ children: [/* @__PURE__ */ jsxs("span", {
12321
+ className: "group flex grow items-center gap-2",
12322
+ children: [
12323
+ /* @__PURE__ */ jsx(Icon, {
12324
+ className: "ml-1.5",
12325
+ path: mdiContentCut,
12326
+ size: "1.25em"
12327
+ }),
12328
+ "Pixel render cut-off ",
12329
+ isTransitioning ? /* @__PURE__ */ jsx("img", {
12330
+ src: truck_default$1,
12331
+ alt: "...",
12332
+ className: "h-5"
12333
+ }) : ""
12334
+ ]
12335
+ }), /* @__PURE__ */ jsx("input", {
12336
+ type: "number",
12337
+ min: 0,
12338
+ defaultValue: renderCutoff,
12339
+ className: "mr-1 w-12 place-self-end border-b-2",
12340
+ onChange: (x) => startTransition$1(() => setRenderCutoff(x.target.valueAsNumber))
12341
+ })]
12342
+ })
12343
+ ]
12344
+ }) });
12345
+ }
12346
+ var modals = {
12347
+ "group-authors": {
12348
+ content: /* @__PURE__ */ jsx(UnionAuthorsModal, {}),
12349
+ title: "Group Authors",
12350
+ icon: mdiAccountMultipleCheck
12351
+ },
12352
+ "app-settings": {
12353
+ content: /* @__PURE__ */ jsx(SettingsModal, {}),
12354
+ title: "Settings",
12355
+ icon: mdiCog
12356
+ }
12357
+ };
12358
+ var modalSearchParamConfig = parseAsStringLiteral(Object.keys(modals));
12359
+ function useModal(modalKey = null) {
12360
+ const [modal, setModal] = useQueryState("modal", modalSearchParamConfig);
12361
+ const openModal = (modal$1 = modalKey) => void setModal(modal$1);
12362
+ const closeModal = () => setModal(null);
12363
+ return {
12364
+ modal,
12365
+ openModal,
12366
+ closeModal
12367
+ };
12368
+ }
12369
+ function ModalManager() {
12370
+ const [modalKey, setModal] = useQueryState("modal", modalSearchParamConfig);
12371
+ const dialogRef = useRef(null);
12372
+ const onClose = () => setModal(null);
12373
+ useEffect(() => {
12374
+ if (!dialogRef.current) return;
12375
+ const dialog = dialogRef.current;
12376
+ if (modalKey) {
12377
+ dialogRef.current.showModal();
12378
+ return;
12379
+ }
12380
+ dialog.close();
12381
+ return () => dialog.close();
12382
+ }, [modalKey]);
12383
+ if (!modalKey) return null;
12384
+ const modal = modals[modalKey];
12385
+ return /* @__PURE__ */ jsx("dialog", {
12386
+ ref: dialogRef,
12387
+ "aria-modal": true,
12388
+ open: false,
12389
+ closedby: "any",
12390
+ className: "z-10 m-auto flex flex-col items-start justify-stretch bg-transparent text-inherit backdrop:bg-gray-500/75 backdrop:p-0",
12391
+ onClose,
12392
+ children: /* @__PURE__ */ jsxs("div", {
12393
+ 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",
12394
+ children: [
12395
+ /* @__PURE__ */ jsxs("div", {
12396
+ className: "flex justify-between gap-2",
12397
+ children: [/* @__PURE__ */ jsxs("div", {
12398
+ className: "flex flex-row items-center gap-2",
12399
+ children: [/* @__PURE__ */ jsx(Icon, {
12400
+ path: modal.icon,
12401
+ size: "1.75em"
12402
+ }), /* @__PURE__ */ jsx("h2", {
12403
+ className: "text-2xl font-semibold",
12404
+ children: modal.title
12405
+ })]
12406
+ }), /* @__PURE__ */ jsx(CloseButton, {
12407
+ absolute: false,
12408
+ className: "justify-self-end px-1",
12409
+ onClick: onClose
12410
+ })]
12411
+ }),
12412
+ /* @__PURE__ */ jsx("span", { className: "w-full border-b-3 border-gray-500 px-20" }),
12413
+ modal.content ?? null
12414
+ ]
12415
+ })
12426
12416
  });
12427
- const [searchResults, setSearchResults] = useState({});
12428
- const hasSearchResults = useMemo(() => Object.values(searchResults).length > 0, [searchResults]);
12429
- const prefersLight = usePrefersLightMode();
12430
- const metricsData = useMemo(() => {
12431
- return createMetricData(data, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12432
- }, [
12433
- data,
12434
- options?.dominantAuthorCutoff,
12435
- prefersLight
12436
- ]);
12437
- const optionsValue = useMemo(() => ({
12438
- ...options,
12439
- setMetricType: (metricType) => setOptions((prevOptions) => ({
12440
- ...prevOptions,
12441
- metricType
12442
- })),
12443
- setChartType: (chartType) => setOptions((prevOptions) => ({
12444
- ...prevOptions,
12445
- chartType
12446
- })),
12447
- setHierarchyType: (hierarchyType) => setOptions((prevOptions) => ({
12448
- ...prevOptions,
12449
- hierarchyType
12450
- })),
12451
- setCommitSearch: (commitSearch) => setOptions((prevOptions) => ({
12452
- ...prevOptions,
12453
- commitSearch
12454
- })),
12455
- setSizeMetricType: (sizeMetric) => setOptions((prevOptions) => ({
12456
- ...prevOptions,
12457
- sizeMetric
12458
- })),
12417
+ }
12418
+ function AuthorOptions() {
12419
+ const transitionState = useNavigation();
12420
+ const action$3 = href("/view");
12421
+ const { openModal } = useModal("group-authors");
12422
+ return /* @__PURE__ */ jsxs("div", {
12423
+ className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
12424
+ children: [/* @__PURE__ */ jsxs("button", {
12425
+ className: "btn",
12426
+ onClick: () => openModal(),
12427
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
12428
+ }), /* @__PURE__ */ jsxs(Form, {
12429
+ method: "post",
12430
+ action: action$3,
12431
+ children: [/* @__PURE__ */ jsx("input", {
12432
+ type: "hidden",
12433
+ name: "rerollColors",
12434
+ value: ""
12435
+ }), /* @__PURE__ */ jsxs("button", {
12436
+ className: "btn w-full",
12437
+ type: "submit",
12438
+ disabled: transitionState.state !== "idle",
12439
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
12440
+ })]
12441
+ })]
12442
+ });
12443
+ }
12444
+ function GradientLegend({ hoveredObject }) {
12445
+ const { metricType } = useOptions();
12446
+ const [metricsData] = useMetrics();
12447
+ const metricCache = metricsData.get(metricType);
12448
+ if (metricCache === void 0) throw new Error("Metric cache is undefined");
12449
+ const { minValue, maxValue, minColor, maxColor } = metricCache.legend;
12450
+ const { clickedObject } = useClickedObject();
12451
+ const path$1 = clickedObject?.path ?? hoveredObject?.path ?? null;
12452
+ const color = path$1 ? metricCache.colormap.get(path$1) : null;
12453
+ let blobLightness = color ? getLightness(color) : -1;
12454
+ if (color === "#c0c0c0") blobLightness = -1;
12455
+ const offset = useMemo(() => {
12456
+ const min = getLightness(minColor);
12457
+ const diff = getLightness(maxColor) - min;
12458
+ if (diff === 0) return 1;
12459
+ return (blobLightness - min) / diff;
12460
+ }, [
12461
+ blobLightness,
12462
+ maxColor,
12463
+ minColor
12464
+ ]);
12465
+ const visible = path$1 !== null;
12466
+ const midValue = Math.round((maxValue - minValue) / 2);
12467
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
12468
+ className: "relative h-4 rounded-sm",
12469
+ style: { backgroundImage: `linear-gradient(to right, ${minColor}, ${maxColor})` },
12470
+ children: /* @__PURE__ */ jsx(LegendBarIndicator, {
12471
+ offset: offset * 100,
12472
+ visible
12473
+ })
12474
+ }), /* @__PURE__ */ jsxs("div", {
12475
+ className: "flex justify-between",
12476
+ children: [
12477
+ /* @__PURE__ */ jsx("span", {
12478
+ className: "font-bold",
12479
+ title: minValue.toLocaleString(),
12480
+ children: numToFriendlyString(minValue)
12481
+ }),
12482
+ /* @__PURE__ */ jsx("span", {
12483
+ className: "absolute left-1/2 -translate-x-1/2 font-bold",
12484
+ title: midValue.toLocaleString(),
12485
+ children: numToFriendlyString(midValue)
12486
+ }),
12487
+ /* @__PURE__ */ jsx("span", {
12488
+ className: "font-bold",
12489
+ title: maxValue.toLocaleString(),
12490
+ children: numToFriendlyString(maxValue)
12491
+ })
12492
+ ]
12493
+ })] });
12494
+ }
12495
+ function Handle({ domain: [min, max], children, disabled, handle, handleType = "round", title, getHandleProps, className, onClick }) {
12496
+ return /* @__PURE__ */ jsx("button", {
12497
+ className: cn("absolute z-10 flex size-5 -translate-x-1/2 -translate-y-1.5 place-content-center disabled:grayscale", { "size-5 rounded-full": handleType === "round" }, disabled ? "cursor-progress" : "cursor-col-resize", className),
12498
+ role: "slider",
12499
+ "aria-disabled": disabled,
12500
+ "aria-valuemin": min,
12501
+ "aria-valuemax": max,
12502
+ "aria-valuenow": handle.value,
12503
+ title,
12504
+ style: { left: `${handle.percent}%` },
12505
+ onClick,
12506
+ ...getHandleProps(handle.id),
12507
+ children: /* @__PURE__ */ jsx("div", {
12508
+ className: cn("btn--primary", {
12509
+ "size-5 rounded-full": handleType === "round",
12510
+ "h-5 w-0.5": handleType === "square"
12511
+ }),
12512
+ children
12513
+ })
12514
+ });
12515
+ }
12516
+ function SliderRail({ getRailProps, className = "", children }) {
12517
+ return /* @__PURE__ */ jsx("div", {
12518
+ className: cn("bg-blue-secondary/20 absolute h-2 w-full cursor-pointer", {}, className),
12519
+ ...getRailProps(),
12520
+ children
12521
+ });
12522
+ }
12523
+ function Track({ source, target, trackType = "round", getTrackProps, disabled }) {
12524
+ return /* @__PURE__ */ jsx("div", {
12525
+ className: cn("btn btn--primary absolute h-2 cursor-pointer p-0", disabled ? "bg-primary-text-dark dark:bg-primary-text" : "bg-blue-primary", {
12526
+ "rounded-full": trackType === "round",
12527
+ "rounded-none": trackType === "square"
12528
+ }),
12529
+ style: {
12530
+ left: `${source.percent}%`,
12531
+ width: `${target.percent - source.percent}%`
12532
+ },
12533
+ ...getTrackProps()
12534
+ });
12535
+ }
12536
+ var alignToJustify = {
12537
+ left: "start",
12538
+ center: "center",
12539
+ right: "end"
12540
+ };
12541
+ function LabeledTicks({ valueMap, titleMap = valueMap, onTop = false }) {
12542
+ const count = Object.entries(valueMap).length;
12543
+ return /* @__PURE__ */ jsx(Ticks, {
12544
+ count,
12545
+ children: ({ ticks }) => /* @__PURE__ */ jsx("div", {
12546
+ className: "grid grid-flow-col grid-cols-3 grid-rows-[1fr_auto] pt-4",
12547
+ children: ticks.map((tick) => {
12548
+ const tickLabelInfo = valueMap[tick.value * 100];
12549
+ const align = tick.value === 0 ? "left" : tick.value === 1 ? "right" : "center";
12550
+ const Tick$1 = /* @__PURE__ */ jsx("div", {
12551
+ className: `flex justify-${tickLabelInfo ? alignToJustify[align] : "center"}`,
12552
+ children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12553
+ });
12554
+ const Label$1 = /* @__PURE__ */ jsx("div", {
12555
+ title: titleMap[tick.value * 100],
12556
+ className: "truncate text-xs",
12557
+ style: { textAlign: align },
12558
+ children: tickLabelInfo
12559
+ });
12560
+ return /* @__PURE__ */ jsxs(Fragment, { children: [onTop ? Label$1 : Tick$1, onTop ? Tick$1 : Label$1] }, tick.id);
12561
+ })
12562
+ })
12563
+ });
12564
+ }
12565
+ function TicksByCount({ className = "", count, tickToLabel, onTop = true, align, below = false }) {
12566
+ return /* @__PURE__ */ jsx("div", {
12567
+ className: cn("grid grid-flow-col grid-cols-(--cols) gap-1 text-xs", {
12568
+ "grid-rows-[auto]": !below && !onTop,
12569
+ "grid-rows-[auto_auto]": below !== onTop,
12570
+ "grid-rows-[auto_auto_auto]": below && onTop
12571
+ }, className),
12572
+ style: { "--cols": `repeat(${count}, minmax(0, 1fr))` },
12573
+ children: Array.from({ length: count }).map((_, i) => {
12574
+ const tick = i / (count - 1);
12575
+ const tickLabelInfo = tickToLabel(tick, i);
12576
+ const alignment = align ?? (tick === 0 ? "left" : tick === 1 ? "right" : "center");
12577
+ const justification = tickLabelInfo ? alignToJustify[alignment] : "center";
12578
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12579
+ onTop ? /* @__PURE__ */ jsx(Tick, { justification }) : null,
12580
+ /* @__PURE__ */ jsx("div", {
12581
+ className: cn(`text-${alignment}`),
12582
+ children: tickLabelInfo === 0 ? "" : tickLabelInfo
12583
+ }),
12584
+ below ? /* @__PURE__ */ jsx(Tick, { justification }) : null
12585
+ ] }, i);
12586
+ })
12587
+ });
12588
+ }
12589
+ const Tick = ({ className = "", justification = "start" }) => /* @__PURE__ */ jsx("div", {
12590
+ className: cn("flex", `justify-${justification}`, className),
12591
+ children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12592
+ });
12593
+ function SegmentLegend({ hoveredObject }) {
12594
+ const { metricType } = useOptions();
12595
+ const [metricsData] = useMetrics();
12596
+ const { steps, textGenerator, colorGenerator, offsetStepCalc } = metricsData.get(metricType).legend;
12597
+ const width = 100 / steps;
12598
+ let arrowVisible = false;
12599
+ let arrowOffset = 0;
12600
+ const clickedObject = useClickedObject().clickedObject ?? hoveredObject ?? null;
12601
+ if (isBlob(clickedObject)) {
12602
+ arrowVisible = true;
12603
+ arrowOffset = width / 2 + width * offsetStepCalc(clickedObject);
12604
+ }
12605
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
12606
+ className: "relative",
12607
+ children: /* @__PURE__ */ jsxs("div", {
12608
+ className: "relative flex text-xs whitespace-nowrap",
12609
+ children: [[...Array(steps)].map((_, i) => {
12610
+ return steps >= 4 ? /* @__PURE__ */ jsx(MetricSegment, {
12611
+ className: i === 0 ? "rounded-l-sm" : i === steps - 1 ? "rounded-r-sm" : "",
12612
+ width,
12613
+ color: colorGenerator(i),
12614
+ text: textGenerator(i),
12615
+ top: i % 2 === 0
12616
+ }, i) : /* @__PURE__ */ jsx(TopMetricSegment, {
12617
+ width,
12618
+ color: colorGenerator(i),
12619
+ text: textGenerator(i)
12620
+ }, i);
12621
+ }), /* @__PURE__ */ jsx(LegendBarIndicator, {
12622
+ offset: arrowOffset,
12623
+ visible: arrowVisible
12624
+ })]
12625
+ })
12626
+ }) });
12627
+ }
12628
+ function MetricSegment({ className = "", width, color, text, top }) {
12629
+ if (top) return /* @__PURE__ */ jsxs("div", {
12630
+ className: "flex flex-col",
12631
+ style: { width: `${width}%` },
12632
+ children: [
12633
+ /* @__PURE__ */ jsx("div", {
12634
+ className: "h-5 truncate text-left",
12635
+ title: text,
12636
+ children: text
12637
+ }),
12638
+ /* @__PURE__ */ jsx(Tick, { className: "ml-1" }),
12639
+ /* @__PURE__ */ jsx("div", {
12640
+ className: cn("h-4", className),
12641
+ style: { backgroundColor: color }
12642
+ }),
12643
+ /* @__PURE__ */ jsx(Tick, { className: "invisible" })
12644
+ ]
12645
+ });
12646
+ else return /* @__PURE__ */ jsxs("div", {
12647
+ className: "flex flex-col",
12648
+ style: { width: `${width}%` },
12649
+ children: [
12650
+ /* @__PURE__ */ jsx("div", {
12651
+ className: "invisible h-5 text-left",
12652
+ children: text
12653
+ }),
12654
+ /* @__PURE__ */ jsx(Tick, { className: "invisible" }),
12655
+ /* @__PURE__ */ jsx("div", {
12656
+ className: cn("h-4", className),
12657
+ style: { backgroundColor: color }
12658
+ }),
12659
+ /* @__PURE__ */ jsx(Tick, {}),
12660
+ /* @__PURE__ */ jsx("div", {
12661
+ className: "h-5 truncate text-left",
12662
+ children: text
12663
+ })
12664
+ ]
12665
+ });
12666
+ }
12667
+ function TopMetricSegment({ width, color, text }) {
12668
+ return /* @__PURE__ */ jsxs("div", {
12669
+ className: "flex flex-col",
12670
+ style: { width: `${width}%` },
12671
+ children: [
12672
+ /* @__PURE__ */ jsx("div", {
12673
+ className: "h-5 truncate text-left",
12674
+ children: text
12675
+ }),
12676
+ /* @__PURE__ */ jsx(Tick, {}),
12677
+ /* @__PURE__ */ jsx("div", {
12678
+ className: "h-5",
12679
+ style: { backgroundColor: color }
12680
+ })
12681
+ ]
12682
+ });
12683
+ }
12684
+ function PercentageSlider({ className = "" }) {
12685
+ const { dominantAuthorCutoff, setDominantAuthorCutoff } = useOptions();
12686
+ const [displayPercentage, setDisplayPercentage] = useState(dominantAuthorCutoff);
12687
+ const domain = [0, 100];
12688
+ return /* @__PURE__ */ jsxs("div", {
12689
+ className: cn("grid grid-cols-[calc(11*var(--spacing))_1fr] px-3", className),
12690
+ children: [/* @__PURE__ */ jsxs("p", {
12691
+ title: "Min percentage of changes a file's top author must account for to be colored.",
12692
+ children: [displayPercentage, "%"]
12693
+ }), /* @__PURE__ */ jsx("div", {
12694
+ className: "relative",
12695
+ title: "Adjust the dominant author percentage cutoff",
12696
+ children: /* @__PURE__ */ jsxs(Slider, {
12697
+ mode: 1,
12698
+ step: 1,
12699
+ domain,
12700
+ values: [displayPercentage],
12701
+ onChange: (e) => {
12702
+ setDominantAuthorCutoff(e[0]);
12703
+ startTransition(() => {
12704
+ setDisplayPercentage(e[0]);
12705
+ });
12706
+ },
12707
+ onUpdate: (e) => {
12708
+ startTransition(() => {
12709
+ setDisplayPercentage(e[0]);
12710
+ });
12711
+ },
12712
+ children: [
12713
+ /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12714
+ /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: handles.map((handle) => /* @__PURE__ */ jsx(Handle, {
12715
+ handle,
12716
+ domain,
12717
+ getHandleProps
12718
+ }, handle.id)) }) }),
12719
+ /* @__PURE__ */ jsx(Tracks, {
12720
+ right: false,
12721
+ children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: tracks.map(({ id, ...props }) => /* @__PURE__ */ createElement(Track, {
12722
+ ...props,
12723
+ key: id,
12724
+ backgroundColor: noEntryColor,
12725
+ getTrackProps
12726
+ })) })
12727
+ }),
12728
+ /* @__PURE__ */ jsx(LabeledTicks, { valueMap: {
12729
+ 0: "Top author",
12730
+ 50: "Majority author",
12731
+ 100: "Single author"
12732
+ } })
12733
+ ]
12734
+ })
12735
+ })]
12736
+ });
12737
+ }
12738
+ function Legend({ hoveredObject }) {
12739
+ const { sizeMetric, metricType } = useOptions();
12740
+ const [metricsData] = useMetrics();
12741
+ const deferredHoveredObject = useDeferredValue(hoveredObject);
12742
+ const metricCache = metricsData.get(metricType) ?? void 0;
12743
+ const legend = useMemo(() => {
12744
+ if (metricCache === void 0) return null;
12745
+ switch (getMetricLegendType(metricType)) {
12746
+ case "POINT": return /* @__PURE__ */ jsx(PointLegend, {});
12747
+ case "GRADIENT": return /* @__PURE__ */ jsx(GradientLegend, { hoveredObject: deferredHoveredObject });
12748
+ case "SEGMENTS": return /* @__PURE__ */ jsx(SegmentLegend, { hoveredObject: deferredHoveredObject });
12749
+ }
12750
+ }, [
12751
+ metricCache,
12752
+ deferredHoveredObject,
12753
+ metricType
12754
+ ]);
12755
+ if (legend === null) return null;
12756
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12757
+ /* @__PURE__ */ jsx("h3", {
12758
+ className: "card__subtitle",
12759
+ children: "Size legend"
12760
+ }),
12761
+ /* @__PURE__ */ jsx("p", {
12762
+ className: "mb-2 text-sm",
12763
+ children: sizeMetricLegendDescriptions[sizeMetric]
12764
+ }),
12765
+ /* @__PURE__ */ jsx("h3", {
12766
+ className: "card__subtitle",
12767
+ children: "Color legend"
12768
+ }),
12769
+ /* @__PURE__ */ jsx("p", {
12770
+ className: "mb-4 text-sm",
12771
+ children: colorMetricDescriptions[metricType]
12772
+ }),
12773
+ metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(PercentageSlider, { className: "my-4" }) : null,
12774
+ legend,
12775
+ metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(AuthorOptions, {}) : null
12776
+ ] });
12777
+ }
12778
+ function LoadingIndicator({ className = "", showProgress = false, fetchProgress = true, loadingText }) {
12779
+ const [path$1] = useQueryState("path");
12780
+ const fetcher = useFetcher();
12781
+ useEffect(() => {
12782
+ if (fetcher.state === "idle" && showProgress && fetchProgress) fetcher.load(href("/view/progress") + viewSerializer({ path: path$1 }));
12783
+ }, [
12784
+ fetchProgress,
12785
+ fetcher,
12786
+ fetcher.state,
12787
+ path$1,
12788
+ showProgress
12789
+ ]);
12790
+ const [progressText, progress] = useMemo(() => {
12791
+ if (!fetcher.data) return ["Loading truck...", 0];
12792
+ const { progress: progress$1, analyzationStatus } = fetcher.data;
12793
+ switch (analyzationStatus) {
12794
+ case "Starting": return ["Loading truck...", 0];
12795
+ case "GeneratingChart": return ["Unloading truck...", 100];
12796
+ default: return [progress$1 < 100 ? "Driving to destination..." : "Parking truck...", progress$1];
12797
+ }
12798
+ }, [fetcher.data]);
12799
+ return /* @__PURE__ */ jsx("div", {
12800
+ className: clsx$1("grid h-full w-full place-items-center px-4", className),
12801
+ children: /* @__PURE__ */ jsxs("div", {
12802
+ className: "flex w-full max-w-[clamp(16rem,80vw,36rem)] flex-col gap-4 px-2 py-2",
12803
+ children: [
12804
+ /* @__PURE__ */ jsx("img", {
12805
+ src: !showProgress || progress > 0 && progress < 100 ? truck_default$1 : truck_default,
12806
+ alt: "🚛",
12807
+ className: "pixelated aspect-square w-full"
12808
+ }),
12809
+ showProgress ? /* @__PURE__ */ jsx("div", {
12810
+ className: "text-center text-3xl font-bold",
12811
+ children: progressText
12812
+ }) : null,
12813
+ showProgress ? /* @__PURE__ */ jsx("div", {
12814
+ className: "h-6 w-3/4 self-center rounded-2xl bg-gray-300",
12815
+ children: /* @__PURE__ */ jsx("div", {
12816
+ className: cn("bg-blue-primary h-[calc(100%-4px)] min-w-[calc(var(--spacing)*6-4px)] translate-x-0.5 translate-y-0.5 rounded-2xl transition-[width] ease-in-out", { "animate-skeet": !progress }),
12817
+ style: { width: progress ? `calc(${Math.min(progress, 100)}% - 4px)` : "40px" }
12818
+ })
12819
+ }) : null,
12820
+ loadingText ? /* @__PURE__ */ jsx("div", {
12821
+ className: "text-center font-bold",
12822
+ children: loadingText
12823
+ }) : null
12824
+ ]
12825
+ })
12826
+ });
12827
+ }
12828
+ function Providers({ children, data }) {
12829
+ const [options, setOptions] = useState(() => {
12830
+ const savedOptions = typeof document !== "undefined" ? localStorage.getItem(OPTIONS_LOCAL_STORAGE_KEY) : null;
12831
+ return {
12832
+ ...getDefaultOptionsContextValue(),
12833
+ ...savedOptions ? JSON.parse(savedOptions) : {},
12834
+ hasLoadedSavedOptions: !!savedOptions
12835
+ };
12836
+ });
12837
+ const [searchResults, setSearchResults] = useState({});
12838
+ const hasSearchResults = useMemo(() => Object.values(searchResults).length > 0, [searchResults]);
12839
+ const prefersLight = usePrefersLightMode();
12840
+ const [zoomPath] = useQueryState("zoomPath");
12841
+ const databaseInfo = useMemo(() => ({
12842
+ ...data.databaseInfo,
12843
+ fileTree: findSubTree(data.databaseInfo.fileTree, zoomPath ?? void 0)
12844
+ }), [data.databaseInfo, zoomPath]);
12845
+ const metricsData = useMemo(() => {
12846
+ return createMetricData({
12847
+ ...data,
12848
+ databaseInfo
12849
+ }, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12850
+ }, [
12851
+ data,
12852
+ databaseInfo,
12853
+ options?.dominantAuthorCutoff,
12854
+ prefersLight
12855
+ ]);
12856
+ const optionsValue = useMemo(() => ({
12857
+ ...options,
12858
+ setMetricType: (metricType) => setOptions((prevOptions) => ({
12859
+ ...prevOptions,
12860
+ metricType
12861
+ })),
12862
+ setChartType: (chartType) => setOptions((prevOptions) => ({
12863
+ ...prevOptions,
12864
+ chartType
12865
+ })),
12866
+ setHierarchyType: (hierarchyType) => setOptions((prevOptions) => ({
12867
+ ...prevOptions,
12868
+ hierarchyType
12869
+ })),
12870
+ setCommitSearch: (commitSearch) => setOptions((prevOptions) => ({
12871
+ ...prevOptions,
12872
+ commitSearch
12873
+ })),
12874
+ setSizeMetricType: (sizeMetric) => setOptions((prevOptions) => ({
12875
+ ...prevOptions,
12876
+ sizeMetric
12877
+ })),
12459
12878
  setHoveredBlob: (blob) => setOptions((prevOptions) => ({
12460
12879
  ...prevOptions,
12461
12880
  hoveredBlob: blob
@@ -12501,7 +12920,10 @@ function Providers({ children, data }) {
12501
12920
  };
12502
12921
  }, [options]);
12503
12922
  return /* @__PURE__ */ jsx(DataContext.Provider, {
12504
- value: data,
12923
+ value: {
12924
+ ...data,
12925
+ databaseInfo
12926
+ },
12505
12927
  children: /* @__PURE__ */ jsx(MetricsContext.Provider, {
12506
12928
  value: metricsData,
12507
12929
  children: /* @__PURE__ */ jsx(OptionsContext.Provider, {
@@ -12531,6 +12953,7 @@ function findSearchResults(tree, searchString) {
12531
12953
  }
12532
12954
  const SearchCard = memo(function SearchCard$1() {
12533
12955
  const searchFieldRef = useRef(null);
12956
+ const [zoomPath] = useQueryState("zoomPath");
12534
12957
  const [isTransitioning, startTransition$1] = useTransition();
12535
12958
  const [searchText, setSearchText] = useState("");
12536
12959
  const { clickedObject, setClickedObject } = useClickedObject();
@@ -12538,6 +12961,13 @@ const SearchCard = memo(function SearchCard$1() {
12538
12961
  const searchResultsArray = useMemo(() => Object.values(searchResults), [searchResults]);
12539
12962
  const id = useId();
12540
12963
  const { databaseInfo } = useData();
12964
+ useEffect(() => {
12965
+ setSearchResults(searchText.length > 0 ? findSearchResults(databaseInfo.fileTree, searchText) : {});
12966
+ }, [
12967
+ searchText,
12968
+ setSearchResults,
12969
+ databaseInfo.fileTree
12970
+ ]);
12541
12971
  const options = useOptions();
12542
12972
  const [metrics] = useMetrics();
12543
12973
  const resultRefs = Object.keys(searchResults).map(() => createRef());
@@ -12548,15 +12978,13 @@ const SearchCard = memo(function SearchCard$1() {
12548
12978
  event.preventDefault();
12549
12979
  searchFieldRef.current?.focus();
12550
12980
  });
12551
- function onClickObject(object) {
12552
- setClickedObject(object);
12553
- }
12554
12981
  function focusResultAtIndex(nextIndex) {
12555
12982
  resultRefs[nextIndex].current?.focus();
12556
12983
  }
12557
12984
  const items = Object.values(searchResults);
12985
+ const helpText = `Search within ${zoomPath ? zoomPath.split(getSeparator(zoomPath)).at(-1) : databaseInfo.repo}`;
12558
12986
  return /* @__PURE__ */ jsxs("form", {
12559
- 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",
12987
+ 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",
12560
12988
  onSubmit: (event) => {
12561
12989
  event.preventDefault();
12562
12990
  setSearchText("");
@@ -12568,20 +12996,19 @@ const SearchCard = memo(function SearchCard$1() {
12568
12996
  type: "submit",
12569
12997
  children: /* @__PURE__ */ jsx(Icon, {
12570
12998
  path: mdiClose,
12571
- size: "1em",
12572
- className: ""
12999
+ size: "1em"
12573
13000
  })
12574
13001
  }),
12575
13002
  /* @__PURE__ */ jsxs("label", {
12576
13003
  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",
12577
- title: "Search for a file or folder",
13004
+ title: helpText,
12578
13005
  children: [
12579
13006
  /* @__PURE__ */ jsx("input", {
12580
13007
  ref: searchFieldRef,
12581
13008
  className: "peer w-full grow placeholder-shown:not-focus:w-0 placeholder-shown:not-focus:min-w-0",
12582
13009
  id,
12583
13010
  type: "search",
12584
- placeholder: "Search for a file or folder...",
13011
+ placeholder: helpText,
12585
13012
  value: searchText,
12586
13013
  onKeyDown: (event) => {
12587
13014
  if (event.key === "Enter") event.preventDefault();
@@ -12630,7 +13057,7 @@ const SearchCard = memo(function SearchCard$1() {
12630
13057
  ]
12631
13058
  }),
12632
13059
  searchResultsArray.length > 0 ? /* @__PURE__ */ jsx("div", {
12633
- 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",
13060
+ 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",
12634
13061
  children: items.map((object, i) => /* @__PURE__ */ jsxs("button", {
12635
13062
  ref: resultRefs[i],
12636
13063
  className: "flex cursor-pointer items-center justify-start gap-2 text-sm font-bold",
@@ -12652,508 +13079,239 @@ const SearchCard = memo(function SearchCard$1() {
12652
13079
  event.preventDefault();
12653
13080
  if (resultRefs.length === 0) return;
12654
13081
  const prevIndex = currentIndex - 1;
12655
- if (prevIndex < 0) {
12656
- searchFieldRef.current?.focus();
12657
- searchFieldRef.current?.select();
12658
- return;
12659
- }
12660
- focusResultAtIndex(prevIndex);
12661
- }
12662
- },
12663
- onClick: () => onClickObject(object),
12664
- children: [object.type === "tree" ? /* @__PURE__ */ jsx(Icon, {
12665
- path: object.type === "tree" ? mdiFolder : mdiFileOutline,
12666
- size: .75,
12667
- className: "shrink-0"
12668
- }) : /* @__PURE__ */ jsx(Icon, {
12669
- color: metrics.get(options.metricType)?.colormap.get(object.path) ?? "grey",
12670
- path: mdiFile,
12671
- size: .75,
12672
- className: "shrink-0"
12673
- }), /* @__PURE__ */ jsx("span", {
12674
- className: "text-secondary-text dark:hover:text-primary-text-dark hover:text-primary-text dark:text-secondary-text-dark truncate",
12675
- children: object.path.split(getSeparator(object.path)).slice(1).join("/") ?? object.path
12676
- })]
12677
- }, object.path))
12678
- }) : null
12679
- ]
12680
- });
12681
- });
12682
- var BarChart = () => {
12683
- const svgRef = useRef(null);
12684
- const { databaseInfo } = useData();
12685
- const [ref, rawSize] = useComponentSize();
12686
- const size = useDeferredValue(rawSize);
12687
- const data = databaseInfo.commitCountPerTimeInterval;
12688
- const [start, end] = databaseInfo.selectedRange;
12689
- const height = 50;
12690
- const width = size.width;
12691
- const xScale = d3.scaleBand().domain(data.map((d) => d.date.toString())).range([0, width]);
12692
- const yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count) || 0]).range([height, 0]);
12693
- const [searchParams] = useSearchParams();
12694
- const submit = useSubmit();
12695
- function updateTimeseries(e) {
12696
- const form = new FormData();
12697
- form.append("timeseries", `${e[0]}-${e[1]}`);
12698
- submit(form, {
12699
- action: getPathFromRepoAndHead({
12700
- path: searchParams.get("path"),
12701
- branch: databaseInfo.branch
12702
- }),
12703
- method: "post"
12704
- });
12705
- }
12706
- return /* @__PURE__ */ jsx("div", {
12707
- ref,
12708
- className: "flex flex-col justify-center",
12709
- children: /* @__PURE__ */ jsx("svg", {
12710
- ref: svgRef,
12711
- width: "100%",
12712
- height,
12713
- className: "fill-transparent",
12714
- children: data.map((d, i) => {
12715
- const barX = (xScale(d.date) ?? 0) + 2;
12716
- const barWidth = Math.max(1, xScale.bandwidth() - 4);
12717
- const barHeight = height - yScale(d.count);
12718
- return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
12719
- x: barX,
12720
- y: yScale(d.count),
12721
- width: barWidth,
12722
- height: barHeight,
12723
- rx: 2,
12724
- ry: 2,
12725
- className: cn("transition-[height,y] duration-300 ease-out", d.timestamp >= start && d.timestamp < end ? "fill-blue-primary" : "fill-blue-primary/40")
12726
- }), /* @__PURE__ */ jsx("rect", {
12727
- x: barX,
12728
- y: 0,
12729
- width: barWidth,
12730
- height,
12731
- rx: 2,
12732
- ry: 2,
12733
- className: "hover:stroke-blue-secondary stroke-transparent stroke-1 hover:fill-transparent",
12734
- onClick: () => {
12735
- const [intervalStart, intervalEnd] = expandIntervalToRange(d.timestamp, databaseInfo.commitCountPerTimeIntervalUnit);
12736
- updateTimeseries([intervalStart, intervalEnd]);
12737
- },
12738
- children: /* @__PURE__ */ jsx("title", { children: `${d.date}: ${d.count.toLocaleString()} commit${d.count !== 1 ? "s" : ""}` })
12739
- })] }, `${d.date}-${i}`);
12740
- })
12741
- })
12742
- });
12743
- };
12744
- var BarChart_default = BarChart;
12745
- function Timeline({ className }) {
12746
- const [_, startTransition$1] = useTransition();
12747
- const { databaseInfo } = useData();
12748
- const { timerange, selectedRange } = databaseInfo;
12749
- const newestChangeDate = timerange[1];
12750
- const oldestChangeDate = timerange[0];
12751
- const submit = useSubmit();
12752
- const [range, setRange] = useState(selectedRange[0] === 0 ? timerange : selectedRange);
12753
- const disabled = useNavigation().state !== "idle";
12754
- const [searchParams] = useSearchParams();
12755
- function updateTimeseries(e) {
12756
- const form = new FormData();
12757
- form.append("timeseries", `${e[0]}-${e[1]}`);
12758
- submit(form, {
12759
- action: getPathFromRepoAndHead({
12760
- path: searchParams.get("path"),
12761
- branch: databaseInfo.branch
12762
- }),
12763
- method: "post"
12764
- });
12765
- }
12766
- const selectedStartDate = range[0] * 1e3;
12767
- const selectedEndDate = range[1] * 1e3;
12768
- return /* @__PURE__ */ jsxs("div", {
12769
- className: cn("group flex flex-col gap-2", className),
12770
- children: [
12771
- /* @__PURE__ */ jsx(BarChart_default, {}),
12772
- /* @__PURE__ */ jsxs(Slider, {
12773
- className: "relative",
12774
- mode: 2,
12775
- step: 1,
12776
- domain: timerange,
12777
- values: range,
12778
- disabled,
12779
- onUpdate: (e) => setRange([...e]),
12780
- onChange: (e) => {
12781
- startTransition$1(() => {
12782
- updateTimeseries(e);
12783
- });
12784
- },
12785
- children: [
12786
- /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12787
- /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx("div", {
12788
- className: "",
12789
- children: handles.map((handle, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
12790
- className: cn("absolute left-(--left) w-max translate-y-[calc(50%)] opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100", {
12791
- "left-(--left)": i === 0,
12792
- "right-[calc(100%-var(--left))] -translate-x-full": i === 1
12793
- }),
12794
- style: { "--left": `${handle.percent}%` },
12795
- children: /* @__PURE__ */ jsx(Popover$1, {
12796
- trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
12797
- className: cn("bg-blue-primary text-primary-text-dark cursor-pointer rounded-lg px-1 break-keep shadow", {
12798
- "rounded-tl-none": i === 0,
12799
- "rounded-tr-none": i === 1
12800
- }),
12801
- onClick,
12802
- children: /* @__PURE__ */ jsx("time", {
12803
- className: "w-max text-xs",
12804
- dateTime: dateFormatISO(i === 0 ? selectedStartDate : selectedEndDate),
12805
- title: `Click or drag to set the ${i === 0 ? "start" : "end"} of time range`,
12806
- children: dateFormatShort(i === 0 ? selectedStartDate : selectedEndDate)
12807
- })
12808
- }),
12809
- children: /* @__PURE__ */ jsx(TimePicker, {
12810
- setsBeginning: i === 0,
12811
- range,
12812
- setRange,
12813
- timerange,
12814
- updateTimeseries,
12815
- disabled
12816
- })
12817
- }, handle.id)
12818
- }), /* @__PURE__ */ jsx(Handle, {
12819
- handleType: "square",
12820
- title: "Drag to adjust interval, click to set specific date",
12821
- className: cn(""),
12822
- handle,
12823
- domain: timerange,
12824
- getHandleProps
12825
- }, handle.id)] }, handle.id))
12826
- }) }),
12827
- /* @__PURE__ */ jsx(Tracks, {
12828
- left: false,
12829
- right: false,
12830
- children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx("div", { children: tracks.map(({ id, source, target }) => /* @__PURE__ */ jsx(Track, {
12831
- trackType: "square",
12832
- backgroundColor: "#7aa0c4",
12833
- source,
12834
- target,
12835
- getTrackProps
12836
- }, id)) })
12837
- })
12838
- ]
12839
- }),
12840
- /* @__PURE__ */ jsx(TicksByCount, {
12841
- className: "mb-2",
12842
- count: 2,
12843
- tickToLabel: (t) => dateFormatShort((oldestChangeDate + (newestChangeDate - oldestChangeDate) * t) * 1e3)
12844
- })
12845
- ]
12846
- });
12847
- }
12848
- function TimePicker({ range, setRange, timerange, setsBeginning, updateTimeseries, disabled }) {
12849
- return /* @__PURE__ */ jsx("div", {
12850
- className: "z-30",
12851
- children: /* @__PURE__ */ jsx(DatePicker, {
12852
- inline: true,
12853
- fixedHeight: true,
12854
- renderCustomHeader: ({ date, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => {
12855
- return /* @__PURE__ */ jsxs("div", {
12856
- className: "m-2 flex justify-between",
12857
- children: [
12858
- /* @__PURE__ */ jsx("button", {
12859
- disabled: prevMonthButtonDisabled,
12860
- onClick: decreaseMonth,
12861
- children: "<"
12862
- }),
12863
- /* @__PURE__ */ jsx("h2", { children: dateFormatCalendarHeader(date.getTime()) }),
12864
- /* @__PURE__ */ jsx("button", {
12865
- disabled: nextMonthButtonDisabled,
12866
- onClick: increaseMonth,
12867
- children: ">"
12868
- })
12869
- ]
12870
- });
12871
- },
12872
- disabled,
12873
- selected: /* @__PURE__ */ new Date(range[setsBeginning ? 0 : 1] * 1e3),
12874
- minDate: new Date(setsBeginning ? timerange[0] * 1e3 : Math.max(timerange[0] * 1e3, range[0] * 1e3)),
12875
- maxDate: new Date(setsBeginning ? Math.min(timerange[1] * 1e3, range[1] * 1e3) : timerange[1] * 1e3),
12876
- onChange: (x) => {
12877
- if (x) {
12878
- let newRange;
12879
- if (setsBeginning) newRange = [x.getTime() / 1e3, range[1]];
12880
- else newRange = [range[0], x.getTime() / 1e3];
12881
- setRange(newRange);
12882
- updateTimeseries(newRange);
12883
- }
12884
- }
12885
- })
13082
+ if (prevIndex < 0) {
13083
+ searchFieldRef.current?.focus();
13084
+ searchFieldRef.current?.select();
13085
+ return;
13086
+ }
13087
+ focusResultAtIndex(prevIndex);
13088
+ }
13089
+ },
13090
+ onClick: () => setClickedObject(object),
13091
+ children: [object.type === "tree" ? /* @__PURE__ */ jsx(Icon, {
13092
+ path: object.type === "tree" ? mdiFolder : mdiFileOutline,
13093
+ size: .75,
13094
+ className: "shrink-0"
13095
+ }) : /* @__PURE__ */ jsx(Icon, {
13096
+ color: metrics.get(options.metricType)?.colormap.get(object.path) ?? "grey",
13097
+ path: mdiFile,
13098
+ size: .75,
13099
+ className: "shrink-0"
13100
+ }), /* @__PURE__ */ jsx("span", {
13101
+ className: "text-secondary-text dark:hover:text-primary-text-dark hover:text-primary-text dark:text-secondary-text-dark truncate",
13102
+ children: object.path.split(getSeparator(object.path)).slice(1).join("/") ?? object.path
13103
+ })]
13104
+ }, object.path))
13105
+ }) : null
13106
+ ]
12886
13107
  });
12887
- }
12888
- function UnionAuthorsModal({ open, onClose }) {
13108
+ });
13109
+ var BarChart = () => {
13110
+ const svgRef = useRef(null);
12889
13111
  const { databaseInfo } = useData();
13112
+ const [ref, rawSize] = useComponentSize();
13113
+ const size = useDeferredValue(rawSize);
13114
+ const data = databaseInfo.commitCountPerTimeInterval;
13115
+ const [start, end] = databaseInfo.selectedRange;
13116
+ const height = 50;
13117
+ const width = size.width;
13118
+ const xScale = d3.scaleBand().domain(data.map((d) => d.date.toString())).range([0, width]);
13119
+ const yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count) || 0]).range([height, 0]);
13120
+ const [searchParams] = useSearchParams();
12890
13121
  const submit = useSubmit();
12891
- const { authors } = databaseInfo;
12892
- const authorUnions = databaseInfo.authorUnions;
12893
- const [selectedAuthors, setSelectedAuthors] = useState([]);
12894
- const [filter, setFilter] = useState("");
12895
- const navigationData = useNavigation();
12896
- const [, authorColors] = useMetrics();
12897
- const [, startTransition$1] = useTransition();
12898
- const location$1 = useLocation();
12899
- const ref = useRef(null);
12900
- useEffect(() => {
12901
- if (!ref.current) return;
12902
- if (open) {
12903
- ref.current.showModal();
12904
- return;
12905
- }
12906
- ref.current.close();
12907
- }, [open]);
12908
- const flattedUnionedAuthors = authorUnions.reduce((acc, union) => {
12909
- return [...acc, ...union];
12910
- }, []).sort(stringSorter);
12911
- function ungroup(groupToUnGroup) {
12912
- const newAuthorUnions = authorUnions.filter((_, i) => i !== groupToUnGroup);
12913
- const form = new FormData();
12914
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12915
- submit(form, {
12916
- action: href("/view") + location$1.search,
12917
- method: "post"
12918
- });
12919
- }
12920
- function ungroupAll() {
12921
- const form = new FormData();
12922
- form.append("unionedAuthors", JSON.stringify([]));
12923
- submit(form, {
12924
- action: href("/view") + location$1.search,
12925
- method: "post"
12926
- });
12927
- }
12928
- function groupSelectedAuthors() {
12929
- if (selectedAuthors.length === 0) return;
12930
- const form = new FormData();
12931
- form.append("unionedAuthors", JSON.stringify([...authorUnions, selectedAuthors]));
12932
- submit(form, {
12933
- action: href("/view") + location$1.search,
12934
- method: "post"
12935
- });
12936
- setSelectedAuthors([]);
12937
- }
12938
- function makePrimaryAlias(alias, groupIndex) {
12939
- const newAuthorUnions = authorUnions.map((group, i) => {
12940
- if (i === groupIndex) return [alias, ...group.filter((a) => a !== alias)];
12941
- return group;
12942
- });
13122
+ function updateTimeseries(e) {
12943
13123
  const form = new FormData();
12944
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
13124
+ form.append("timeseries", `${e[0]}-${e[1]}`);
12945
13125
  submit(form, {
12946
- action: href("/view") + location$1.search,
13126
+ action: getPathFromRepoAndHead({
13127
+ path: searchParams.get("path"),
13128
+ branch: databaseInfo.branch
13129
+ }),
12947
13130
  method: "post"
12948
13131
  });
12949
13132
  }
12950
- const disabled = navigationData.state !== "idle";
12951
- const ungroupedAuthorsSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
12952
- useKey({ key: "Escape" }, onClose);
12953
- const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
12954
- if (!open) return null;
12955
- const ungroupedAuthorsMessage = ungroupedAuthorsSorted.length === 0 ? "All detected authors have been grouped" : "Select the authors that you know are the same person";
12956
- const ungroupedAuthorsFiltered = ungroupedAuthorsSorted.filter((author) => author.toLowerCase().includes(filter.toLowerCase()));
12957
- const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => /* @__PURE__ */ jsx(CheckboxWithLabel, {
12958
- className: "hover:opacity-70",
12959
- checked: selectedAuthors.includes(author),
12960
- onChange: (e) => {
12961
- setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
12962
- },
12963
- children: /* @__PURE__ */ jsxs("div", {
12964
- className: "inline-flex flex-row place-items-center gap-2",
12965
- children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
13133
+ return /* @__PURE__ */ jsx("div", {
13134
+ ref,
13135
+ className: "flex flex-col justify-center",
13136
+ children: /* @__PURE__ */ jsx("svg", {
13137
+ ref: svgRef,
13138
+ width: "100%",
13139
+ height,
13140
+ className: "fill-transparent",
13141
+ children: data.map((d, i) => {
13142
+ const barX = (xScale(d.date) ?? 0) + 2;
13143
+ const barWidth = Math.max(1, xScale.bandwidth() - 4);
13144
+ const barHeight = height - yScale(d.count);
13145
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
13146
+ x: barX,
13147
+ y: yScale(d.count),
13148
+ width: barWidth,
13149
+ height: barHeight,
13150
+ rx: 2,
13151
+ ry: 2,
13152
+ className: cn("transition-[height,y] duration-300 ease-out", d.timestamp >= start && d.timestamp < end ? "fill-blue-primary" : "fill-blue-primary/40")
13153
+ }), /* @__PURE__ */ jsx("rect", {
13154
+ x: barX,
13155
+ y: 0,
13156
+ width: barWidth,
13157
+ height,
13158
+ rx: 2,
13159
+ ry: 2,
13160
+ className: "hover:stroke-blue-secondary stroke-transparent stroke-1 hover:fill-transparent",
13161
+ onClick: () => {
13162
+ const [intervalStart, intervalEnd] = expandIntervalToRange(d.timestamp, databaseInfo.commitCountPerTimeIntervalUnit);
13163
+ updateTimeseries([intervalStart, intervalEnd]);
13164
+ },
13165
+ children: /* @__PURE__ */ jsx("title", { children: `${d.date}: ${d.count.toLocaleString()} commit${d.count !== 1 ? "s" : ""}` })
13166
+ })] }, `${d.date}-${i}`);
13167
+ })
12966
13168
  })
12967
- }, author));
12968
- const groupedAuthorsEntries = authorUnions.map((aliasGroup, aliasGroupIndex) => {
12969
- const displayName = aliasGroup[0];
12970
- const disabled$1 = navigationData.state !== "idle";
12971
- return /* @__PURE__ */ jsxs("div", {
12972
- className: "card group m-0 flex h-full flex-col p-2",
12973
- children: [
12974
- /* @__PURE__ */ jsxs("div", {
12975
- className: "inline-flex flex-row place-items-center gap-2",
12976
- children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(displayName) }), /* @__PURE__ */ jsx("b", {
12977
- className: "truncate",
12978
- title: displayName,
12979
- children: displayName
12980
- })]
12981
- }),
12982
- aliasGroup.slice(1).sort(stringSorter).map((alias) => /* @__PURE__ */ jsx(AliasEntry, {
12983
- alias,
12984
- disabled: disabled$1,
12985
- onClick: () => makePrimaryAlias(alias, aliasGroupIndex)
12986
- }, alias)),
12987
- /* @__PURE__ */ jsx("div", { className: "grow" }),
12988
- /* @__PURE__ */ jsxs("div", {
12989
- className: "flex items-end justify-end gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
12990
- children: [/* @__PURE__ */ jsxs(Form, {
12991
- action: href("/view") + location$1.search,
12992
- method: "post",
12993
- children: [/* @__PURE__ */ jsx("input", {
12994
- type: "hidden",
12995
- name: "unionedAuthors",
12996
- value: JSON.stringify(authorUnions)
12997
- }), /* @__PURE__ */ jsx("button", {
12998
- className: "btn",
12999
- title: "Add selected authors to this group",
13000
- disabled: disabled$1 || selectedAuthors.length === 0,
13001
- onClick: () => {
13002
- const newAuthorUnions = authorUnions.map(([displayName$1, ...group], i) => {
13003
- if (i === aliasGroupIndex) return [
13004
- displayName$1,
13005
- ...group,
13006
- ...selectedAuthors
13007
- ];
13008
- return [displayName$1, ...group];
13009
- });
13010
- const form = new FormData();
13011
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
13012
- submit(form, {
13013
- action: href("/view") + location$1.search,
13014
- method: "post"
13015
- });
13016
- setSelectedAuthors([]);
13017
- },
13018
- children: "Add selected"
13019
- })]
13020
- }), /* @__PURE__ */ jsx("button", {
13021
- className: "btn",
13022
- title: "Ungroup",
13023
- disabled: disabled$1,
13024
- onClick: () => ungroup(aliasGroupIndex),
13025
- children: "Ungroup"
13026
- })]
13027
- })
13028
- ]
13029
- }, aliasGroupIndex);
13030
13169
  });
13031
- return createPortal(/* @__PURE__ */ jsx("dialog", {
13032
- ref,
13033
- "aria-modal": true,
13034
- className: "z-10 m-auto flex h-full w-full flex-col items-start justify-stretch bg-transparent text-inherit backdrop:bg-gray-500/75 backdrop:p-0",
13035
- children: /* @__PURE__ */ jsxs("div", {
13036
- className: "card m-auto grid h-full w-full max-w-(--breakpoint-2xl) grow grid-cols-[1fr_1fr] grid-rows-[max-content_max-content_max-content_1fr_max-content] gap-2 overflow-hidden p-2 shadow-sm",
13037
- children: [
13038
- /* @__PURE__ */ jsx("h2", {
13039
- className: "text-2xl",
13040
- children: "Group authors"
13041
- }),
13042
- /* @__PURE__ */ jsx(CloseButton, {
13043
- absolute: false,
13044
- className: "justify-self-end",
13045
- onClick: onClose
13046
- }),
13047
- /* @__PURE__ */ jsxs("h3", {
13048
- className: "text-center text-lg font-bold",
13049
- children: [
13050
- "Ungrouped authors (",
13051
- ungroupedAuthorsSorted.length,
13052
- ")"
13053
- ]
13054
- }),
13055
- /* @__PURE__ */ jsx("h3", {
13056
- className: "text-center text-lg font-bold",
13057
- children: "Grouped authors"
13058
- }),
13059
- /* @__PURE__ */ jsx("div", {
13060
- className: "flex justify-end gap-2",
13061
- children: /* @__PURE__ */ jsxs("button", {
13062
- className: "btn btn--primary justify-self-end",
13063
- title: "Group the selected authors",
13064
- disabled: disabled || selectedAuthors.length === 0,
13065
- onClick: groupSelectedAuthors,
13066
- children: [/* @__PURE__ */ jsx(Icon, {
13067
- path: mdiAccountMultiple,
13068
- size: 1
13069
- }), "Create group"]
13070
- })
13071
- }),
13072
- /* @__PURE__ */ jsx("div", {
13073
- className: "flex justify-end gap-4",
13074
- children: /* @__PURE__ */ jsx("button", {
13075
- className: "btn btn--danger",
13076
- disabled: disabled || authorUnions.length === 0,
13077
- onClick: () => {
13078
- if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
13079
- },
13080
- children: "Ungroup all"
13081
- })
13082
- }),
13083
- /* @__PURE__ */ jsx("div", {
13084
- className: "overflow-y-auto",
13085
- children: /* @__PURE__ */ jsxs("div", {
13086
- className: "flex h-min min-h-0 flex-col gap-2 rounded-md bg-white p-4 pt-2 shadow-sm dark:bg-gray-700",
13087
- children: [/* @__PURE__ */ jsxs("div", {
13088
- className: "sticky top-0 flex gap-2 bg-inherit pt-2",
13089
- children: [
13090
- /* @__PURE__ */ jsx("input", {
13091
- className: "input min-w-0",
13092
- type: "search",
13093
- placeholder: "Filter...",
13094
- disabled: ungroupedAuthorsSorted.length === 0,
13095
- onChange: (e) => startTransition$1(() => setFilter(e.target.value))
13096
- }),
13097
- /* @__PURE__ */ jsx("button", {
13098
- disabled: disabled || selectedAuthors.length === 0,
13099
- className: "btn btn--outlined w-max grow",
13100
- title: "Clear selection",
13101
- onClick: () => setSelectedAuthors([]),
13102
- children: "Clear"
13170
+ };
13171
+ var BarChart_default = BarChart;
13172
+ function Timeline({ className }) {
13173
+ const [_, startTransition$1] = useTransition();
13174
+ const { databaseInfo } = useData();
13175
+ const { timerange, selectedRange } = databaseInfo;
13176
+ const newestChangeDate = timerange[1];
13177
+ const oldestChangeDate = timerange[0];
13178
+ const submit = useSubmit();
13179
+ const [range, setRange] = useState(selectedRange[0] === 0 ? timerange : selectedRange);
13180
+ const disabled = useNavigation().state !== "idle";
13181
+ const [searchParams] = useSearchParams();
13182
+ function updateTimeseries(e) {
13183
+ const form = new FormData();
13184
+ form.append("timeseries", `${e[0]}-${e[1]}`);
13185
+ submit(form, {
13186
+ action: getPathFromRepoAndHead({
13187
+ path: searchParams.get("path"),
13188
+ branch: databaseInfo.branch
13189
+ }),
13190
+ method: "post"
13191
+ });
13192
+ }
13193
+ const selectedStartDate = range[0] * 1e3;
13194
+ const selectedEndDate = range[1] * 1e3;
13195
+ return /* @__PURE__ */ jsxs("div", {
13196
+ className: cn("group flex flex-col gap-2", className),
13197
+ children: [
13198
+ /* @__PURE__ */ jsx(BarChart_default, {}),
13199
+ /* @__PURE__ */ jsxs(Slider, {
13200
+ className: "relative",
13201
+ mode: 2,
13202
+ step: 1,
13203
+ domain: timerange,
13204
+ values: range,
13205
+ disabled,
13206
+ onUpdate: (e) => setRange([...e]),
13207
+ onChange: (e) => {
13208
+ startTransition$1(() => {
13209
+ updateTimeseries(e);
13210
+ });
13211
+ },
13212
+ children: [
13213
+ /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
13214
+ /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx("div", {
13215
+ className: "",
13216
+ children: handles.map((handle, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
13217
+ className: cn("absolute left-(--left) w-max translate-y-[calc(50%)] opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100", {
13218
+ "left-(--left)": i === 0,
13219
+ "right-[calc(100%-var(--left))] -translate-x-full": i === 1
13220
+ }),
13221
+ style: { "--left": `${handle.percent}%` },
13222
+ children: /* @__PURE__ */ jsx(Popover$1, {
13223
+ trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
13224
+ className: cn("bg-blue-primary text-primary-text-dark cursor-pointer rounded-lg px-1 break-keep shadow", {
13225
+ "rounded-tl-none": i === 0,
13226
+ "rounded-tr-none": i === 1
13227
+ }),
13228
+ onClick,
13229
+ children: /* @__PURE__ */ jsx("time", {
13230
+ className: "w-max text-xs",
13231
+ dateTime: dateFormatISO(i === 0 ? selectedStartDate : selectedEndDate),
13232
+ title: `Click or drag to set the ${i === 0 ? "start" : "end"} of time range`,
13233
+ children: dateFormatShort(i === 0 ? selectedStartDate : selectedEndDate)
13234
+ })
13103
13235
  }),
13104
- /* @__PURE__ */ jsx("button", {
13105
- disabled: ungroupedAuthorsSorted.length === 0,
13106
- className: "btn btn--outlined w-max grow",
13107
- title: "Clear selection",
13108
- onClick: () => selectedAuthors.length === ungroupedAuthorsFiltered.length ? setSelectedAuthors([]) : setSelectedAuthors((selected) => Array.from(new Set([...selected, ...ungroupedAuthorsFiltered]))),
13109
- children: selectedAuthors.length === ungroupedAuthorsFiltered.length ? "Deselect all" : "Select all"
13236
+ children: /* @__PURE__ */ jsx(TimePicker, {
13237
+ setsBeginning: i === 0,
13238
+ range,
13239
+ setRange,
13240
+ timerange,
13241
+ updateTimeseries,
13242
+ disabled
13110
13243
  })
13111
- ]
13112
- }), ungroupedAuthorsEntries.length > 0 ? ungroupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
13113
- className: "place-self-center",
13114
- children: filter.length > 0 ? "No authors found" : "All authors have been grouped"
13115
- })]
13244
+ }, handle.id)
13245
+ }), /* @__PURE__ */ jsx(Handle, {
13246
+ handleType: "square",
13247
+ title: "Drag to adjust interval, click to set specific date",
13248
+ className: cn(""),
13249
+ handle,
13250
+ domain: timerange,
13251
+ getHandleProps
13252
+ }, handle.id)] }, handle.id))
13253
+ }) }),
13254
+ /* @__PURE__ */ jsx(Tracks, {
13255
+ left: false,
13256
+ right: false,
13257
+ children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx("div", { children: tracks.map(({ id, source, target }) => /* @__PURE__ */ jsx(Track, {
13258
+ trackType: "square",
13259
+ backgroundColor: "#7aa0c4",
13260
+ source,
13261
+ target,
13262
+ getTrackProps
13263
+ }, id)) })
13116
13264
  })
13117
- }),
13118
- /* @__PURE__ */ jsx("div", {
13119
- className: "overflow-y-auto",
13120
- children: /* @__PURE__ */ jsx("div", {
13121
- 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",
13122
- children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
13123
- className: "place-self-center",
13124
- children: "No authors have been grouped yet"
13265
+ ]
13266
+ }),
13267
+ /* @__PURE__ */ jsx(TicksByCount, {
13268
+ className: "mb-2",
13269
+ count: 2,
13270
+ tickToLabel: (t) => dateFormatShort((oldestChangeDate + (newestChangeDate - oldestChangeDate) * t) * 1e3)
13271
+ })
13272
+ ]
13273
+ });
13274
+ }
13275
+ function TimePicker({ range, setRange, timerange, setsBeginning, updateTimeseries, disabled }) {
13276
+ return /* @__PURE__ */ jsx("div", {
13277
+ className: "z-30",
13278
+ children: /* @__PURE__ */ jsx(DatePicker, {
13279
+ inline: true,
13280
+ fixedHeight: true,
13281
+ renderCustomHeader: ({ date, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => {
13282
+ return /* @__PURE__ */ jsxs("div", {
13283
+ className: "m-2 flex justify-between",
13284
+ children: [
13285
+ /* @__PURE__ */ jsx("button", {
13286
+ disabled: prevMonthButtonDisabled,
13287
+ onClick: decreaseMonth,
13288
+ children: "<"
13289
+ }),
13290
+ /* @__PURE__ */ jsx("h2", { children: dateFormatCalendarHeader(date.getTime()) }),
13291
+ /* @__PURE__ */ jsx("button", {
13292
+ disabled: nextMonthButtonDisabled,
13293
+ onClick: increaseMonth,
13294
+ children: ">"
13125
13295
  })
13126
- })
13127
- }),
13128
- /* @__PURE__ */ jsxs("div", {
13129
- className: "col-span-2 mr-6 grid w-full grid-cols-2 gap-4",
13130
- children: [/* @__PURE__ */ jsx("p", { children: ungroupedAuthorsMessage }), /* @__PURE__ */ jsx("button", {
13131
- className: "btn btn--primary justify-self-end",
13132
- onClick: onClose,
13133
- children: "Done"
13134
- })]
13135
- })
13136
- ]
13296
+ ]
13297
+ });
13298
+ },
13299
+ disabled,
13300
+ selected: /* @__PURE__ */ new Date(range[setsBeginning ? 0 : 1] * 1e3),
13301
+ minDate: new Date(setsBeginning ? timerange[0] * 1e3 : Math.max(timerange[0] * 1e3, range[0] * 1e3)),
13302
+ maxDate: new Date(setsBeginning ? Math.min(timerange[1] * 1e3, range[1] * 1e3) : timerange[1] * 1e3),
13303
+ onChange: (x) => {
13304
+ if (x) {
13305
+ let newRange;
13306
+ if (setsBeginning) newRange = [x.getTime() / 1e3, range[1]];
13307
+ else newRange = [range[0], x.getTime() / 1e3];
13308
+ setRange(newRange);
13309
+ updateTimeseries(newRange);
13310
+ }
13311
+ }
13137
13312
  })
13138
- }), document.body);
13139
- function AliasEntry({ alias, onClick, disabled: disabled$1 }) {
13140
- return /* @__PURE__ */ jsxs("button", {
13141
- className: "btn flex grid-flow-col gap-2 text-sm [&:hover>svg]:opacity-50 [&>svg]:opacity-0",
13142
- disabled: disabled$1,
13143
- title: "Make display name for this grouping",
13144
- onClick,
13145
- children: [/* @__PURE__ */ jsx(Icon, {
13146
- path: mdiArrowUp,
13147
- size: .75
13148
- }), /* @__PURE__ */ jsx("label", {
13149
- title: alias,
13150
- className: "label truncate",
13151
- children: alias
13152
- })]
13153
- }, alias);
13154
- }
13313
+ });
13155
13314
  }
13156
- var stringSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
13157
13315
  function RefreshButton() {
13158
13316
  const navigation = useNavigation();
13159
13317
  const isRefreshing = navigation.formData?.get("refresh") === "true";
@@ -13351,6 +13509,18 @@ function ChartTooltip({ hoveredObject }) {
13351
13509
  className: chartType === "BUBBLE_CHART" ? "rounded-xl" : "rounded-xs"
13352
13510
  });
13353
13511
  }
13512
+ function FullscreenButton() {
13513
+ const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
13514
+ return /* @__PURE__ */ jsx("button", {
13515
+ className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
13516
+ title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
13517
+ onClick: toggleFullscreen,
13518
+ children: /* @__PURE__ */ jsx(Icon, {
13519
+ path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
13520
+ size: 1
13521
+ })
13522
+ });
13523
+ }
13354
13524
  var CollapsibleHeaderContext = createContext$1((_open) => {});
13355
13525
  function useSetOpenCollapsibleHeader() {
13356
13526
  return use(CollapsibleHeaderContext);
@@ -13405,124 +13575,15 @@ function RevisionSelect({ headGroups, disabled, className = "", analyzedBranches
13405
13575
  })]
13406
13576
  });
13407
13577
  }
13408
- function CollapsableSettings({ className = "" }) {
13409
- return /* @__PURE__ */ jsx(Popover$1, {
13410
- trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
13411
- title: "Visualization settings",
13412
- className: cn("btn hover:text-primary-text dark:hover:text-primary-text-dark relative flex cursor-pointer justify-between gap-2", className),
13413
- onClick,
13414
- children: /* @__PURE__ */ jsx(Icon, {
13415
- path: mdiCog,
13416
- size: "1.25em"
13417
- })
13418
- }),
13419
- children: /* @__PURE__ */ jsx(Settings, {})
13578
+ function SettingsButton() {
13579
+ const { openModal } = useModal("app-settings");
13580
+ return /* @__PURE__ */ jsx("button", {
13581
+ className: "btn hover:text-primary-text dark:hover:text-primary-text-dark relative flex cursor-pointer justify-between gap-2",
13582
+ title: "Visualization settings",
13583
+ onClick: () => openModal(),
13584
+ children: /* @__PURE__ */ jsx(Icon, { path: mdiCog })
13420
13585
  });
13421
13586
  }
13422
- function Settings() {
13423
- const { metricType, hierarchyType, transitionsEnabled, renderCutoff, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setRenderCutoff, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
13424
- const [isTransitioning, startTransition$1] = useTransition();
13425
- return /* @__PURE__ */ jsxs(Fragment, { children: [
13426
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13427
- className: "text-sm",
13428
- checked: Boolean(linkMetricAndSizeMetric),
13429
- title: "Enable to sync size metric with color metric",
13430
- onChange: (e) => {
13431
- setLinkMetricAndSizeMetric(e.target.checked);
13432
- if (e.target.checked) setSizeMetricType(relatedSizeMetric[metricType]);
13433
- },
13434
- children: [/* @__PURE__ */ jsx(Icon, {
13435
- className: "ml-1.5",
13436
- path: mdiLink,
13437
- size: "1.25em"
13438
- }), /* @__PURE__ */ jsx("span", { children: "Link size and color option" })]
13439
- }),
13440
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13441
- className: "text-sm",
13442
- checked: transitionsEnabled,
13443
- title: "Disable to improve performance when zooming",
13444
- onChange: (e) => setTransitionsEnabled(e.target.checked),
13445
- children: [/* @__PURE__ */ jsx(Icon, {
13446
- className: "ml-1.5",
13447
- path: mdiTransition,
13448
- size: "1.25em"
13449
- }), "Transitions"]
13450
- }),
13451
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13452
- className: "text-sm",
13453
- checked: labelsVisible,
13454
- title: "Disable to improve performance",
13455
- onChange: (e) => setLabelsVisible(e.target.checked),
13456
- children: [/* @__PURE__ */ jsx(Icon, {
13457
- className: "ml-1.5",
13458
- path: mdiLabel,
13459
- size: "1.25em"
13460
- }), "Labels"]
13461
- }),
13462
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13463
- className: "text-sm",
13464
- checked: showFilesWithoutChanges,
13465
- title: "Show files that have had no changes in the selected time range",
13466
- onChange: (e) => setShowFilesWithoutChanges(e.target.checked),
13467
- children: [/* @__PURE__ */ jsx(Icon, {
13468
- className: "ml-1.5",
13469
- path: mdiClockEdit,
13470
- size: "1.25em"
13471
- }), "Show files with no activity"]
13472
- }),
13473
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13474
- className: "text-sm",
13475
- checked: hierarchyType === "FLAT",
13476
- title: "Show all files on the same level, instead of as a file tree",
13477
- onChange: () => {
13478
- if (hierarchyType === "FLAT") setHierarchyType("NESTED");
13479
- else setHierarchyType("FLAT");
13480
- },
13481
- children: [/* @__PURE__ */ jsx(Icon, {
13482
- className: "ml-1.5",
13483
- path: mdiFileTree,
13484
- size: "1.25em"
13485
- }), "Flatten file tree"]
13486
- }),
13487
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13488
- className: "text-sm",
13489
- checked: showOnlySearchMatches,
13490
- title: "When searching, hide files that do not match the search query",
13491
- onChange: (e) => setShowOnlySearchMatches(e.target.checked),
13492
- children: [/* @__PURE__ */ jsx(Icon, {
13493
- className: "ml-1.5",
13494
- path: mdiFilter,
13495
- size: "1.25em"
13496
- }), "Show only search matches"]
13497
- }),
13498
- /* @__PURE__ */ jsxs("label", {
13499
- className: "label flex w-full items-center justify-start gap-2 text-sm",
13500
- title: "Increase this to improve render performance, decrease it to get higher level of detail",
13501
- children: [/* @__PURE__ */ jsxs("span", {
13502
- className: "flex grow items-center gap-2",
13503
- children: [
13504
- /* @__PURE__ */ jsx(Icon, {
13505
- className: "ml-1.5",
13506
- path: mdiContentCut,
13507
- size: "1.25em"
13508
- }),
13509
- "Pixel render cut-off ",
13510
- isTransitioning ? /* @__PURE__ */ jsx("img", {
13511
- src: truck_default,
13512
- alt: "...",
13513
- className: "h-5"
13514
- }) : ""
13515
- ]
13516
- }), /* @__PURE__ */ jsx("input", {
13517
- type: "number",
13518
- min: 0,
13519
- defaultValue: renderCutoff,
13520
- className: "mr-1 w-12 place-self-end border-b-2",
13521
- onChange: (x) => startTransition$1(() => setRenderCutoff(x.target.valueAsNumber))
13522
- })]
13523
- })
13524
- ] });
13525
- }
13526
13587
  function BrowseParentFolder() {
13527
13588
  const dataPromise = useRouteLoaderData(href("/view"))?.dataPromise;
13528
13589
  const data = dataPromise ? use(dataPromise) : null;
@@ -13537,11 +13598,9 @@ var view_exports = /* @__PURE__ */ __export({
13537
13598
  loader: () => loader$8,
13538
13599
  meta: () => meta,
13539
13600
  middleware: () => middleware,
13540
- useRepoContext: () => useRepoContext,
13541
13601
  viewSearchParamsConfig: () => viewSearchParamsConfig,
13542
13602
  viewSerializer: () => viewSerializer
13543
13603
  }, 1);
13544
- const useRepoContext = () => useOutletContext();
13545
13604
  const currentRepositoryContext = createContext();
13546
13605
  const viewSearchParamsConfig = {
13547
13606
  path: parseAsString,
@@ -13765,9 +13824,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13765
13824
  }
13766
13825
  }, { leftExpanded: true });
13767
13826
  const toggleLeft = () => dispatch("toggleLeft");
13768
- const [unionAuthorsModalOpen, setUnionAuthorsModalOpen] = useState(false);
13769
13827
  const [hoveredObject, setHoveredObject] = useState(null);
13770
- const showUnionAuthorsModal = () => setUnionAuthorsModalOpen(true);
13771
13828
  const location$1 = useLocation();
13772
13829
  const navigate = useNavigate();
13773
13830
  const [objectPath] = useQueryState("objectPath", viewSearchParamsConfig.objectPath);
@@ -13830,7 +13887,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13830
13887
  ]
13831
13888
  }) }) : "Details" }),
13832
13889
  contentClassName: "pb-6",
13833
- children: /* @__PURE__ */ jsx(Outlet, { context: { showUnionAuthorsModal } })
13890
+ children: /* @__PURE__ */ jsx(Outlet, {})
13834
13891
  }),
13835
13892
  /* @__PURE__ */ jsx(CollapsibleHeader, {
13836
13893
  className: "card",
@@ -13842,15 +13899,12 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13842
13899
  className: "card",
13843
13900
  title: "Legend",
13844
13901
  contentClassName: "pb-6",
13845
- children: /* @__PURE__ */ jsx(Legend, {
13846
- hoveredObject,
13847
- showUnionAuthorsModal
13848
- })
13902
+ children: /* @__PURE__ */ jsx(Legend, { hoveredObject })
13849
13903
  })
13850
13904
  ]
13851
13905
  })
13852
13906
  }), /* @__PURE__ */ jsxs("main", {
13853
- className: cn("relative grid h-full min-w-25 grid-rows-[auto_1fr_auto] gap-2 [grid-area:main] lg:transition-transform"),
13907
+ className: cn("relative grid h-full min-h-screen min-w-25 grid-rows-[auto_1fr_auto] gap-2 [grid-area:main] lg:transition-transform"),
13854
13908
  children: [
13855
13909
  /* @__PURE__ */ jsxs("header", {
13856
13910
  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",
@@ -13875,8 +13929,12 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13875
13929
  /* @__PURE__ */ jsxs("div", {
13876
13930
  className: "flex gap-2",
13877
13931
  children: [
13932
+ /* @__PURE__ */ jsx("div", {
13933
+ className: "relative",
13934
+ children: /* @__PURE__ */ jsx(SearchCard, {})
13935
+ }),
13878
13936
  data.repo.status === "Success" ? /* @__PURE__ */ jsx(RevisionSelect, {
13879
- className: "max-w-3xs",
13937
+ className: "max-w-32",
13880
13938
  title: "Select branch",
13881
13939
  defaultValue: data.databaseInfo.branch,
13882
13940
  headGroups: data.repo.refs,
@@ -13885,12 +13943,11 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13885
13943
  path: data.repo.repositoryPath,
13886
13944
  branch: e.target.value
13887
13945
  }))
13888
- }, data.databaseInfo.branch) : null,
13889
- /* @__PURE__ */ jsx(SearchCard, {}),
13946
+ }, data.databaseInfo.branch) : /* @__PURE__ */ jsx("div", {}),
13890
13947
  /* @__PURE__ */ jsx(RefreshButton, {}),
13891
13948
  /* @__PURE__ */ jsx(HiddenFiles, {}),
13892
- /* @__PURE__ */ jsx(FullscreenButton, {}),
13893
- /* @__PURE__ */ jsx(CollapsableSettings, {})
13949
+ /* @__PURE__ */ jsx(SettingsButton, {}),
13950
+ /* @__PURE__ */ jsx(FullscreenButton, {})
13894
13951
  ]
13895
13952
  })
13896
13953
  ]
@@ -13934,12 +13991,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13934
13991
  })
13935
13992
  ]
13936
13993
  })]
13937
- }), /* @__PURE__ */ jsx(UnionAuthorsModal, {
13938
- open: unionAuthorsModalOpen,
13939
- onClose: () => {
13940
- setUnionAuthorsModalOpen(false);
13941
- }
13942
- })]
13994
+ }), /* @__PURE__ */ jsx(ModalManager, {})]
13943
13995
  })
13944
13996
  })
13945
13997
  });
@@ -13984,14 +14036,6 @@ async function loader$4({ request, params }) {
13984
14036
  const path$1 = params.path ?? loadViewSearchParams(request).path ?? ".";
13985
14037
  throw redirect(await GitCaller.isValidGitRepo(path$1) ? href("/view") + viewSerializer({ path: path$1 }) : href("/browse") + browseSerializer({ path: path$1 }));
13986
14038
  }
13987
- var view_merge_authors_exports = /* @__PURE__ */ __export({ default: () => view_merge_authors_default }, 1);
13988
- var view_merge_authors_default = UNSAFE_withComponentProps(function MergeAuthorsView() {
13989
- const navigate = useNavigate();
13990
- return /* @__PURE__ */ jsx(UnionAuthorsModal, {
13991
- open: true,
13992
- onClose: () => navigate("..")
13993
- });
13994
- });
13995
14039
  var view_progress_exports = /* @__PURE__ */ __export({ loader: () => loader$3 }, 1);
13996
14040
  var POLLING_RATE = 500;
13997
14041
  const loader$3 = async ({ context }) => {
@@ -14294,7 +14338,6 @@ const action = async ({ request, context }) => {
14294
14338
  };
14295
14339
  var view_details_default = UNSAFE_withComponentProps(function Details() {
14296
14340
  const { setPath } = usePath();
14297
- const { showUnionAuthorsModal } = useRepoContext();
14298
14341
  const { path: path$1, authorDistributionPromise } = useLoaderData();
14299
14342
  const data = useData();
14300
14343
  const { state } = useNavigation();
@@ -14306,6 +14349,7 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14306
14349
  ...viewSearchParams,
14307
14350
  zoomPath: path$1
14308
14351
  });
14352
+ const { openModal } = useModal("group-authors");
14309
14353
  useEffect(() => {
14310
14354
  setOpen(!!clickedObject);
14311
14355
  }, [clickedObject, setOpen]);
@@ -14344,7 +14388,7 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14344
14388
  }),
14345
14389
  /* @__PURE__ */ jsxs("button", {
14346
14390
  className: "btn",
14347
- onClick: showUnionAuthorsModal,
14391
+ onClick: () => openModal(),
14348
14392
  children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
14349
14393
  }),
14350
14394
  /* @__PURE__ */ jsxs("div", {
@@ -14971,7 +15015,7 @@ var ui_default = UNSAFE_withComponentProps(function UI() {
14971
15015
  children: [/* @__PURE__ */ jsx("h2", {
14972
15016
  className: "card__title",
14973
15017
  children: "AuthorOptions"
14974
- }), /* @__PURE__ */ jsx(AuthorOptions, { showUnionAuthorsModal: () => alert("Show union authors modal") })]
15018
+ }), /* @__PURE__ */ jsx(AuthorOptions, {})]
14975
15019
  }),
14976
15020
  /* @__PURE__ */ jsxs("div", {
14977
15021
  className: "card",
@@ -15093,12 +15137,11 @@ async function loader({ request, params }) {
15093
15137
  }
15094
15138
  var server_manifest_default = {
15095
15139
  "entry": {
15096
- "module": "/assets/entry.client-NklqTk8N.js",
15140
+ "module": "/assets/entry.client-lU2GATMj.js",
15097
15141
  "imports": [
15098
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15099
- "/assets/react-dom-_FKEaPb7.js",
15100
- "/assets/chunk-JPUPSTYD-XO4UPun8.js",
15101
- "/assets/jsx-runtime-BVRj4wYA.js"
15142
+ "/assets/jsx-runtime-s_YwNCZb.js",
15143
+ "/assets/react-dom-BkDQo3BN.js",
15144
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15102
15145
  ],
15103
15146
  "css": []
15104
15147
  },
@@ -15116,40 +15159,16 @@ var server_manifest_default = {
15116
15159
  "hasClientMiddleware": false,
15117
15160
  "hasDefaultExport": true,
15118
15161
  "hasErrorBoundary": true,
15119
- "module": "/assets/root-Djh_4EO9.js",
15162
+ "module": "/assets/root-CMX9SEB-.js",
15120
15163
  "imports": [
15121
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15122
- "/assets/react-dom-_FKEaPb7.js",
15123
- "/assets/chunk-JPUPSTYD-XO4UPun8.js",
15124
- "/assets/jsx-runtime-BVRj4wYA.js",
15125
- "/assets/styling-B8OEge8x.js",
15126
- "/assets/util-1G1Ez1rt.js",
15127
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15128
- "/assets/GitTruckInfo-C1VOiqSW.js",
15129
- "/assets/clear-cache-CrL0weu5.js"
15164
+ "/assets/jsx-runtime-s_YwNCZb.js",
15165
+ "/assets/react-dom-BkDQo3BN.js",
15166
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js",
15167
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15168
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15169
+ "/assets/clear-cache-CAdofhUa.js"
15130
15170
  ],
15131
- "css": ["/assets/root-qwQAVU-u.css"],
15132
- "clientActionModule": void 0,
15133
- "clientLoaderModule": void 0,
15134
- "clientMiddlewareModule": void 0,
15135
- "hydrateFallbackModule": void 0
15136
- },
15137
- "routes/FullscreenButton": {
15138
- "id": "routes/FullscreenButton",
15139
- "parentId": "root",
15140
- "path": "FullscreenButton",
15141
- "index": void 0,
15142
- "caseSensitive": void 0,
15143
- "hasAction": false,
15144
- "hasLoader": false,
15145
- "hasClientAction": false,
15146
- "hasClientLoader": false,
15147
- "hasClientMiddleware": false,
15148
- "hasDefaultExport": false,
15149
- "hasErrorBoundary": false,
15150
- "module": "/assets/FullscreenButton-CFqdBqf-.js",
15151
- "imports": ["/assets/styling-B8OEge8x.js", "/assets/jsx-runtime-BVRj4wYA.js"],
15152
- "css": [],
15171
+ "css": ["/assets/root-DEstG3hL.css"],
15153
15172
  "clientActionModule": void 0,
15154
15173
  "clientLoaderModule": void 0,
15155
15174
  "clientMiddlewareModule": void 0,
@@ -15168,7 +15187,7 @@ var server_manifest_default = {
15168
15187
  "hasClientMiddleware": false,
15169
15188
  "hasDefaultExport": false,
15170
15189
  "hasErrorBoundary": false,
15171
- "module": "/assets/_._well-known._-DIwl00oI.js",
15190
+ "module": "/assets/_._well-known._-8QUD84tm.js",
15172
15191
  "imports": [],
15173
15192
  "css": [],
15174
15193
  "clientActionModule": void 0,
@@ -15189,15 +15208,12 @@ var server_manifest_default = {
15189
15208
  "hasClientMiddleware": false,
15190
15209
  "hasDefaultExport": true,
15191
15210
  "hasErrorBoundary": false,
15192
- "module": "/assets/clear-cache-82hexQD7.js",
15211
+ "module": "/assets/clear-cache-Ciosxv25.js",
15193
15212
  "imports": [
15194
- "/assets/styling-B8OEge8x.js",
15195
- "/assets/util-1G1Ez1rt.js",
15196
- "/assets/react-dom-_FKEaPb7.js",
15197
- "/assets/jsx-runtime-BVRj4wYA.js",
15198
- "/assets/GitTruckInfo-C1VOiqSW.js",
15199
- "/assets/clear-cache-CrL0weu5.js",
15200
- "/assets/chunk-LFPYN7LY-CPTP6242.js"
15213
+ "/assets/jsx-runtime-s_YwNCZb.js",
15214
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15215
+ "/assets/react-dom-BkDQo3BN.js",
15216
+ "/assets/clear-cache-CAdofhUa.js"
15201
15217
  ],
15202
15218
  "css": [],
15203
15219
  "clientActionModule": void 0,
@@ -15218,7 +15234,7 @@ var server_manifest_default = {
15218
15234
  "hasClientMiddleware": false,
15219
15235
  "hasDefaultExport": false,
15220
15236
  "hasErrorBoundary": false,
15221
- "module": "/assets/commitcount-eFguKtSp.js",
15237
+ "module": "/assets/commitcount-B_3CeIvr.js",
15222
15238
  "imports": [],
15223
15239
  "css": [],
15224
15240
  "clientActionModule": void 0,
@@ -15239,7 +15255,7 @@ var server_manifest_default = {
15239
15255
  "hasClientMiddleware": false,
15240
15256
  "hasDefaultExport": false,
15241
15257
  "hasErrorBoundary": false,
15242
- "module": "/assets/commits-DPfZp-l3.js",
15258
+ "module": "/assets/commits-DDIAHyHU.js",
15243
15259
  "imports": [],
15244
15260
  "css": [],
15245
15261
  "clientActionModule": void 0,
@@ -15260,7 +15276,7 @@ var server_manifest_default = {
15260
15276
  "hasClientMiddleware": false,
15261
15277
  "hasDefaultExport": false,
15262
15278
  "hasErrorBoundary": false,
15263
- "module": "/assets/_index-J0jvF8An.js",
15279
+ "module": "/assets/_index-VLoUE117.js",
15264
15280
  "imports": [],
15265
15281
  "css": [],
15266
15282
  "clientActionModule": void 0,
@@ -15281,18 +15297,14 @@ var server_manifest_default = {
15281
15297
  "hasClientMiddleware": false,
15282
15298
  "hasDefaultExport": true,
15283
15299
  "hasErrorBoundary": false,
15284
- "module": "/assets/browse-CPxPbHtx.js",
15300
+ "module": "/assets/browse-BVbfiHLu.js",
15285
15301
  "imports": [
15286
- "/assets/browse-CuAoODR1.js",
15287
- "/assets/styling-B8OEge8x.js",
15288
- "/assets/util-1G1Ez1rt.js",
15289
- "/assets/react-dom-_FKEaPb7.js",
15290
- "/assets/jsx-runtime-BVRj4wYA.js",
15291
- "/assets/GitTruckInfo-C1VOiqSW.js",
15292
- "/assets/UnionAuthorsModal-D-H775E5.js",
15293
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15294
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15295
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15302
+ "/assets/jsx-runtime-s_YwNCZb.js",
15303
+ "/assets/browse-DMoBg6Vh.js",
15304
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15305
+ "/assets/react-dom-BkDQo3BN.js",
15306
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15307
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15296
15308
  ],
15297
15309
  "css": [],
15298
15310
  "clientActionModule": void 0,
@@ -15313,7 +15325,7 @@ var server_manifest_default = {
15313
15325
  "hasClientMiddleware": false,
15314
15326
  "hasDefaultExport": false,
15315
15327
  "hasErrorBoundary": false,
15316
- "module": "/assets/_path-bRAhzWvL.js",
15328
+ "module": "/assets/_path-DzSNgPJP.js",
15317
15329
  "imports": [],
15318
15330
  "css": [],
15319
15331
  "clientActionModule": void 0,
@@ -15334,47 +15346,14 @@ var server_manifest_default = {
15334
15346
  "hasClientMiddleware": false,
15335
15347
  "hasDefaultExport": true,
15336
15348
  "hasErrorBoundary": false,
15337
- "module": "/assets/view-ESMBoxIY.js",
15338
- "imports": [
15339
- "/assets/browse-CuAoODR1.js",
15340
- "/assets/styling-B8OEge8x.js",
15341
- "/assets/util-1G1Ez1rt.js",
15342
- "/assets/react-dom-_FKEaPb7.js",
15343
- "/assets/jsx-runtime-BVRj4wYA.js",
15344
- "/assets/GitTruckInfo-C1VOiqSW.js",
15345
- "/assets/UnionAuthorsModal-D-H775E5.js",
15346
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15347
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15348
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15349
- ],
15350
- "css": [],
15351
- "clientActionModule": void 0,
15352
- "clientLoaderModule": void 0,
15353
- "clientMiddlewareModule": void 0,
15354
- "hydrateFallbackModule": void 0
15355
- },
15356
- "routes/view.merge-authors": {
15357
- "id": "routes/view.merge-authors",
15358
- "parentId": "routes/view",
15359
- "path": "merge-authors",
15360
- "index": void 0,
15361
- "caseSensitive": void 0,
15362
- "hasAction": false,
15363
- "hasLoader": false,
15364
- "hasClientAction": false,
15365
- "hasClientLoader": false,
15366
- "hasClientMiddleware": false,
15367
- "hasDefaultExport": true,
15368
- "hasErrorBoundary": false,
15369
- "module": "/assets/view.merge-authors-VwPHWL-d.js",
15349
+ "module": "/assets/view-JlAiRFiK.js",
15370
15350
  "imports": [
15371
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15372
- "/assets/styling-B8OEge8x.js",
15373
- "/assets/util-1G1Ez1rt.js",
15374
- "/assets/react-dom-_FKEaPb7.js",
15375
- "/assets/jsx-runtime-BVRj4wYA.js",
15376
- "/assets/UnionAuthorsModal-D-H775E5.js",
15377
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15351
+ "/assets/jsx-runtime-s_YwNCZb.js",
15352
+ "/assets/browse-DMoBg6Vh.js",
15353
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15354
+ "/assets/react-dom-BkDQo3BN.js",
15355
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15356
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15378
15357
  ],
15379
15358
  "css": [],
15380
15359
  "clientActionModule": void 0,
@@ -15395,7 +15374,7 @@ var server_manifest_default = {
15395
15374
  "hasClientMiddleware": false,
15396
15375
  "hasDefaultExport": false,
15397
15376
  "hasErrorBoundary": false,
15398
- "module": "/assets/view.progress-B7Os3EHt.js",
15377
+ "module": "/assets/view.progress-CEmcNaLe.js",
15399
15378
  "imports": [],
15400
15379
  "css": [],
15401
15380
  "clientActionModule": void 0,
@@ -15416,19 +15395,15 @@ var server_manifest_default = {
15416
15395
  "hasClientMiddleware": false,
15417
15396
  "hasDefaultExport": true,
15418
15397
  "hasErrorBoundary": false,
15419
- "module": "/assets/view.commits-DP_ltd6_.js",
15398
+ "module": "/assets/view.commits-CIgkE9N1.js",
15420
15399
  "imports": [
15421
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15422
- "/assets/browse-CuAoODR1.js",
15423
- "/assets/styling-B8OEge8x.js",
15424
- "/assets/util-1G1Ez1rt.js",
15425
- "/assets/react-dom-_FKEaPb7.js",
15426
- "/assets/jsx-runtime-BVRj4wYA.js",
15427
- "/assets/GitTruckInfo-C1VOiqSW.js",
15428
- "/assets/RepoTabs-xQ7LCUn1.js",
15429
- "/assets/UnionAuthorsModal-D-H775E5.js",
15430
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15431
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15400
+ "/assets/jsx-runtime-s_YwNCZb.js",
15401
+ "/assets/browse-DMoBg6Vh.js",
15402
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15403
+ "/assets/react-dom-BkDQo3BN.js",
15404
+ "/assets/RepoTabs-B_gVZfyQ.js",
15405
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15406
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15432
15407
  ],
15433
15408
  "css": [],
15434
15409
  "clientActionModule": void 0,
@@ -15449,19 +15424,15 @@ var server_manifest_default = {
15449
15424
  "hasClientMiddleware": false,
15450
15425
  "hasDefaultExport": true,
15451
15426
  "hasErrorBoundary": false,
15452
- "module": "/assets/view.details-C6kuGyhb.js",
15427
+ "module": "/assets/view.details-DAXK445Q.js",
15453
15428
  "imports": [
15454
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15455
- "/assets/browse-CuAoODR1.js",
15456
- "/assets/styling-B8OEge8x.js",
15457
- "/assets/util-1G1Ez1rt.js",
15458
- "/assets/react-dom-_FKEaPb7.js",
15459
- "/assets/jsx-runtime-BVRj4wYA.js",
15460
- "/assets/GitTruckInfo-C1VOiqSW.js",
15461
- "/assets/RepoTabs-xQ7LCUn1.js",
15462
- "/assets/UnionAuthorsModal-D-H775E5.js",
15463
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15464
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15429
+ "/assets/jsx-runtime-s_YwNCZb.js",
15430
+ "/assets/browse-DMoBg6Vh.js",
15431
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15432
+ "/assets/react-dom-BkDQo3BN.js",
15433
+ "/assets/RepoTabs-B_gVZfyQ.js",
15434
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15435
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15465
15436
  ],
15466
15437
  "css": [],
15467
15438
  "clientActionModule": void 0,
@@ -15482,8 +15453,8 @@ var server_manifest_default = {
15482
15453
  "hasClientMiddleware": false,
15483
15454
  "hasDefaultExport": true,
15484
15455
  "hasErrorBoundary": false,
15485
- "module": "/assets/view._index-CjsEuBV6.js",
15486
- "imports": ["/assets/chunk-LFPYN7LY-CPTP6242.js", "/assets/jsx-runtime-BVRj4wYA.js"],
15456
+ "module": "/assets/view._index-jmCCN9mA.js",
15457
+ "imports": ["/assets/jsx-runtime-s_YwNCZb.js"],
15487
15458
  "css": [],
15488
15459
  "clientActionModule": void 0,
15489
15460
  "clientLoaderModule": void 0,
@@ -15503,18 +15474,14 @@ var server_manifest_default = {
15503
15474
  "hasClientMiddleware": false,
15504
15475
  "hasDefaultExport": true,
15505
15476
  "hasErrorBoundary": false,
15506
- "module": "/assets/ui-nIuMvo3S.js",
15477
+ "module": "/assets/ui-C21Ckl3a.js",
15507
15478
  "imports": [
15508
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15509
- "/assets/browse-CuAoODR1.js",
15510
- "/assets/styling-B8OEge8x.js",
15511
- "/assets/util-1G1Ez1rt.js",
15512
- "/assets/react-dom-_FKEaPb7.js",
15513
- "/assets/jsx-runtime-BVRj4wYA.js",
15514
- "/assets/GitTruckInfo-C1VOiqSW.js",
15515
- "/assets/UnionAuthorsModal-D-H775E5.js",
15516
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15517
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15479
+ "/assets/jsx-runtime-s_YwNCZb.js",
15480
+ "/assets/browse-DMoBg6Vh.js",
15481
+ "/assets/GitTruckInfo-Bu3InKk0.js",
15482
+ "/assets/react-dom-BkDQo3BN.js",
15483
+ "/assets/compare-Br3z3FUS-Bm1os1Jk.js",
15484
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15518
15485
  ],
15519
15486
  "css": [],
15520
15487
  "clientActionModule": void 0,
@@ -15535,7 +15502,7 @@ var server_manifest_default = {
15535
15502
  "hasClientMiddleware": false,
15536
15503
  "hasDefaultExport": false,
15537
15504
  "hasErrorBoundary": false,
15538
- "module": "/assets/_-RagYKtT_.js",
15505
+ "module": "/assets/_-CT17qCl3.js",
15539
15506
  "imports": [],
15540
15507
  "css": [],
15541
15508
  "clientActionModule": void 0,
@@ -15544,8 +15511,8 @@ var server_manifest_default = {
15544
15511
  "hydrateFallbackModule": void 0
15545
15512
  }
15546
15513
  },
15547
- "url": "/assets/manifest-b5d81016.js",
15548
- "version": "b5d81016",
15514
+ "url": "/assets/manifest-bbde1677.js",
15515
+ "version": "bbde1677",
15549
15516
  "sri": void 0
15550
15517
  };
15551
15518
  const assetsBuildDirectory = "build/client";
@@ -15577,14 +15544,6 @@ const routes = {
15577
15544
  caseSensitive: void 0,
15578
15545
  module: root_exports
15579
15546
  },
15580
- "routes/FullscreenButton": {
15581
- id: "routes/FullscreenButton",
15582
- parentId: "root",
15583
- path: "FullscreenButton",
15584
- index: void 0,
15585
- caseSensitive: void 0,
15586
- module: FullscreenButton_exports
15587
- },
15588
15547
  "routes/[.]well-known.$": {
15589
15548
  id: "routes/[.]well-known.$",
15590
15549
  parentId: "root",
@@ -15649,14 +15608,6 @@ const routes = {
15649
15608
  caseSensitive: void 0,
15650
15609
  module: view_exports
15651
15610
  },
15652
- "routes/view.merge-authors": {
15653
- id: "routes/view.merge-authors",
15654
- parentId: "routes/view",
15655
- path: "merge-authors",
15656
- index: void 0,
15657
- caseSensitive: void 0,
15658
- module: view_merge_authors_exports
15659
- },
15660
15611
  "routes/view.progress": {
15661
15612
  id: "routes/view.progress",
15662
15613
  parentId: "routes/view",