git-truck 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -2203,7 +2203,7 @@ var import_fs3 = require("fs");
2203
2203
 
2204
2204
  // src/analyzer/model.ts
2205
2205
  init_react();
2206
- var AnalyzerDataInterfaceVersion = 9;
2206
+ var AnalyzerDataInterfaceVersion = 10;
2207
2207
 
2208
2208
  // src/analyzer/log.server.ts
2209
2209
  init_react();
@@ -2301,7 +2301,7 @@ var import_path2 = require("path");
2301
2301
 
2302
2302
  // package.json
2303
2303
  var name = "git-truck";
2304
- var version = "1.1.0";
2304
+ var version = "1.1.1";
2305
2305
  var private2 = false;
2306
2306
  var description = "Visualizing a Git repository";
2307
2307
  var license = "MIT";
@@ -3062,6 +3062,62 @@ function TreeCleanup(tree) {
3062
3062
 
3063
3063
  // src/analyzer/analyze.server.ts
3064
3064
  var import_child_process2 = require("child_process");
3065
+
3066
+ // src/authorUnionUtil.server.ts
3067
+ init_react();
3068
+ var makeDupeMap = (authors) => {
3069
+ const dupeMap = {};
3070
+ for (const aliases of authors) {
3071
+ for (const alias of aliases) {
3072
+ dupeMap[alias] = aliases[0];
3073
+ }
3074
+ }
3075
+ return dupeMap;
3076
+ };
3077
+ function unionAuthors(authors, authorAliasMap) {
3078
+ return Object.entries(authors).reduce((newAuthorObject, [authorOrAlias, contributionCount]) => {
3079
+ const author = authorAliasMap[authorOrAlias];
3080
+ if (author) {
3081
+ const credits = (newAuthorObject[author] ?? 0) + contributionCount;
3082
+ return __spreadProps(__spreadValues({}, newAuthorObject), {
3083
+ [author]: credits
3084
+ });
3085
+ }
3086
+ return __spreadProps(__spreadValues({}, newAuthorObject), { [authorOrAlias]: contributionCount });
3087
+ }, {});
3088
+ }
3089
+ function nameUnion(names, authorAliasMap) {
3090
+ let collector = [];
3091
+ for (const name2 of names) {
3092
+ const lookup = authorAliasMap[name2];
3093
+ if (lookup) {
3094
+ if (collector.includes(lookup))
3095
+ continue;
3096
+ collector.push(lookup);
3097
+ } else
3098
+ collector.push(name2);
3099
+ }
3100
+ return collector;
3101
+ }
3102
+ function addAuthorUnion(data, dupeMap) {
3103
+ data.commit.tree = addAuthorUnionRec(data.commit.tree, dupeMap);
3104
+ return data;
3105
+ }
3106
+ function addAuthorUnionRec(tree, authorUnions) {
3107
+ for (const child of tree.children) {
3108
+ if (child.type === "blob") {
3109
+ child.unionedAuthors = {
3110
+ HISTORICAL: unionAuthors(child.authors, authorUnions),
3111
+ BLAME: unionAuthors(child.blameAuthors, authorUnions)
3112
+ };
3113
+ } else {
3114
+ addAuthorUnionRec(child, authorUnions);
3115
+ }
3116
+ }
3117
+ return tree;
3118
+ }
3119
+
3120
+ // src/analyzer/analyze.server.ts
3065
3121
  var repoDir = ".";
3066
3122
  async function analyzeCommitLight(hash) {
3067
3123
  const rawContent = await GitCaller.getInstance().catFileCached(hash);
@@ -3260,10 +3316,12 @@ ${reasons.map((r) => ` - ${r}`).join("\n")}`);
3260
3316
  let outPath = (0, import_path4.resolve)(args2.out ?? defaultOutPath);
3261
3317
  if (!(0, import_path4.isAbsolute)(outPath))
3262
3318
  outPath = (0, import_path4.resolve)(process.cwd(), outPath);
3319
+ const authorsUnion = nameUnion(authors, makeDupeMap(args2.unionedAuthors ?? []));
3263
3320
  data = {
3264
3321
  cached: false,
3265
3322
  hiddenFiles,
3266
3323
  authors,
3324
+ authorsUnion,
3267
3325
  repo: repoName,
3268
3326
  branch: branchName,
3269
3327
  commit: hydratedRepoTree,
@@ -4312,47 +4370,6 @@ async function getTruckConfigWithArgs(repo) {
4312
4370
  ];
4313
4371
  }
4314
4372
 
4315
- // src/authorUnionUtil.server.ts
4316
- init_react();
4317
- var makeDupeMap = (authors) => {
4318
- const dupeMap = {};
4319
- for (const aliases of authors) {
4320
- for (const alias of aliases) {
4321
- dupeMap[alias] = aliases[0];
4322
- }
4323
- }
4324
- return dupeMap;
4325
- };
4326
- function unionAuthors(authors, authorAliasMap) {
4327
- return Object.entries(authors).reduce((newAuthorObject, [authorOrAlias, contributionCount]) => {
4328
- const author = authorAliasMap[authorOrAlias];
4329
- if (author) {
4330
- const credits = (newAuthorObject[author] ?? 0) + contributionCount;
4331
- return __spreadProps(__spreadValues({}, newAuthorObject), {
4332
- [author]: credits
4333
- });
4334
- }
4335
- return __spreadProps(__spreadValues({}, newAuthorObject), { [authorOrAlias]: contributionCount });
4336
- }, {});
4337
- }
4338
- function addAuthorUnion(data, dupeMap) {
4339
- data.commit.tree = addAuthorUnionRec(data.commit.tree, dupeMap);
4340
- return data;
4341
- }
4342
- function addAuthorUnionRec(tree, authorUnions) {
4343
- for (const child of tree.children) {
4344
- if (child.type === "blob") {
4345
- child.unionedAuthors = {
4346
- HISTORICAL: unionAuthors(child.authors, authorUnions),
4347
- BLAME: unionAuthors(child.blameAuthors, authorUnions)
4348
- };
4349
- } else {
4350
- addAuthorUnionRec(child, authorUnions);
4351
- }
4352
- }
4353
- return tree;
4354
- }
4355
-
4356
4373
  // src/components/Details.tsx
4357
4374
  init_react();
4358
4375
  var import_react7 = require("react");
@@ -4533,57 +4550,6 @@ function getColorFromExtension(extension) {
4533
4550
  return colorResult.color;
4534
4551
  }
4535
4552
 
4536
- // src/util.ts
4537
- init_react();
4538
- function dateFormatLong(epochTime) {
4539
- if (!epochTime)
4540
- return "Invalid date";
4541
- return new Date(epochTime * 1e3).toLocaleString("en-gb", {
4542
- day: "2-digit",
4543
- month: "short",
4544
- year: "numeric"
4545
- });
4546
- }
4547
- function dateTimeFormatShort(epochTime) {
4548
- return new Date(epochTime).toLocaleString("en-gb", {
4549
- hour: "2-digit",
4550
- minute: "2-digit",
4551
- day: "2-digit",
4552
- month: "short",
4553
- year: "2-digit"
4554
- });
4555
- }
4556
- function dateFormatRelative(epochTime) {
4557
- const now = Date.now();
4558
- const hourMillis = 60 * 60 * 1e3;
4559
- const dayMillis = 24 * hourMillis;
4560
- const difference = now - epochTime * 1e3;
4561
- if (difference < 0)
4562
- return "Unknown time ago";
4563
- if (difference > dayMillis) {
4564
- const days = Math.floor(difference / dayMillis);
4565
- return `${days} day${days > 1 ? "s" : ""} ago`;
4566
- }
4567
- const hours = Math.floor(difference / hourMillis);
4568
- if (hours > 1)
4569
- return `${hours} hours ago`;
4570
- if (hours === 1)
4571
- return "1 hour ago";
4572
- return "<1 hour ago";
4573
- }
4574
- var last = (arr) => arr[arr.length - 1];
4575
- var allExceptLast = (arr) => {
4576
- if (arr.length <= 1)
4577
- return [];
4578
- return arr.slice(0, arr.length - 1);
4579
- };
4580
- function getSeparator(path) {
4581
- if (path.includes("\\"))
4582
- return "\\";
4583
- return "/";
4584
- }
4585
- var getPathFromRepoAndHead = (repo, branch) => [repo, branch].join("/");
4586
-
4587
4553
  // src/metrics.ts
4588
4554
  var Authorship = {
4589
4555
  HISTORICAL: "Complete history",
@@ -4594,14 +4560,18 @@ var Metric = {
4594
4560
  MOST_COMMITS: "Number of commits",
4595
4561
  LAST_CHANGED: "Last changed",
4596
4562
  SINGLE_AUTHOR: "Single author",
4597
- TOP_CONTRIBUTOR: "Top contributor"
4563
+ TOP_CONTRIBUTOR: "Top contributor",
4564
+ TRUCK_FACTOR: "Truck factor"
4598
4565
  };
4599
4566
  function createMetricData(data) {
4600
- const authorColors = generateAuthorColors(data);
4601
- return {
4602
- HISTORICAL: setupMetricsCache(data.commit.tree, getMetricCalcs(data, "HISTORICAL", authorColors)),
4603
- BLAME: setupMetricsCache(data.commit.tree, getMetricCalcs(data, "BLAME", authorColors))
4604
- };
4567
+ const authorColors = generateAuthorColors(data.authors);
4568
+ return [
4569
+ {
4570
+ HISTORICAL: setupMetricsCache(data.commit.tree, getMetricCalcs(data, "HISTORICAL", authorColors)),
4571
+ BLAME: setupMetricsCache(data.commit.tree, getMetricCalcs(data, "BLAME", authorColors))
4572
+ },
4573
+ authorColors
4574
+ ];
4605
4575
  }
4606
4576
  function getMetricDescription(metric, authorshipType) {
4607
4577
  switch (metric) {
@@ -4610,24 +4580,28 @@ function getMetricDescription(metric, authorshipType) {
4610
4580
  case "MOST_COMMITS":
4611
4581
  return "Which files have had the most commits, throughout the repository's history?";
4612
4582
  case "LAST_CHANGED":
4613
- return "Where are the most recent or least recent commits made?";
4583
+ return "How long ago did the files change?";
4614
4584
  case "SINGLE_AUTHOR":
4615
4585
  return authorshipType === "HISTORICAL" ? "Which files are authored by only one person, throughout the repository's history?" : "Which files are authored by only one person, in the newest version?";
4616
4586
  case "TOP_CONTRIBUTOR":
4617
4587
  return authorshipType === "HISTORICAL" ? "Which person has made the most line-changes to a file, throughout the repository's history?" : "Which person has made the most line-changes to a file, in the newest version?";
4588
+ case "TRUCK_FACTOR":
4589
+ return "How many have contributed to a file?";
4618
4590
  default:
4619
4591
  throw new Error("Uknown metric type: " + metric);
4620
4592
  }
4621
4593
  }
4622
- function isGradientMetric(metric) {
4594
+ function getMetricLegendType(metric) {
4623
4595
  switch (metric) {
4624
4596
  case "FILE_EXTENSION":
4625
4597
  case "TOP_CONTRIBUTOR":
4626
4598
  case "SINGLE_AUTHOR":
4627
- return false;
4628
- case "LAST_CHANGED":
4599
+ return "POINT";
4629
4600
  case "MOST_COMMITS":
4630
- return true;
4601
+ return "GRADIENT";
4602
+ case "LAST_CHANGED":
4603
+ case "TRUCK_FACTOR":
4604
+ return "SEGMENTS";
4631
4605
  default:
4632
4606
  throw new Error("Uknown metric type: " + metric);
4633
4607
  }
@@ -4641,11 +4615,10 @@ var PointInfo = class {
4641
4615
  this.weight += value;
4642
4616
  }
4643
4617
  };
4644
- function generateAuthorColors(data) {
4618
+ function generateAuthorColors(authors) {
4645
4619
  const authorsMap = {};
4646
- for (const author of data.authors)
4620
+ for (const author of Object.keys(authors))
4647
4621
  authorsMap[author] = 0;
4648
- const authors = data.authors;
4649
4622
  const palette = (0, import_distinct_colors.default)({ count: authors.length });
4650
4623
  let index = 0;
4651
4624
  const map = /* @__PURE__ */ new Map();
@@ -4659,7 +4632,7 @@ function generateAuthorColors(data) {
4659
4632
  function getMetricCalcs(data, authorshipType, authorColors) {
4660
4633
  const commit = data.commit;
4661
4634
  const heatmap = new HeatMapTranslater(commit.minNoCommits, commit.maxNoCommits);
4662
- const coldmap = new ColdMapTranslater(commit.oldestLatestChangeEpoch, commit.newestLatestChangeEpoch);
4635
+ const truckmap = new TruckFactorTranslater(data.authorsUnion.length);
4663
4636
  return [
4664
4637
  [
4665
4638
  "FILE_EXTENSION",
@@ -4699,15 +4672,13 @@ function getMetricCalcs(data, authorshipType, authorColors) {
4699
4672
  (blob, cache) => {
4700
4673
  if (!cache.legend) {
4701
4674
  cache.legend = [
4702
- dateFormatRelative(commit.oldestLatestChangeEpoch),
4703
- dateFormatRelative(commit.newestLatestChangeEpoch),
4704
- dateFormatLong(commit.oldestLatestChangeEpoch),
4705
- dateFormatLong(commit.newestLatestChangeEpoch),
4706
- coldmap.getColor(commit.oldestLatestChangeEpoch),
4707
- coldmap.getColor(commit.newestLatestChangeEpoch)
4675
+ 6,
4676
+ (n) => lastChangedColorText(n),
4677
+ (n) => `${lastChangedColorSteps(n)}`,
4678
+ (blob2) => mapEpochToStep(blob2.lastChangeEpoch ?? 0) ?? -1
4708
4679
  ];
4709
4680
  }
4710
- coldmap.setColor(blob, cache);
4681
+ cacheAgeColor(blob, cache);
4711
4682
  }
4712
4683
  ],
4713
4684
  [
@@ -4719,6 +4690,23 @@ function getMetricCalcs(data, authorshipType, authorColors) {
4719
4690
  cache.legend = /* @__PURE__ */ new Map();
4720
4691
  setDominantAuthorColor(authorColors, blob, cache, authorshipType);
4721
4692
  }
4693
+ ],
4694
+ [
4695
+ "TRUCK_FACTOR",
4696
+ (blob, cache) => {
4697
+ if (!cache.legend) {
4698
+ cache.legend = [
4699
+ Math.floor(Math.log2(data.authorsUnion.length)) + 1,
4700
+ (n) => `${Math.pow(2, n)}`,
4701
+ (n) => `hsl(0,75%,${50 + n * (40 / (Math.floor(Math.log2(data.authorsUnion.length)) + 1))}%)`,
4702
+ (blob2) => {
4703
+ var _a;
4704
+ return Math.floor(Math.log2(Object.entries(((_a = blob2.unionedAuthors) == null ? void 0 : _a.HISTORICAL) ?? []).length));
4705
+ }
4706
+ ];
4707
+ }
4708
+ truckmap.setColor(blob, cache);
4709
+ }
4722
4710
  ]
4723
4711
  ];
4724
4712
  }
@@ -4829,6 +4817,60 @@ function setDominanceColor(blob, cache, authorshipType) {
4829
4817
  return;
4830
4818
  }
4831
4819
  }
4820
+ function lastChangedColorSteps(n) {
4821
+ switch (n) {
4822
+ case 5:
4823
+ return "#08519c";
4824
+ case 4:
4825
+ return "#3182bd";
4826
+ case 3:
4827
+ return "#6baed6";
4828
+ case 2:
4829
+ return "#9ecae1";
4830
+ case 1:
4831
+ return "#c6dbef";
4832
+ case 0:
4833
+ return "#cff2ff";
4834
+ default:
4835
+ return "grey";
4836
+ }
4837
+ }
4838
+ function lastChangedColorText(n) {
4839
+ switch (n) {
4840
+ case 5:
4841
+ return "1y";
4842
+ case 4:
4843
+ return "1m";
4844
+ case 3:
4845
+ return "1w";
4846
+ case 2:
4847
+ return "2d";
4848
+ case 1:
4849
+ return "1d";
4850
+ case 0:
4851
+ return "now";
4852
+ default:
4853
+ return "grey";
4854
+ }
4855
+ }
4856
+ function mapEpochToStep(input) {
4857
+ const diff = Date.now() / 1e3 - input;
4858
+ if (diff >= 31556926)
4859
+ return 5;
4860
+ else if (diff < 31556926 && diff >= 2629743)
4861
+ return 4;
4862
+ else if (diff < 2629743 && diff >= 604800)
4863
+ return 3;
4864
+ else if (diff < 604800 && diff >= 172800)
4865
+ return 2;
4866
+ else if (diff < 172800 && diff >= 86400)
4867
+ return 1;
4868
+ else if (diff < 86400)
4869
+ return 0;
4870
+ }
4871
+ function cacheAgeColor(blob, cache) {
4872
+ cache.colormap.set(blob.path, lastChangedColorSteps(mapEpochToStep(blob.lastChangeEpoch ?? 0) ?? -1));
4873
+ }
4832
4874
  var SpectrumTranslater = class {
4833
4875
  constructor(input_min, input_max, target_min, target_max) {
4834
4876
  this.scale = (target_max - target_min) / (input_max - input_min);
@@ -4843,27 +4885,29 @@ var SpectrumTranslater = class {
4843
4885
  return this.target_max - this.translate(input) + this.target_min;
4844
4886
  }
4845
4887
  };
4846
- var ColdMapTranslater = class {
4847
- constructor(min, max) {
4888
+ var TruckFactorTranslater = class {
4889
+ constructor(author_count) {
4848
4890
  this.min_lightness = 50;
4849
- this.max_lightness = 90;
4850
- this.translator = new SpectrumTranslater(min, max, this.min_lightness, this.max_lightness);
4891
+ this.max_lighness = 90;
4892
+ this.step = (this.max_lighness - this.min_lightness) / Math.floor(Math.log2(author_count));
4851
4893
  }
4852
4894
  getColor(value) {
4853
- return `hsl(240,100%,${this.translator.inverseTranslate(value)}%)`;
4895
+ const level = Math.floor(Math.log2(value));
4896
+ return `hsl(0,75%,${this.min_lightness + level * this.step}%)`;
4854
4897
  }
4855
4898
  setColor(blob, cache) {
4856
- cache.colormap.set(blob.path, this.getColor(blob.lastChangeEpoch ?? 0));
4899
+ var _a;
4900
+ cache.colormap.set(blob.path, this.getColor(Object.entries(((_a = blob.unionedAuthors) == null ? void 0 : _a.HISTORICAL) ?? []).length));
4857
4901
  }
4858
4902
  };
4859
4903
  var HeatMapTranslater = class {
4860
4904
  constructor(min, max) {
4861
4905
  this.min_lightness = 50;
4862
4906
  this.max_lightness = 95;
4863
- this.translator = new SpectrumTranslater(min, max, this.min_lightness, this.max_lightness);
4907
+ this.translater = new SpectrumTranslater(min, max, this.min_lightness, this.max_lightness);
4864
4908
  }
4865
4909
  getColor(value) {
4866
- return `hsl(20,100%,${this.translator.inverseTranslate(value)}%)`;
4910
+ return `hsl(20,100%,${this.translater.inverseTranslate(value)}%)`;
4867
4911
  }
4868
4912
  setColor(blob, cache) {
4869
4913
  cache.colormap.set(blob.path, this.getColor(blob.noCommits));
@@ -4916,6 +4960,57 @@ function usePath() {
4916
4960
  return context;
4917
4961
  }
4918
4962
 
4963
+ // src/util.ts
4964
+ init_react();
4965
+ function dateFormatLong(epochTime) {
4966
+ if (!epochTime)
4967
+ return "Invalid date";
4968
+ return new Date(epochTime * 1e3).toLocaleString("en-gb", {
4969
+ day: "2-digit",
4970
+ month: "short",
4971
+ year: "numeric"
4972
+ });
4973
+ }
4974
+ function dateTimeFormatShort(epochTime) {
4975
+ return new Date(epochTime).toLocaleString("en-gb", {
4976
+ hour: "2-digit",
4977
+ minute: "2-digit",
4978
+ day: "2-digit",
4979
+ month: "short",
4980
+ year: "2-digit"
4981
+ });
4982
+ }
4983
+ function dateFormatRelative(epochTime) {
4984
+ const now = Date.now();
4985
+ const hourMillis = 60 * 60 * 1e3;
4986
+ const dayMillis = 24 * hourMillis;
4987
+ const difference = now - epochTime * 1e3;
4988
+ if (difference < 0)
4989
+ return "Unknown time ago";
4990
+ if (difference > dayMillis) {
4991
+ const days = Math.floor(difference / dayMillis);
4992
+ return `${days} day${days > 1 ? "s" : ""} ago`;
4993
+ }
4994
+ const hours = Math.floor(difference / hourMillis);
4995
+ if (hours > 1)
4996
+ return `${hours} hours ago`;
4997
+ if (hours === 1)
4998
+ return "1 hour ago";
4999
+ return "<1 hour ago";
5000
+ }
5001
+ var last = (arr) => arr[arr.length - 1];
5002
+ var allExceptLast = (arr) => {
5003
+ if (arr.length <= 1)
5004
+ return [];
5005
+ return arr.slice(0, arr.length - 1);
5006
+ };
5007
+ function getSeparator(path) {
5008
+ if (path.includes("\\"))
5009
+ return "\\";
5010
+ return "/";
5011
+ }
5012
+ var getPathFromRepoAndHead = (repo, branch) => [repo, branch].join("/");
5013
+
4919
5014
  // src/components/Details.tsx
4920
5015
  var import_byte_size = __toESM(require("byte-size"));
4921
5016
  var import_material3 = require("@styled-icons/material");
@@ -5453,16 +5548,42 @@ var GradArrow = import_styled_components9.default.i`
5453
5548
  left: calc(${({ position }) => position * 100}% - ${estimatedLetterWidth}px);
5454
5549
  filter: drop-shadow(0px -2px 0px #fff);
5455
5550
  `;
5551
+ var SegmentArrow = import_styled_components9.default.i`
5552
+ display: ${({ visible }) => visible ? "initital" : "none"};
5553
+ transition: 500ms;
5554
+ position: relative;
5555
+ bottom: ${({ height }) => `${height}px`};
5556
+ left: calc(${({ position }) => position}% - ${estimatedLetterWidth}px);
5557
+ filter: drop-shadow(0px -2px 0px #fff);
5558
+ `;
5456
5559
  var StyledBox = (0, import_styled_components9.default)(Box)`
5457
5560
  position: sticky;
5458
5561
  bottom: 0;
5459
5562
  `;
5460
5563
  function Legend(props) {
5461
5564
  const { metricType, authorshipType } = useOptions();
5462
- const metricsData = useMetrics();
5565
+ const [metricsData, _] = useMetrics();
5463
5566
  const metricCache = metricsData[authorshipType].get(metricType) ?? void 0;
5464
5567
  if (metricCache === void 0)
5465
5568
  return null;
5569
+ let legend = /* @__PURE__ */ React.createElement(React.Fragment, null);
5570
+ switch (getMetricLegendType(metricType)) {
5571
+ case "POINT":
5572
+ legend = /* @__PURE__ */ React.createElement(PointMetricLegend, {
5573
+ metricCache
5574
+ });
5575
+ break;
5576
+ case "GRADIENT":
5577
+ legend = /* @__PURE__ */ React.createElement(GradientMetricLegend, {
5578
+ metricCache
5579
+ });
5580
+ break;
5581
+ case "SEGMENTS":
5582
+ legend = /* @__PURE__ */ React.createElement(SegmentMetricLegend, {
5583
+ metricCache
5584
+ });
5585
+ break;
5586
+ }
5466
5587
  return /* @__PURE__ */ React.createElement(StyledBox, null, /* @__PURE__ */ React.createElement(BoxSubTitle, null, Metric[metricType]), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(BoxP, null, getMetricDescription(metricType, authorshipType)), /* @__PURE__ */ React.createElement(Spacer, {
5467
5588
  lg: true
5468
5589
  }), metricType === "TOP_CONTRIBUTOR" || metricType === "SINGLE_AUTHOR" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Button, {
@@ -5472,10 +5593,74 @@ function Legend(props) {
5472
5593
  height: "1rem"
5473
5594
  }), "Merge duplicate users"), /* @__PURE__ */ React.createElement(Spacer, {
5474
5595
  lg: true
5475
- })) : null, isGradientMetric(metricType) ? /* @__PURE__ */ React.createElement(GradientMetricLegend, {
5476
- metricCache
5477
- }) : /* @__PURE__ */ React.createElement(PointMetricLegend, {
5478
- metricCache
5596
+ })) : null, legend);
5597
+ }
5598
+ function SegmentMetricLegend({ metricCache }) {
5599
+ const [steps, textGenerator, colorGenerator, offsetStepCalc] = metricCache.legend;
5600
+ const width = 100 / steps;
5601
+ let arrowVisible = false;
5602
+ let arrowOffset = 0;
5603
+ const { clickedObject } = useClickedObject();
5604
+ if ((clickedObject == null ? void 0 : clickedObject.type) == "blob") {
5605
+ arrowVisible = true;
5606
+ arrowOffset = width / 2 + width * offsetStepCalc(clickedObject);
5607
+ }
5608
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", {
5609
+ style: { display: `flex`, flexDirection: `row` }
5610
+ }, [...Array(steps).fill(1)].map((_, i) => {
5611
+ return steps > 5 ? /* @__PURE__ */ React.createElement(MetricSegment, {
5612
+ key: `legend-${i}`,
5613
+ width,
5614
+ color: colorGenerator(i),
5615
+ text: textGenerator(i),
5616
+ top: i % 2 === 0
5617
+ }) : /* @__PURE__ */ React.createElement(TopMetricSegment, {
5618
+ key: `legend-${i}`,
5619
+ width,
5620
+ color: colorGenerator(i),
5621
+ text: textGenerator(i)
5622
+ });
5623
+ })), /* @__PURE__ */ React.createElement(SegmentArrow, {
5624
+ visible: arrowVisible,
5625
+ position: arrowOffset,
5626
+ height: steps > 5 ? 50 : 10
5627
+ }, "\u25B2"));
5628
+ }
5629
+ function MetricSegment({ width, color, text, top }) {
5630
+ if (top)
5631
+ return /* @__PURE__ */ React.createElement("div", {
5632
+ style: { display: "flex", flexDirection: "column", width: `${width}%` }
5633
+ }, /* @__PURE__ */ React.createElement("div", {
5634
+ style: { textAlign: "left", height: "20px", marginBottom: "-6px" }
5635
+ }, text), /* @__PURE__ */ React.createElement("div", {
5636
+ style: { textAlign: "left", height: "20px", marginBottom: "-2px" }
5637
+ }, "/"), /* @__PURE__ */ React.createElement("div", {
5638
+ style: { backgroundColor: color, height: "20px" }
5639
+ }), /* @__PURE__ */ React.createElement("div", {
5640
+ style: { textAlign: "left", height: "40px" }
5641
+ }));
5642
+ else
5643
+ return /* @__PURE__ */ React.createElement("div", {
5644
+ style: { display: "flex", flexDirection: "column", width: `${width}%` }
5645
+ }, /* @__PURE__ */ React.createElement("div", {
5646
+ style: { textAlign: "left", height: "32px" }
5647
+ }), /* @__PURE__ */ React.createElement("div", {
5648
+ style: { backgroundColor: color, height: "20px" }
5649
+ }), /* @__PURE__ */ React.createElement("div", {
5650
+ style: { textAlign: "left", height: "20px", marginTop: "-7px" }
5651
+ }, "\\"), /* @__PURE__ */ React.createElement("div", {
5652
+ style: { textAlign: "left", height: "20px", marginTop: "-4px" }
5653
+ }, text));
5654
+ }
5655
+ function TopMetricSegment({ width, color, text }) {
5656
+ return /* @__PURE__ */ React.createElement("div", {
5657
+ style: { display: "flex", flexDirection: "column", width: `${width}%` }
5658
+ }, /* @__PURE__ */ React.createElement("div", {
5659
+ style: { textAlign: "left", height: "20px", marginBottom: "-6px" }
5660
+ }, text), /* @__PURE__ */ React.createElement("div", {
5661
+ style: { textAlign: "left", height: "20px", marginBottom: "-2px" }
5662
+ }, "/"), /* @__PURE__ */ React.createElement("div", {
5663
+ style: { backgroundColor: color, height: "20px" }
5479
5664
  }));
5480
5665
  }
5481
5666
  function GradientMetricLegend({ metricCache }) {
@@ -6287,7 +6472,7 @@ function Tooltip({ hoveredBlob }) {
6287
6472
  const mouse = (0, import_react_use3.useMouse)(documentElementRef);
6288
6473
  const unitRaw = useCSSVar("--unit");
6289
6474
  const unit = unitRaw ? Number(unitRaw.replace("px", "")) : 0;
6290
- const metricsData = useMetrics();
6475
+ const [metricsData, _] = useMetrics();
6291
6476
  const color = (0, import_react12.useMemo)(() => {
6292
6477
  var _a, _b;
6293
6478
  if (!hoveredBlob) {
@@ -6326,7 +6511,7 @@ function Tooltip({ hoveredBlob }) {
6326
6511
  })));
6327
6512
  }
6328
6513
  function ColorMetricDependentInfo(props) {
6329
- var _a, _b, _c, _d, _e, _f;
6514
+ var _a, _b, _c, _d, _e, _f, _g, _h;
6330
6515
  switch (props.metric) {
6331
6516
  case "MOST_COMMITS":
6332
6517
  const noCommits = (_a = props.hoveredBlob) == null ? void 0 : _a.noCommits;
@@ -6353,6 +6538,17 @@ function ColorMetricDependentInfo(props) {
6353
6538
  if (!dominant)
6354
6539
  return null;
6355
6540
  return /* @__PURE__ */ React.createElement(React.Fragment, null, dominant[0]);
6541
+ case "TRUCK_FACTOR":
6542
+ const authorCount = Object.entries(((_h = (_g = props.hoveredBlob) == null ? void 0 : _g.unionedAuthors) == null ? void 0 : _h.HISTORICAL) ?? []).length;
6543
+ switch (authorCount) {
6544
+ case 0:
6545
+ return null;
6546
+ case 1:
6547
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, "1 author");
6548
+ default:
6549
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, authorCount, " authors");
6550
+ }
6551
+ return /* @__PURE__ */ React.createElement(React.Fragment, null);
6356
6552
  default:
6357
6553
  return null;
6358
6554
  }
@@ -6473,7 +6669,7 @@ var Node3 = (0, import_react13.memo)(function Node4({ d, isRoot }) {
6473
6669
  });
6474
6670
  function Circle({ d, isSearchMatch }) {
6475
6671
  var _a;
6476
- const metricsData = useMetrics();
6672
+ const [metricsData, _] = useMetrics();
6477
6673
  const { metricType, authorshipType } = useOptions();
6478
6674
  const props = useToggleableSpring({
6479
6675
  cx: d.x,
@@ -6497,7 +6693,7 @@ var CircleSVG = (0, import_styled_components12.default)(import_web2.animated.cir
6497
6693
  `;
6498
6694
  function Rect({ d, isSearchMatch }) {
6499
6695
  var _a;
6500
- const metricsData = useMetrics();
6696
+ const [metricsData, _] = useMetrics();
6501
6697
  const { metricType, authorshipType } = useOptions();
6502
6698
  const props = useToggleableSpring({
6503
6699
  x: d.x0,
@@ -7020,17 +7216,14 @@ function UnionAuthorsModal({ visible, onClose }) {
7020
7216
  }
7021
7217
  const transitionData = (0, import_remix6.useTransition)();
7022
7218
  const disabled = transitionData.state !== "idle";
7023
- const metrics = useMetrics();
7219
+ const [metrics, authorColors] = useMetrics();
7024
7220
  const ungroupedUsersSorted = authors.filter((a) => !flattedUnionedAuthors.includes(a)).slice(0).sort(stringSorter);
7025
7221
  function handleModalWrapperClick(event) {
7026
7222
  if (event.target === event.currentTarget)
7027
7223
  onClose();
7028
7224
  }
7029
7225
  (0, import_react_use5.useKey)("Escape", onClose);
7030
- const getColorFromDisplayName = (displayName) => {
7031
- var _a, _b;
7032
- return ((_b = ((_a = metrics.HISTORICAL.get("TOP_CONTRIBUTOR")) == null ? void 0 : _a.legend).get(displayName)) == null ? void 0 : _b.color) ?? "#333";
7033
- };
7226
+ const getColorFromDisplayName = (displayName) => authorColors.get(displayName) ?? "#333";
7034
7227
  if (!visible)
7035
7228
  return null;
7036
7229
  return /* @__PURE__ */ React.createElement(ModalWrapper, {
@@ -7518,7 +7711,7 @@ var AnalyzedTag = import_styled_components20.default.span`
7518
7711
 
7519
7712
  // server-assets-manifest:@remix-run/dev/assets-manifest
7520
7713
  init_react();
7521
- var assets_manifest_default = { "version": "2cbafb4c", "entry": { "module": "/build/entry.client-CDN524WE.js", "imports": ["/build/_shared/chunk-Y7FU7OLJ.js", "/build/_shared/chunk-YDCVFN7Q.js"] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "module": "/build/root-RVNUJ3UD.js", "imports": ["/build/_shared/chunk-FSZBSYJE.js", "/build/_shared/chunk-4LCJRRXI.js"], "hasAction": false, "hasLoader": false, "hasCatchBoundary": true, "hasErrorBoundary": true }, "routes/$repo.$": { "id": "routes/$repo.$", "parentId": "root", "path": ":repo/*", "index": void 0, "caseSensitive": void 0, "module": "/build/routes/$repo.$-OYBDJR6E.js", "imports": ["/build/_shared/chunk-6WSXAQ4H.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": true }, "routes/index": { "id": "routes/index", "parentId": "root", "path": void 0, "index": true, "caseSensitive": void 0, "module": "/build/routes/index-QLBDLJWG.js", "imports": ["/build/_shared/chunk-6WSXAQ4H.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": false } }, "url": "/build/manifest-2CBAFB4C.js" };
7714
+ var assets_manifest_default = { "version": "1ef27dc7", "entry": { "module": "/build/entry.client-CDN524WE.js", "imports": ["/build/_shared/chunk-Y7FU7OLJ.js", "/build/_shared/chunk-YDCVFN7Q.js"] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "module": "/build/root-RVNUJ3UD.js", "imports": ["/build/_shared/chunk-FSZBSYJE.js", "/build/_shared/chunk-4LCJRRXI.js"], "hasAction": false, "hasLoader": false, "hasCatchBoundary": true, "hasErrorBoundary": true }, "routes/$repo.$": { "id": "routes/$repo.$", "parentId": "root", "path": ":repo/*", "index": void 0, "caseSensitive": void 0, "module": "/build/routes/$repo.$-DOVI33K6.js", "imports": ["/build/_shared/chunk-6WSXAQ4H.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": true }, "routes/index": { "id": "routes/index", "parentId": "root", "path": void 0, "index": true, "caseSensitive": void 0, "module": "/build/routes/index-QLBDLJWG.js", "imports": ["/build/_shared/chunk-6WSXAQ4H.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": false } }, "url": "/build/manifest-1EF27DC7.js" };
7522
7715
 
7523
7716
  // server-entry-module:@remix-run/dev/server-build
7524
7717
  var entry = { module: entry_server_exports };