git-truck 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/build/client/assets/GitTruckInfo-CiYjIacT.js +1 -0
  2. package/build/client/assets/RepoTabs-CWt-EVgp.js +1 -0
  3. package/build/client/assets/browse-Ce7uXDK8.js +1 -0
  4. package/build/client/assets/browse-wwHpvs7Z.js +9 -0
  5. package/build/client/assets/chunk-JPUPSTYD-YBbsfvLw.js +1 -0
  6. package/build/client/assets/clear-cache-C5k9U711.js +1 -0
  7. package/build/client/assets/clear-cache-Xrw5kNtu.js +1 -0
  8. package/build/client/assets/{compare-Br3z3FUS-OODPzXmX.js → compare-Br3z3FUS-CQWcjqXQ.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-3c232792.js +1 -0
  12. package/build/client/assets/{react-dom-_FKEaPb7.js → react-dom-BkDQo3BN.js} +1 -1
  13. package/build/client/assets/{root-qwQAVU-u.css → root-CaiUVml-.css} +1 -1
  14. package/build/client/assets/{root-Djh_4EO9.js → root-CpxyjKLd.js} +2 -2
  15. package/build/client/assets/ui-EnssW_g_.js +1 -0
  16. package/build/client/assets/view-DhREUKDU.js +1 -0
  17. package/build/client/assets/view._index-jmCCN9mA.js +1 -0
  18. package/build/client/assets/view.commits-D8Z4b-x3.js +1 -0
  19. package/build/client/assets/view.details-Bsu_yyiE.js +1 -0
  20. package/build/server/assets/{server-build-xXpv9ITX.js → server-build-DEt84pjT.js} +1218 -1275
  21. package/build/server/index.js +1 -1
  22. package/cli.mjs +1 -1
  23. package/package.json +2 -3
  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-4-CQeS6M.js +0 -1
  27. package/build/client/assets/UnionAuthorsModal-D-H775E5.js +0 -1
  28. package/build/client/assets/browse-7BJg-qfM.js +0 -9
  29. package/build/client/assets/browse-CPjwuHQe.js +0 -1
  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-c5f30ffe.js +0 -1
  36. package/build/client/assets/styling-B8OEge8x.js +0 -1
  37. package/build/client/assets/ui-CGg0wrDR.js +0 -1
  38. package/build/client/assets/util-1G1Ez1rt.js +0 -1
  39. package/build/client/assets/view-ZFr-KZ5c.js +0 -1
  40. package/build/client/assets/view._index-CjsEuBV6.js +0 -1
  41. package/build/client/assets/view.commits-D4Q38_m5.js +0 -1
  42. package/build/client/assets/view.details-CVK0Sh-y.js +0 -1
  43. package/build/client/assets/view.merge-authors-VwPHWL-d.js +0 -1
  44. /package/build/client/assets/{_-RagYKtT_.js → _-CT17qCl3.js} +0 -0
  45. /package/build/client/assets/{_._well-known._-DIwl00oI.js → _._well-known._-8QUD84tm.js} +0 -0
  46. /package/build/client/assets/{_index-J0jvF8An.js → _index-VLoUE117.js} +0 -0
  47. /package/build/client/assets/{_path-bRAhzWvL.js → _path-DzSNgPJP.js} +0 -0
  48. /package/build/client/assets/{commitcount-eFguKtSp.js → commitcount-B_3CeIvr.js} +0 -0
  49. /package/build/client/assets/{commits-DPfZp-l3.js → commits-DDIAHyHU.js} +0 -0
  50. /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.1";
183
+ const version = "3.1.0";
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",
@@ -348,11 +348,7 @@ var package_default = {
348
348
  devDependencies,
349
349
  tsdown,
350
350
  prettier,
351
- "lint-staged": { "{src,scripts}/**/*.{ts,tsx,js,mjs}": [
352
- "knip --fix",
353
- "eslint --cache --fix",
354
- "prettier --write"
355
- ] },
351
+ "lint-staged": { "{src,scripts}/**/*.{ts,tsx,js,mjs}": ["eslint --cache --fix", "prettier --write"] },
356
352
  knip,
357
353
  engines,
358
354
  repository,
@@ -379,7 +375,6 @@ function Icon({ path: path$1, size = "1rem", color = "currentColor", style = {},
379
375
  children: /* @__PURE__ */ jsx("path", { d: path$1 })
380
376
  });
381
377
  }
382
- var truck_default = "/assets/truck-BgAoc4Gr.gif";
383
378
  function Popover$1({ popoverTitle, positions = [
384
379
  "top",
385
380
  "bottom",
@@ -8750,12 +8745,12 @@ var defaultOptions = {
8750
8745
  };
8751
8746
  const getDefaultOptionsContextValue = () => defaultOptions;
8752
8747
  const CloseButton = ({ className = "", absolute = true, ...props }) => /* @__PURE__ */ jsx("button", {
8753
- 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 }),
8748
+ 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 }),
8754
8749
  title: "Close",
8755
8750
  ...props,
8756
8751
  children: /* @__PURE__ */ jsx(Icon, {
8757
8752
  path: mdiClose,
8758
- size: 1
8753
+ size: 1.25
8759
8754
  })
8760
8755
  });
8761
8756
  const LegendDot = ({ className = "", dotColor, authorColorToChange = void 0 }) => {
@@ -8825,34 +8820,6 @@ const Code = ({ inline = false, className = "", ...props }) => /* @__PURE__ */ j
8825
8820
  className: cn("primary rounded border px-2 py-1 font-mono text-sm", inline ? "inline-block" : "block", { "select-all": inline }, className),
8826
8821
  ...props
8827
8822
  });
