git-truck 0.8.13-0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -1852,7 +1852,7 @@ function handleRequest(request, responseStatusCode, responseHeaders, remixContex
1852
1852
  });
1853
1853
  }
1854
1854
 
1855
- // route:C:\Users\jonas\p\git-truck\src\root.tsx
1855
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\root.tsx
1856
1856
  var root_exports = {};
1857
1857
  __export(root_exports, {
1858
1858
  CatchBoundary: () => CatchBoundary,
@@ -1862,7 +1862,9 @@ __export(root_exports, {
1862
1862
  meta: () => meta
1863
1863
  });
1864
1864
  init_react();
1865
+ var import_react_konami_code = require("react-konami-code");
1865
1866
  var import_remix2 = __toESM(require_remix());
1867
+ var import_ssr = require("@react-aria/ssr");
1866
1868
 
1867
1869
  // src/styles/App.css
1868
1870
  var App_default = "/build/_assets/App-CCO27G3C.css";
@@ -1876,7 +1878,7 @@ var styles_default = "/build/_assets/index-OING3CPO.css";
1876
1878
  // src/styles/Chart.css
1877
1879
  var Chart_default = "/build/_assets/Chart-OOZGWRB3.css";
1878
1880
 
1879
- // route:C:\Users\jonas\p\git-truck\src\root.tsx
1881
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\root.tsx
1880
1882
  var import_react = require("react");
1881
1883
 
1882
1884
  // src/components/util.tsx
@@ -1892,6 +1894,9 @@ var titleBaseStyles = import_styled_components2.css`
1892
1894
  word-break: keep-all;
1893
1895
  white-space: nowrap;
1894
1896
  overflow-x: hidden;
1897
+ display: flex;
1898
+ align-items: center;
1899
+ justify-content: space-between;
1895
1900
  `;
1896
1901
  var BoxTitle = import_styled_components2.default.h2`
1897
1902
  ${titleBaseStyles}
@@ -1918,7 +1923,12 @@ var TextButton = import_styled_components2.default.button`
1918
1923
  /* color: #fff; */
1919
1924
  cursor: pointer;
1920
1925
  transition: backround-color var(--hover-transition-duration);
1921
- &:hover {
1926
+
1927
+ &:disabled {
1928
+ cursor: not-allowed;
1929
+ }
1930
+
1931
+ &:enabled:hover {
1922
1932
  background-color: var(--button-hovered-bg);
1923
1933
  }
1924
1934
  `;
@@ -2042,8 +2052,53 @@ var Code = import_styled_components2.default.code`
2042
2052
  var Grower = import_styled_components2.default.div`
2043
2053
  flex-grow: 1;
2044
2054
  `;
2055
+ var SelectWithEllipsis = import_styled_components2.default.select`
2056
+ text-overflow: ellipsis;
2057
+ overflow: scroll;
2058
+ width: 100%;
2059
+
2060
+ font: inherit;
2061
+ color: inherit;
2062
+ font-size: 0.9em;
2063
+ padding: 0.2em 0;
2064
+
2065
+ background: none;
2066
+ border-radius: 4px;
2067
+
2068
+ transition: border-color 0.1s;
2069
+ border: 1px solid hsla(0, 0%, 50%, 0);
2070
+
2071
+ &:enabled {
2072
+ cursor: pointer;
2073
+ }
2074
+
2075
+ &:disabled {
2076
+ cursor: not-allowed;
2077
+ }
2078
+
2079
+ &:enabled:hover,
2080
+ &:enabled:active {
2081
+ border: 1px solid hsla(0, 0%, 50%, 1);
2082
+ }
2083
+ `;
2084
+ var OptionWithEllipsis = import_styled_components2.default.option`
2085
+ text-overflow: ellipsis;
2086
+ overflow: scroll;
2087
+ `;
2088
+ var SelectWithIconWrapper = import_styled_components2.default.div`
2089
+ width: 100%;
2090
+ display: grid;
2091
+ grid-template-columns: auto 1fr;
2092
+ gap: 0.5em;
2093
+ place-items: center left;
2094
+ `;
2095
+ var SelectPlaceholder = import_styled_components2.default.div`
2096
+ font-size: 0.9em;
2097
+ padding: 0.2em 0;
2098
+ border: 1px solid transparent;
2099
+ `;
2045
2100
 
2046
- // route:C:\Users\jonas\p\git-truck\src\root.tsx
2101
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\root.tsx
2047
2102
  var meta = () => {
2048
2103
  return { title: "Git Truck \u{1F69B}" };
2049
2104
  };
@@ -2054,6 +2109,7 @@ function links() {
2054
2109
  }));
2055
2110
  }
2056
2111
  function App() {
2112
+ (0, import_react_konami_code.useKonami)(() => window.open("https://fruit-rush.joglr.dev", "_self"));
2057
2113
  return /* @__PURE__ */ React.createElement("html", {
2058
2114
  lang: "en"
2059
2115
  }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
@@ -2061,7 +2117,7 @@ function App() {
2061
2117
  }), /* @__PURE__ */ React.createElement("meta", {
2062
2118
  name: "viewport",
2063
2119
  content: "width=device-width,initial-scale=1"
2064
- }), /* @__PURE__ */ React.createElement(import_remix2.Meta, null), /* @__PURE__ */ React.createElement(import_remix2.Links, null), typeof document === "undefined" ? "__STYLES__" : null), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement(import_remix2.Outlet, null), /* @__PURE__ */ React.createElement(import_remix2.ScrollRestoration, null), /* @__PURE__ */ React.createElement(import_remix2.Scripts, null), /* @__PURE__ */ React.createElement(import_remix2.LiveReload, null)));
2120
+ }), /* @__PURE__ */ React.createElement(import_remix2.Meta, null), /* @__PURE__ */ React.createElement(import_remix2.Links, null), typeof document === "undefined" ? "__STYLES__" : null), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement(import_ssr.SSRProvider, null, /* @__PURE__ */ React.createElement(import_remix2.Outlet, null)), /* @__PURE__ */ React.createElement(import_remix2.ScrollRestoration, null), /* @__PURE__ */ React.createElement(import_remix2.Scripts, null), /* @__PURE__ */ React.createElement(import_remix2.LiveReload, null)));
2065
2121
  }
2066
2122
  function CatchBoundary() {
2067
2123
  const caught = (0, import_remix2.useCatch)();
@@ -2074,7 +2130,7 @@ var ErrorBoundary = ({ error: error2 }) => {
2074
2130
  return /* @__PURE__ */ React.createElement("html", null, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("title", null, "Oops! An error wasn't handled"), /* @__PURE__ */ React.createElement(import_remix2.Meta, null), /* @__PURE__ */ React.createElement(import_remix2.Links, null)), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("h1", null, error2.message), /* @__PURE__ */ React.createElement(Code, null, error2.stack), /* @__PURE__ */ React.createElement(import_remix2.Scripts, null)));
2075
2131
  };
2076
2132
 
2077
- // route:C:\Users\jonas\p\git-truck\src\routes\$repo.tsx
2133
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\routes\$repo.$.tsx
2078
2134
  var repo_exports = {};
2079
2135
  __export(repo_exports, {
2080
2136
  ErrorBoundary: () => ErrorBoundary2,
@@ -2087,7 +2143,6 @@ var import_remix6 = __toESM(require_remix());
2087
2143
 
2088
2144
  // src/components/Providers.tsx
2089
2145
  init_react();
2090
- var import_ssr = require("@react-aria/ssr");
2091
2146
  var import_react8 = require("react");
2092
2147
 
2093
2148
  // src/contexts/ClickedContext.ts
@@ -2274,6 +2329,7 @@ function getSeparator(path) {
2274
2329
  return "\\";
2275
2330
  return "/";
2276
2331
  }
2332
+ var getPathFromRepoAndBranch = (repo, branch) => [repo, branch].join("/");
2277
2333
 
2278
2334
  // src/metrics.ts
2279
2335
  var Authorship = {
@@ -2650,7 +2706,7 @@ function Providers({ children, data }) {
2650
2706
  animationsEnabled: enabled
2651
2707
  }))
2652
2708
  }), [options]);