8828
- function CheckboxWithLabel({ children, checked, onChange, className = "", checkedIcon = mdiCheckboxOutline, uncheckedIcon = mdiCheckboxBlankOutline, ...props }) {
8829
- const [isTransitioning, startTransition$1] = useTransition();
8830
- return /* @__PURE__ */ jsxs("label", {
8831
- className: `label flex w-full items-center justify-start gap-2 ${className}`,
8832
- ...props,
8833
- children: [
8834
- /* @__PURE__ */ jsxs("span", {
8835
- className: "flex grow items-center gap-2",
8836
- children: [children, isTransitioning ? /* @__PURE__ */ jsx("img", {
8837
- src: truck_default,
8838
- alt: "...",
8839
- className: "h-5"
8840
- }) : ""]
8841
- }),
8842
- /* @__PURE__ */ jsx(Icon, {
8843
- className: "place-self-end",
8844
- path: checked ? checkedIcon : uncheckedIcon,
8845
- size: 1
8846
- }),
8847
- /* @__PURE__ */ jsx("input", {
8848
- type: "checkbox",
8849
- defaultChecked: checked,
8850
- className: "hidden",
8851
- onChange: (e) => startTransition$1(() => onChange(e))
8852
- })
8853
- ]
8854
- });
8855
- }
8856
8823
  const LegendBarIndicator = ({ visible, offset }) => {
8857
8824
  return /* @__PURE__ */ jsx("div", {
8858
8825
  className: clsx$1("absolute top-1/2 w-min -translate-x-1/2 -translate-y-1/2 transition-all", { "opacity-0": !visible }),
@@ -8868,18 +8835,6 @@ function ClientOnly({ children, fallback = null }) {
8868
8835
  if (!useIsClient()) return fallback;
8869
8836
  return children();
8870
8837
  }
8871
- function FullscreenButton() {
8872
- const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
8873
- return /* @__PURE__ */ jsx("button", {
8874
- className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
8875
- title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
8876
- onClick: toggleFullscreen,
8877
- children: /* @__PURE__ */ jsx(Icon, {
8878
- path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
8879
- size: 1
8880
- })
8881
- });
8882
- }
8883
8838
  var DB = class DB {
8884
8839
  connectionPromise;
8885
8840
  repoSanitized;
@@ -10336,7 +10291,8 @@ var InstanceManager = class {
10336
10291
  this.instances = /* @__PURE__ */ new Map();
10337
10292
  }
10338
10293
  };
10339
- 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";
10294
+ 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";
10295
+ var truck_default$1 = "/assets/truck-BgAoc4Gr.gif";
10340
10296
  function UpdateNotifier({ installedVersion, latestVersion: latestVersion$1 }) {
10341
10297
  const isExperimental = installedVersion.includes("0.0.0");
10342
10298
  return /* @__PURE__ */ jsxs(Popover$1, {
@@ -10394,7 +10350,7 @@ function GitTruckInfo({ className = "", installedVersion, latestVersion: latestV
10394
10350
  installedVersion,
10395
10351
  latestVersion: latestVersion$1
10396
10352
  }) : /* @__PURE__ */ jsx("img", {
10397
- src: loading ? truck_default : truck_default$1,
10353
+ src: loading ? truck_default$1 : truck_default,
10398
10354
  alt: "Git Truck Logo",
10399
10355
  className: "tertiary size-10 rounded-full border p-1"
10400
10356
  }), /* @__PURE__ */ jsxs("div", {
@@ -10502,7 +10458,7 @@ function ErrorPage({ className, message, noTruck = false, children }) {
10502
10458
  className: "flex max-w-2xl flex-col items-center gap-3 text-center",
10503
10459
  children: [
10504
10460
  !noTruck ? /* @__PURE__ */ jsx("img", {
10505
- src: truck_default$1,
10461
+ src: truck_default,
10506
10462
  alt: "Git Truck",
10507
10463
  className: "w-full max-w-sm min-w-0"
10508
10464
  }) : null,
@@ -10586,19 +10542,6 @@ const ErrorBoundary = UNSAFE_withErrorBoundaryProps(() => {
10586
10542
  })
10587
10543
  }) : null] });
10588
10544
  });
10589
- var FullscreenButton_exports = /* @__PURE__ */ __export({ FullscreenButton: () => FullscreenButton$1 }, 1);
10590
- function FullscreenButton$1() {
10591
- const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
10592
- return /* @__PURE__ */ jsx("button", {
10593
- className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
10594
- title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
10595
- onClick: toggleFullscreen,
10596
- children: /* @__PURE__ */ jsx(Icon, {
10597
- path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
10598
- size: 1
10599
- })
10600
- });
10601
- }
10602
10545
  var ____well_known_$_exports = /* @__PURE__ */ __export({ loader: () => loader$10 }, 1);
10603
10546
  function loader$10() {
10604
10547
  return "";
@@ -10782,7 +10725,7 @@ const browseSearchParamsConfig = {
10782
10725
  path: parseAsString.withOptions({ shallow: false }),
10783
10726
  count: parseAsNumberLiteral(COUNT_OPTIONS).withOptions({ shallow: false }).withDefault(DEFAULT_COUNT),
10784
10727
  offset: parseAsInteger$1.withOptions({ shallow: false }).withDefault(DEFAULT_OFFSET),
10785
- sort: parseAsStringLiteral([
10728
+ sort: parseAsStringLiteral$1([
10786
10729
  "most-recent",
10787
10730
  "least-recent",
10788
10731
  "asc",
@@ -11416,12 +11359,29 @@ function Breadcrumb({ className = "", zoom = false }) {
11416
11359
  }
11417
11360
  function useClickedObject() {
11418
11361
  const location$1 = useLocation();
11419
- const [objectPath, setObjectPath] = useQueryState("objectPath", viewSearchParamsConfig.objectPath);
11362
+ const [params] = useQueryStates(viewSearchParamsConfig);
11363
+ const clickedObject = params.objectPath ? location$1.state?.clickedObject ?? null : null;
11364
+ const navigate = useNavigate();
11365
+ const tabURL = useMatch(href("/view/commits")) ? "/view/commits" : "/view/details";
11420
11366
  return {
11421
- clickedObject: objectPath ? location$1.state?.clickedObject ?? null : null,
11367
+ clickedObject,
11422
11368
  setClickedObject: useCallback((object) => {
11423
- setObjectPath(!object ? null : object.path);
11424
- }, [setObjectPath])
11369
+ if (object) {
11370
+ navigate(tabURL + viewSerializer({
11371
+ ...params,
11372
+ objectPath: object.path
11373
+ }), { state: { clickedObject: object } });
11374
+ return;
11375
+ }
11376
+ navigate(href("/view") + viewSerializer({
11377
+ ...params,
11378
+ objectPath: null
11379
+ }));
11380
+ }, [
11381
+ navigate,
11382
+ params,
11383
+ tabURL
11384
+ ])
11425
11385
  };
11426
11386
  }
11427
11387
  const SearchContext = createContext$1(void 0);
@@ -11898,415 +11858,283 @@ const HiddenFiles = memo(function HiddenFiles$1() {
11898
11858
  })
11899
11859
  });
11900
11860
  });
11901
- function AuthorOptions({ showUnionAuthorsModal }) {
11902
- const transitionState = useNavigation();
11903
- const action$3 = href("/view");
11904
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
11905
- className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
11906
- children: [/* @__PURE__ */ jsxs("button", {
11907
- className: "btn",
11908
- onClick: showUnionAuthorsModal,
11909
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
11910
- }), /* @__PURE__ */ jsxs(Form, {
11911
- method: "post",
11912
- action: action$3,
11913
- children: [/* @__PURE__ */ jsx("input", {
11914
- type: "hidden",
11915
- name: "rerollColors",
11916
- value: ""
11917
- }), /* @__PURE__ */ jsxs("button", {
11918
- className: "btn w-full",
11919
- type: "submit",
11920
- disabled: transitionState.state !== "idle",
11921
- children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
11922
- })]
11923
- })]
11924
- }) });
11925
- }
11926
- function GradientLegend({ hoveredObject }) {
11927
- const { metricType } = useOptions();
11928
- const [metricsData] = useMetrics();
11929
- const metricCache = metricsData.get(metricType);
11930
- if (metricCache === void 0) throw new Error("Metric cache is undefined");
11931
- const { minValue, maxValue, minColor, maxColor } = metricCache.legend;
11932
- const { clickedObject } = useClickedObject();
11933
- const path$1 = clickedObject?.path ?? hoveredObject?.path ?? null;
11934
- const color = path$1 ? metricCache.colormap.get(path$1) : null;
11935
- let blobLightness = color ? getLightness(color) : -1;
11936
- if (color === "#c0c0c0") blobLightness = -1;
11937
- const offset = useMemo(() => {
11938
- const min = getLightness(minColor);
11939
- const diff = getLightness(maxColor) - min;
11940
- if (diff === 0) return 1;
11941
- return (blobLightness - min) / diff;
11942
- }, [
11943
- blobLightness,
11944
- maxColor,
11945
- minColor
11946
- ]);
11947
- const visible = path$1 !== null;
11948
- const midValue = Math.round((maxValue - minValue) / 2);
11949
- return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
11950
- className: "relative h-4 rounded-sm",
11951
- style: { backgroundImage: `linear-gradient(to right, ${minColor}, ${maxColor})` },
11952
- children: /* @__PURE__ */ jsx(LegendBarIndicator, {
11953
- offset: offset * 100,
11954
- visible
11955
- })
11956
- }), /* @__PURE__ */ jsxs("div", {
11957
- className: "flex justify-between",
11861
+ function CheckboxWithLabel({ children, checked, onChange, className = "", checkedIcon = mdiCheckboxOutline, uncheckedIcon = mdiCheckboxBlankOutline, ...props }) {
11862
+ const [isTransitioning, startTransition$1] = useTransition();
11863
+ return /* @__PURE__ */ jsxs("label", {
11864
+ className: `label group flex w-full items-center justify-start gap-2 hover:text-blue-500 ${className}`,
11865
+ ...props,
11958
11866
  children: [
11959
- /* @__PURE__ */ jsx("span", {
11960
- className: "font-bold",
11961
- title: minValue.toLocaleString(),
11962
- children: numToFriendlyString(minValue)
11867
+ /* @__PURE__ */ jsxs("span", {
11868
+ className: "flex grow items-center gap-2",
11869
+ children: [children, isTransitioning ? /* @__PURE__ */ jsx("img", {
11870
+ src: truck_default$1,
11871
+ alt: "...",
11872
+ className: "h-5"
11873
+ }) : ""]
11963
11874
  }),
11964
- /* @__PURE__ */ jsx("span", {
11965
- className: "absolute left-1/2 -translate-x-1/2 font-bold",
11966
- title: midValue.toLocaleString(),
11967
- children: numToFriendlyString(midValue)
11875
+ /* @__PURE__ */ jsx(Icon, {
11876
+ className: "place-self-end group-hover:text-blue-500",
11877
+ path: checked ? checkedIcon : uncheckedIcon,
11878
+ size: 1
11968
11879
  }),
11969
- /* @__PURE__ */ jsx("span", {
11970
- className: "font-bold",
11971
- title: maxValue.toLocaleString(),
11972
- children: numToFriendlyString(maxValue)
11880
+ /* @__PURE__ */ jsx("input", {
11881
+ type: "checkbox",
11882
+ defaultChecked: checked,
11883
+ className: "hidden",
11884
+ onChange: (e) => startTransition$1(() => onChange(e))
11973
11885
  })
11974
11886
  ]
11975
- })] });
11976
- }
11977
- function Handle({ domain: [min, max], children, disabled, handle, handleType = "round", title, getHandleProps, className, onClick }) {
11978
- return /* @__PURE__ */ jsx("button", {
11979
- 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),
11980
- role: "slider",
11981
- "aria-disabled": disabled,
11982
- "aria-valuemin": min,
11983
- "aria-valuemax": max,
11984
- "aria-valuenow": handle.value,
11985
- title,
11986
- style: { left: `${handle.percent}%` },
11987
- onClick,
11988
- ...getHandleProps(handle.id),
11989
- children: /* @__PURE__ */ jsx("div", {
11990
- className: cn("btn--primary", {
11991
- "size-5 rounded-full": handleType === "round",
11992
- "h-5 w-0.5": handleType === "square"
11993
- }),
11994
- children
11995
- })
11996
- });
11997
- }
11998
- function SliderRail({ getRailProps, className = "", children }) {
11999
- return /* @__PURE__ */ jsx("div", {
12000
- className: cn("bg-blue-secondary/20 absolute h-2 w-full cursor-pointer", {}, className),
12001
- ...getRailProps(),
12002
- children
12003
11887
  });
12004
11888
  }
12005
- function Track({ source, target, trackType = "round", getTrackProps, disabled }) {
12006
- return /* @__PURE__ */ jsx("div", {
12007
- className: cn("btn btn--primary absolute h-2 cursor-pointer p-0", disabled ? "bg-primary-text-dark dark:bg-primary-text" : "bg-blue-primary", {
12008
- "rounded-full": trackType === "round",
12009
- "rounded-none": trackType === "square"
12010
- }),
12011
- style: {
12012
- left: `${source.percent}%`,
12013
- width: `${target.percent - source.percent}%`
11889
+ function UnionAuthorsModal() {
11890
+ const { databaseInfo } = useData();
11891
+ const submit = useSubmit();
11892
+ const { authors } = databaseInfo;
11893
+ const authorUnions = databaseInfo.authorUnions;
11894
+ const [selectedAuthors, setSelectedAuthors] = useState([]);
11895
+ const [filter, setFilter] = useState("");
11896
+ const navigationData = useNavigation();
11897
+ const [, authorColors] = useMetrics();
11898
+ const [, startTransition$1] = useTransition();
11899
+ const location$1 = useLocation();
11900
+ const flattedUnionedAuthors = authorUnions.reduce((acc, union) => {
11901
+ return [...acc, ...union];
11902
+ }, []).sort(stringSorter);
11903
+ function ungroup(groupToUnGroup) {
11904
+ const newAuthorUnions = authorUnions.filter((_, i) => i !== groupToUnGroup);
11905
+ const form = new FormData();
11906
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11907
+ submit(form, {
11908
+ action: href("/view") + location$1.search,
11909
+ method: "post"
11910
+ });
11911
+ }
11912
+ function ungroupAll() {
11913
+ const form = new FormData();
11914
+ form.append("unionedAuthors", JSON.stringify([]));
11915
+ submit(form, {
11916
+ action: href("/view") + location$1.search,
11917
+ method: "post"
11918
+ });
11919
+ }
11920
+ function groupSelectedAuthors() {
11921
+ if (selectedAuthors.length < 2) return;
11922
+ const form = new FormData();
11923
+ form.append("unionedAuthors", JSON.stringify([...authorUnions, selectedAuthors]));
11924
+ submit(form, {
11925
+ action: href("/view") + location$1.search,
11926
+ method: "post"
11927
+ });
11928
+ setSelectedAuthors([]);
11929
+ }
11930
+ function makePrimaryAlias(alias, groupIndex) {
11931
+ const newAuthorUnions = authorUnions.map((group, i) => {
11932
+ if (i === groupIndex) return [alias, ...group.filter((a) => a !== alias)];
11933
+ return group;
11934
+ });
11935
+ const form = new FormData();
11936
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
11937
+ submit(form, {
11938
+ action: href("/view") + location$1.search,
11939
+ method: "post"
11940
+ });
11941
+ }
11942
+ const disabled = navigationData.state !== "idle";
11943
+ const ungroupedAuthorsSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
11944
+ const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
11945
+ const ungroupedAuthorsFiltered = ungroupedAuthorsSorted.filter((author) => author.toLowerCase().includes(filter.toLowerCase()));
11946
+ const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => /* @__PURE__ */ jsx(CheckboxWithLabel, {
11947
+ className: "hover:opacity-70",
11948
+ checked: selectedAuthors.includes(author),
11949
+ onChange: (e) => {
11950
+ setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
12014
11951
  },
12015
- ...getTrackProps()
12016
- });
12017
- }
12018
- var alignToJustify = {
12019
- left: "start",
12020
- center: "center",
12021
- right: "end"
12022
- };
12023
- function LabeledTicks({ valueMap, titleMap = valueMap, onTop = false }) {
12024
- const count = Object.entries(valueMap).length;
12025
- return /* @__PURE__ */ jsx(Ticks, {
12026
- count,
12027
- children: ({ ticks }) => /* @__PURE__ */ jsx("div", {
12028
- className: "grid grid-flow-col grid-cols-3 grid-rows-[1fr_auto] pt-4",
12029
- children: ticks.map((tick) => {
12030
- const tickLabelInfo = valueMap[tick.value * 100];
12031
- const align = tick.value === 0 ? "left" : tick.value === 1 ? "right" : "center";
12032
- const Tick$1 = /* @__PURE__ */ jsx("div", {
12033
- className: `flex justify-${tickLabelInfo ? alignToJustify[align] : "center"}`,
12034
- children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12035
- });
12036
- const Label$1 = /* @__PURE__ */ jsx("div", {
12037
- title: titleMap[tick.value * 100],
12038
- className: "truncate text-xs",
12039
- style: { textAlign: align },
12040
- children: tickLabelInfo
12041
- });
12042
- return /* @__PURE__ */ jsxs(Fragment, { children: [onTop ? Label$1 : Tick$1, onTop ? Tick$1 : Label$1] }, tick.id);
12043
- })
12044
- })
12045
- });
12046
- }
12047
- function TicksByCount({ className = "", count, tickToLabel, onTop = true, align, below = false }) {
12048
- return /* @__PURE__ */ jsx("div", {
12049
- className: cn("grid grid-flow-col grid-cols-(--cols) gap-1 text-xs", {
12050
- "grid-rows-[auto]": !below && !onTop,
12051
- "grid-rows-[auto_auto]": below !== onTop,
12052
- "grid-rows-[auto_auto_auto]": below && onTop
12053
- }, className),
12054
- style: { "--cols": `repeat(${count}, minmax(0, 1fr))` },
12055
- children: Array.from({ length: count }).map((_, i) => {
12056
- const tick = i / (count - 1);
12057
- const tickLabelInfo = tickToLabel(tick, i);
12058
- const alignment = align ?? (tick === 0 ? "left" : tick === 1 ? "right" : "center");
12059
- const justification = tickLabelInfo ? alignToJustify[alignment] : "center";
12060
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12061
- onTop ? /* @__PURE__ */ jsx(Tick, { justification }) : null,
12062
- /* @__PURE__ */ jsx("div", {
12063
- className: cn(`text-${alignment}`),
12064
- children: tickLabelInfo === 0 ? "" : tickLabelInfo
12065
- }),
12066
- below ? /* @__PURE__ */ jsx(Tick, { justification }) : null
12067
- ] }, i);
12068
- })
12069
- });
12070
- }
12071
- const Tick = ({ className = "", justification = "start" }) => /* @__PURE__ */ jsx("div", {
12072
- className: cn("flex", `justify-${justification}`, className),
12073
- children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12074
- });
12075
- function SegmentLegend({ hoveredObject }) {
12076
- const { metricType } = useOptions();
12077
- const [metricsData] = useMetrics();
12078
- const { steps, textGenerator, colorGenerator, offsetStepCalc } = metricsData.get(metricType).legend;
12079
- const width = 100 / steps;
12080
- let arrowVisible = false;
12081
- let arrowOffset = 0;
12082
- const clickedObject = useClickedObject().clickedObject ?? hoveredObject ?? null;
12083
- if (isBlob(clickedObject)) {
12084
- arrowVisible = true;
12085
- arrowOffset = width / 2 + width * offsetStepCalc(clickedObject);
12086
- }
12087
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
12088
- className: "relative",
12089
11952
  children: /* @__PURE__ */ jsxs("div", {
12090
- className: "relative flex text-xs whitespace-nowrap",
12091
- children: [[...Array(steps)].map((_, i) => {
12092
- return steps >= 4 ? /* @__PURE__ */ jsx(MetricSegment, {
12093
- className: i === 0 ? "rounded-l-sm" : i === steps - 1 ? "rounded-r-sm" : "",
12094
- width,
12095
- color: colorGenerator(i),
12096
- text: textGenerator(i),
12097
- top: i % 2 === 0
12098
- }, i) : /* @__PURE__ */ jsx(TopMetricSegment, {
12099
- width,
12100
- color: colorGenerator(i),
12101
- text: textGenerator(i)
12102
- }, i);
12103
- }), /* @__PURE__ */ jsx(LegendBarIndicator, {
12104
- offset: arrowOffset,
12105
- visible: arrowVisible
12106
- })]
11953
+ className: "inline-flex flex-row place-items-center gap-2",
11954
+ children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
12107
11955
  })
12108
- }) });
12109
- }
12110
- function MetricSegment({ className = "", width, color, text, top }) {
12111
- if (top) return /* @__PURE__ */ jsxs("div", {
12112
- className: "flex flex-col",
12113
- style: { width: `${width}%` },
12114
- children: [
12115
- /* @__PURE__ */ jsx("div", {
12116
- className: "h-5 truncate text-left",
12117
- title: text,
12118
- children: text
12119
- }),
12120
- /* @__PURE__ */ jsx(Tick, { className: "ml-1" }),
12121
- /* @__PURE__ */ jsx("div", {
12122
- className: cn("h-4", className),
12123
- style: { backgroundColor: color }
12124
- }),
12125
- /* @__PURE__ */ jsx(Tick, { className: "invisible" })
12126
- ]
11956
+ }, author));
11957
+ const groupedAuthorsEntries = authorUnions.map((aliasGroup, aliasGroupIndex) => {
11958
+ const displayName = aliasGroup[0];
11959
+ const disabled$1 = navigationData.state !== "idle";
11960
+ return /* @__PURE__ */ jsxs("div", {
11961
+ className: "card group m-0 flex h-full flex-col p-2",
11962
+ children: [
11963
+ /* @__PURE__ */ jsxs("div", {
11964
+ className: "inline-flex flex-row place-items-center gap-2",
11965
+ children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(displayName) }), /* @__PURE__ */ jsx("b", {
11966
+ className: "truncate",
11967
+ title: displayName,
11968
+ children: displayName
11969
+ })]
11970
+ }),
11971
+ aliasGroup.slice(1).sort(stringSorter).map((alias) => /* @__PURE__ */ jsx(AliasEntry, {
11972
+ alias,
11973
+ disabled: disabled$1,
11974
+ onClick: () => makePrimaryAlias(alias, aliasGroupIndex)
11975
+ }, alias)),
11976
+ /* @__PURE__ */ jsx("div", { className: "grow" }),
11977
+ /* @__PURE__ */ jsxs("div", {
11978
+ className: "flex items-end justify-end gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
11979
+ children: [/* @__PURE__ */ jsxs(Form, {
11980
+ action: href("/view") + location$1.search,
11981
+ method: "post",
11982
+ children: [/* @__PURE__ */ jsx("input", {
11983
+ type: "hidden",
11984
+ name: "unionedAuthors",
11985
+ value: JSON.stringify(authorUnions)
11986
+ }), /* @__PURE__ */ jsx("button", {
11987
+ className: "btn",
11988
+ title: "Add selected authors to this group",
11989
+ disabled: disabled$1 || selectedAuthors.length === 0,
11990
+ onClick: () => {
11991
+ const newAuthorUnions = authorUnions.map(([displayName$1, ...group], i) => {
11992
+ if (i === aliasGroupIndex) return [
11993
+ displayName$1,
11994
+ ...group,
11995
+ ...selectedAuthors
11996
+ ];
11997
+ return [displayName$1, ...group];
11998
+ });
11999
+ const form = new FormData();
12000
+ form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12001
+ submit(form, {
12002
+ action: href("/view") + location$1.search,
12003
+ method: "post"
12004
+ });
12005
+ setSelectedAuthors([]);
12006
+ },
12007
+ children: "Add selected"
12008
+ })]
12009
+ }), /* @__PURE__ */ jsx("button", {
12010
+ className: "btn",
12011
+ title: "Ungroup",
12012
+ disabled: disabled$1,
12013
+ onClick: () => ungroup(aliasGroupIndex),
12014
+ children: "Ungroup"
12015
+ })]
12016
+ })
12017
+ ]
12018
+ }, aliasGroupIndex);
12127
12019
  });
12128
- else return /* @__PURE__ */ jsxs("div", {
12129
- className: "flex flex-col",
12130
- style: { width: `${width}%` },
12020
+ return /* @__PURE__ */ jsxs("div", {
12021
+ className: "m-auto grid h-full max-h-200 w-full max-w-(--breakpoint-2xl) grid-cols-[1fr_1fr] grid-rows-[max-content_1fr] gap-2 overflow-hidden bg-gray-100 p-4",
12131
12022
  children: [
12132
- /* @__PURE__ */ jsx("div", {
12133
- className: "invisible h-5 text-left",
12134
- children: text
12023
+ /* @__PURE__ */ jsxs("div", {
12024
+ className: "flex justify-between",
12025
+ children: [/* @__PURE__ */ jsxs("h3", {
12026
+ className: "grow text-center text-lg font-bold",
12027
+ children: [
12028
+ "Ungrouped Authors (",
12029
+ ungroupedAuthorsSorted.length,
12030
+ ")"
12031
+ ]
12032
+ }), /* @__PURE__ */ jsx("div", {
12033
+ className: "flex justify-end gap-2",
12034
+ children: /* @__PURE__ */ jsxs("button", {
12035
+ className: "btn btn--primary justify-self-end",
12036
+ title: "Group the selected authors",
12037
+ disabled: disabled || selectedAuthors.length < 2,
12038
+ onClick: groupSelectedAuthors,
12039
+ children: [/* @__PURE__ */ jsx(Icon, {
12040
+ path: mdiAccountMultiplePlus,
12041
+ size: 1
12042
+ }), "Create group"]
12043
+ })
12044
+ })]
12135
12045
  }),
12136
- /* @__PURE__ */ jsx(Tick, { className: "invisible" }),
12137
- /* @__PURE__ */ jsx("div", {
12138
- className: cn("h-4", className),
12139
- style: { backgroundColor: color }
12046
+ /* @__PURE__ */ jsxs("div", {
12047
+ className: "flex justify-between",
12048
+ children: [/* @__PURE__ */ jsxs("h3", {
12049
+ className: "grow text-center text-lg font-bold",
12050
+ children: [
12051
+ "Author Groups (",
12052
+ groupedAuthorsEntries.length,
12053
+ ")"
12054
+ ]
12055
+ }), /* @__PURE__ */ jsx("div", {
12056
+ className: "flex justify-end gap-4",
12057
+ children: /* @__PURE__ */ jsxs("button", {
12058
+ className: "btn btn--danger",
12059
+ disabled: disabled || authorUnions.length === 0,
12060
+ onClick: () => {
12061
+ if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
12062
+ },
12063
+ children: [/* @__PURE__ */ jsx(Icon, {
12064
+ path: mdiAccountMultipleMinus,
12065
+ size: 1
12066
+ }), "Ungroup all"]
12067
+ })
12068
+ })]
12140
12069
  }),
12141
- /* @__PURE__ */ jsx(Tick, {}),
12142
- /* @__PURE__ */ jsx("div", {
12143
- className: "h-5 truncate text-left",
12144
- children: text
12145
- })
12146
- ]
12147
- });
12148
- }
12149
- function TopMetricSegment({ width, color, text }) {
12150
- return /* @__PURE__ */ jsxs("div", {
12151
- className: "flex flex-col",
12152
- style: { width: `${width}%` },
12153
- children: [
12154
12070
  /* @__PURE__ */ jsx("div", {
12155
- className: "h-5 truncate text-left",
12156
- children: text
12071
+ className: "max-h-full overflow-y-scroll",
12072
+ children: /* @__PURE__ */ jsxs("div", {
12073
+ className: "h-fill flex min-h-0 flex-col rounded-md dark:bg-gray-700",
12074
+ children: [/* @__PURE__ */ jsxs("div", {
12075
+ className: "sticky top-0 z-10 flex gap-2 bg-gray-100 p-2 dark:bg-gray-700",
12076
+ children: [
12077
+ /* @__PURE__ */ jsx("input", {
12078
+ className: "input min-w-0",
12079
+ type: "search",
12080
+ placeholder: "Filter...",
12081
+ disabled: ungroupedAuthorsSorted.length === 0,
12082
+ onChange: (e) => startTransition$1(() => setFilter(e.target.value))
12083
+ }),
12084
+ /* @__PURE__ */ jsx("button", {
12085
+ disabled: disabled || selectedAuthors.length === 0,
12086
+ className: "btn btn--outlined w-max grow",
12087
+ title: "Clear selection",
12088
+ onClick: () => setSelectedAuthors([]),
12089
+ children: "Clear"
12090
+ }),
12091
+ /* @__PURE__ */ jsx("button", {
12092
+ disabled: ungroupedAuthorsSorted.length === 0,
12093
+ className: "btn btn--outlined w-max grow",
12094
+ title: "Clear selection",
12095
+ onClick: () => selectedAuthors.length === ungroupedAuthorsFiltered.length ? setSelectedAuthors([]) : setSelectedAuthors((selected) => Array.from(new Set([...selected, ...ungroupedAuthorsFiltered]))),
12096
+ children: selectedAuthors.length === ungroupedAuthorsFiltered.length ? "Deselect all" : "Select all"
12097
+ })
12098
+ ]
12099
+ }), /* @__PURE__ */ jsx("div", {
12100
+ className: "min-h-fill max-h-full overflow-y-auto p-2",
12101
+ children: ungroupedAuthorsEntries.length > 0 ? ungroupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12102
+ className: "place-self-center",
12103
+ children: filter.length > 0 ? "No authors found" : "All authors have been grouped"
12104
+ })
12105
+ })]
12106
+ })
12157
12107
  }),
12158
- /* @__PURE__ */ jsx(Tick, {}),
12159
12108
  /* @__PURE__ */ jsx("div", {
12160
- className: "h-5",
12161
- style: { backgroundColor: color }
12109
+ className: "min-h-0 overflow-y-auto",
12110
+ children: /* @__PURE__ */ jsx("div", {
12111
+ className: "grid h-min min-h-0 grid-cols-1 gap-4 rounded-md bg-white p-4 shadow-sm lg:grid-cols-2 xl:grid-cols-3 dark:bg-gray-700",
12112
+ children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
12113
+ className: "place-self-center",
12114
+ children: "No authors have been grouped yet"
12115
+ })
12116
+ })
12162
12117
  })
12163
12118
  ]
12164
12119
  });
12120
+ function AliasEntry({ alias, onClick, disabled: disabled$1 }) {
12121
+ return /* @__PURE__ */ jsxs("button", {
12122
+ className: "btn flex grid-flow-col gap-2 text-sm [&:hover>svg]:opacity-50 [&>svg]:opacity-0",
12123
+ disabled: disabled$1,
12124
+ title: "Make display name for this grouping",
12125
+ onClick,
12126
+ children: [/* @__PURE__ */ jsx(Icon, {
12127
+ path: mdiArrowUp,
12128
+ size: .75
12129
+ }), /* @__PURE__ */ jsx("label", {
12130
+ title: alias,
12131
+ className: "label truncate",
12132
+ children: alias
12133
+ })]
12134
+ }, alias);
12135
+ }
12165
12136
  }
12166
- function PercentageSlider({ className = "" }) {
12167
- const { dominantAuthorCutoff, setDominantAuthorCutoff } = useOptions();
12168
- const [displayPercentage, setDisplayPercentage] = useState(dominantAuthorCutoff);
12169
- const domain = [0, 100];
12170
- return /* @__PURE__ */ jsxs("div", {
12171
- className: cn("grid grid-cols-[calc(11*var(--spacing))_1fr] px-3", className),
12172
- children: [/* @__PURE__ */ jsxs("p", {
12173
- title: "Min percentage of changes a file's top author must account for to be colored.",
12174
- children: [displayPercentage, "%"]
12175
- }), /* @__PURE__ */ jsx("div", {
12176
- className: "relative",
12177
- title: "Adjust the dominant author percentage cutoff",
12178
- children: /* @__PURE__ */ jsxs(Slider, {
12179
- mode: 1,
12180
- step: 1,
12181
- domain,
12182
- values: [displayPercentage],
12183
- onChange: (e) => {
12184
- setDominantAuthorCutoff(e[0]);
12185
- startTransition(() => {
12186
- setDisplayPercentage(e[0]);
12187
- });
12188
- },
12189
- onUpdate: (e) => {
12190
- startTransition(() => {
12191
- setDisplayPercentage(e[0]);
12192
- });
12193
- },
12194
- children: [
12195
- /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12196
- /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: handles.map((handle) => /* @__PURE__ */ jsx(Handle, {
12197
- handle,
12198
- domain,
12199
- getHandleProps
12200
- }, handle.id)) }) }),
12201
- /* @__PURE__ */ jsx(Tracks, {
12202
- right: false,
12203
- children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: tracks.map(({ id, ...props }) => /* @__PURE__ */ createElement(Track, {
12204
- ...props,
12205
- key: id,
12206
- backgroundColor: noEntryColor,
12207
- getTrackProps
12208
- })) })
12209
- }),
12210
- /* @__PURE__ */ jsx(LabeledTicks, { valueMap: {
12211
- 0: "Top author",
12212
- 50: "Majority author",
12213
- 100: "Single author"
12214
- } })
12215
- ]
12216
- })
12217
- })]
12218
- });
12219
- }
12220
- function Legend({ hoveredObject, showUnionAuthorsModal }) {
12221
- const { sizeMetric, metricType } = useOptions();
12222
- const [metricsData] = useMetrics();
12223
- const deferredHoveredObject = useDeferredValue(hoveredObject);
12224
- const metricCache = metricsData.get(metricType) ?? void 0;
12225
- const legend = useMemo(() => {
12226
- if (metricCache === void 0) return null;
12227
- switch (getMetricLegendType(metricType)) {
12228
- case "POINT": return /* @__PURE__ */ jsx(PointLegend, {});
12229
- case "GRADIENT": return /* @__PURE__ */ jsx(GradientLegend, { hoveredObject: deferredHoveredObject });
12230
- case "SEGMENTS": return /* @__PURE__ */ jsx(SegmentLegend, { hoveredObject: deferredHoveredObject });
12231
- }
12232
- }, [
12233
- metricCache,
12234
- deferredHoveredObject,
12235
- metricType
12236
- ]);
12237
- if (legend === null) return null;
12238
- return /* @__PURE__ */ jsxs(Fragment, { children: [
12239
- /* @__PURE__ */ jsx("h3", {
12240
- className: "card__subtitle",
12241
- children: "Size legend"
12242
- }),
12243
- /* @__PURE__ */ jsx("p", {
12244
- className: "mb-2 text-sm",
12245
- children: sizeMetricLegendDescriptions[sizeMetric]
12246
- }),
12247
- /* @__PURE__ */ jsx("h3", {
12248
- className: "card__subtitle",
12249
- children: "Color legend"
12250
- }),
12251
- /* @__PURE__ */ jsx("p", {
12252
- className: "mb-4 text-sm",
12253
- children: colorMetricDescriptions[metricType]
12254
- }),
12255
- metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(PercentageSlider, { className: "my-4" }) : null,
12256
- legend,
12257
- metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(AuthorOptions, { showUnionAuthorsModal }) : null
12258
- ] });
12259
- }
12260
- function LoadingIndicator({ className = "", showProgress = false, fetchProgress = true, loadingText }) {
12261
- const [path$1] = useQueryState("path");
12262
- const fetcher = useFetcher();
12263
- useEffect(() => {
12264
- if (fetcher.state === "idle" && showProgress && fetchProgress) fetcher.load(href("/view/progress") + viewSerializer({ path: path$1 }));
12265
- }, [
12266
- fetchProgress,
12267
- fetcher,
12268
- fetcher.state,
12269
- path$1,
12270
- showProgress
12271
- ]);
12272
- const [progressText, progress] = useMemo(() => {
12273
- if (!fetcher.data) return ["Loading truck...", 0];
12274
- const { progress: progress$1, analyzationStatus } = fetcher.data;
12275
- switch (analyzationStatus) {
12276
- case "Starting": return ["Loading truck...", 0];
12277
- case "GeneratingChart": return ["Unloading truck...", 100];
12278
- default: return [progress$1 < 100 ? "Driving to destination..." : "Parking truck...", progress$1];
12279
- }
12280
- }, [fetcher.data]);
12281
- return /* @__PURE__ */ jsx("div", {
12282
- className: clsx$1("grid h-full w-full place-items-center px-4", className),
12283
- children: /* @__PURE__ */ jsxs("div", {
12284
- className: "flex w-full max-w-[clamp(16rem,80vw,36rem)] flex-col gap-4 px-2 py-2",
12285
- children: [
12286
- /* @__PURE__ */ jsx("img", {
12287
- src: !showProgress || progress > 0 && progress < 100 ? truck_default : truck_default$1,
12288
- alt: "🚛",
12289
- className: "pixelated aspect-square w-full"
12290
- }),
12291
- showProgress ? /* @__PURE__ */ jsx("div", {
12292
- className: "text-center text-3xl font-bold",
12293
- children: progressText
12294
- }) : null,
12295
- showProgress ? /* @__PURE__ */ jsx("div", {
12296
- className: "h-6 w-3/4 self-center rounded-2xl bg-gray-300",
12297
- children: /* @__PURE__ */ jsx("div", {
12298
- 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 }),
12299
- style: { width: progress ? `calc(${Math.min(progress, 100)}% - 4px)` : "40px" }
12300
- })
12301
- }) : null,
12302
- loadingText ? /* @__PURE__ */ jsx("div", {
12303
- className: "text-center font-bold",
12304
- children: loadingText
12305
- }) : null
12306
- ]
12307
- })
12308
- });
12309
- }
12137
+ var stringSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
12310
12138
  function IconRadioGroup({ group, titleMap, defaultValue, className, onChange, large, iconMap }) {
12311
12139
  const enumEntries = Object.entries(group);
12312
12140
  return /* @__PURE__ */ jsx(RadioGroup, {
@@ -12402,34 +12230,623 @@ const Options = memo(function Options$1() {
12402
12230
  })] })
12403
12231
  ] });
12404
12232
  });