2653
- return /* @__PURE__ */ React.createElement(import_ssr.SSRProvider, null, /* @__PURE__ */ React.createElement(DataContext.Provider, {
2709
+ return /* @__PURE__ */ React.createElement(DataContext.Provider, {
2654
2710
  value: data
2655
2711
  }, /* @__PURE__ */ React.createElement(MetricsContext.Provider, {
2656
2712
  value: metricsData
@@ -2667,7 +2723,7 @@ function Providers({ children, data }) {
2667
2723
  value: { path, setPath }
2668
2724
  }, /* @__PURE__ */ React.createElement(ClickedObjectContext.Provider, {
2669
2725
  value: { clickedObject, setClickedObject }
2670
- }, children)))))));
2726
+ }, children))))));
2671
2727
  }
2672
2728
 
2673
2729
  // src/components/SidePanel.tsx
@@ -3896,7 +3952,7 @@ var import_fs3 = require("fs");
3896
3952
 
3897
3953
  // src/analyzer/model.ts
3898
3954
  init_react();
3899
- var AnalyzerDataInterfaceVersion = 4;
3955
+ var AnalyzerDataInterfaceVersion = 5;
3900
3956
 
3901
3957
  // src/analyzer/log.server.ts
3902
3958
  init_react();
@@ -4027,7 +4083,7 @@ var _GitCaller = class {
4027
4083
  }