12405
- function Providers({ children, data }) {
12406
- const [options, setOptions] = useState(() => {
12407
- const savedOptions = typeof document !== "undefined" ? localStorage.getItem(OPTIONS_LOCAL_STORAGE_KEY) : null;
12408
- return {
12409
- ...getDefaultOptionsContextValue(),
12410
- ...savedOptions ? JSON.parse(savedOptions) : {},
12411
- hasLoadedSavedOptions: !!savedOptions
12412
- };
12233
+ function SettingsModal() {
12234
+ const { metricType, hierarchyType, transitionsEnabled, renderCutoff, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setRenderCutoff, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
12235
+ const [isTransitioning, startTransition$1] = useTransition();
12236
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
12237
+ className: "flex min-w-120 flex-col items-start justify-center gap-4 p-2 pl-0",
12238
+ children: [
12239
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12240
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12241
+ checked: Boolean(linkMetricAndSizeMetric),
12242
+ title: "Enable to sync size metric with color metric",
12243
+ onChange: (e) => {
12244
+ setLinkMetricAndSizeMetric(e.target.checked);
12245
+ if (e.target.checked) setSizeMetricType(relatedSizeMetric[metricType]);
12246
+ },
12247
+ children: [/* @__PURE__ */ jsx(Icon, {
12248
+ className: "ml-1.5",
12249
+ path: mdiLink,
12250
+ size: "1.25em"
12251
+ }), /* @__PURE__ */ jsx("span", { children: "Link size and color option" })]
12252
+ }),
12253
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12254
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12255
+ checked: transitionsEnabled,
12256
+ title: "Disable to improve performance when zooming",
12257
+ onChange: (e) => setTransitionsEnabled(e.target.checked),
12258
+ children: [/* @__PURE__ */ jsx(Icon, {
12259
+ className: "ml-1.5",
12260
+ path: mdiTransition,
12261
+ size: "1.25em"
12262
+ }), "Transitions"]
12263
+ }),
12264
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12265
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12266
+ checked: labelsVisible,
12267
+ title: "Disable to improve performance",
12268
+ onChange: (e) => setLabelsVisible(e.target.checked),
12269
+ children: [/* @__PURE__ */ jsx(Icon, {
12270
+ className: "ml-1.5",
12271
+ path: mdiLabel,
12272
+ size: "1.25em"
12273
+ }), "Labels"]
12274
+ }),
12275
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12276
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12277
+ checked: showFilesWithoutChanges,
12278
+ title: "Show files that have had no changes in the selected time range",
12279
+ onChange: (e) => setShowFilesWithoutChanges(e.target.checked),
12280
+ children: [/* @__PURE__ */ jsx(Icon, {
12281
+ className: "ml-1.5",
12282
+ path: mdiClockEdit,
12283
+ size: "1.25em"
12284
+ }), "Show files with no activity"]
12285
+ }),
12286
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12287
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12288
+ checked: hierarchyType === "FLAT",
12289
+ title: "Show all files on the same level, instead of as a file tree",
12290
+ onChange: () => {
12291
+ if (hierarchyType === "FLAT") setHierarchyType("NESTED");
12292
+ else setHierarchyType("FLAT");
12293
+ },
12294
+ children: [/* @__PURE__ */ jsx(Icon, {
12295
+ className: "ml-1.5",
12296
+ path: mdiFileTree,
12297
+ size: "1.25em"
12298
+ }), "Flatten file tree"]
12299
+ }),
12300
+ /* @__PURE__ */ jsxs(CheckboxWithLabel, {
12301
+ className: "group text-sm hover:text-blue-500 hover:opacity-100",
12302
+ checked: showOnlySearchMatches,
12303
+ title: "When searching, hide files that do not match the search query",
12304
+ onChange: (e) => setShowOnlySearchMatches(e.target.checked),
12305
+ children: [/* @__PURE__ */ jsx(Icon, {
12306
+ className: "ml-1.5",
12307
+ path: mdiFilter,
12308
+ size: "1.25em"
12309
+ }), "Show only search matches"]
12310
+ }),
12311
+ /* @__PURE__ */ jsxs("label", {
12312
+ className: "label group flex w-full items-center justify-start gap-2 text-sm hover:text-blue-500 hover:opacity-100",
12313
+ title: "Increase this to improve render performance, decrease it to get higher level of detail",
12314
+ children: [/* @__PURE__ */ jsxs("span", {
12315
+ className: "group flex grow items-center gap-2",
12316
+ children: [
12317
+ /* @__PURE__ */ jsx(Icon, {
12318
+ className: "ml-1.5",
12319
+ path: mdiContentCut,
12320
+ size: "1.25em"
12321
+ }),
12322
+ "Pixel render cut-off ",
12323
+ isTransitioning ? /* @__PURE__ */ jsx("img", {
12324
+ src: truck_default$1,
12325
+ alt: "...",
12326
+ className: "h-5"
12327
+ }) : ""
12328
+ ]
12329
+ }), /* @__PURE__ */ jsx("input", {
12330
+ type: "number",
12331
+ min: 0,
12332
+ defaultValue: renderCutoff,
12333
+ className: "mr-1 w-12 place-self-end border-b-2",
12334
+ onChange: (x) => startTransition$1(() => setRenderCutoff(x.target.valueAsNumber))
12335
+ })]
12336
+ })
12337
+ ]
12338
+ }) });
12339
+ }
12340
+ var modals = {
12341
+ "group-authors": {
12342
+ content: /* @__PURE__ */ jsx(UnionAuthorsModal, {}),
12343
+ title: "Group Authors",
12344
+ icon: mdiAccountMultipleCheck
12345
+ },
12346
+ "app-settings": {
12347
+ content: /* @__PURE__ */ jsx(SettingsModal, {}),
12348
+ title: "Settings",
12349
+ icon: mdiCog
12350
+ }
12351
+ };
12352
+ var modalSearchParamConfig = parseAsStringLiteral(Object.keys(modals));
12353
+ function useModal(modalKey = null) {
12354
+ const [modal, setModal] = useQueryState("modal", modalSearchParamConfig);
12355
+ const openModal = (modal$1 = modalKey) => void setModal(modal$1);
12356
+ const closeModal = () => setModal(null);
12357
+ return {
12358
+ modal,
12359
+ openModal,
12360
+ closeModal
12361
+ };
12362
+ }
12363
+ function ModalManager() {
12364
+ const [modalKey, setModal] = useQueryState("modal", modalSearchParamConfig);
12365
+ const dialogRef = useRef(null);
12366
+ const onClose = () => setModal(null);
12367
+ useEffect(() => {
12368
+ if (!dialogRef.current) return;
12369
+ const dialog = dialogRef.current;
12370
+ if (modalKey) {
12371
+ dialogRef.current.showModal();
12372
+ return;
12373
+ }
12374
+ dialog.close();
12375
+ return () => dialog.close();
12376
+ }, [modalKey]);
12377
+ if (!modalKey) return null;
12378
+ const modal = modals[modalKey];
12379
+ return /* @__PURE__ */ jsx("dialog", {
12380
+ ref: dialogRef,
12381
+ "aria-modal": true,
12382
+ open: false,
12383
+ closedby: "any",
12384
+ className: "z-10 m-auto flex flex-col items-start justify-stretch bg-transparent text-inherit backdrop:bg-gray-500/75 backdrop:p-0",
12385
+ onClose,
12386
+ children: /* @__PURE__ */ jsxs("div", {
12387
+ className: "card m-auto h-full w-full max-w-(--breakpoint-2xl) gap-2 overflow-hidden rounded-xl bg-gray-100 p-4 shadow-sm",
12388
+ children: [
12389
+ /* @__PURE__ */ jsxs("div", {
12390
+ className: "flex justify-between gap-2",
12391
+ children: [/* @__PURE__ */ jsxs("div", {
12392
+ className: "flex flex-row items-center gap-2",
12393
+ children: [/* @__PURE__ */ jsx(Icon, {
12394
+ path: modal.icon,
12395
+ size: "1.75em"
12396
+ }), /* @__PURE__ */ jsx("h2", {
12397
+ className: "text-2xl font-semibold",
12398
+ children: modal.title
12399
+ })]
12400
+ }), /* @__PURE__ */ jsx(CloseButton, {
12401
+ absolute: false,
12402
+ className: "justify-self-end px-1",
12403
+ onClick: onClose
12404
+ })]
12405
+ }),
12406
+ /* @__PURE__ */ jsx("span", { className: "w-full border-b-3 border-gray-500 px-20" }),
12407
+ modal.content ?? null
12408
+ ]
12409
+ })
12413
12410
  });
12414
- const [searchResults, setSearchResults] = useState({});
12415
- const hasSearchResults = useMemo(() => Object.values(searchResults).length > 0, [searchResults]);
12416
- const prefersLight = usePrefersLightMode();
12417
- const metricsData = useMemo(() => {
12418
- return createMetricData(data, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12411
+ }
12412
+ function AuthorOptions() {
12413
+ const transitionState = useNavigation();
12414
+ const action$3 = href("/view");
12415
+ const { openModal } = useModal("group-authors");
12416
+ return /* @__PURE__ */ jsxs("div", {
12417
+ className: "mt-2 grid w-full grid-cols-[1fr_1fr] gap-2",
12418
+ children: [/* @__PURE__ */ jsxs("button", {
12419
+ className: "btn",
12420
+ onClick: () => openModal(),
12421
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
12422
+ }), /* @__PURE__ */ jsxs(Form, {
12423
+ method: "post",
12424
+ action: action$3,
12425
+ children: [/* @__PURE__ */ jsx("input", {
12426
+ type: "hidden",
12427
+ name: "rerollColors",
12428
+ value: ""
12429
+ }), /* @__PURE__ */ jsxs("button", {
12430
+ className: "btn w-full",
12431
+ type: "submit",
12432
+ disabled: transitionState.state !== "idle",
12433
+ children: [/* @__PURE__ */ jsx(Icon, { path: mdiDiceMultipleOutline }), "Shuffle colors"]
12434
+ })]
12435
+ })]
12436
+ });
12437
+ }
12438
+ function GradientLegend({ hoveredObject }) {
12439
+ const { metricType } = useOptions();
12440
+ const [metricsData] = useMetrics();
12441
+ const metricCache = metricsData.get(metricType);
12442
+ if (metricCache === void 0) throw new Error("Metric cache is undefined");
12443
+ const { minValue, maxValue, minColor, maxColor } = metricCache.legend;
12444
+ const { clickedObject } = useClickedObject();
12445
+ const path$1 = clickedObject?.path ?? hoveredObject?.path ?? null;
12446
+ const color = path$1 ? metricCache.colormap.get(path$1) : null;
12447
+ let blobLightness = color ? getLightness(color) : -1;
12448
+ if (color === "#c0c0c0") blobLightness = -1;
12449
+ const offset = useMemo(() => {
12450
+ const min = getLightness(minColor);
12451
+ const diff = getLightness(maxColor) - min;
12452
+ if (diff === 0) return 1;
12453
+ return (blobLightness - min) / diff;
12419
12454
  }, [
12420
- data,
12421
- options?.dominantAuthorCutoff,
12422
- prefersLight
12455
+ blobLightness,
12456
+ maxColor,
12457
+ minColor
12423
12458
  ]);
12424
- const optionsValue = useMemo(() => ({
12425
- ...options,
12426
- setMetricType: (metricType) => setOptions((prevOptions) => ({
12427
- ...prevOptions,
12428
- metricType
12429
- })),
12430
- setChartType: (chartType) => setOptions((prevOptions) => ({
12431
- ...prevOptions,
12432
- chartType
12459
+ const visible = path$1 !== null;
12460
+ const midValue = Math.round((maxValue - minValue) / 2);
12461
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
12462
+ className: "relative h-4 rounded-sm",
12463
+ style: { backgroundImage: `linear-gradient(to right, ${minColor}, ${maxColor})` },
12464
+ children: /* @__PURE__ */ jsx(LegendBarIndicator, {
12465
+ offset: offset * 100,
12466
+ visible
12467
+ })
12468
+ }), /* @__PURE__ */ jsxs("div", {
12469
+ className: "flex justify-between",
12470
+ children: [
12471
+ /* @__PURE__ */ jsx("span", {
12472
+ className: "font-bold",
12473
+ title: minValue.toLocaleString(),
12474
+ children: numToFriendlyString(minValue)
12475
+ }),
12476
+ /* @__PURE__ */ jsx("span", {
12477
+ className: "absolute left-1/2 -translate-x-1/2 font-bold",
12478
+ title: midValue.toLocaleString(),
12479
+ children: numToFriendlyString(midValue)
12480
+ }),
12481
+ /* @__PURE__ */ jsx("span", {
12482
+ className: "font-bold",
12483
+ title: maxValue.toLocaleString(),
12484
+ children: numToFriendlyString(maxValue)
12485
+ })
12486
+ ]
12487
+ })] });
12488
+ }
12489
+ function Handle({ domain: [min, max], children, disabled, handle, handleType = "round", title, getHandleProps, className, onClick }) {
12490
+ return /* @__PURE__ */ jsx("button", {
12491
+ 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),
12492
+ role: "slider",
12493
+ "aria-disabled": disabled,
12494
+ "aria-valuemin": min,
12495
+ "aria-valuemax": max,
12496
+ "aria-valuenow": handle.value,
12497
+ title,
12498
+ style: { left: `${handle.percent}%` },
12499
+ onClick,
12500
+ ...getHandleProps(handle.id),
12501
+ children: /* @__PURE__ */ jsx("div", {
12502
+ className: cn("btn--primary", {
12503
+ "size-5 rounded-full": handleType === "round",
12504
+ "h-5 w-0.5": handleType === "square"
12505
+ }),
12506
+ children
12507
+ })
12508
+ });
12509
+ }
12510
+ function SliderRail({ getRailProps, className = "", children }) {
12511
+ return /* @__PURE__ */ jsx("div", {
12512
+ className: cn("bg-blue-secondary/20 absolute h-2 w-full cursor-pointer", {}, className),
12513
+ ...getRailProps(),
12514
+ children
12515
+ });
12516
+ }
12517
+ function Track({ source, target, trackType = "round", getTrackProps, disabled }) {
12518
+ return /* @__PURE__ */ jsx("div", {
12519
+ className: cn("btn btn--primary absolute h-2 cursor-pointer p-0", disabled ? "bg-primary-text-dark dark:bg-primary-text" : "bg-blue-primary", {
12520
+ "rounded-full": trackType === "round",
12521
+ "rounded-none": trackType === "square"
12522
+ }),
12523
+ style: {
12524
+ left: `${source.percent}%`,
12525
+ width: `${target.percent - source.percent}%`
12526
+ },
12527
+ ...getTrackProps()
12528
+ });
12529
+ }
12530
+ var alignToJustify = {
12531
+ left: "start",
12532
+ center: "center",
12533
+ right: "end"
12534
+ };
12535
+ function LabeledTicks({ valueMap, titleMap = valueMap, onTop = false }) {
12536
+ const count = Object.entries(valueMap).length;
12537
+ return /* @__PURE__ */ jsx(Ticks, {
12538
+ count,
12539
+ children: ({ ticks }) => /* @__PURE__ */ jsx("div", {
12540
+ className: "grid grid-flow-col grid-cols-3 grid-rows-[1fr_auto] pt-4",
12541
+ children: ticks.map((tick) => {
12542
+ const tickLabelInfo = valueMap[tick.value * 100];
12543
+ const align = tick.value === 0 ? "left" : tick.value === 1 ? "right" : "center";
12544
+ const Tick$1 = /* @__PURE__ */ jsx("div", {
12545
+ className: `flex justify-${tickLabelInfo ? alignToJustify[align] : "center"}`,
12546
+ children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12547
+ });
12548
+ const Label$1 = /* @__PURE__ */ jsx("div", {
12549
+ title: titleMap[tick.value * 100],
12550
+ className: "truncate text-xs",
12551
+ style: { textAlign: align },
12552
+ children: tickLabelInfo
12553
+ });
12554
+ return /* @__PURE__ */ jsxs(Fragment, { children: [onTop ? Label$1 : Tick$1, onTop ? Tick$1 : Label$1] }, tick.id);
12555
+ })
12556
+ })
12557
+ });
12558
+ }
12559
+ function TicksByCount({ className = "", count, tickToLabel, onTop = true, align, below = false }) {
12560
+ return /* @__PURE__ */ jsx("div", {
12561
+ className: cn("grid grid-flow-col grid-cols-(--cols) gap-1 text-xs", {
12562
+ "grid-rows-[auto]": !below && !onTop,
12563
+ "grid-rows-[auto_auto]": below !== onTop,
12564
+ "grid-rows-[auto_auto_auto]": below && onTop
12565
+ }, className),
12566
+ style: { "--cols": `repeat(${count}, minmax(0, 1fr))` },
12567
+ children: Array.from({ length: count }).map((_, i) => {
12568
+ const tick = i / (count - 1);
12569
+ const tickLabelInfo = tickToLabel(tick, i);
12570
+ const alignment = align ?? (tick === 0 ? "left" : tick === 1 ? "right" : "center");
12571
+ const justification = tickLabelInfo ? alignToJustify[alignment] : "center";
12572
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12573
+ onTop ? /* @__PURE__ */ jsx(Tick, { justification }) : null,
12574
+ /* @__PURE__ */ jsx("div", {
12575
+ className: cn(`text-${alignment}`),
12576
+ children: tickLabelInfo === 0 ? "" : tickLabelInfo
12577
+ }),
12578
+ below ? /* @__PURE__ */ jsx(Tick, { justification }) : null
12579
+ ] }, i);
12580
+ })
12581
+ });
12582
+ }
12583
+ const Tick = ({ className = "", justification = "start" }) => /* @__PURE__ */ jsx("div", {
12584
+ className: cn("flex", `justify-${justification}`, className),
12585
+ children: /* @__PURE__ */ jsx("div", { className: "h-2 w-px bg-gray-950 dark:bg-gray-50" })
12586
+ });
12587
+ function SegmentLegend({ hoveredObject }) {
12588
+ const { metricType } = useOptions();
12589
+ const [metricsData] = useMetrics();
12590
+ const { steps, textGenerator, colorGenerator, offsetStepCalc } = metricsData.get(metricType).legend;
12591
+ const width = 100 / steps;
12592
+ let arrowVisible = false;
12593
+ let arrowOffset = 0;
12594
+ const clickedObject = useClickedObject().clickedObject ?? hoveredObject ?? null;
12595
+ if (isBlob(clickedObject)) {
12596
+ arrowVisible = true;
12597
+ arrowOffset = width / 2 + width * offsetStepCalc(clickedObject);
12598
+ }
12599
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
12600
+ className: "relative",
12601
+ children: /* @__PURE__ */ jsxs("div", {
12602
+ className: "relative flex text-xs whitespace-nowrap",
12603
+ children: [[...Array(steps)].map((_, i) => {
12604
+ return steps >= 4 ? /* @__PURE__ */ jsx(MetricSegment, {
12605
+ className: i === 0 ? "rounded-l-sm" : i === steps - 1 ? "rounded-r-sm" : "",
12606
+ width,
12607
+ color: colorGenerator(i),
12608
+ text: textGenerator(i),
12609
+ top: i % 2 === 0
12610
+ }, i) : /* @__PURE__ */ jsx(TopMetricSegment, {
12611
+ width,
12612
+ color: colorGenerator(i),
12613
+ text: textGenerator(i)
12614
+ }, i);
12615
+ }), /* @__PURE__ */ jsx(LegendBarIndicator, {
12616
+ offset: arrowOffset,
12617
+ visible: arrowVisible
12618
+ })]
12619
+ })
12620
+ }) });
12621
+ }
12622
+ function MetricSegment({ className = "", width, color, text, top }) {
12623
+ if (top) return /* @__PURE__ */ jsxs("div", {
12624
+ className: "flex flex-col",
12625
+ style: { width: `${width}%` },
12626
+ children: [
12627
+ /* @__PURE__ */ jsx("div", {
12628
+ className: "h-5 truncate text-left",
12629
+ title: text,
12630
+ children: text
12631
+ }),
12632
+ /* @__PURE__ */ jsx(Tick, { className: "ml-1" }),
12633
+ /* @__PURE__ */ jsx("div", {
12634
+ className: cn("h-4", className),
12635
+ style: { backgroundColor: color }
12636
+ }),
12637
+ /* @__PURE__ */ jsx(Tick, { className: "invisible" })
12638
+ ]
12639
+ });
12640
+ else return /* @__PURE__ */ jsxs("div", {
12641
+ className: "flex flex-col",
12642
+ style: { width: `${width}%` },
12643
+ children: [
12644
+ /* @__PURE__ */ jsx("div", {
12645
+ className: "invisible h-5 text-left",
12646
+ children: text
12647
+ }),
12648
+ /* @__PURE__ */ jsx(Tick, { className: "invisible" }),
12649
+ /* @__PURE__ */ jsx("div", {
12650
+ className: cn("h-4", className),
12651
+ style: { backgroundColor: color }
12652
+ }),
12653
+ /* @__PURE__ */ jsx(Tick, {}),
12654
+ /* @__PURE__ */ jsx("div", {
12655
+ className: "h-5 truncate text-left",
12656
+ children: text
12657
+ })
12658
+ ]
12659
+ });
12660
+ }
12661
+ function TopMetricSegment({ width, color, text }) {
12662
+ return /* @__PURE__ */ jsxs("div", {
12663
+ className: "flex flex-col",
12664
+ style: { width: `${width}%` },
12665
+ children: [
12666
+ /* @__PURE__ */ jsx("div", {
12667
+ className: "h-5 truncate text-left",
12668
+ children: text
12669
+ }),
12670
+ /* @__PURE__ */ jsx(Tick, {}),
12671
+ /* @__PURE__ */ jsx("div", {
12672
+ className: "h-5",
12673
+ style: { backgroundColor: color }
12674
+ })
12675
+ ]
12676
+ });
12677
+ }
12678
+ function PercentageSlider({ className = "" }) {
12679
+ const { dominantAuthorCutoff, setDominantAuthorCutoff } = useOptions();
12680
+ const [displayPercentage, setDisplayPercentage] = useState(dominantAuthorCutoff);
12681
+ const domain = [0, 100];
12682
+ return /* @__PURE__ */ jsxs("div", {
12683
+ className: cn("grid grid-cols-[calc(11*var(--spacing))_1fr] px-3", className),
12684
+ children: [/* @__PURE__ */ jsxs("p", {
12685
+ title: "Min percentage of changes a file's top author must account for to be colored.",
12686
+ children: [displayPercentage, "%"]
12687
+ }), /* @__PURE__ */ jsx("div", {
12688
+ className: "relative",
12689
+ title: "Adjust the dominant author percentage cutoff",
12690
+ children: /* @__PURE__ */ jsxs(Slider, {
12691
+ mode: 1,
12692
+ step: 1,
12693
+ domain,
12694
+ values: [displayPercentage],
12695
+ onChange: (e) => {
12696
+ setDominantAuthorCutoff(e[0]);
12697
+ startTransition(() => {
12698
+ setDisplayPercentage(e[0]);
12699
+ });
12700
+ },
12701
+ onUpdate: (e) => {
12702
+ startTransition(() => {
12703
+ setDisplayPercentage(e[0]);
12704
+ });
12705
+ },
12706
+ children: [
12707
+ /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12708
+ /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: handles.map((handle) => /* @__PURE__ */ jsx(Handle, {
12709
+ handle,
12710
+ domain,
12711
+ getHandleProps
12712
+ }, handle.id)) }) }),
12713
+ /* @__PURE__ */ jsx(Tracks, {
12714
+ right: false,
12715
+ children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx(Fragment$1, { children: tracks.map(({ id, ...props }) => /* @__PURE__ */ createElement(Track, {
12716
+ ...props,
12717
+ key: id,
12718
+ backgroundColor: noEntryColor,
12719
+ getTrackProps
12720
+ })) })
12721
+ }),
12722
+ /* @__PURE__ */ jsx(LabeledTicks, { valueMap: {
12723
+ 0: "Top author",
12724
+ 50: "Majority author",
12725
+ 100: "Single author"
12726
+ } })
12727
+ ]
12728
+ })
12729
+ })]
12730
+ });
12731
+ }
12732
+ function Legend({ hoveredObject }) {
12733
+ const { sizeMetric, metricType } = useOptions();
12734
+ const [metricsData] = useMetrics();
12735
+ const deferredHoveredObject = useDeferredValue(hoveredObject);
12736
+ const metricCache = metricsData.get(metricType) ?? void 0;
12737
+ const legend = useMemo(() => {
12738
+ if (metricCache === void 0) return null;
12739
+ switch (getMetricLegendType(metricType)) {
12740
+ case "POINT": return /* @__PURE__ */ jsx(PointLegend, {});
12741
+ case "GRADIENT": return /* @__PURE__ */ jsx(GradientLegend, { hoveredObject: deferredHoveredObject });
12742
+ case "SEGMENTS": return /* @__PURE__ */ jsx(SegmentLegend, { hoveredObject: deferredHoveredObject });
12743
+ }
12744
+ }, [
12745
+ metricCache,
12746
+ deferredHoveredObject,
12747
+ metricType
12748
+ ]);
12749
+ if (legend === null) return null;
12750
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
12751
+ /* @__PURE__ */ jsx("h3", {
12752
+ className: "card__subtitle",
12753
+ children: "Size legend"
12754
+ }),
12755
+ /* @__PURE__ */ jsx("p", {
12756
+ className: "mb-2 text-sm",
12757
+ children: sizeMetricLegendDescriptions[sizeMetric]
12758
+ }),
12759
+ /* @__PURE__ */ jsx("h3", {
12760
+ className: "card__subtitle",
12761
+ children: "Color legend"
12762
+ }),
12763
+ /* @__PURE__ */ jsx("p", {
12764
+ className: "mb-4 text-sm",
12765
+ children: colorMetricDescriptions[metricType]
12766
+ }),
12767
+ metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(PercentageSlider, { className: "my-4" }) : null,
12768
+ legend,
12769
+ metricType === "TOP_CONTRIBUTOR" ? /* @__PURE__ */ jsx(AuthorOptions, {}) : null
12770
+ ] });
12771
+ }
12772
+ function LoadingIndicator({ className = "", showProgress = false, fetchProgress = true, loadingText }) {
12773
+ const [path$1] = useQueryState("path");
12774
+ const fetcher = useFetcher();
12775
+ useEffect(() => {
12776
+ if (fetcher.state === "idle" && showProgress && fetchProgress) fetcher.load(href("/view/progress") + viewSerializer({ path: path$1 }));
12777
+ }, [
12778
+ fetchProgress,
12779
+ fetcher,
12780
+ fetcher.state,
12781
+ path$1,
12782
+ showProgress
12783
+ ]);
12784
+ const [progressText, progress] = useMemo(() => {
12785
+ if (!fetcher.data) return ["Loading truck...", 0];
12786
+ const { progress: progress$1, analyzationStatus } = fetcher.data;
12787
+ switch (analyzationStatus) {
12788
+ case "Starting": return ["Loading truck...", 0];
12789
+ case "GeneratingChart": return ["Unloading truck...", 100];
12790
+ default: return [progress$1 < 100 ? "Driving to destination..." : "Parking truck...", progress$1];
12791
+ }
12792
+ }, [fetcher.data]);
12793
+ return /* @__PURE__ */ jsx("div", {
12794
+ className: clsx$1("grid h-full w-full place-items-center px-4", className),
12795
+ children: /* @__PURE__ */ jsxs("div", {
12796
+ className: "flex w-full max-w-[clamp(16rem,80vw,36rem)] flex-col gap-4 px-2 py-2",
12797
+ children: [
12798
+ /* @__PURE__ */ jsx("img", {
12799
+ src: !showProgress || progress > 0 && progress < 100 ? truck_default$1 : truck_default,
12800
+ alt: "🚛",
12801
+ className: "pixelated aspect-square w-full"
12802
+ }),
12803
+ showProgress ? /* @__PURE__ */ jsx("div", {
12804
+ className: "text-center text-3xl font-bold",
12805
+ children: progressText
12806
+ }) : null,
12807
+ showProgress ? /* @__PURE__ */ jsx("div", {
12808
+ className: "h-6 w-3/4 self-center rounded-2xl bg-gray-300",
12809
+ children: /* @__PURE__ */ jsx("div", {
12810
+ 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 }),
12811
+ style: { width: progress ? `calc(${Math.min(progress, 100)}% - 4px)` : "40px" }
12812
+ })
12813
+ }) : null,
12814
+ loadingText ? /* @__PURE__ */ jsx("div", {
12815
+ className: "text-center font-bold",
12816
+ children: loadingText
12817
+ }) : null
12818
+ ]
12819
+ })
12820
+ });
12821
+ }
12822
+ function Providers({ children, data }) {
12823
+ const [options, setOptions] = useState(() => {
12824
+ const savedOptions = typeof document !== "undefined" ? localStorage.getItem(OPTIONS_LOCAL_STORAGE_KEY) : null;
12825
+ return {
12826
+ ...getDefaultOptionsContextValue(),
12827
+ ...savedOptions ? JSON.parse(savedOptions) : {},
12828
+ hasLoadedSavedOptions: !!savedOptions
12829
+ };
12830
+ });
12831
+ const [searchResults, setSearchResults] = useState({});
12832
+ const hasSearchResults = useMemo(() => Object.values(searchResults).length > 0, [searchResults]);
12833
+ const prefersLight = usePrefersLightMode();
12834
+ const metricsData = useMemo(() => {
12835
+ return createMetricData(data, data.databaseInfo.colorSeed, data.databaseInfo.authorColors, options?.dominantAuthorCutoff ?? 70, prefersLight);
12836
+ }, [
12837
+ data,
12838
+ options?.dominantAuthorCutoff,
12839
+ prefersLight
12840
+ ]);
12841
+ const optionsValue = useMemo(() => ({
12842
+ ...options,
12843
+ setMetricType: (metricType) => setOptions((prevOptions) => ({
12844
+ ...prevOptions,
12845
+ metricType
12846
+ })),
12847
+ setChartType: (chartType) => setOptions((prevOptions) => ({
12848
+ ...prevOptions,
12849
+ chartType
12433
12850
  })),
12434
12851
  setHierarchyType: (hierarchyType) => setOptions((prevOptions) => ({
12435
12852
  ...prevOptions,
@@ -12626,6 +13043,10 @@ const SearchCard = memo(function SearchCard$1() {
12626
13043
  value: object.path,
12627
13044
  onKeyDown: (event) => {
12628
13045
  const currentIndex = resultRefs.findIndex((ref) => ref.current === event.currentTarget);
13046
+ if (event.key === "Escape") {
13047
+ searchFieldRef.current?.focus();
13048
+ searchFieldRef.current?.select();
13049
+ }
12629
13050
  if (event.key === "ArrowDown") {
12630
13051
  event.preventDefault();
12631
13052
  if (resultRefs.length === 0) return;
@@ -12636,507 +13057,238 @@ const SearchCard = memo(function SearchCard$1() {
12636
13057
  if (resultRefs.length === 0) return;
12637
13058
  const prevIndex = currentIndex - 1;
12638
13059
  if (prevIndex < 0) {
12639
- searchFieldRef.current?.focus();
12640
- searchFieldRef.current?.select();
12641
- return;
12642
- }
12643
- focusResultAtIndex(prevIndex);
12644
- }
12645
- },
12646
- onClick: () => onClickObject(object),
12647
- children: [object.type === "tree" ? /* @__PURE__ */ jsx(Icon, {
12648
- path: object.type === "tree" ? mdiFolder : mdiFileOutline,
12649
- size: .75,
12650
- className: "shrink-0"
12651
- }) : /* @__PURE__ */ jsx(Icon, {
12652
- color: metrics.get(options.metricType)?.colormap.get(object.path) ?? "grey",
12653
- path: mdiFile,
12654
- size: .75,
12655
- className: "shrink-0"
12656
- }), /* @__PURE__ */ jsx("span", {
12657
- className: "text-secondary-text dark:hover:text-primary-text-dark hover:text-primary-text dark:text-secondary-text-dark truncate",
12658
- children: object.path.split(getSeparator(object.path)).slice(1).join("/") ?? object.path
12659
- })]
12660
- }, object.path))
12661
- }) : null
12662
- ]
12663
- });
12664
- });
12665
- var BarChart = () => {
12666
- const svgRef = useRef(null);
12667
- const { databaseInfo } = useData();
12668
- const [ref, rawSize] = useComponentSize();
12669
- const size = useDeferredValue(rawSize);
12670
- const data = databaseInfo.commitCountPerTimeInterval;
12671
- const [start, end] = databaseInfo.selectedRange;
12672
- const height = 50;
12673
- const width = size.width;
12674
- const xScale = d3.scaleBand().domain(data.map((d) => d.date.toString())).range([0, width]);
12675
- const yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count) || 0]).range([height, 0]);
12676
- const [searchParams] = useSearchParams();
12677
- const submit = useSubmit();
12678
- function updateTimeseries(e) {
12679
- const form = new FormData();
12680
- form.append("timeseries", `${e[0]}-${e[1]}`);
12681
- submit(form, {
12682
- action: getPathFromRepoAndHead({
12683
- path: searchParams.get("path"),
12684
- branch: databaseInfo.branch
12685
- }),
12686
- method: "post"
12687
- });
12688
- }
12689
- return /* @__PURE__ */ jsx("div", {
12690
- ref,
12691
- className: "flex flex-col justify-center",
12692
- children: /* @__PURE__ */ jsx("svg", {
12693
- ref: svgRef,
12694
- width: "100%",
12695
- height,
12696
- className: "fill-transparent",
12697
- children: data.map((d, i) => {
12698
- const barX = (xScale(d.date) ?? 0) + 2;
12699
- const barWidth = Math.max(1, xScale.bandwidth() - 4);
12700
- const barHeight = height - yScale(d.count);
12701
- return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
12702
- x: barX,
12703
- y: yScale(d.count),
12704
- width: barWidth,
12705
- height: barHeight,
12706
- rx: 2,
12707
- ry: 2,
12708
- className: cn("transition-[height,y] duration-300 ease-out", d.timestamp >= start && d.timestamp < end ? "fill-blue-primary" : "fill-blue-primary/40")
12709
- }), /* @__PURE__ */ jsx("rect", {
12710
- x: barX,
12711
- y: 0,
12712
- width: barWidth,
12713
- height,
12714
- rx: 2,
12715
- ry: 2,
12716
- className: "hover:stroke-blue-secondary stroke-transparent stroke-1 hover:fill-transparent",
12717
- onClick: () => {
12718
- const [intervalStart, intervalEnd] = expandIntervalToRange(d.timestamp, databaseInfo.commitCountPerTimeIntervalUnit);
12719
- updateTimeseries([intervalStart, intervalEnd]);
12720
- },
12721
- children: /* @__PURE__ */ jsx("title", { children: `${d.date}: ${d.count.toLocaleString()} commit${d.count !== 1 ? "s" : ""}` })
12722
- })] }, `${d.date}-${i}`);
12723
- })
12724
- })
12725
- });
12726
- };
12727
- var BarChart_default = BarChart;
12728
- function Timeline({ className }) {
12729
- const [_, startTransition$1] = useTransition();
12730
- const { databaseInfo } = useData();
12731
- const { timerange, selectedRange } = databaseInfo;
12732
- const newestChangeDate = timerange[1];
12733
- const oldestChangeDate = timerange[0];
12734
- const submit = useSubmit();
12735
- const [range, setRange] = useState(selectedRange[0] === 0 ? timerange : selectedRange);
12736
- const disabled = useNavigation().state !== "idle";
12737
- const [searchParams] = useSearchParams();
12738
- function updateTimeseries(e) {
12739
- const form = new FormData();
12740
- form.append("timeseries", `${e[0]}-${e[1]}`);
12741
- submit(form, {
12742
- action: getPathFromRepoAndHead({
12743
- path: searchParams.get("path"),
12744
- branch: databaseInfo.branch
12745
- }),
12746
- method: "post"
12747
- });
12748
- }
12749
- const selectedStartDate = range[0] * 1e3;
12750
- const selectedEndDate = range[1] * 1e3;
12751
- return /* @__PURE__ */ jsxs("div", {
12752
- className: cn("group flex flex-col gap-2", className),
12753
- children: [
12754
- /* @__PURE__ */ jsx(BarChart_default, {}),
12755
- /* @__PURE__ */ jsxs(Slider, {
12756
- className: "relative",
12757
- mode: 2,
12758
- step: 1,
12759
- domain: timerange,
12760
- values: range,
12761
- disabled,
12762
- onUpdate: (e) => setRange([...e]),
12763
- onChange: (e) => {
12764
- startTransition$1(() => {
12765
- updateTimeseries(e);
12766
- });
12767
- },
12768
- children: [
12769
- /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
12770
- /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx("div", {
12771
- className: "",
12772
- children: handles.map((handle, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
12773
- className: cn("absolute left-(--left) w-max translate-y-[calc(50%)] opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100", {
12774
- "left-(--left)": i === 0,
12775
- "right-[calc(100%-var(--left))] -translate-x-full": i === 1
12776
- }),
12777
- style: { "--left": `${handle.percent}%` },
12778
- children: /* @__PURE__ */ jsx(Popover$1, {
12779
- trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
12780
- className: cn("bg-blue-primary text-primary-text-dark cursor-pointer rounded-lg px-1 break-keep shadow", {
12781
- "rounded-tl-none": i === 0,
12782
- "rounded-tr-none": i === 1
12783
- }),
12784
- onClick,
12785
- children: /* @__PURE__ */ jsx("time", {
12786
- className: "w-max text-xs",
12787
- dateTime: dateFormatISO(i === 0 ? selectedStartDate : selectedEndDate),
12788
- title: `Click or drag to set the ${i === 0 ? "start" : "end"} of time range`,
12789
- children: dateFormatShort(i === 0 ? selectedStartDate : selectedEndDate)
12790
- })
12791
- }),
12792
- children: /* @__PURE__ */ jsx(TimePicker, {
12793
- setsBeginning: i === 0,
12794
- range,
12795
- setRange,
12796
- timerange,
12797
- updateTimeseries,
12798
- disabled
12799
- })
12800
- }, handle.id)
12801
- }), /* @__PURE__ */ jsx(Handle, {
12802
- handleType: "square",
12803
- title: "Drag to adjust interval, click to set specific date",
12804
- className: cn(""),
12805
- handle,
12806
- domain: timerange,
12807
- getHandleProps
12808
- }, handle.id)] }, handle.id))
12809
- }) }),
12810
- /* @__PURE__ */ jsx(Tracks, {
12811
- left: false,
12812
- right: false,
12813
- children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx("div", { children: tracks.map(({ id, source, target }) => /* @__PURE__ */ jsx(Track, {
12814
- trackType: "square",
12815
- backgroundColor: "#7aa0c4",
12816
- source,
12817
- target,
12818
- getTrackProps
12819
- }, id)) })
12820
- })
12821
- ]
12822
- }),
12823
- /* @__PURE__ */ jsx(TicksByCount, {
12824
- className: "mb-2",
12825
- count: 2,
12826
- tickToLabel: (t) => dateFormatShort((oldestChangeDate + (newestChangeDate - oldestChangeDate) * t) * 1e3)
12827
- })
12828
- ]
12829
- });
12830
- }
12831
- function TimePicker({ range, setRange, timerange, setsBeginning, updateTimeseries, disabled }) {
12832
- return /* @__PURE__ */ jsx("div", {
12833
- className: "z-30",
12834
- children: /* @__PURE__ */ jsx(DatePicker, {
12835
- inline: true,
12836
- fixedHeight: true,
12837
- renderCustomHeader: ({ date, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => {
12838
- return /* @__PURE__ */ jsxs("div", {
12839
- className: "m-2 flex justify-between",
12840
- children: [
12841
- /* @__PURE__ */ jsx("button", {
12842
- disabled: prevMonthButtonDisabled,
12843
- onClick: decreaseMonth,
12844
- children: "<"
12845
- }),
12846
- /* @__PURE__ */ jsx("h2", { children: dateFormatCalendarHeader(date.getTime()) }),
12847
- /* @__PURE__ */ jsx("button", {
12848
- disabled: nextMonthButtonDisabled,
12849
- onClick: increaseMonth,
12850
- children: ">"
12851
- })
12852
- ]
12853
- });
12854
- },
12855
- disabled,
12856
- selected: /* @__PURE__ */ new Date(range[setsBeginning ? 0 : 1] * 1e3),
12857
- minDate: new Date(setsBeginning ? timerange[0] * 1e3 : Math.max(timerange[0] * 1e3, range[0] * 1e3)),
12858
- maxDate: new Date(setsBeginning ? Math.min(timerange[1] * 1e3, range[1] * 1e3) : timerange[1] * 1e3),
12859
- onChange: (x) => {
12860
- if (x) {
12861
- let newRange;
12862
- if (setsBeginning) newRange = [x.getTime() / 1e3, range[1]];
12863
- else newRange = [range[0], x.getTime() / 1e3];
12864
- setRange(newRange);
12865
- updateTimeseries(newRange);
12866
- }
12867
- }
12868
- })
13060
+ searchFieldRef.current?.focus();
13061
+ searchFieldRef.current?.select();
13062
+ return;
13063
+ }
13064
+ focusResultAtIndex(prevIndex);
13065
+ }
13066
+ },
13067
+ onClick: () => onClickObject(object),
13068
+ children: [object.type === "tree" ? /* @__PURE__ */ jsx(Icon, {
13069
+ path: object.type === "tree" ? mdiFolder : mdiFileOutline,
13070
+ size: .75,
13071
+ className: "shrink-0"
13072
+ }) : /* @__PURE__ */ jsx(Icon, {
13073
+ color: metrics.get(options.metricType)?.colormap.get(object.path) ?? "grey",
13074
+ path: mdiFile,
13075
+ size: .75,
13076
+ className: "shrink-0"
13077
+ }), /* @__PURE__ */ jsx("span", {
13078
+ className: "text-secondary-text dark:hover:text-primary-text-dark hover:text-primary-text dark:text-secondary-text-dark truncate",
13079
+ children: object.path.split(getSeparator(object.path)).slice(1).join("/") ?? object.path
13080
+ })]
13081
+ }, object.path))
13082
+ }) : null
13083
+ ]
12869
13084
  });
12870
- }
12871
- function UnionAuthorsModal({ open, onClose }) {
13085
+ });
13086
+ var BarChart = () => {
13087
+ const svgRef = useRef(null);
12872
13088
  const { databaseInfo } = useData();
13089
+ const [ref, rawSize] = useComponentSize();
13090
+ const size = useDeferredValue(rawSize);
13091
+ const data = databaseInfo.commitCountPerTimeInterval;
13092
+ const [start, end] = databaseInfo.selectedRange;
13093
+ const height = 50;
13094
+ const width = size.width;
13095
+ const xScale = d3.scaleBand().domain(data.map((d) => d.date.toString())).range([0, width]);
13096
+ const yScale = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count) || 0]).range([height, 0]);
13097
+ const [searchParams] = useSearchParams();
12873
13098
  const submit = useSubmit();
12874
- const { authors } = databaseInfo;
12875
- const authorUnions = databaseInfo.authorUnions;
12876
- const [selectedAuthors, setSelectedAuthors] = useState([]);
12877
- const [filter, setFilter] = useState("");
12878
- const navigationData = useNavigation();
12879
- const [, authorColors] = useMetrics();
12880
- const [, startTransition$1] = useTransition();
12881
- const location$1 = useLocation();
12882
- const ref = useRef(null);
12883
- useEffect(() => {
12884
- if (!ref.current) return;
12885
- if (open) {
12886
- ref.current.showModal();
12887
- return;
12888
- }
12889
- ref.current.close();
12890
- }, [open]);
12891
- const flattedUnionedAuthors = authorUnions.reduce((acc, union) => {
12892
- return [...acc, ...union];
12893
- }, []).sort(stringSorter);
12894
- function ungroup(groupToUnGroup) {
12895
- const newAuthorUnions = authorUnions.filter((_, i) => i !== groupToUnGroup);
12896
- const form = new FormData();
12897
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12898
- submit(form, {
12899
- action: href("/view") + location$1.search,
12900
- method: "post"
12901
- });
12902
- }
12903
- function ungroupAll() {
12904
- const form = new FormData();
12905
- form.append("unionedAuthors", JSON.stringify([]));
12906
- submit(form, {
12907
- action: href("/view") + location$1.search,
12908
- method: "post"
12909
- });
12910
- }
12911
- function groupSelectedAuthors() {
12912
- if (selectedAuthors.length === 0) return;
12913
- const form = new FormData();
12914
- form.append("unionedAuthors", JSON.stringify([...authorUnions, selectedAuthors]));
12915
- submit(form, {
12916
- action: href("/view") + location$1.search,
12917
- method: "post"
12918
- });
12919
- setSelectedAuthors([]);
12920
- }
12921
- function makePrimaryAlias(alias, groupIndex) {
12922
- const newAuthorUnions = authorUnions.map((group, i) => {
12923
- if (i === groupIndex) return [alias, ...group.filter((a) => a !== alias)];
12924
- return group;
12925
- });
13099
+ function updateTimeseries(e) {
12926
13100
  const form = new FormData();
12927
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
13101
+ form.append("timeseries", `${e[0]}-${e[1]}`);
12928
13102
  submit(form, {
12929
- action: href("/view") + location$1.search,
13103
+ action: getPathFromRepoAndHead({
13104
+ path: searchParams.get("path"),
13105
+ branch: databaseInfo.branch
13106
+ }),
12930
13107
  method: "post"
12931
13108
  });
12932
13109
  }
12933
- const disabled = navigationData.state !== "idle";
12934
- const ungroupedAuthorsSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
12935
- useKey({ key: "Escape" }, onClose);
12936
- const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
12937
- if (!open) return null;
12938
- const ungroupedAuthorsMessage = ungroupedAuthorsSorted.length === 0 ? "All detected authors have been grouped" : "Select the authors that you know are the same person";
12939
- const ungroupedAuthorsFiltered = ungroupedAuthorsSorted.filter((author) => author.toLowerCase().includes(filter.toLowerCase()));
12940
- const ungroupedAuthorsEntries = ungroupedAuthorsFiltered.map((author) => /* @__PURE__ */ jsx(CheckboxWithLabel, {
12941
- className: "hover:opacity-70",
12942
- checked: selectedAuthors.includes(author),
12943
- onChange: (e) => {
12944
- setSelectedAuthors(e.target?.checked ? [...selectedAuthors, author] : selectedAuthors.filter((a) => a !== author));
12945
- },
12946
- children: /* @__PURE__ */ jsxs("div", {
12947
- className: "inline-flex flex-row place-items-center gap-2",
12948
- children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(author) }), author]
13110
+ return /* @__PURE__ */ jsx("div", {
13111
+ ref,
13112
+ className: "flex flex-col justify-center",
13113
+ children: /* @__PURE__ */ jsx("svg", {
13114
+ ref: svgRef,
13115
+ width: "100%",
13116
+ height,
13117
+ className: "fill-transparent",
13118
+ children: data.map((d, i) => {
13119
+ const barX = (xScale(d.date) ?? 0) + 2;
13120
+ const barWidth = Math.max(1, xScale.bandwidth() - 4);
13121
+ const barHeight = height - yScale(d.count);
13122
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
13123
+ x: barX,
13124
+ y: yScale(d.count),
13125
+ width: barWidth,
13126
+ height: barHeight,
13127
+ rx: 2,
13128
+ ry: 2,
13129
+ className: cn("transition-[height,y] duration-300 ease-out", d.timestamp >= start && d.timestamp < end ? "fill-blue-primary" : "fill-blue-primary/40")
13130
+ }), /* @__PURE__ */ jsx("rect", {
13131
+ x: barX,
13132
+ y: 0,
13133
+ width: barWidth,
13134
+ height,
13135
+ rx: 2,
13136
+ ry: 2,
13137
+ className: "hover:stroke-blue-secondary stroke-transparent stroke-1 hover:fill-transparent",
13138
+ onClick: () => {
13139
+ const [intervalStart, intervalEnd] = expandIntervalToRange(d.timestamp, databaseInfo.commitCountPerTimeIntervalUnit);
13140
+ updateTimeseries([intervalStart, intervalEnd]);
13141
+ },
13142
+ children: /* @__PURE__ */ jsx("title", { children: `${d.date}: ${d.count.toLocaleString()} commit${d.count !== 1 ? "s" : ""}` })
13143
+ })] }, `${d.date}-${i}`);
13144
+ })
12949
13145
  })