4028
4084
  static async findBranchHead(repo, branch) {
4029
4085
  if (!branch) {
4030
- const [foundBranch, getBranchError] = await promiseHelper(_GitCaller.getCurrentBranch(repo));
4086
+ const [foundBranch, getBranchError] = await promiseHelper(_GitCaller._getRepositoryHead(repo));
4031
4087
  if (getBranchError) {
4032
4088
  throw getBranchError;
4033
4089
  }
@@ -4037,22 +4093,27 @@ var _GitCaller = class {
4037
4093
  if (!(0, import_fs.existsSync)(gitFolder)) {
4038
4094
  throw Error("No git folder exists at " + gitFolder);
4039
4095
  }
4040
- const branchPath = (0, import_path2.join)(gitFolder, "refs/heads/" + branch);
4041
- const absolutePath = (0, import_path2.join)(process.cwd(), branchPath);
4042
- log.debug("Looking for branch head at " + absolutePath);
4043
- const branchHead = (await import_fs.promises.readFile(branchPath, "utf-8")).trim();
4096
+ const branchHead = await _GitCaller._revParse(gitFolder, branch);
4044
4097
  log.debug(`${branch} -> [commit]${branchHead}`);
4045
- if (!branchHead)
4046
- throw Error("Branch head not found");
4047
4098
  return [branchHead, branch];
4048
4099
  }
4049
4100
  static getCachePath(repo, branch) {
4050
4101
  return (0, import_path2.resolve)(__dirname, "..", ".temp", repo, `${branch}.json`);
4051
4102
  }
4052
- static async getCurrentBranch(dir) {
4103
+ async getRepositoryHead() {
4104
+ return await _GitCaller._getRepositoryHead(this.repo);
4105
+ }
4106
+ static async _getRepositoryHead(dir) {
4053
4107
  const result = await runProcess(dir, "git", ["rev-parse", "--abbrev-ref", "HEAD"]);
4054
4108
  return result.trim();
4055
4109
  }
4110
+ async revParse(ref) {
4111
+ return await _GitCaller._revParse(this.repo, ref);
4112
+ }
4113
+ static async _revParse(dir, ref) {
4114
+ const result = await runProcess(dir, "git", ["rev-parse", ref]);
4115
+ return result.trim();
4116
+ }
4056
4117
  static async scanDirectoryForRepositories(argPath) {
4057
4118
  let userRepo = null;
4058
4119
  const pathIsRepo = await _GitCaller.isGitRepo(argPath);
@@ -4064,7 +4125,28 @@ var _GitCaller = class {
4064
4125
  const [isRepo] = await promiseHelper(_GitCaller.isGitRepo(repoPath));
4065
4126
  if (!isRepo)
4066
4127
  return null;
4067
- const repo = { name: repoDir2, path: repoPath, data: null, reasons: [] };
4128
+ const refs = _GitCaller.parseRefs(await _GitCaller._getRefs(repoPath));
4129
+ const branchesWithCaches = await Promise.all(Object.entries(refs.heads).map(async ([branch, branchHead]) => {
4130
+ const [result] = await _GitCaller.retrieveCachedResult({ repo: getDirName(repoPath), branch, branchHead });
4131
+ return {
4132
+ branch,
4133
+ branchHead,
4134
+ isAnalyzed: result !== null
4135
+ };
4136
+ }));
4137
+ const analyzedBranches = branchesWithCaches.filter((branch) => branch.isAnalyzed).reduce((acc, branch) => {
4138
+ acc[branch.branch] = branch.branchHead;
4139
+ return acc;
4140
+ }, {});
4141
+ const repo = {
4142
+ name: repoDir2,
4143
+ path: repoPath,
4144
+ data: null,
4145
+ reasons: [],
4146
+ currentHead: await _GitCaller._getRepositoryHead(repoPath),
4147
+ refs,
4148
+ analyzedBranches
4149
+ };
4068
4150
  try {
4069
4151
  const [findBranchHeadResult, error2] = await promiseHelper(_GitCaller.findBranchHead(repoPath));
4070
4152
  if (!error2) {
@@ -4092,6 +4174,37 @@ var _GitCaller = class {
4092
4174
  });
4093
4175
  return [userRepo, onlyRepos];
4094
4176
  }
4177
+ static parseRefs(refsAsMultilineString) {
4178
+ const gitRefs = {
4179
+ heads: {},
4180
+ remotes: {},
4181
+ tags: {}
4182
+ };
4183
+ const regex = /^(?<hash>.*) refs\/(?<ref_type>.*?)\/(?<path>.*)$/gm;
4184
+ const matches = refsAsMultilineString.matchAll(regex);
4185
+ let next = matches.next();
4186
+ while (next.value) {
4187
+ const groups = next.value.groups;
4188
+ next = matches.next();
4189
+ const hash = groups["hash"];
4190
+ const ref_type = groups["ref_type"];
4191
+ const path = groups["path"];
4192
+ switch (ref_type) {
4193
+ case "heads":
4194
+ gitRefs.heads[path] = hash;
4195
+ break;
4196
+ case "remotes":
4197
+ gitRefs.remotes[path] = hash;
4198
+ break;
4199
+ case "tags":
4200
+ gitRefs.tags[path] = hash;
4201
+ break;
4202
+ default:
4203
+ throw new Error(`Analyser error, ref_type: ${ref_type}, is invalid`);
4204
+ }
4205
+ }
4206
+ return gitRefs;
4207
+ }
4095
4208
  static async retrieveCachedResult({
4096
4209
  repo,
4097
4210
  branch,
@@ -4121,6 +4234,13 @@ var _GitCaller = class {
4121
4234
  setUseCache(useCache) {
4122
4235
  this.useCache = useCache;
4123
4236
  }
4237
+ async getRefs() {
4238
+ return await _GitCaller._getRefs(this.repo);
4239
+ }
4240
+ static async _getRefs(repo) {
4241
+ const result = await runProcess(repo, "git", ["show-ref"]);
4242
+ return result;
4243
+ }
4124
4244
  async catFile(hash) {
4125
4245
  const result = await runProcess(this.repo, "git", ["cat-file", "-p", hash]);
4126
4246
  return result;
@@ -4639,7 +4759,7 @@ async function latestVersion(packageName, options) {
4639
4759
 
4640
4760
  // package.json
4641
4761
  var name = "git-truck";
4642
- var version = "0.8.13-0";
4762
+ var version = "0.9.0";
4643
4763
  var private2 = false;
4644
4764
  var description = "Visualizing a Git repository";
4645
4765
  var license = "MIT";
@@ -4659,7 +4779,7 @@ var scripts = {
4659
4779
  build: "rimraf -rf build public/build && remix setup node && cross-env NODE_ENV=production remix build",
4660
4780
  dev: "cross-env NODE_ENV=development remix build && node dev.js",
4661
4781
  "dev:remix": "cross-env NODE_ENV=development remix watch",
4662
- prepublishOnly: "npm run build",
4782
+ prepublishOnly: "npm install && npm run build",
4663
4783
  "dev:node": "cross-env NODE_ENV=development nodemon --watch ./build/index.js ./build/index.js --",
4664
4784
  start: "cross-env NODE_ENV=production node ./build/index.js",
4665
4785
  format: "eslint --cache --fix src/**/*.{ts,tsx} && prettier --loglevel warn --write src/**/*.{ts,tsx}"
@@ -4692,6 +4812,7 @@ var dependencies = {
4692
4812
  open: "^8.4.0",
4693
4813
  react: "^17.0.2",
4694
4814
  "react-dom": "^17.0.2",
4815
+ "react-konami-code": "^2.2.2",
4695
4816
  "react-use": "^17.3.2",
4696
4817
  "react-use-size": "^2.0.4",
4697
4818
  remix: "^1.2.3",
@@ -4889,15 +5010,16 @@ function getCommandLine() {
4889
5010
  case "darwin":
4890
5011
  return "open";
4891
5012
  case "win32":
4892
- return "start";
5013
+ return 'start ""';
4893
5014
  default:
4894
5015
  return "xdg-open";
4895
5016
  }
4896
5017
  }
4897
5018
  function openFile(path) {
4898
5019
  var _a;
4899
- path = path.split("/").slice(1).join("/") ?? path.split("\\").slice(1).join("\\");
4900
- (_a = (0, import_child_process2.exec)(`${getCommandLine()} ${(0, import_path6.resolve)(repoDir, path)}`).stderr) == null ? void 0 : _a.on("data", (e) => {
5020
+ path = (0, import_path6.resolve)(repoDir, "..", path.split("/").join(import_path6.sep));
5021
+ const command = `${getCommandLine()} "${path}"`;
5022
+ (_a = (0, import_child_process2.exec)(command).stderr) == null ? void 0 : _a.on("data", (e) => {
4901
5023
  log.error(`Cannot open file ${(0, import_path6.resolve)(repoDir, path)}: ${e}`);
4902
5024
  });
4903
5025
  }
@@ -4961,12 +5083,12 @@ ${reasons.map((r) => ` - ${r}`).join("\n")}`);
4961
5083
  if (repoTreeError)
4962
5084
  throw repoTreeError;
4963
5085
  const [hydratedRepoTree, hydratedRepoTreeError] = await describeAsyncJob(() => hydrateData(repoDir, repoTree), "Hydrating commit tree", "Commit tree hydrated", "Error hydrating commit tree");
5086
+ if (hydratedRepoTreeError)
5087
+ throw hydratedRepoTreeError;
4964
5088
  await git.resetGitSetting("core.quotepath", quotePathDefaultValue);
4965
5089
  await git.resetGitSetting("diff.renames", renamesDefaultValue);
4966
5090
  await git.resetGitSetting("diff.renameLimit", renameLimitDefaultValue);
4967
5091
  const defaultOutPath = GitCaller.getCachePath(repoName, branchName);
4968
- if (hydratedRepoTreeError)
4969
- throw hydratedRepoTreeError;
4970
5092
  let outPath = (0, import_path6.resolve)(args2.out ?? defaultOutPath);
4971
5093
  if (!(0, import_path6.isAbsolute)(outPath))
4972
5094
  outPath = (0, import_path6.resolve)(process.cwd(), outPath);
@@ -4977,6 +5099,7 @@ ${reasons.map((r) => ` - ${r}`).join("\n")}`);
4977
5099
  }
4978
5100
  const authorUnions = args2.unionedAuthors;
4979
5101
  data = {
5102
+ refs: GitCaller.parseRefs(await GitCaller.getInstance().getRefs()),
4980
5103
  cached: false,
4981
5104
  hiddenFiles,
4982
5105
  authors: getAuthorSet(),
@@ -5007,15 +5130,64 @@ Done in ${formatMs(stop - start)}`);
5007
5130
 
5008
5131
  // src/components/GlobalInfo.tsx
5009
5132
  init_react();
5010
- var import_react_fontawesome2 = require("@fortawesome/react-fontawesome");
5011
- var import_free_solid_svg_icons = require("@fortawesome/free-solid-svg-icons");
5133
+ var import_react_fontawesome3 = require("@fortawesome/react-fontawesome");
5134
+ var import_free_solid_svg_icons2 = require("@fortawesome/free-solid-svg-icons");
5012
5135
  var import_remix3 = __toESM(require_remix());
5013
5136
  var import_styled_components8 = __toESM(require_styled_components_cjs());
5137
+ var import_react12 = require("react");
5138
+
5139
+ // src/components/BranchSelect.tsx
5140
+ init_react();
5141
+ var import_react_fontawesome2 = require("@fortawesome/react-fontawesome");
5142
+ var import_free_solid_svg_icons = require("@fortawesome/free-solid-svg-icons");
5143
+ function BranchSelect(_a) {
5144
+ var _b = _a, { heads } = _b, props = __objRest(_b, ["heads"]);
5145
+ const headsEntries = Object.entries(heads);
5146
+ return /* @__PURE__ */ React.createElement(SelectWithIconWrapper, null, /* @__PURE__ */ React.createElement(import_react_fontawesome2.FontAwesomeIcon, {
5147
+ icon: import_free_solid_svg_icons.faCodeBranch,
5148
+ color: "#333"
5149
+ }), headsEntries.length === 1 ? /* @__PURE__ */ React.createElement(SelectPlaceholder, null, headsEntries[0][0]) : /* @__PURE__ */ React.createElement(SelectWithEllipsis, __spreadValues({}, props), headsEntries.map(([branchName, hash]) => {
5150
+ return /* @__PURE__ */ React.createElement(OptionWithEllipsis, {
5151
+ key: hash,
5152
+ value: branchName
5153
+ }, branchName);
5154
+ })));
5155
+ }
5156
+ function GroupedBranchSelect(_a) {
5157
+ var _b = _a, { headGroups, iconColor } = _b, props = __objRest(_b, ["headGroups", "iconColor"]);
5158
+ const headGroupsEntries = Object.entries(headGroups).map(([group, heads]) => [group, Object.entries(heads)]);
5159
+ const allEntriesFlattened = headGroupsEntries.reduce((acc, [group, heads]) => {
5160
+ return acc.concat(heads.map(([branchName]) => branchName));
5161
+ }, []);
5162
+ return /* @__PURE__ */ React.createElement(SelectWithIconWrapper, null, /* @__PURE__ */ React.createElement(import_react_fontawesome2.FontAwesomeIcon, {
5163
+ icon: import_free_solid_svg_icons.faCodeBranch,
5164
+ color: iconColor ?? "#333"
5165
+ }), allEntriesFlattened.length === 1 ? /* @__PURE__ */ React.createElement(SelectPlaceholder, null, allEntriesFlattened[0]) : /* @__PURE__ */ React.createElement(SelectWithEllipsis, __spreadValues({}, props), headGroupsEntries.map(([group, heads]) => heads.length > 0 ? /* @__PURE__ */ React.createElement("optgroup", {
5166
+ key: group,
5167
+ label: group
5168
+ }, heads.map(([branchName]) => /* @__PURE__ */ React.createElement(OptionWithEllipsis, {
5169
+ key: branchName,
5170
+ value: branchName
5171
+ }, branchName))) : null)));
5172
+ }
5173
+
5174
+ // src/components/GlobalInfo.tsx
5014
5175
  function GlobalInfo() {
5015
- var _a;
5016
5176
  const data = useData();
5017
5177
  const { path } = usePath();
5018
5178
  const transitionState = (0, import_remix3.useTransition)();
5179
+ const location = (0, import_remix3.useLocation)();
5180
+ const navigate = (0, import_remix3.useNavigate)();
5181
+ const [isAnalyzing, setIsAnalyzing] = (0, import_react12.useState)(false);
5182
+ const switchBranch = (branch) => {
5183
+ setIsAnalyzing(true);
5184
+ navigate(["", data.repo, branch].join("/"));
5185
+ };
5186
+ (0, import_react12.useEffect)(() => {
5187
+ if (transitionState.state === "idle") {
5188
+ setIsAnalyzing(false);
5189
+ }
5190
+ }, [transitionState.state]);
5019
5191
  let temppath = path;
5020
5192
  let paths = [];
5021
5193
  for (let i = 0; i < 3; i++) {
@@ -5031,31 +5203,48 @@ function GlobalInfo() {
5031
5203
  paths.push(["...", ""]);
5032
5204
  paths.push([data.repo, data.repo]);
5033
5205
  }
5034
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(StyledLink, {
5206
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(SelectWithIconWrapper, null, /* @__PURE__ */ React.createElement(StyledLink, {
5035
5207
  to: "..",
5036
- title: "See all projects"
5037
- }, /* @__PURE__ */ React.createElement(import_react_fontawesome2.FontAwesomeIcon, {
5038
- icon: import_free_solid_svg_icons.faLeftLong,
5039
- color: "#333"
5040
- })), /* @__PURE__ */ React.createElement(BoxTitle, null, data.repo), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("strong", null, "Branch: "), /* @__PURE__ */ React.createElement("span", null, data.branch), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("strong", null, "Analyzed: "), /* @__PURE__ */ React.createElement("span", null, dateTimeFormatShort(data.lastRunEpoch)), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("strong", null, "As of commit: "), /* @__PURE__ */ React.createElement(Code, {
5208
+ title: "See all repositories"
5209
+ }, /* @__PURE__ */ React.createElement(import_react_fontawesome3.FontAwesomeIcon, {
5210
+ icon: import_free_solid_svg_icons2.faFolder
5211
+ }), /* @__PURE__ */ React.createElement(StyledP2, null, "See more repositories"))), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(BoxTitle, null, data.repo), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(BranchSelect, {
5212
+ heads: data.refs.heads,
5213
+ defaultValue: data.branch,
5214
+ onChange: (e) => switchBranch(e.target.value),
5215
+ disabled: transitionState.state !== "idle"
5216
+ }), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("strong", null, "Analyzed: "), /* @__PURE__ */ React.createElement("span", null, dateTimeFormatShort(data.lastRunEpoch)), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("strong", null, "As of commit: "), /* @__PURE__ */ React.createElement(Code, {
5041
5217
  inline: true,
5042
5218
  title: data.commit.message ?? "No commit message"
5043
- }, data.commit.hash.slice(0, 7))), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(import_remix3.Form, {
5219
+ }, data.commit.hash.slice(0, 7)), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(import_remix3.Form, {
5044
5220
  method: "post",
5045
- action: "."
5221
+ action: location.pathname,
5222
+ onSubmit: () => {
5223
+ setIsAnalyzing(true);
5224
+ }
5046
5225
  }, /* @__PURE__ */ React.createElement("input", {
5047
5226
  type: "hidden",
5048
5227
  name: "refresh",
5049
5228
  value: "true"
5050
5229
  }), /* @__PURE__ */ React.createElement(TextButton, {
5051
5230
  disabled: transitionState.state !== "idle"
5052
- }, /* @__PURE__ */ React.createElement(import_react_fontawesome2.FontAwesomeIcon, {
5053
- icon: import_free_solid_svg_icons.faRotate
5054
- }), " ", !((_a = transitionState.submission) == null ? void 0 : _a.formData.has("refresh")) ? "Rerun analyzer" : "Analyzing...")));
5231
+ }, /* @__PURE__ */ React.createElement(import_react_fontawesome3.FontAwesomeIcon, {
5232
+ icon: import_free_solid_svg_icons2.faRotate
5233
+ }), " ", isAnalyzing ? "Analyzing..." : "Rerun analyzer")));
5055
5234
  }
5235
+ var StyledP2 = import_styled_components8.default.p`
5236
+ margin-left: var(--unit);
5237
+ `;
5056
5238
  var StyledLink = (0, import_styled_components8.default)(import_remix3.Link)`
5057
5239
  display: inline-flex;
5058
5240
  margin-right: var(--unit);
5241
+ text-decoration: none;
5242
+ color: hsl(0, 0%, 60%);
5243
+ font-size: 0.9em;
5244
+ align-items: center;
5245
+ &:hover {
5246
+ color: hsl(0, 0%, 20%);
5247
+ }
5059
5248
  `;
5060
5249
 
5061
5250
  // src/components/Options.tsx
@@ -5066,14 +5255,17 @@ init_react();
5066
5255
  var import_utils = __toESM(require_main());
5067
5256
  function EnumSelect(props) {
5068
5257
  const id = (0, import_utils.useId)();
5069
- return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Label, {
5258
+ const enumEntries = Object.entries(props.enum);
5259
+ return /* @__PURE__ */ React.createElement("div", {
5260
+ style: props.hidden ? { display: "none" } : {}
5261
+ }, /* @__PURE__ */ React.createElement(Label, {
5070
5262
  htmlFor: id
5071
5263
  }, props.label), /* @__PURE__ */ React.createElement(Spacer, {
5072
5264
  xs: true
5073
5265
  }), /* @__PURE__ */ React.createElement(Select, {
5074
5266
  id,
5075
5267
  onChange: (event) => props.onChange(event.target.value)
5076
- }, Object.entries(props.enum).map(([key, value]) => /* @__PURE__ */ React.createElement("option", {
5268
+ }, enumEntries.map(([key, value]) => /* @__PURE__ */ React.createElement("option", {
5077
5269
  key: value,
5078
5270
  value: key
5079
5271
  }, value))));
@@ -5084,8 +5276,16 @@ var import_styled_components9 = __toESM(require_styled_components_cjs());
5084
5276
  var Checkbox = import_styled_components9.default.input`
5085
5277
  margin: var(--unit);
5086
5278
  `;
5279
+ function isMetricWithHistoricalOption(metric) {
5280
+ switch (metric) {
5281
+ case "SINGLE_AUTHOR":
5282
+ case "TOP_CONTRIBUTOR":
5283
+ return true;
5284
+ }
5285
+ return false;
5286
+ }
5087
5287
  function Options2() {
5088
- const { animationsEnabled, setMetricType, setChartType, setAnimationsEnabled, setAuthorshipType } = useOptions();
5288
+ const { animationsEnabled, setAnimationsEnabled, metricType, setMetricType, setChartType, setAuthorshipType } = useOptions();
5089
5289
  return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(EnumSelect, {
5090
5290
  label: "Chart type",
5091
5291
  enum: Chart,
@@ -5095,9 +5295,10 @@ function Options2() {
5095
5295
  enum: Metric,
5096
5296
  onChange: (metric) => setMetricType(metric)
5097
5297
  }), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(EnumSelect, {
5098
- label: "Authorship Data",
5298
+ label: "Authorship data",
5099
5299
  enum: Authorship,
5100
- onChange: (baseData) => setAuthorshipType(baseData)
5300
+ onChange: (baseData) => setAuthorshipType(baseData),
5301
+ hidden: !isMetricWithHistoricalOption(metricType)
5101
5302
  }), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement(Checkbox, {
5102
5303
  type: "checkbox",
5103
5304
  checked: animationsEnabled,
@@ -5108,10 +5309,10 @@ function Options2() {
5108
5309
  // src/components/SearchBar.tsx
5109
5310
  init_react();
5110
5311
  var import_styled_components10 = __toESM(require_styled_components_cjs());
5111
- var import_react12 = require("react");
5312
+ var import_react13 = require("react");
5112
5313
  var import_react_use2 = require("react-use");
5113
5314
  var import_utils2 = __toESM(require_main());
5114
- var import_free_solid_svg_icons2 = require("@fortawesome/free-solid-svg-icons");
5315
+ var import_free_solid_svg_icons3 = require("@fortawesome/free-solid-svg-icons");
5115
5316
  var StyledBox = (0, import_styled_components10.default)(Box)`
5116
5317
  display: flex;
5117
5318
  flex-direction: column;
@@ -5134,9 +5335,9 @@ function findSearchResults(tree, searchString) {
5134
5335
  return searchResults;
5135
5336
  }
5136
5337
  function SearchBar() {
5137
- const searchFieldRef = (0, import_react12.useRef)(null);
5338
+ const searchFieldRef = (0, import_react13.useRef)(null);
5138
5339
  const { searchText, setSearchText, searchResults, setSearchResults } = useSearch();
5139
- const [value, setValue] = (0, import_react12.useState)("");
5340
+ const [value, setValue] = (0, import_react13.useState)("");
5140
5341
  const id = (0, import_utils2.useId)();
5141
5342
  const data = useData();
5142
5343
  const { setPath } = usePath();
@@ -5146,8 +5347,8 @@ function SearchBar() {
5146
5347
  if (object.type === "tree") {
5147
5348
  setPath(object.path);
5148
5349
  } else {
5149
- const sep2 = getSeparator(object.path);
5150
- setPath(allExceptLast(object.path.split(sep2)).join(sep2));
5350
+ const sep3 = getSeparator(object.path);
5351
+ setPath(allExceptLast(object.path.split(sep3)).join(sep3));
5151
5352
  }
5152
5353
  }
5153
5354
  function debounceUpdate() {
@@ -5155,7 +5356,7 @@ function SearchBar() {
5155
5356
  setSearchResults(findSearchResults(data.commit.tree, value));
5156
5357
  }
5157
5358
  (0, import_react_use2.useDebounce)(() => debounceUpdate(), 200, [value]);
5158
- (0, import_react12.useEffect)(() => {
5359
+ (0, import_react13.useEffect)(() => {
5159
5360
  const searchOverride = (event) => {
5160
5361
  var _a;
5161
5362
  if (event.ctrlKey && event.key === "f") {
@@ -5182,14 +5383,14 @@ function SearchBar() {
5182
5383
  setValue(event.target.value);
5183
5384
  }
5184
5385
  }), searchText.length > 0 ? /* @__PURE__ */ React.createElement(StyledP, null, searchResults.length, " results") : null, searchResults.map((result) => {
5185
- return /* @__PURE__ */ React.createElement(import_react12.Fragment, {
5386
+ return /* @__PURE__ */ React.createElement(import_react13.Fragment, {
5186
5387
  key: result.path
5187
5388
  }, /* @__PURE__ */ React.createElement(SearchResultButton, {
5188
5389
  title: result.path,
5189
5390
  value: result.path,
5190
5391
  onClick: () => onClick(result)
5191
5392
  }, /* @__PURE__ */ React.createElement(SearchResultSpan, null, /* @__PURE__ */ React.createElement(LightFontAwesomeIcon, {
5192
- icon: result.type === "tree" ? import_free_solid_svg_icons2.faFolderOpen : import_free_solid_svg_icons2.faFile
5393
+ icon: result.type === "tree" ? import_free_solid_svg_icons3.faFolderOpen : import_free_solid_svg_icons3.faFile
5193
5394
  }), " ", result.name)), /* @__PURE__ */ React.createElement(Spacer, {
5194
5395
  xs: true
5195
5396
  }));
@@ -5198,7 +5399,7 @@ function SearchBar() {
5198
5399
 
5199
5400
  // src/components/Legend.tsx
5200
5401
  init_react();
5201
- var import_react13 = require("react");
5402
+ var import_react14 = require("react");
5202
5403
  var import_styled_components13 = __toESM(require_styled_components_cjs());
5203
5404
 
5204
5405
  // src/components/LegendFragment.tsx
@@ -5248,8 +5449,8 @@ function LegendOther(props) {
5248
5449
 
5249
5450
  // src/components/Toggle.tsx
5250
5451
  init_react();
5251
- var import_react_fontawesome3 = require("@fortawesome/react-fontawesome");
5252
- var import_free_solid_svg_icons3 = require("@fortawesome/free-solid-svg-icons");
5452
+ var import_react_fontawesome4 = require("@fortawesome/react-fontawesome");
5453
+ var import_free_solid_svg_icons4 = require("@fortawesome/free-solid-svg-icons");
5253
5454
  var import_styled_components12 = __toESM(require_styled_components_cjs());
5254
5455
  function ExpandDown({ relative = false, collapse, toggle }) {
5255
5456
  return /* @__PURE__ */ React.createElement(ToggleButton, {
@@ -5257,8 +5458,8 @@ function ExpandDown({ relative = false, collapse, toggle }) {
5257
5458
  collapse,
5258
5459
  onClick: toggle,
5259
5460
  up: false
5260
- }, /* @__PURE__ */ React.createElement(import_react_fontawesome3.FontAwesomeIcon, {
5261
- icon: import_free_solid_svg_icons3.faAngleUp
5461
+ }, /* @__PURE__ */ React.createElement(import_react_fontawesome4.FontAwesomeIcon, {
5462
+ icon: import_free_solid_svg_icons4.faAngleUp
5262
5463
  }));
5263
5464
  }
5264
5465
  function ExpandUp({ relative = false, collapse, toggle }) {
@@ -5267,8 +5468,8 @@ function ExpandUp({ relative = false, collapse, toggle }) {
5267
5468
  collapse,
5268
5469
  onClick: toggle,
5269
5470
  up: true
5270
- }, /* @__PURE__ */ React.createElement(import_react_fontawesome3.FontAwesomeIcon, {
5271
- icon: import_free_solid_svg_icons3.faAngleUp
5471
+ }, /* @__PURE__ */ React.createElement(import_react_fontawesome4.FontAwesomeIcon, {
5472
+ icon: import_free_solid_svg_icons4.faAngleUp
5272
5473
  }));
5273
5474
  }
5274
5475
  var ToggleButton = import_styled_components12.default.button`
@@ -5355,7 +5556,7 @@ function GradientMetricLegend({ metricCache }) {
5355
5556
  }, "\u25B2"));
5356
5557
  }
5357
5558
  function PointMetricLegend({ metricCache }) {
5358
- const [collapse, setCollapse] = (0, import_react13.useState)(true);
5559
+ const [collapse, setCollapse] = (0, import_react14.useState)(true);
5359
5560
  const items = Array.from(metricCache.legend).sort(([, info1], [, info2]) => {
5360
5561
  if (info1.weight < info2.weight)
5361
5562
  return 1;
@@ -5393,7 +5594,7 @@ init_react();
5393
5594
 
5394
5595
  // node_modules/yargs-parser/build/lib/index.js
5395
5596
  init_react();
5396
- var import_util17 = require("util");
5597
+ var import_util18 = require("util");
5397
5598
  var import_path7 = require("path");
5398
5599
 
5399
5600
  // node_modules/yargs-parser/build/lib/string-utils.js
@@ -6352,7 +6553,7 @@ var parser = new YargsParser({
6352
6553
  env: () => {
6353
6554
  return env;
6354
6555
  },
6355
- format: import_util17.format,
6556
+ format: import_util18.format,
6356
6557
  normalize: import_path7.normalize,
6357
6558
  resolve: import_path7.resolve,
6358
6559
  require: (path) => {
@@ -6413,8 +6614,8 @@ async function getTruckConfigWithArgs(repo) {
6413
6614
 
6414
6615
  // src/components/HiddenFiles.tsx
6415
6616
  init_react();
6416
- var import_react_fontawesome4 = require("@fortawesome/react-fontawesome");
6417
- var import_free_solid_svg_icons4 = require("@fortawesome/free-solid-svg-icons");
6617
+ var import_react_fontawesome5 = require("@fortawesome/react-fontawesome");
6618
+ var import_free_solid_svg_icons5 = require("@fortawesome/free-solid-svg-icons");
6418
6619
  var import_react_use3 = require("react-use");
6419
6620
  var import_styled_components14 = __toESM(require_styled_components_cjs());
6420
6621
  var import_remix4 = __toESM(require_remix());
@@ -6450,7 +6651,7 @@ var StyledButton = import_styled_components14.default.button`
6450
6651
  display: none;
6451
6652
  }
6452
6653
  `;
6453
- var StyledFontAwesomeIcon = (0, import_styled_components14.default)(import_react_fontawesome4.FontAwesomeIcon)`
6654
+ var StyledFontAwesomeIcon = (0, import_styled_components14.default)(import_react_fontawesome5.FontAwesomeIcon)`
6454
6655
  position: absolute;
6455
6656
  top: 50%;
6456
6657
  left: 50%;
@@ -6463,6 +6664,7 @@ function hiddenFileFormat(ignored) {
6463
6664
  return split[split.length - 1];
6464
6665
  }
6465
6666
  function HiddenFiles() {
6667
+ const location = (0, import_remix4.useLocation)();
6466
6668
  const [collapse, setCollapse] = (0, import_react_use3.useBoolean)(false);
6467
6669
  const transitionState = (0, import_remix4.useTransition)();
6468
6670
  const data = useData();
@@ -6474,7 +6676,7 @@ function HiddenFiles() {
6474
6676
  title: hidden
6475
6677
  }, /* @__PURE__ */ React.createElement(InlineForm, {
6476
6678
  method: "post",
6477
- action: "."
6679
+ action: location.pathname
6478
6680
  }, /* @__PURE__ */ React.createElement("input", {
6479
6681
  type: "hidden",
6480
6682
  name: "unignore",
@@ -6484,32 +6686,32 @@ function HiddenFiles() {
6484
6686
  disabled: transitionState.state !== "idle"
6485
6687
  }, /* @__PURE__ */ React.createElement(StyledFontAwesomeIcon, {
6486
6688
  id: "eyeslash",
6487
- icon: import_free_solid_svg_icons4.faEyeSlash
6689
+ icon: import_free_solid_svg_icons5.faEyeSlash
6488
6690
  }), /* @__PURE__ */ React.createElement(StyledFontAwesomeIcon, {
6489
6691
  id: "eye",
6490
- icon: import_free_solid_svg_icons4.faEye
6692
+ icon: import_free_solid_svg_icons5.faEye
6491
6693
  }))), /* @__PURE__ */ React.createElement(Code, null, hiddenFileFormat(hidden))))) : null);
6492
6694
  }
6493
6695
 
6494
- // route:C:\Users\jonas\p\git-truck\src\routes\$repo.tsx
6696
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\routes\$repo.$.tsx
6495
6697
  var import_semver_compare = __toESM(require("semver-compare"));
6496
6698
 
6497
6699
  // src/components/Details.tsx
6498
6700
  init_react();
6499
- var import_react15 = require("react");
6701
+ var import_react16 = require("react");
6500
6702
  var import_remix5 = __toESM(require_remix());
6501
6703
  var import_styled_components16 = __toESM(require_styled_components_cjs());
6502
6704
 
6503
6705
  // src/components/AuthorDistFragment.tsx
6504
6706
  init_react();
6505
- var import_react14 = require("react");
6707
+ var import_react15 = require("react");
6506
6708
  function AuthorDistFragment(props) {
6507
6709
  if (!props.show)
6508
6710
  return null;
6509
6711
  return /* @__PURE__ */ React.createElement(React.Fragment, null, props.items.map((legendItem) => {
6510
6712
  const [author, contrib] = legendItem;
6511
6713
  const roundedContrib = Math.round(contrib * 100);
6512
- return /* @__PURE__ */ React.createElement(import_react14.Fragment, {
6714
+ return /* @__PURE__ */ React.createElement(import_react15.Fragment, {
6513
6715
  key: author + contrib
6514
6716
  }, /* @__PURE__ */ React.createElement(DetailsKey, {
6515
6717
  title: author,
@@ -6551,19 +6753,19 @@ function OneFolderOut(path) {
6551
6753
  function Details() {
6552
6754
  var _a;
6553
6755
  const { setClickedObject, clickedObject } = useClickedObject();
6756
+ const location = (0, import_remix5.useLocation)();
6554
6757
  const { authorshipType } = useOptions();
6555
6758
  const { state } = (0, import_remix5.useTransition)();
6556
6759
  const { setPath, path } = usePath();
6557
6760
  const data = useData();
6558
- const isProcessingHideRef = (0, import_react15.useRef)(false);
6559
- const navigate = (0, import_remix5.useNavigate)();
6560
- (0, import_react15.useEffect)(() => {
6761
+ const isProcessingHideRef = (0, import_react16.useRef)(false);
6762
+ (0, import_react16.useEffect)(() => {
6561
6763
  if (isProcessingHideRef.current) {
6562
6764
  setClickedObject(null);
6563
6765
  isProcessingHideRef.current = false;
6564
6766
  }
6565
6767
  }, [clickedObject, setClickedObject, state]);
6566
- (0, import_react15.useEffect)(() => {
6768
+ (0, import_react16.useEffect)(() => {
6567
6769
  setClickedObject((clickedObject2) => findObjectInTree(data.commit.tree, clickedObject2));
6568
6770
  }, [data, setClickedObject]);
6569
6771
  if (!clickedObject)
@@ -6597,7 +6799,7 @@ function Details() {
6597
6799
  lg: true
6598
6800
  }), isBlob2 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(import_remix5.Form, {
6599
6801
  method: "post",
6600
- action: "."
6802
+ action: location.pathname
6601
6803
  }, /* @__PURE__ */ React.createElement("input", {
6602
6804
  type: "hidden",
6603
6805
  name: "ignore",
@@ -6610,7 +6812,7 @@ function Details() {
6610
6812
  }
6611
6813
  }, "Hide this file")), clickedObject.name.includes(".") ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(import_remix5.Form, {
6612
6814
  method: "post",
6613
- action: "."
6815
+ action: location.pathname
6614
6816
  }, /* @__PURE__ */ React.createElement("input", {
6615
6817
  type: "hidden",
6616
6818
  name: "ignore",
@@ -6625,7 +6827,7 @@ function Details() {
6625
6827
  inline: true
6626
6828
  }, ".", extension), " files"))) : null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(import_remix5.Form, {
6627
6829
  method: "post",
6628
- action: "."
6830
+ action: location.pathname
6629
6831
  }, /* @__PURE__ */ React.createElement("input", {
6630
6832
  type: "hidden",
6631
6833
  name: "open",
@@ -6634,7 +6836,7 @@ function Details() {
6634
6836
  disabled: state !== "idle"
6635
6837
  }, "Open file"))) : /* @__PURE__ */ React.createElement(import_remix5.Form, {
6636
6838
  method: "post",
6637
- action: "."
6839
+ action: location.pathname
6638
6840
  }, /* @__PURE__ */ React.createElement("input", {
6639
6841
  type: "hidden",
6640
6842
  name: "ignore",
@@ -6708,7 +6910,7 @@ var AuthorDistHeader = import_styled_components16.default.div`
6708
6910
  `;
6709
6911
  var authorCutoff = 2;
6710
6912
  function AuthorDistribution(props) {
6711
- const [collapse, setCollapse] = (0, import_react15.useState)(true);
6913
+ const [collapse, setCollapse] = (0, import_react16.useState)(true);
6712
6914
  const contribDist = Object.entries(makePercentResponsibilityDistribution(props.authors)).sort((a, b) => a[1] < b[1] ? 1 : -1);
6713
6915
  if (contribDist.length === 0)
6714
6916
  return null;
@@ -6776,8 +6978,9 @@ function hasZeroContributions(authors2) {
6776
6978
  return true;
6777
6979
  }
6778
6980
 
6779
- // route:C:\Users\jonas\p\git-truck\src\routes\$repo.tsx
6981
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\routes\$repo.$.tsx
6780
6982
  var import_path9 = require("path");
6983
+ var import_free_solid_svg_icons6 = require("@fortawesome/free-solid-svg-icons");
6781
6984
  var invalidateCache = false;
6782
6985
  var loader = async ({ params, request }) => {
6783
6986
  if (params["repo"] === "favicon.ico") {
@@ -6790,9 +6993,9 @@ var loader = async ({ params, request }) => {
6790
6993
  if (params["repo"]) {
6791
6994
  options.path = (0, import_path9.resolve)(args2.path, params["repo"]);
6792
6995
  }
6793
- const branch = new URL(request.url).searchParams.get("branch");
6794
- if (branch)
6795
- options.branch = decodeURIComponent(branch);
6996
+ if (params["*"]) {
6997
+ options.branch = params["*"];
6998
+ }
6796
6999
  const data = await analyze(__spreadValues(__spreadValues({}, args2), options));
6797
7000
  invalidateCache = false;
6798
7001
  return (0, import_remix6.json)(data);
@@ -6855,10 +7058,12 @@ function Index() {
6855
7058
  data
6856
7059
  }, /* @__PURE__ */ React.createElement(Container, null, /* @__PURE__ */ React.createElement(SidePanel, null, /* @__PURE__ */ React.createElement(GlobalInfo, null), /* @__PURE__ */ React.createElement(Options2, null), /* @__PURE__ */ React.createElement(SearchBar, null), /* @__PURE__ */ React.createElement(Spacer, null)), typeof document !== "undefined" ? /* @__PURE__ */ React.createElement(Main, null) : /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement(SidePanel, null, data.latestVersion && (0, import_semver_compare.default)(data.latestVersion, data.currentVersion) === 1 ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement("p", null, "Update available: ", data.latestVersion), /* @__PURE__ */ React.createElement(StyledP, null, "Currently installed: ", data.currentVersion), /* @__PURE__ */ React.createElement(StyledP, null, "To update, close application and run: ", /* @__PURE__ */ React.createElement(Code, {
6857
7060
  inline: true
6858
- }, "npx git-truck@latest"))) : null, /* @__PURE__ */ React.createElement(Grower, null), /* @__PURE__ */ React.createElement(Details, null), data.hiddenFiles.length > 0 ? /* @__PURE__ */ React.createElement(HiddenFiles, null) : null, /* @__PURE__ */ React.createElement(Legend, null))));
7061
+ }, "npx git-truck@latest"))) : null, data.hasUnstagedChanges ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement(LightFontAwesomeIcon, {
7062
+ icon: import_free_solid_svg_icons6.faTriangleExclamation
7063
+ }), "You have unstaged changes"), /* @__PURE__ */ React.createElement(StyledP, null, "This means that some data might be incorrect. Please stash or commit changes and rerun analyzer.")) : null, /* @__PURE__ */ React.createElement(Grower, null), /* @__PURE__ */ React.createElement(Details, null), data.hiddenFiles.length > 0 ? /* @__PURE__ */ React.createElement(HiddenFiles, null) : null, /* @__PURE__ */ React.createElement(Legend, null))));
6859
7064
  }
6860
7065
 
6861
- // route:C:\Users\jonas\p\git-truck\src\routes\index.tsx
7066
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\routes\index.tsx
6862
7067
  var routes_exports = {};
6863
7068
  __export(routes_exports, {
6864
7069
  action: () => action2,
@@ -6918,16 +7123,34 @@ function AnalyzingIndicator() {
6918
7123
  }))));
6919
7124
  }
6920
7125
 
6921
- // route:C:\Users\jonas\p\git-truck\src\routes\index.tsx
7126
+ // route:C:\Users\Tjoms\Documents\GitHub\git-visual\src\routes\index.tsx
6922
7127
  var import_path10 = require("path");
6923
7128
  var import_react_use4 = require("react-use");
7129
+ var import_react17 = require("react");
6924
7130
  var hasRedirected = false;
6925
7131
  var loader2 = async () => {
6926
7132
  const args2 = await getArgsWithDefaults();
6927
7133
  const [repo, repositories] = await GitCaller.scanDirectoryForRepositories(args2.path);
7134
+ const repositoriesWithGroupedBranches = repositories.map((repo2) => {
7135
+ const analyzedBranchNames = Object.entries(repo2.analyzedBranches).map(([branchName]) => {
7136
+ return branchName;
7137
+ });
7138
+ const groups = {
7139
+ Analyzed: repo2.analyzedBranches,
7140
+ "Not analyzed": Object.entries(repo2.refs.heads).reduce((acc, [branchName, branch]) => {
7141
+ if (!analyzedBranchNames.includes(branchName)) {
7142
+ acc[branchName] = branch;
7143
+ }
7144
+ return acc;
7145
+ }, {})
7146
+ };
7147
+ return __spreadProps(__spreadValues({}, repo2), {
7148
+ groups
7149
+ });
7150
+ });
6928
7151
  const baseDir = (0, import_path10.resolve)(repo ? getBaseDirFromPath(args2.path) : args2.path);
6929
7152
  const repositoriesResponse = (0, import_remix7.json)({
6930
- repositories,
7153
+ repositories: repositoriesWithGroupedBranches,
6931
7154
  baseDir,
6932
7155
  baseDirName: getDirName(baseDir),
6933
7156
  repo,
@@ -6945,8 +7168,8 @@ var action2 = async ({ request }) => {
6945
7168
  return null;
6946
7169
  };
6947
7170
  function Index2() {
6948
- const loadterData = (0, import_remix7.useLoaderData)();
6949
- const { repositories, baseDir, baseDirName, repo, hasRedirected: hasRedirected2 } = loadterData;
7171
+ const loaderData = (0, import_remix7.useLoaderData)();
7172
+ const { repositories, baseDir, baseDirName, repo, hasRedirected: hasRedirected2 } = loaderData;
6950
7173
  const transitionData = (0, import_remix7.useTransition)();
6951
7174
  const navigate = (0, import_remix7.useNavigate)();
6952
7175
  const submit = (0, import_remix7.useSubmit)();
@@ -6956,83 +7179,102 @@ function Index2() {
6956
7179
  const data = new FormData();
6957
7180
  data.append("hasRedirected", "true");
6958
7181
  submit(data, { method: "post" });
6959
- navigate(`/${repo.name}`);
7182
+ navigate(`/${getPathFromRepoAndBranch(repo.name, repo.currentHead)}`);
6960
7183
  }
6961
7184
  });
6962
- const cachedRepositories = repositories.filter((repo2) => {
6963
- var _a;
6964
- return (_a = repo2.data) == null ? void 0 : _a.cached;
6965
- });
6966
- const notCachedRepositories = repositories.filter((repo2) => {
6967
- var _a;
6968
- return !((_a = repo2.data) == null ? void 0 : _a.cached);
6969
- });
6970
7185
  if (transitionData.state !== "idle" || willRedirect)
6971
7186
  return /* @__PURE__ */ React.createElement(AnalyzingIndicator, null);
6972
- return /* @__PURE__ */ React.createElement(Wrapper, null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(H1, null, baseDir), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("p", null, "Found ", repositories.length, " git repositories in the folder ", /* @__PURE__ */ React.createElement(Code, {
6973
- inline: true
6974
- }, baseDirName), "."), repositories.length === 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("p", null, "Try running ", /* @__PURE__ */ React.createElement(Code, {
7187
+ return /* @__PURE__ */ React.createElement(Wrapper, null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(H1, null, "Welcome to Git Truck!"), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("p", null, "Found ", repositories.length, " git repositories in the folder ", /* @__PURE__ */ React.createElement(Code, {
7188
+ inline: true,
7189
+ title: baseDir
7190
+ }, baseDirName), "."), /* @__PURE__ */ React.createElement(Spacer, null), repositories.length === 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement("p", null, "Try running ", /* @__PURE__ */ React.createElement(Code, {
6975
7191
  inline: true
6976
- }, "git-truck"), " in another folder or provide another path as argument.")) : null, cachedRepositories.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, {
7192
+ }, "git-truck"), " in another folder or provide another path as argument.")) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, {
6977
7193
  xxl: true
6978
- }), /* @__PURE__ */ React.createElement("h2", null, "Ready to view"), /* @__PURE__ */ React.createElement("nav", null, /* @__PURE__ */ React.createElement(Ul, null, cachedRepositories.map((repo2) => {
6979
- var _a;
6980
- return /* @__PURE__ */ React.createElement(Li, {
6981
- key: repo2.name
6982
- }, /* @__PURE__ */ React.createElement(SLink, {
6983
- to: repo2.name,
6984
- tabIndex: -1
6985
- }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(BoxSubTitle, {
6986
- title: repo2.name
6987
- }, repo2.name), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(Actions, null, /* @__PURE__ */ React.createElement(Grower, null), /* @__PURE__ */ React.createElement(TextButton, null, ((_a = repo2.data) == null ? void 0 : _a.cached) ? "View" : "Analyze")))));
6988
- })))) : null, notCachedRepositories.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Spacer, {
6989
- xxl: true
6990
- }), /* @__PURE__ */ React.createElement("h2", null, "Needs to be analyzed before viewing"), /* @__PURE__ */ React.createElement("nav", null, /* @__PURE__ */ React.createElement(Ul, null, notCachedRepositories.map((repo2) => {
6991
- var _a;
6992
- return /* @__PURE__ */ React.createElement(Li, {
6993
- key: repo2.name
6994
- }, /* @__PURE__ */ React.createElement(SLink, {
6995
- to: repo2.name,
6996
- tabIndex: -1
6997
- }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(BoxSubTitle, {
6998
- title: repo2.name
6999
- }, repo2.name), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(Actions, null, /* @__PURE__ */ React.createElement(Grower, null), /* @__PURE__ */ React.createElement(TextButton, null, ((_a = repo2.data) == null ? void 0 : _a.cached) ? "View" : "Analyze")))));
7000
- })))) : null);
7194
+ }), /* @__PURE__ */ React.createElement("nav", null, /* @__PURE__ */ React.createElement(Ul, null, repositories.map((repo2) => /* @__PURE__ */ React.createElement(RepositoryEntry, {
7195
+ key: repo2.path,
7196
+ repo: repo2
7197
+ }))))));
7198
+ }
7199
+ function RepositoryEntry({ repo }) {
7200
+ const [head, setHead] = (0, import_react17.useState)(repo.currentHead);
7201
+ const path = getPathFromRepoAndBranch(repo.name, head);
7202
+ const branchIsAnalyzed = repo.analyzedBranches[head];
7203
+ const iconColor = branchIsAnalyzed ? "green" : void 0;
7204
+ return /* @__PURE__ */ React.createElement(Li, {
7205
+ key: repo.name
7206
+ }, /* @__PURE__ */ React.createElement(Box, {
7207
+ style: {
7208
+ outline: branchIsAnalyzed ? "1px solid green" : void 0
7209
+ }
7210
+ }, /* @__PURE__ */ React.createElement(BoxSubTitle, {
7211
+ title: repo.name
7212
+ }, repo.name, branchIsAnalyzed ? /* @__PURE__ */ React.createElement(AnalyzedTag, null, "Analyzed") : null), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(GroupedBranchSelect, {
7213
+ value: head,
7214
+ onChange: (e) => setHead(e.target.value),
7215
+ headGroups: repo.groups,
7216
+ iconColor
7217
+ }), /* @__PURE__ */ React.createElement(Spacer, null), /* @__PURE__ */ React.createElement(Actions, null, /* @__PURE__ */ React.createElement(Grower, null), /* @__PURE__ */ React.createElement(SLink, {
7218
+ to: path
7219
+ }, branchIsAnalyzed ? "View" : "Analyze"))));
7001
7220
  }
7002
7221
  var Wrapper = import_styled_components18.default.div`
7003
7222
  width: calc(100vw - 2 * var(--side-panel-width));
7004
7223
  margin: auto;
7224
+ padding: var(--unit);
7225
+
7226
+ @media(max-width: 1000px) {
7227
+ width: 100vw;
7228
+ }
7005
7229
  `;
7006
7230
  var H1 = import_styled_components18.default.h1`
7007
- font-family: "Courier New", Courier, monospace;
7231
+ font-weight: normal;
7008
7232
  `;
7009
7233
  var Ul = import_styled_components18.default.ul`
7010
7234
  list-style: none;
7011
7235
  display: grid;
7012
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
7236
+ grid-template-columns: repeat(auto-fit, minmax(225px, 1fr));
7013
7237
  `;
7014
7238
  var Li = import_styled_components18.default.li`
7015
7239
  margin: 0;
7016
7240
  `;
7017
- var Tag = import_styled_components18.default.span`
7018
- font-size: 0.7em;
7019
- text-transform: uppercase;
7020
- padding: calc(0.25 * var(--unit)) calc(var(--unit));
7021
- letter-spacing: 0.2em;
7022
- color: white;
7023
- background-color: hsl(210, 100%, 50%);
7024
- border-radius: 1000px;
7025
- `;
7026
7241
  var Actions = import_styled_components18.default.div`
7027
7242
  display: flex;
7028
7243
  `;
7029
7244
  var SLink = (0, import_styled_components18.default)(import_remix7.Link)`
7245
+ line-height: 100%;
7030
7246
  text-decoration: none;
7247
+ font-weight: bold;
7248
+ font-size: 0.9em;
7249
+ color: ${(props) => props.green ? " green" : import_styled_components18.css`var(--text-color)`};
7250
+ text-transform: uppercase;
7251
+ opacity: 75%;
7252
+ cursor: pointer;
7253
+ &:hover {
7254
+ opacity: 100%;
7255
+ }
7256
+ `;
7257
+ var AnalyzedTag = import_styled_components18.default.span`
7258
+ text-transform: uppercase;
7259
+ font-weight: normal;
7260
+ font-size: 0.6rem;
7261
+ border: 1px solid currentColor;
7262
+ color: green;
7263
+ border-radius: 100000px;
7264
+ padding: 2px 4px;
7265
+ letter-spacing: 1px;
7266
+ user-select: none;
7267
+ font-weight: bold;
7268
+ display: flex;
7269
+ place-items: center;
7270
+ line-height: 100%;
7271
+ vertical-align: middle;
7272
+ align-content: flex-start;
7031
7273
  `;
7032
7274
 
7033
7275
  // server-assets-manifest:@remix-run/dev/assets-manifest
7034
7276
  init_react();
7035
- var assets_manifest_default = { "version": "87dfcc32", "entry": { "module": "/build/entry.client-LYPBBYXO.js", "imports": ["/build/_shared/chunk-3UBBWI7X.js", "/build/_shared/chunk-IWPB7HVR.js"] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "module": "/build/root-YFFMOAIE.js", "imports": ["/build/_shared/chunk-XQMVET66.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-ORF3ZRBA.js", "imports": ["/build/_shared/chunk-4JENRIWS.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-JOOJGTNK.js", "imports": ["/build/_shared/chunk-4JENRIWS.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": false } }, "url": "/build/manifest-87DFCC32.js" };
7277
+ var assets_manifest_default = { "version": "9b9925ec", "entry": { "module": "/build/entry.client-BOQTIOMQ.js", "imports": ["/build/_shared/chunk-46KIHHI7.js", "/build/_shared/chunk-KAUONBO3.js"] }, "routes": { "root": { "id": "root", "parentId": void 0, "path": "", "index": void 0, "caseSensitive": void 0, "module": "/build/root-ZFPEDWF7.js", "imports": ["/build/_shared/chunk-J6HYE3WJ.js", "/build/_shared/chunk-D3JIIV5V.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.$-YVRVKTZC.js", "imports": ["/build/_shared/chunk-TA33COY5.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-AS3DS2NT.js", "imports": ["/build/_shared/chunk-TA33COY5.js"], "hasAction": true, "hasLoader": true, "hasCatchBoundary": false, "hasErrorBoundary": false } }, "url": "/build/manifest-9B9925EC.js" };
7036
7278
 
7037
7279
  // server-entry-module:@remix-run/dev/server-build
7038
7280
  var entry = { module: entry_server_exports };
@@ -7045,10 +7287,10 @@ var routes = {
7045
7287
  caseSensitive: void 0,
7046
7288
  module: root_exports
7047
7289
  },
7048
- "routes/$repo": {
7049
- id: "routes/$repo",
7290
+ "routes/$repo.$": {
7291
+ id: "routes/$repo.$",
7050
7292
  parentId: "root",
7051
- path: ":repo",
7293
+ path: ":repo/*",
7052
7294
  index: void 0,
7053
7295
  caseSensitive: void 0,
7054
7296
  module: repo_exports
@@ -7073,11 +7315,13 @@ var import_path11 = require("path");
7073
7315
  var import_semver_compare2 = __toESM(require("semver-compare"));
7074
7316
  var args = parseArgs();
7075
7317
  (async () => {
7076
- const latestV = await latestVersion(package_default.name);
7077
7318
  const currentV = package_default.version;
7078
- process.stdout.write("\x1B[2J\x1B[0;0H");
7079
- console.log();
7080
- const updateMessage = latestV && (0, import_semver_compare2.default)(latestV, currentV) === 1 ? ` [!] Update available: ${latestV}
7319
+ let updateMessage = "";
7320
+ try {
7321
+ const latestV = await latestVersion(package_default.name);
7322
+ process.stdout.write("\x1B[2J\x1B[0;0H");
7323
+ console.log();
7324
+ updateMessage = latestV && (0, import_semver_compare2.default)(latestV, currentV) === 1 ? ` [!] Update available: ${latestV}
7081
7325
 
7082
7326
  To update, run:
7083
7327
 
@@ -7088,6 +7332,8 @@ Or to install globally:
7088
7332
  npm install -g git-truck@latest
7089
7333
 
7090
7334
  ` : " (latest)";
7335
+ } catch (e) {
7336
+ }
7091
7337
  console.log(`Git Truck version ${currentV}${updateMessage}`);
7092
7338
  if (args.h || args.help) {
7093
7339
  console.log();