12950
- }, author));
12951
- const groupedAuthorsEntries = authorUnions.map((aliasGroup, aliasGroupIndex) => {
12952
- const displayName = aliasGroup[0];
12953
- const disabled$1 = navigationData.state !== "idle";
12954
- return /* @__PURE__ */ jsxs("div", {
12955
- className: "card group m-0 flex h-full flex-col p-2",
12956
- children: [
12957
- /* @__PURE__ */ jsxs("div", {
12958
- className: "inline-flex flex-row place-items-center gap-2",
12959
- children: [/* @__PURE__ */ jsx(LegendDot, { dotColor: getColorFromDisplayName(displayName) }), /* @__PURE__ */ jsx("b", {
12960
- className: "truncate",
12961
- title: displayName,
12962
- children: displayName
12963
- })]
12964
- }),
12965
- aliasGroup.slice(1).sort(stringSorter).map((alias) => /* @__PURE__ */ jsx(AliasEntry, {
12966
- alias,
12967
- disabled: disabled$1,
12968
- onClick: () => makePrimaryAlias(alias, aliasGroupIndex)
12969
- }, alias)),
12970
- /* @__PURE__ */ jsx("div", { className: "grow" }),
12971
- /* @__PURE__ */ jsxs("div", {
12972
- className: "flex items-end justify-end gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100",
12973
- children: [/* @__PURE__ */ jsxs(Form, {
12974
- action: href("/view") + location$1.search,
12975
- method: "post",
12976
- children: [/* @__PURE__ */ jsx("input", {
12977
- type: "hidden",
12978
- name: "unionedAuthors",
12979
- value: JSON.stringify(authorUnions)
12980
- }), /* @__PURE__ */ jsx("button", {
12981
- className: "btn",
12982
- title: "Add selected authors to this group",
12983
- disabled: disabled$1 || selectedAuthors.length === 0,
12984
- onClick: () => {
12985
- const newAuthorUnions = authorUnions.map(([displayName$1, ...group], i) => {
12986
- if (i === aliasGroupIndex) return [
12987
- displayName$1,
12988
- ...group,
12989
- ...selectedAuthors
12990
- ];
12991
- return [displayName$1, ...group];
12992
- });
12993
- const form = new FormData();
12994
- form.append("unionedAuthors", JSON.stringify(newAuthorUnions));
12995
- submit(form, {
12996
- action: href("/view") + location$1.search,
12997
- method: "post"
12998
- });
12999
- setSelectedAuthors([]);
13000
- },
13001
- children: "Add selected"
13002
- })]
13003
- }), /* @__PURE__ */ jsx("button", {
13004
- className: "btn",
13005
- title: "Ungroup",
13006
- disabled: disabled$1,
13007
- onClick: () => ungroup(aliasGroupIndex),
13008
- children: "Ungroup"
13009
- })]
13010
- })
13011
- ]
13012
- }, aliasGroupIndex);
13013
13146
  });
13014
- return createPortal(/* @__PURE__ */ jsx("dialog", {
13015
- ref,
13016
- "aria-modal": true,
13017
- 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",
13018
- children: /* @__PURE__ */ jsxs("div", {
13019
- 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",
13020
- children: [
13021
- /* @__PURE__ */ jsx("h2", {
13022
- className: "text-2xl",
13023
- children: "Group authors"
13024
- }),
13025
- /* @__PURE__ */ jsx(CloseButton, {
13026
- absolute: false,
13027
- className: "justify-self-end",
13028
- onClick: onClose
13029
- }),
13030
- /* @__PURE__ */ jsxs("h3", {
13031
- className: "text-center text-lg font-bold",
13032
- children: [
13033
- "Ungrouped authors (",
13034
- ungroupedAuthorsSorted.length,
13035
- ")"
13036
- ]
13037
- }),
13038
- /* @__PURE__ */ jsx("h3", {
13039
- className: "text-center text-lg font-bold",
13040
- children: "Grouped authors"
13041
- }),
13042
- /* @__PURE__ */ jsx("div", {
13043
- className: "flex justify-end gap-2",
13044
- children: /* @__PURE__ */ jsxs("button", {
13045
- className: "btn btn--primary justify-self-end",
13046
- title: "Group the selected authors",
13047
- disabled: disabled || selectedAuthors.length === 0,
13048
- onClick: groupSelectedAuthors,
13049
- children: [/* @__PURE__ */ jsx(Icon, {
13050
- path: mdiAccountMultiple,
13051
- size: 1
13052
- }), "Create group"]
13053
- })
13054
- }),
13055
- /* @__PURE__ */ jsx("div", {
13056
- className: "flex justify-end gap-4",
13057
- children: /* @__PURE__ */ jsx("button", {
13058
- className: "btn btn--danger",
13059
- disabled: disabled || authorUnions.length === 0,
13060
- onClick: () => {
13061
- if (confirm("Are you sure you want to ungroup all grouped authors?")) ungroupAll();
13062
- },
13063
- children: "Ungroup all"
13064
- })
13065
- }),
13066
- /* @__PURE__ */ jsx("div", {
13067
- className: "overflow-y-auto",
13068
- children: /* @__PURE__ */ jsxs("div", {
13069
- 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",
13070
- children: [/* @__PURE__ */ jsxs("div", {
13071
- className: "sticky top-0 flex gap-2 bg-inherit pt-2",
13072
- children: [
13073
- /* @__PURE__ */ jsx("input", {
13074
- className: "input min-w-0",
13075
- type: "search",
13076
- placeholder: "Filter...",
13077
- disabled: ungroupedAuthorsSorted.length === 0,
13078
- onChange: (e) => startTransition$1(() => setFilter(e.target.value))
13079
- }),
13080
- /* @__PURE__ */ jsx("button", {
13081
- disabled: disabled || selectedAuthors.length === 0,
13082
- className: "btn btn--outlined w-max grow",
13083
- title: "Clear selection",
13084
- onClick: () => setSelectedAuthors([]),
13085
- children: "Clear"
13147
+ };
13148
+ var BarChart_default = BarChart;
13149
+ function Timeline({ className }) {
13150
+ const [_, startTransition$1] = useTransition();
13151
+ const { databaseInfo } = useData();
13152
+ const { timerange, selectedRange } = databaseInfo;
13153
+ const newestChangeDate = timerange[1];
13154
+ const oldestChangeDate = timerange[0];
13155
+ const submit = useSubmit();
13156
+ const [range, setRange] = useState(selectedRange[0] === 0 ? timerange : selectedRange);
13157
+ const disabled = useNavigation().state !== "idle";
13158
+ const [searchParams] = useSearchParams();
13159
+ function updateTimeseries(e) {
13160
+ const form = new FormData();
13161
+ form.append("timeseries", `${e[0]}-${e[1]}`);
13162
+ submit(form, {
13163
+ action: getPathFromRepoAndHead({
13164
+ path: searchParams.get("path"),
13165
+ branch: databaseInfo.branch
13166
+ }),
13167
+ method: "post"
13168
+ });
13169
+ }
13170
+ const selectedStartDate = range[0] * 1e3;
13171
+ const selectedEndDate = range[1] * 1e3;
13172
+ return /* @__PURE__ */ jsxs("div", {
13173
+ className: cn("group flex flex-col gap-2", className),
13174
+ children: [
13175
+ /* @__PURE__ */ jsx(BarChart_default, {}),
13176
+ /* @__PURE__ */ jsxs(Slider, {
13177
+ className: "relative",
13178
+ mode: 2,
13179
+ step: 1,
13180
+ domain: timerange,
13181
+ values: range,
13182
+ disabled,
13183
+ onUpdate: (e) => setRange([...e]),
13184
+ onChange: (e) => {
13185
+ startTransition$1(() => {
13186
+ updateTimeseries(e);
13187
+ });
13188
+ },
13189
+ children: [
13190
+ /* @__PURE__ */ jsx(Rail, { children: SliderRail }),
13191
+ /* @__PURE__ */ jsx(Handles, { children: ({ handles, getHandleProps }) => /* @__PURE__ */ jsx("div", {
13192
+ className: "",
13193
+ children: handles.map((handle, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
13194
+ className: cn("absolute left-(--left) w-max translate-y-[calc(50%)] opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100", {
13195
+ "left-(--left)": i === 0,
13196
+ "right-[calc(100%-var(--left))] -translate-x-full": i === 1
13197
+ }),
13198
+ style: { "--left": `${handle.percent}%` },
13199
+ children: /* @__PURE__ */ jsx(Popover$1, {
13200
+ trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
13201
+ className: cn("bg-blue-primary text-primary-text-dark cursor-pointer rounded-lg px-1 break-keep shadow", {
13202
+ "rounded-tl-none": i === 0,
13203
+ "rounded-tr-none": i === 1
13204
+ }),
13205
+ onClick,
13206
+ children: /* @__PURE__ */ jsx("time", {
13207
+ className: "w-max text-xs",
13208
+ dateTime: dateFormatISO(i === 0 ? selectedStartDate : selectedEndDate),
13209
+ title: `Click or drag to set the ${i === 0 ? "start" : "end"} of time range`,
13210
+ children: dateFormatShort(i === 0 ? selectedStartDate : selectedEndDate)
13211
+ })
13086
13212
  }),
13087
- /* @__PURE__ */ jsx("button", {
13088
- disabled: ungroupedAuthorsSorted.length === 0,
13089
- className: "btn btn--outlined w-max grow",
13090
- title: "Clear selection",
13091
- onClick: () => selectedAuthors.length === ungroupedAuthorsFiltered.length ? setSelectedAuthors([]) : setSelectedAuthors((selected) => Array.from(new Set([...selected, ...ungroupedAuthorsFiltered]))),
13092
- children: selectedAuthors.length === ungroupedAuthorsFiltered.length ? "Deselect all" : "Select all"
13213
+ children: /* @__PURE__ */ jsx(TimePicker, {
13214
+ setsBeginning: i === 0,
13215
+ range,
13216
+ setRange,
13217
+ timerange,
13218
+ updateTimeseries,
13219
+ disabled
13093
13220
  })
13094
- ]
13095
- }), ungroupedAuthorsEntries.length > 0 ? ungroupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
13096
- className: "place-self-center",
13097
- children: filter.length > 0 ? "No authors found" : "All authors have been grouped"
13098
- })]
13221
+ }, handle.id)
13222
+ }), /* @__PURE__ */ jsx(Handle, {
13223
+ handleType: "square",
13224
+ title: "Drag to adjust interval, click to set specific date",
13225
+ className: cn(""),
13226
+ handle,
13227
+ domain: timerange,
13228
+ getHandleProps
13229
+ }, handle.id)] }, handle.id))
13230
+ }) }),
13231
+ /* @__PURE__ */ jsx(Tracks, {
13232
+ left: false,
13233
+ right: false,
13234
+ children: ({ tracks, getTrackProps }) => /* @__PURE__ */ jsx("div", { children: tracks.map(({ id, source, target }) => /* @__PURE__ */ jsx(Track, {
13235
+ trackType: "square",
13236
+ backgroundColor: "#7aa0c4",
13237
+ source,
13238
+ target,
13239
+ getTrackProps
13240
+ }, id)) })
13099
13241
  })
13100
- }),
13101
- /* @__PURE__ */ jsx("div", {
13102
- className: "overflow-y-auto",
13103
- children: /* @__PURE__ */ jsx("div", {
13104
- 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",
13105
- children: authorUnions.length > 0 ? groupedAuthorsEntries : /* @__PURE__ */ jsx("p", {
13106
- className: "place-self-center",
13107
- children: "No authors have been grouped yet"
13242
+ ]
13243
+ }),
13244
+ /* @__PURE__ */ jsx(TicksByCount, {
13245
+ className: "mb-2",
13246
+ count: 2,
13247
+ tickToLabel: (t) => dateFormatShort((oldestChangeDate + (newestChangeDate - oldestChangeDate) * t) * 1e3)
13248
+ })
13249
+ ]
13250
+ });
13251
+ }
13252
+ function TimePicker({ range, setRange, timerange, setsBeginning, updateTimeseries, disabled }) {
13253
+ return /* @__PURE__ */ jsx("div", {
13254
+ className: "z-30",
13255
+ children: /* @__PURE__ */ jsx(DatePicker, {
13256
+ inline: true,
13257
+ fixedHeight: true,
13258
+ renderCustomHeader: ({ date, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => {
13259
+ return /* @__PURE__ */ jsxs("div", {
13260
+ className: "m-2 flex justify-between",
13261
+ children: [
13262
+ /* @__PURE__ */ jsx("button", {
13263
+ disabled: prevMonthButtonDisabled,
13264
+ onClick: decreaseMonth,
13265
+ children: "<"
13266
+ }),
13267
+ /* @__PURE__ */ jsx("h2", { children: dateFormatCalendarHeader(date.getTime()) }),
13268
+ /* @__PURE__ */ jsx("button", {
13269
+ disabled: nextMonthButtonDisabled,
13270
+ onClick: increaseMonth,
13271
+ children: ">"
13108
13272
  })
13109
- })
13110
- }),
13111
- /* @__PURE__ */ jsxs("div", {
13112
- className: "col-span-2 mr-6 grid w-full grid-cols-2 gap-4",
13113
- children: [/* @__PURE__ */ jsx("p", { children: ungroupedAuthorsMessage }), /* @__PURE__ */ jsx("button", {
13114
- className: "btn btn--primary justify-self-end",
13115
- onClick: onClose,
13116
- children: "Done"
13117
- })]
13118
- })
13119
- ]
13273
+ ]
13274
+ });
13275
+ },
13276
+ disabled,
13277
+ selected: /* @__PURE__ */ new Date(range[setsBeginning ? 0 : 1] * 1e3),
13278
+ minDate: new Date(setsBeginning ? timerange[0] * 1e3 : Math.max(timerange[0] * 1e3, range[0] * 1e3)),
13279
+ maxDate: new Date(setsBeginning ? Math.min(timerange[1] * 1e3, range[1] * 1e3) : timerange[1] * 1e3),
13280
+ onChange: (x) => {
13281
+ if (x) {
13282
+ let newRange;
13283
+ if (setsBeginning) newRange = [x.getTime() / 1e3, range[1]];
13284
+ else newRange = [range[0], x.getTime() / 1e3];
13285
+ setRange(newRange);
13286
+ updateTimeseries(newRange);
13287
+ }
13288
+ }
13120
13289
  })
13121
- }), document.body);
13122
- function AliasEntry({ alias, onClick, disabled: disabled$1 }) {
13123
- return /* @__PURE__ */ jsxs("button", {
13124
- className: "btn flex grid-flow-col gap-2 text-sm [&:hover>svg]:opacity-50 [&>svg]:opacity-0",
13125
- disabled: disabled$1,
13126
- title: "Make display name for this grouping",
13127
- onClick,
13128
- children: [/* @__PURE__ */ jsx(Icon, {
13129
- path: mdiArrowUp,
13130
- size: .75
13131
- }), /* @__PURE__ */ jsx("label", {
13132
- title: alias,
13133
- className: "label truncate",
13134
- children: alias
13135
- })]
13136
- }, alias);
13137
- }
13290
+ });
13138
13291
  }
13139
- var stringSorter = (a, b) => a.toLowerCase().localeCompare(b.toLowerCase());
13140
13292
  function RefreshButton() {
13141
13293
  const navigation = useNavigation();
13142
13294
  const isRefreshing = navigation.formData?.get("refresh") === "true";
@@ -13334,6 +13486,18 @@ function ChartTooltip({ hoveredObject }) {
13334
13486
  className: chartType === "BUBBLE_CHART" ? "rounded-xl" : "rounded-xs"
13335
13487
  });
13336
13488
  }
13489
+ function FullscreenButton() {
13490
+ const { isFullscreen, toggleFullscreen } = useFullscreen(() => document.documentElement);
13491
+ return /* @__PURE__ */ jsx("button", {
13492
+ className: cn("btn aspect-square p-1", { "btn--primary": isFullscreen }),
13493
+ title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
13494
+ onClick: toggleFullscreen,
13495
+ children: /* @__PURE__ */ jsx(Icon, {
13496
+ path: isFullscreen ? mdiFullscreenExit : mdiFullscreen,
13497
+ size: 1
13498
+ })
13499
+ });
13500
+ }
13337
13501
  var CollapsibleHeaderContext = createContext$1((_open) => {});
13338
13502
  function useSetOpenCollapsibleHeader() {
13339
13503
  return use(CollapsibleHeaderContext);
@@ -13388,124 +13552,15 @@ function RevisionSelect({ headGroups, disabled, className = "", analyzedBranches
13388
13552
  })]
13389
13553
  });
13390
13554
  }
13391
- function CollapsableSettings({ className = "" }) {
13392
- return /* @__PURE__ */ jsx(Popover$1, {
13393
- trigger: ({ onClick }) => /* @__PURE__ */ jsx("button", {
13394
- title: "Visualization settings",
13395
- className: cn("btn hover:text-primary-text dark:hover:text-primary-text-dark relative flex cursor-pointer justify-between gap-2", className),
13396
- onClick,
13397
- children: /* @__PURE__ */ jsx(Icon, {
13398
- path: mdiCog,
13399
- size: "1.25em"
13400
- })
13401
- }),
13402
- children: /* @__PURE__ */ jsx(Settings, {})
13555
+ function SettingsButton() {
13556
+ const { openModal } = useModal("app-settings");
13557
+ return /* @__PURE__ */ jsx("button", {
13558
+ className: "btn hover:text-primary-text dark:hover:text-primary-text-dark relative flex cursor-pointer justify-between gap-2",
13559
+ title: "Visualization settings",
13560
+ onClick: () => openModal(),
13561
+ children: /* @__PURE__ */ jsx(Icon, { path: mdiCog })
13403
13562
  });
13404
13563
  }
13405
- function Settings() {
13406
- const { metricType, hierarchyType, transitionsEnabled, renderCutoff, showFilesWithoutChanges, linkMetricAndSizeMetric, showOnlySearchMatches, setLinkMetricAndSizeMetric, setTransitionsEnabled, labelsVisible, setLabelsVisible, setHierarchyType, setSizeMetricType, setRenderCutoff, setShowFilesWithoutChanges, setShowOnlySearchMatches } = useOptions();
13407
- const [isTransitioning, startTransition$1] = useTransition();
13408
- return /* @__PURE__ */ jsxs(Fragment, { children: [
13409
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13410
- className: "text-sm",
13411
- checked: Boolean(linkMetricAndSizeMetric),
13412
- title: "Enable to sync size metric with color metric",
13413
- onChange: (e) => {
13414
- setLinkMetricAndSizeMetric(e.target.checked);
13415
- if (e.target.checked) setSizeMetricType(relatedSizeMetric[metricType]);
13416
- },
13417
- children: [/* @__PURE__ */ jsx(Icon, {
13418
- className: "ml-1.5",
13419
- path: mdiLink,
13420
- size: "1.25em"
13421
- }), /* @__PURE__ */ jsx("span", { children: "Link size and color option" })]
13422
- }),
13423
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13424
- className: "text-sm",
13425
- checked: transitionsEnabled,
13426
- title: "Disable to improve performance when zooming",
13427
- onChange: (e) => setTransitionsEnabled(e.target.checked),
13428
- children: [/* @__PURE__ */ jsx(Icon, {
13429
- className: "ml-1.5",
13430
- path: mdiTransition,
13431
- size: "1.25em"
13432
- }), "Transitions"]
13433
- }),
13434
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13435
- className: "text-sm",
13436
- checked: labelsVisible,
13437
- title: "Disable to improve performance",
13438
- onChange: (e) => setLabelsVisible(e.target.checked),
13439
- children: [/* @__PURE__ */ jsx(Icon, {
13440
- className: "ml-1.5",
13441
- path: mdiLabel,
13442
- size: "1.25em"
13443
- }), "Labels"]
13444
- }),
13445
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13446
- className: "text-sm",
13447
- checked: showFilesWithoutChanges,
13448
- title: "Show files that have had no changes in the selected time range",
13449
- onChange: (e) => setShowFilesWithoutChanges(e.target.checked),
13450
- children: [/* @__PURE__ */ jsx(Icon, {
13451
- className: "ml-1.5",
13452
- path: mdiClockEdit,
13453
- size: "1.25em"
13454
- }), "Show files with no activity"]
13455
- }),
13456
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13457
- className: "text-sm",
13458
- checked: hierarchyType === "FLAT",
13459
- title: "Show all files on the same level, instead of as a file tree",
13460
- onChange: () => {
13461
- if (hierarchyType === "FLAT") setHierarchyType("NESTED");
13462
- else setHierarchyType("FLAT");
13463
- },
13464
- children: [/* @__PURE__ */ jsx(Icon, {
13465
- className: "ml-1.5",
13466
- path: mdiFileTree,
13467
- size: "1.25em"
13468
- }), "Flatten file tree"]
13469
- }),
13470
- /* @__PURE__ */ jsxs(CheckboxWithLabel, {
13471
- className: "text-sm",
13472
- checked: showOnlySearchMatches,
13473
- title: "When searching, hide files that do not match the search query",
13474
- onChange: (e) => setShowOnlySearchMatches(e.target.checked),
13475
- children: [/* @__PURE__ */ jsx(Icon, {
13476
- className: "ml-1.5",
13477
- path: mdiFilter,
13478
- size: "1.25em"
13479
- }), "Show only search matches"]
13480
- }),
13481
- /* @__PURE__ */ jsxs("label", {
13482
- className: "label flex w-full items-center justify-start gap-2 text-sm",
13483
- title: "Increase this to improve render performance, decrease it to get higher level of detail",
13484
- children: [/* @__PURE__ */ jsxs("span", {
13485
- className: "flex grow items-center gap-2",
13486
- children: [
13487
- /* @__PURE__ */ jsx(Icon, {
13488
- className: "ml-1.5",
13489
- path: mdiContentCut,
13490
- size: "1.25em"
13491
- }),
13492
- "Pixel render cut-off ",
13493
- isTransitioning ? /* @__PURE__ */ jsx("img", {
13494
- src: truck_default,
13495
- alt: "...",
13496
- className: "h-5"
13497
- }) : ""
13498
- ]
13499
- }), /* @__PURE__ */ jsx("input", {
13500
- type: "number",
13501
- min: 0,
13502
- defaultValue: renderCutoff,
13503
- className: "mr-1 w-12 place-self-end border-b-2",
13504
- onChange: (x) => startTransition$1(() => setRenderCutoff(x.target.valueAsNumber))
13505
- })]
13506
- })
13507
- ] });
13508
- }
13509
13564
  function BrowseParentFolder() {
13510
13565
  const dataPromise = useRouteLoaderData(href("/view"))?.dataPromise;
13511
13566
  const data = dataPromise ? use(dataPromise) : null;
@@ -13520,11 +13575,9 @@ var view_exports = /* @__PURE__ */ __export({
13520
13575
  loader: () => loader$8,
13521
13576
  meta: () => meta,
13522
13577
  middleware: () => middleware,
13523
- useRepoContext: () => useRepoContext,
13524
13578
  viewSearchParamsConfig: () => viewSearchParamsConfig,
13525
13579
  viewSerializer: () => viewSerializer
13526
13580
  }, 1);
13527
- const useRepoContext = () => useOutletContext();
13528
13581
  const currentRepositoryContext = createContext();
13529
13582
  const viewSearchParamsConfig = {
13530
13583
  path: parseAsString,
@@ -13748,9 +13801,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13748
13801
  }
13749
13802
  }, { leftExpanded: true });
13750
13803
  const toggleLeft = () => dispatch("toggleLeft");
13751
- const [unionAuthorsModalOpen, setUnionAuthorsModalOpen] = useState(false);
13752
13804
  const [hoveredObject, setHoveredObject] = useState(null);
13753
- const showUnionAuthorsModal = () => setUnionAuthorsModalOpen(true);
13754
13805
  const location$1 = useLocation();
13755
13806
  const navigate = useNavigate();
13756
13807
  const [objectPath] = useQueryState("objectPath", viewSearchParamsConfig.objectPath);
@@ -13813,11 +13864,11 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13813
13864
  ]
13814
13865
  }) }) : "Details" }),
13815
13866
  contentClassName: "pb-6",
13816
- children: /* @__PURE__ */ jsx(Outlet, { context: { showUnionAuthorsModal } })
13867
+ children: /* @__PURE__ */ jsx(Outlet, {})
13817
13868
  }),
13818
13869
  /* @__PURE__ */ jsx(CollapsibleHeader, {
13819
13870
  className: "card",
13820
- title: /* @__PURE__ */ jsxs(Fragment, { children: ["Visualization options", /* @__PURE__ */ jsx(CollapsableSettings, {})] }),
13871
+ title: /* @__PURE__ */ jsx(Fragment, { children: "Visualization options" }),
13821
13872
  contentClassName: "pb-6 flex flex-col gap-2",
13822
13873
  children: /* @__PURE__ */ jsx(Options, {})
13823
13874
  }),
@@ -13825,10 +13876,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13825
13876
  className: "card",
13826
13877
  title: "Legend",
13827
13878
  contentClassName: "pb-6",
13828
- children: /* @__PURE__ */ jsx(Legend, {
13829
- hoveredObject,
13830
- showUnionAuthorsModal
13831
- })
13879
+ children: /* @__PURE__ */ jsx(Legend, { hoveredObject })
13832
13880
  })
13833
13881
  ]
13834
13882
  })
@@ -13872,6 +13920,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13872
13920
  /* @__PURE__ */ jsx(SearchCard, {}),
13873
13921
  /* @__PURE__ */ jsx(RefreshButton, {}),
13874
13922
  /* @__PURE__ */ jsx(HiddenFiles, {}),
13923
+ /* @__PURE__ */ jsx(SettingsButton, {}),
13875
13924
  /* @__PURE__ */ jsx(FullscreenButton, {})
13876
13925
  ]
13877
13926
  })
@@ -13916,12 +13965,7 @@ var view_default = UNSAFE_withComponentProps(function Repo() {
13916
13965
  })
13917
13966
  ]
13918
13967
  })]
13919
- }), /* @__PURE__ */ jsx(UnionAuthorsModal, {
13920
- open: unionAuthorsModalOpen,
13921
- onClose: () => {
13922
- setUnionAuthorsModalOpen(false);
13923
- }
13924
- })]
13968
+ }), /* @__PURE__ */ jsx(ModalManager, {})]
13925
13969
  })
13926
13970
  })
13927
13971
  });
@@ -13966,14 +14010,6 @@ async function loader$4({ request, params }) {
13966
14010
  const path$1 = params.path ?? loadViewSearchParams(request).path ?? ".";
13967
14011
  throw redirect(await GitCaller.isValidGitRepo(path$1) ? href("/view") + viewSerializer({ path: path$1 }) : href("/browse") + browseSerializer({ path: path$1 }));
13968
14012
  }
13969
- var view_merge_authors_exports = /* @__PURE__ */ __export({ default: () => view_merge_authors_default }, 1);
13970
- var view_merge_authors_default = UNSAFE_withComponentProps(function MergeAuthorsView() {
13971
- const navigate = useNavigate();
13972
- return /* @__PURE__ */ jsx(UnionAuthorsModal, {
13973
- open: true,
13974
- onClose: () => navigate("..")
13975
- });
13976
- });
13977
14013
  var view_progress_exports = /* @__PURE__ */ __export({ loader: () => loader$3 }, 1);
13978
14014
  var POLLING_RATE = 500;
13979
14015
  const loader$3 = async ({ context }) => {
@@ -14276,7 +14312,6 @@ const action = async ({ request, context }) => {
14276
14312
  };
14277
14313
  var view_details_default = UNSAFE_withComponentProps(function Details() {
14278
14314
  const { setPath } = usePath();
14279
- const { showUnionAuthorsModal } = useRepoContext();
14280
14315
  const { path: path$1, authorDistributionPromise } = useLoaderData();
14281
14316
  const data = useData();
14282
14317
  const { state } = useNavigation();
@@ -14288,6 +14323,7 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14288
14323
  ...viewSearchParams,
14289
14324
  zoomPath: path$1
14290
14325
  });
14326
+ const { openModal } = useModal("group-authors");
14291
14327
  useEffect(() => {
14292
14328
  setOpen(!!clickedObject);
14293
14329
  }, [clickedObject, setOpen]);
@@ -14326,7 +14362,7 @@ var view_details_default = UNSAFE_withComponentProps(function Details() {
14326
14362
  }),
14327
14363
  /* @__PURE__ */ jsxs("button", {
14328
14364
  className: "btn",
14329
- onClick: showUnionAuthorsModal,
14365
+ onClick: () => openModal(),
14330
14366
  children: [/* @__PURE__ */ jsx(Icon, { path: mdiAccountMultiple }), "Group authors"]
14331
14367
  }),
14332
14368
  /* @__PURE__ */ jsxs("div", {
@@ -14953,7 +14989,7 @@ var ui_default = UNSAFE_withComponentProps(function UI() {
14953
14989
  children: [/* @__PURE__ */ jsx("h2", {
14954
14990
  className: "card__title",
14955
14991
  children: "AuthorOptions"
14956
- }), /* @__PURE__ */ jsx(AuthorOptions, { showUnionAuthorsModal: () => alert("Show union authors modal") })]
14992
+ }), /* @__PURE__ */ jsx(AuthorOptions, {})]
14957
14993
  }),
14958
14994
  /* @__PURE__ */ jsxs("div", {
14959
14995
  className: "card",
@@ -15075,12 +15111,11 @@ async function loader({ request, params }) {
15075
15111
  }
15076
15112
  var server_manifest_default = {
15077
15113
  "entry": {
15078
- "module": "/assets/entry.client-NklqTk8N.js",
15114
+ "module": "/assets/entry.client-lU2GATMj.js",
15079
15115
  "imports": [
15080
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15081
- "/assets/react-dom-_FKEaPb7.js",
15082
- "/assets/chunk-JPUPSTYD-XO4UPun8.js",
15083
- "/assets/jsx-runtime-BVRj4wYA.js"
15116
+ "/assets/jsx-runtime-s_YwNCZb.js",
15117
+ "/assets/react-dom-BkDQo3BN.js",
15118
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15084
15119
  ],
15085
15120
  "css": []
15086
15121
  },
@@ -15098,40 +15133,16 @@ var server_manifest_default = {
15098
15133
  "hasClientMiddleware": false,
15099
15134
  "hasDefaultExport": true,
15100
15135
  "hasErrorBoundary": true,
15101
- "module": "/assets/root-Djh_4EO9.js",
15136
+ "module": "/assets/root-CpxyjKLd.js",
15102
15137
  "imports": [
15103
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15104
- "/assets/react-dom-_FKEaPb7.js",
15105
- "/assets/chunk-JPUPSTYD-XO4UPun8.js",
15106
- "/assets/jsx-runtime-BVRj4wYA.js",
15107
- "/assets/styling-B8OEge8x.js",
15108
- "/assets/util-1G1Ez1rt.js",
15109
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15110
- "/assets/GitTruckInfo-C1VOiqSW.js",
15111
- "/assets/clear-cache-CrL0weu5.js"
15138
+ "/assets/jsx-runtime-s_YwNCZb.js",
15139
+ "/assets/react-dom-BkDQo3BN.js",
15140
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js",
15141
+ "/assets/GitTruckInfo-CiYjIacT.js",
15142
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15143
+ "/assets/clear-cache-C5k9U711.js"
15112
15144
  ],
15113
- "css": ["/assets/root-qwQAVU-u.css"],
15114
- "clientActionModule": void 0,
15115
- "clientLoaderModule": void 0,
15116
- "clientMiddlewareModule": void 0,
15117
- "hydrateFallbackModule": void 0
15118
- },
15119
- "routes/FullscreenButton": {
15120
- "id": "routes/FullscreenButton",
15121
- "parentId": "root",
15122
- "path": "FullscreenButton",
15123
- "index": void 0,
15124
- "caseSensitive": void 0,
15125
- "hasAction": false,
15126
- "hasLoader": false,
15127
- "hasClientAction": false,
15128
- "hasClientLoader": false,
15129
- "hasClientMiddleware": false,
15130
- "hasDefaultExport": false,
15131
- "hasErrorBoundary": false,
15132
- "module": "/assets/FullscreenButton-CFqdBqf-.js",
15133
- "imports": ["/assets/styling-B8OEge8x.js", "/assets/jsx-runtime-BVRj4wYA.js"],
15134
- "css": [],
15145
+ "css": ["/assets/root-CaiUVml-.css"],
15135
15146
  "clientActionModule": void 0,
15136
15147
  "clientLoaderModule": void 0,
15137
15148
  "clientMiddlewareModule": void 0,
@@ -15150,7 +15161,7 @@ var server_manifest_default = {
15150
15161
  "hasClientMiddleware": false,
15151
15162
  "hasDefaultExport": false,
15152
15163
  "hasErrorBoundary": false,
15153
- "module": "/assets/_._well-known._-DIwl00oI.js",
15164
+ "module": "/assets/_._well-known._-8QUD84tm.js",
15154
15165
  "imports": [],
15155
15166
  "css": [],
15156
15167
  "clientActionModule": void 0,
@@ -15171,15 +15182,12 @@ var server_manifest_default = {
15171
15182
  "hasClientMiddleware": false,
15172
15183
  "hasDefaultExport": true,
15173
15184
  "hasErrorBoundary": false,
15174
- "module": "/assets/clear-cache-82hexQD7.js",
15185
+ "module": "/assets/clear-cache-Xrw5kNtu.js",
15175
15186
  "imports": [
15176
- "/assets/styling-B8OEge8x.js",
15177
- "/assets/util-1G1Ez1rt.js",
15178
- "/assets/react-dom-_FKEaPb7.js",
15179
- "/assets/jsx-runtime-BVRj4wYA.js",
15180
- "/assets/GitTruckInfo-C1VOiqSW.js",
15181
- "/assets/clear-cache-CrL0weu5.js",
15182
- "/assets/chunk-LFPYN7LY-CPTP6242.js"
15187
+ "/assets/jsx-runtime-s_YwNCZb.js",
15188
+ "/assets/GitTruckInfo-CiYjIacT.js",
15189
+ "/assets/react-dom-BkDQo3BN.js",
15190
+ "/assets/clear-cache-C5k9U711.js"
15183
15191
  ],
15184
15192
  "css": [],
15185
15193
  "clientActionModule": void 0,
@@ -15200,7 +15208,7 @@ var server_manifest_default = {
15200
15208
  "hasClientMiddleware": false,
15201
15209
  "hasDefaultExport": false,
15202
15210
  "hasErrorBoundary": false,
15203
- "module": "/assets/commitcount-eFguKtSp.js",
15211
+ "module": "/assets/commitcount-B_3CeIvr.js",
15204
15212
  "imports": [],
15205
15213
  "css": [],
15206
15214
  "clientActionModule": void 0,
@@ -15221,7 +15229,7 @@ var server_manifest_default = {
15221
15229
  "hasClientMiddleware": false,
15222
15230
  "hasDefaultExport": false,
15223
15231
  "hasErrorBoundary": false,
15224
- "module": "/assets/commits-DPfZp-l3.js",
15232
+ "module": "/assets/commits-DDIAHyHU.js",
15225
15233
  "imports": [],
15226
15234
  "css": [],
15227
15235
  "clientActionModule": void 0,
@@ -15242,7 +15250,7 @@ var server_manifest_default = {
15242
15250
  "hasClientMiddleware": false,
15243
15251
  "hasDefaultExport": false,
15244
15252
  "hasErrorBoundary": false,
15245
- "module": "/assets/_index-J0jvF8An.js",
15253
+ "module": "/assets/_index-VLoUE117.js",
15246
15254
  "imports": [],
15247
15255
  "css": [],
15248
15256
  "clientActionModule": void 0,
@@ -15263,18 +15271,14 @@ var server_manifest_default = {
15263
15271
  "hasClientMiddleware": false,
15264
15272
  "hasDefaultExport": true,
15265
15273
  "hasErrorBoundary": false,
15266
- "module": "/assets/browse-CPjwuHQe.js",
15274
+ "module": "/assets/browse-Ce7uXDK8.js",
15267
15275
  "imports": [
15268
- "/assets/browse-7BJg-qfM.js",
15269
- "/assets/styling-B8OEge8x.js",
15270
- "/assets/util-1G1Ez1rt.js",
15271
- "/assets/react-dom-_FKEaPb7.js",
15272
- "/assets/jsx-runtime-BVRj4wYA.js",
15273
- "/assets/GitTruckInfo-C1VOiqSW.js",
15274
- "/assets/UnionAuthorsModal-D-H775E5.js",
15275
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15276
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15277
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15276
+ "/assets/jsx-runtime-s_YwNCZb.js",
15277
+ "/assets/browse-wwHpvs7Z.js",
15278
+ "/assets/GitTruckInfo-CiYjIacT.js",
15279
+ "/assets/react-dom-BkDQo3BN.js",
15280
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15281
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15278
15282
  ],
15279
15283
  "css": [],
15280
15284
  "clientActionModule": void 0,
@@ -15295,7 +15299,7 @@ var server_manifest_default = {
15295
15299
  "hasClientMiddleware": false,
15296
15300
  "hasDefaultExport": false,
15297
15301
  "hasErrorBoundary": false,
15298
- "module": "/assets/_path-bRAhzWvL.js",
15302
+ "module": "/assets/_path-DzSNgPJP.js",
15299
15303
  "imports": [],
15300
15304
  "css": [],
15301
15305
  "clientActionModule": void 0,
@@ -15316,47 +15320,14 @@ var server_manifest_default = {
15316
15320
  "hasClientMiddleware": false,
15317
15321
  "hasDefaultExport": true,
15318
15322
  "hasErrorBoundary": false,
15319
- "module": "/assets/view-ZFr-KZ5c.js",
15320
- "imports": [
15321
- "/assets/browse-7BJg-qfM.js",
15322
- "/assets/styling-B8OEge8x.js",
15323
- "/assets/util-1G1Ez1rt.js",
15324
- "/assets/react-dom-_FKEaPb7.js",
15325
- "/assets/jsx-runtime-BVRj4wYA.js",
15326
- "/assets/GitTruckInfo-C1VOiqSW.js",
15327
- "/assets/UnionAuthorsModal-D-H775E5.js",
15328
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15329
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15330
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15331
- ],
15332
- "css": [],
15333
- "clientActionModule": void 0,
15334
- "clientLoaderModule": void 0,
15335
- "clientMiddlewareModule": void 0,
15336
- "hydrateFallbackModule": void 0
15337
- },
15338
- "routes/view.merge-authors": {
15339
- "id": "routes/view.merge-authors",
15340
- "parentId": "routes/view",
15341
- "path": "merge-authors",
15342
- "index": void 0,
15343
- "caseSensitive": void 0,
15344
- "hasAction": false,
15345
- "hasLoader": false,
15346
- "hasClientAction": false,
15347
- "hasClientLoader": false,
15348
- "hasClientMiddleware": false,
15349
- "hasDefaultExport": true,
15350
- "hasErrorBoundary": false,
15351
- "module": "/assets/view.merge-authors-VwPHWL-d.js",
15323
+ "module": "/assets/view-DhREUKDU.js",
15352
15324
  "imports": [
15353
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15354
- "/assets/styling-B8OEge8x.js",
15355
- "/assets/util-1G1Ez1rt.js",
15356
- "/assets/react-dom-_FKEaPb7.js",
15357
- "/assets/jsx-runtime-BVRj4wYA.js",
15358
- "/assets/UnionAuthorsModal-D-H775E5.js",
15359
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15325
+ "/assets/jsx-runtime-s_YwNCZb.js",
15326
+ "/assets/browse-wwHpvs7Z.js",
15327
+ "/assets/GitTruckInfo-CiYjIacT.js",
15328
+ "/assets/react-dom-BkDQo3BN.js",
15329
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15330
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15360
15331
  ],
15361
15332
  "css": [],
15362
15333
  "clientActionModule": void 0,
@@ -15377,7 +15348,7 @@ var server_manifest_default = {
15377
15348
  "hasClientMiddleware": false,
15378
15349
  "hasDefaultExport": false,
15379
15350
  "hasErrorBoundary": false,
15380
- "module": "/assets/view.progress-B7Os3EHt.js",
15351
+ "module": "/assets/view.progress-CEmcNaLe.js",
15381
15352
  "imports": [],
15382
15353
  "css": [],
15383
15354
  "clientActionModule": void 0,
@@ -15398,19 +15369,15 @@ var server_manifest_default = {
15398
15369
  "hasClientMiddleware": false,
15399
15370
  "hasDefaultExport": true,
15400
15371
  "hasErrorBoundary": false,
15401
- "module": "/assets/view.commits-D4Q38_m5.js",
15372
+ "module": "/assets/view.commits-D8Z4b-x3.js",
15402
15373
  "imports": [
15403
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15404
- "/assets/browse-7BJg-qfM.js",
15405
- "/assets/styling-B8OEge8x.js",
15406
- "/assets/util-1G1Ez1rt.js",
15407
- "/assets/react-dom-_FKEaPb7.js",
15408
- "/assets/jsx-runtime-BVRj4wYA.js",
15409
- "/assets/GitTruckInfo-C1VOiqSW.js",
15410
- "/assets/RepoTabs-4-CQeS6M.js",
15411
- "/assets/UnionAuthorsModal-D-H775E5.js",
15412
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15413
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15374
+ "/assets/jsx-runtime-s_YwNCZb.js",
15375
+ "/assets/browse-wwHpvs7Z.js",
15376
+ "/assets/GitTruckInfo-CiYjIacT.js",
15377
+ "/assets/react-dom-BkDQo3BN.js",
15378
+ "/assets/RepoTabs-CWt-EVgp.js",
15379
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15380
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15414
15381
  ],
15415
15382
  "css": [],
15416
15383
  "clientActionModule": void 0,
@@ -15431,19 +15398,15 @@ var server_manifest_default = {
15431
15398
  "hasClientMiddleware": false,
15432
15399
  "hasDefaultExport": true,
15433
15400
  "hasErrorBoundary": false,
15434
- "module": "/assets/view.details-CVK0Sh-y.js",
15401
+ "module": "/assets/view.details-Bsu_yyiE.js",
15435
15402
  "imports": [
15436
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15437
- "/assets/browse-7BJg-qfM.js",
15438
- "/assets/styling-B8OEge8x.js",
15439
- "/assets/util-1G1Ez1rt.js",
15440
- "/assets/react-dom-_FKEaPb7.js",
15441
- "/assets/jsx-runtime-BVRj4wYA.js",
15442
- "/assets/GitTruckInfo-C1VOiqSW.js",
15443
- "/assets/RepoTabs-4-CQeS6M.js",
15444
- "/assets/UnionAuthorsModal-D-H775E5.js",
15445
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15446
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15403
+ "/assets/jsx-runtime-s_YwNCZb.js",
15404
+ "/assets/browse-wwHpvs7Z.js",
15405
+ "/assets/GitTruckInfo-CiYjIacT.js",
15406
+ "/assets/react-dom-BkDQo3BN.js",
15407
+ "/assets/RepoTabs-CWt-EVgp.js",
15408
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15409
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15447
15410
  ],
15448
15411
  "css": [],
15449
15412
  "clientActionModule": void 0,
@@ -15464,8 +15427,8 @@ var server_manifest_default = {
15464
15427
  "hasClientMiddleware": false,
15465
15428
  "hasDefaultExport": true,
15466
15429
  "hasErrorBoundary": false,
15467
- "module": "/assets/view._index-CjsEuBV6.js",
15468
- "imports": ["/assets/chunk-LFPYN7LY-CPTP6242.js", "/assets/jsx-runtime-BVRj4wYA.js"],
15430
+ "module": "/assets/view._index-jmCCN9mA.js",
15431
+ "imports": ["/assets/jsx-runtime-s_YwNCZb.js"],
15469
15432
  "css": [],
15470
15433
  "clientActionModule": void 0,
15471
15434
  "clientLoaderModule": void 0,
@@ -15485,18 +15448,14 @@ var server_manifest_default = {
15485
15448
  "hasClientMiddleware": false,
15486
15449
  "hasDefaultExport": true,
15487
15450
  "hasErrorBoundary": false,
15488
- "module": "/assets/ui-CGg0wrDR.js",
15451
+ "module": "/assets/ui-EnssW_g_.js",
15489
15452
  "imports": [
15490
- "/assets/chunk-LFPYN7LY-CPTP6242.js",
15491
- "/assets/browse-7BJg-qfM.js",
15492
- "/assets/styling-B8OEge8x.js",
15493
- "/assets/util-1G1Ez1rt.js",
15494
- "/assets/react-dom-_FKEaPb7.js",
15495
- "/assets/jsx-runtime-BVRj4wYA.js",
15496
- "/assets/GitTruckInfo-C1VOiqSW.js",
15497
- "/assets/UnionAuthorsModal-D-H775E5.js",
15498
- "/assets/compare-Br3z3FUS-OODPzXmX.js",
15499
- "/assets/chunk-JPUPSTYD-XO4UPun8.js"
15453
+ "/assets/jsx-runtime-s_YwNCZb.js",
15454
+ "/assets/browse-wwHpvs7Z.js",
15455
+ "/assets/GitTruckInfo-CiYjIacT.js",
15456
+ "/assets/react-dom-BkDQo3BN.js",
15457
+ "/assets/compare-Br3z3FUS-CQWcjqXQ.js",
15458
+ "/assets/chunk-JPUPSTYD-YBbsfvLw.js"
15500
15459
  ],
15501
15460
  "css": [],
15502
15461
  "clientActionModule": void 0,
@@ -15517,7 +15476,7 @@ var server_manifest_default = {
15517
15476
  "hasClientMiddleware": false,
15518
15477
  "hasDefaultExport": false,
15519
15478
  "hasErrorBoundary": false,
15520
- "module": "/assets/_-RagYKtT_.js",
15479
+ "module": "/assets/_-CT17qCl3.js",
15521
15480
  "imports": [],
15522
15481
  "css": [],
15523
15482
  "clientActionModule": void 0,
@@ -15526,8 +15485,8 @@ var server_manifest_default = {
15526
15485
  "hydrateFallbackModule": void 0
15527
15486
  }
15528
15487
  },
15529
- "url": "/assets/manifest-c5f30ffe.js",
15530
- "version": "c5f30ffe",
15488
+ "url": "/assets/manifest-3c232792.js",
15489
+ "version": "3c232792",
15531
15490
  "sri": void 0
15532
15491
  };
15533
15492
  const assetsBuildDirectory = "build/client";
@@ -15559,14 +15518,6 @@ const routes = {
15559
15518
  caseSensitive: void 0,
15560
15519
  module: root_exports
15561
15520
  },
15562
- "routes/FullscreenButton": {
15563
- id: "routes/FullscreenButton",
15564
- parentId: "root",
15565
- path: "FullscreenButton",
15566
- index: void 0,
15567
- caseSensitive: void 0,
15568
- module: FullscreenButton_exports
15569
- },
15570
15521
  "routes/[.]well-known.$": {
15571
15522
  id: "routes/[.]well-known.$",
15572
15523
  parentId: "root",
@@ -15631,14 +15582,6 @@ const routes = {
15631
15582
  caseSensitive: void 0,
15632
15583
  module: view_exports
15633
15584
  },
15634
- "routes/view.merge-authors": {
15635
- id: "routes/view.merge-authors",
15636
- parentId: "routes/view",
15637
- path: "merge-authors",
15638
- index: void 0,
15639
- caseSensitive: void 0,
15640
- module: view_merge_authors_exports
15641
- },
15642
15585
  "routes/view.progress": {
15643
15586
  id: "routes/view.progress",
15644
15587
  parentId: "routes/view",