unity-hub-cli 0.8.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.
Files changed (2) hide show
  1. package/dist/index.js +339 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -623,6 +623,52 @@ var UnityTempDirectoryCleaner = class {
623
623
  import clipboard from "clipboardy";
624
624
  import { Box, Text, useApp, useInput, useStdout } from "ink";
625
625
  import { useCallback, useEffect, useMemo, useState } from "react";
626
+
627
+ // src/infrastructure/config.ts
628
+ import { mkdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
629
+ var defaultPreferences = {
630
+ favoritesFirst: true,
631
+ primary: "updated",
632
+ direction: "desc"
633
+ };
634
+ var getConfigDir = () => {
635
+ const home = process.env.HOME ?? "";
636
+ return `${home}/Library/Application Support/UnityHubCli`;
637
+ };
638
+ var getConfigPath = () => `${getConfigDir()}/config.json`;
639
+ var isValidPrimary = (value) => value === "updated" || value === "name";
640
+ var isValidDirection = (value) => value === "asc" || value === "desc";
641
+ var sanitizePreferences = (input) => {
642
+ if (!input || typeof input !== "object") {
643
+ return defaultPreferences;
644
+ }
645
+ const record = input;
646
+ const favoritesFirst = typeof record.favoritesFirst === "boolean" ? record.favoritesFirst : defaultPreferences.favoritesFirst;
647
+ const primary = isValidPrimary(record.primary) ? record.primary : defaultPreferences.primary;
648
+ const direction = isValidDirection(record.direction) ? record.direction : defaultPreferences.direction;
649
+ return { favoritesFirst, primary, direction };
650
+ };
651
+ var readSortPreferences = async () => {
652
+ try {
653
+ const content = await readFile3(getConfigPath(), "utf8");
654
+ const json = JSON.parse(content);
655
+ return sanitizePreferences(json);
656
+ } catch {
657
+ return defaultPreferences;
658
+ }
659
+ };
660
+ var writeSortPreferences = async (prefs) => {
661
+ try {
662
+ await mkdir(getConfigDir(), { recursive: true });
663
+ } catch {
664
+ }
665
+ const sanitized = sanitizePreferences(prefs);
666
+ const json = JSON.stringify(sanitized, void 0, 2);
667
+ await writeFile2(getConfigPath(), json, "utf8");
668
+ };
669
+ var getDefaultSortPreferences = () => defaultPreferences;
670
+
671
+ // src/presentation/App.tsx
626
672
  import { jsx, jsxs } from "react/jsx-runtime";
627
673
  var extractRootFolder = (repository) => {
628
674
  if (!repository?.root) {
@@ -707,7 +753,7 @@ var formatUpdatedText = (lastModified) => {
707
753
  var homeDirectory = process.env.HOME ?? "";
708
754
  var homePrefix = homeDirectory ? `${homeDirectory}/` : "";
709
755
  var minimumVisibleProjectCount = 4;
710
- var defaultHintMessage = "Move with arrows or j/k \xB7 Launch with o \xB7 Quit Unity with q \xB7 Refresh with r \xB7 Copy cd path with c";
756
+ var defaultHintMessage = "Select: j/k \xB7 Open: o \xB7 Quit: q \xB7 Refresh: r \xB7 CopyPath: c \xB7 Sort: s \xB7 Close: ctrl + c";
711
757
  var PROJECT_COLOR = "#abd8e7";
712
758
  var BRANCH_COLOR = "#e3839c";
713
759
  var PATH_COLOR = "#719bd8";
@@ -730,8 +776,50 @@ var shortenHomePath = (targetPath) => {
730
776
  return targetPath;
731
777
  };
732
778
  var buildCdCommand = (targetPath) => {
733
- const escapedPath = targetPath.replaceAll('"', '\\"');
734
- return `cd "${escapedPath}"`;
779
+ return `cd ${targetPath}`;
780
+ };
781
+ var getCopyTargetPath = (view) => {
782
+ const root = view.repository?.root;
783
+ return root && root.length > 0 ? root : view.project.path;
784
+ };
785
+ var isControl = (code) => code >= 0 && code < 32 || code >= 127 && code < 160;
786
+ var isFullwidth = (code) => {
787
+ if (code >= 4352 && (code <= 4447 || // Hangul Jamo
788
+ code === 9001 || code === 9002 || code >= 11904 && code <= 42191 && code !== 12351 || // CJK ... Yi
789
+ code >= 44032 && code <= 55203 || // Hangul Syllables
790
+ code >= 63744 && code <= 64255 || // CJK Compatibility Ideographs
791
+ code >= 65040 && code <= 65049 || // Vertical forms
792
+ code >= 65072 && code <= 65135 || // CJK Compatibility Forms
793
+ code >= 65280 && code <= 65376 || // Fullwidth forms
794
+ code >= 65504 && code <= 65510 || code >= 127744 && code <= 128591 || // Emojis
795
+ code >= 129280 && code <= 129535 || code >= 131072 && code <= 262141)) {
796
+ return true;
797
+ }
798
+ return false;
799
+ };
800
+ var charWidth = (char) => {
801
+ const code = char.codePointAt(0);
802
+ if (code === void 0) return 0;
803
+ if (isControl(code)) return 0;
804
+ return isFullwidth(code) ? 2 : 1;
805
+ };
806
+ var stringWidth = (text) => {
807
+ let width = 0;
808
+ for (const ch of text) {
809
+ width += charWidth(ch);
810
+ }
811
+ return width;
812
+ };
813
+ var truncateToWidth = (text, maxWidth) => {
814
+ let width = 0;
815
+ let result = "";
816
+ for (const ch of text) {
817
+ const w = charWidth(ch);
818
+ if (width + w > maxWidth) break;
819
+ result += ch;
820
+ width += w;
821
+ }
822
+ return result;
735
823
  };
736
824
  var App = ({
737
825
  projects,
@@ -752,36 +840,87 @@ var App = ({
752
840
  const [releasedProjects, setReleasedProjects] = useState(/* @__PURE__ */ new Set());
753
841
  const [launchedProjects, setLaunchedProjects] = useState(/* @__PURE__ */ new Set());
754
842
  const [isRefreshing, setIsRefreshing] = useState(false);
843
+ const [isSortMenuOpen, setIsSortMenuOpen] = useState(false);
844
+ const [sortMenuIndex, setSortMenuIndex] = useState(0);
845
+ const [sortPreferences, setSortPreferences] = useState(getDefaultSortPreferences());
846
+ const [isSortPreferencesLoaded, setIsSortPreferencesLoaded] = useState(false);
847
+ const [renderEpoch, setRenderEpoch] = useState(0);
755
848
  const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
849
+ const forceFullRepaint = useCallback(() => {
850
+ if (!stdout) {
851
+ return;
852
+ }
853
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
854
+ }, [stdout]);
756
855
  const sortedProjects = useMemo(() => {
757
856
  const fallbackTime = 0;
758
- const toSortKey = (view) => {
759
- if (useGitRootName) {
760
- const rootName = extractRootFolder(view.repository);
761
- if (rootName) {
762
- return rootName.toLocaleLowerCase();
763
- }
857
+ const getNameKey = (view) => {
858
+ const rootName = extractRootFolder(view.repository);
859
+ const base = rootName || view.project.title;
860
+ return base.toLocaleLowerCase();
861
+ };
862
+ const tieBreaker = (view) => view.project.path.toLocaleLowerCase();
863
+ const compareByUpdated = (a, b, direction) => {
864
+ const timeA = a.project.lastModified?.getTime() ?? fallbackTime;
865
+ const timeB = b.project.lastModified?.getTime() ?? fallbackTime;
866
+ if (timeA === timeB) {
867
+ return 0;
868
+ }
869
+ return direction === "desc" ? timeB - timeA : timeA - timeB;
870
+ };
871
+ const compareByName = (a, b, direction) => {
872
+ const keyA = getNameKey(a);
873
+ const keyB = getNameKey(b);
874
+ if (keyA === keyB) {
875
+ return 0;
764
876
  }
765
- return view.project.title.toLocaleLowerCase();
877
+ return direction === "desc" ? keyB.localeCompare(keyA) : keyA.localeCompare(keyB);
766
878
  };
767
- const toTieBreaker = (view) => view.project.path.toLocaleLowerCase();
768
879
  return [...projectViews].sort((a, b) => {
769
- if (a.project.favorite !== b.project.favorite) {
880
+ if (sortPreferences.favoritesFirst && a.project.favorite !== b.project.favorite) {
770
881
  return a.project.favorite ? -1 : 1;
771
882
  }
772
- const timeA = a.project.lastModified?.getTime() ?? fallbackTime;
773
- const timeB = b.project.lastModified?.getTime() ?? fallbackTime;
774
- if (timeA !== timeB) {
775
- return timeB - timeA;
883
+ const primary = sortPreferences.primary;
884
+ const direction = sortPreferences.direction;
885
+ if (primary === "updated") {
886
+ const updatedOrder2 = compareByUpdated(a, b, direction);
887
+ if (updatedOrder2 !== 0) {
888
+ return updatedOrder2;
889
+ }
890
+ const nameOrder2 = compareByName(a, b, "asc");
891
+ if (nameOrder2 !== 0) {
892
+ return nameOrder2;
893
+ }
894
+ return tieBreaker(a).localeCompare(tieBreaker(b));
776
895
  }
777
- const keyA = toSortKey(a);
778
- const keyB = toSortKey(b);
779
- if (keyA === keyB) {
780
- return toTieBreaker(a).localeCompare(toTieBreaker(b));
896
+ const nameOrder = compareByName(a, b, direction);
897
+ if (nameOrder !== 0) {
898
+ return nameOrder;
899
+ }
900
+ const updatedOrder = compareByUpdated(a, b, "desc");
901
+ if (updatedOrder !== 0) {
902
+ return updatedOrder;
781
903
  }
782
- return keyA.localeCompare(keyB);
904
+ return tieBreaker(a).localeCompare(tieBreaker(b));
783
905
  });
784
- }, [projectViews, useGitRootName]);
906
+ }, [projectViews, sortPreferences]);
907
+ useEffect(() => {
908
+ void (async () => {
909
+ try {
910
+ const prefs = await readSortPreferences();
911
+ setSortPreferences(prefs);
912
+ } catch {
913
+ } finally {
914
+ setIsSortPreferencesLoaded(true);
915
+ }
916
+ })();
917
+ }, []);
918
+ useEffect(() => {
919
+ if (!isSortPreferencesLoaded) {
920
+ return;
921
+ }
922
+ void writeSortPreferences(sortPreferences);
923
+ }, [sortPreferences, isSortPreferencesLoaded]);
785
924
  useEffect(() => {
786
925
  const handleSigint = () => {
787
926
  exit();
@@ -800,12 +939,9 @@ var App = ({
800
939
  const borderRows = 2;
801
940
  const hintRows = 1;
802
941
  const reservedRows = borderRows + hintRows;
803
- const availableRows = stdout.rows - reservedRows;
942
+ const availableRows = Math.max(0, stdout.rows - reservedRows);
804
943
  const rowsPerProject = Math.max(linesPerProject, 1);
805
- const calculatedCount = Math.max(
806
- minimumVisibleProjectCount,
807
- Math.floor(availableRows / rowsPerProject)
808
- );
944
+ const calculatedCount = Math.max(1, Math.floor(availableRows / rowsPerProject));
809
945
  setVisibleCount(calculatedCount);
810
946
  };
811
947
  updateVisibleCount();
@@ -814,7 +950,7 @@ var App = ({
814
950
  stdout?.off("resize", updateVisibleCount);
815
951
  };
816
952
  }, [linesPerProject, stdout]);
817
- const limit = Math.max(minimumVisibleProjectCount, visibleCount);
953
+ const limit = Math.max(1, visibleCount);
818
954
  const move = useCallback(
819
955
  (delta) => {
820
956
  setIndex((prev) => {
@@ -880,7 +1016,8 @@ var App = ({
880
1016
  });
881
1017
  }, [index, limit, sortedProjects.length]);
882
1018
  const copyProjectPath = useCallback(() => {
883
- const projectPath = sortedProjects[index]?.project.path;
1019
+ const projectView = sortedProjects[index];
1020
+ const projectPath = projectView ? getCopyTargetPath(projectView) : void 0;
884
1021
  if (!projectPath) {
885
1022
  setHint("No project to copy");
886
1023
  setTimeout(() => {
@@ -892,7 +1029,7 @@ var App = ({
892
1029
  const command = buildCdCommand(projectPath);
893
1030
  clipboard.writeSync(command);
894
1031
  const displayPath = shortenHomePath(projectPath);
895
- setHint(`Copied command: cd "${displayPath}"`);
1032
+ setHint(`Copied command: cd ${displayPath}`);
896
1033
  } catch (error) {
897
1034
  const message = error instanceof Error ? error.message : String(error);
898
1035
  setHint(`Failed to copy: ${message}`);
@@ -912,7 +1049,8 @@ var App = ({
912
1049
  }
913
1050
  const { project } = projectView;
914
1051
  try {
915
- const command = buildCdCommand(project.path);
1052
+ const cdTarget = getCopyTargetPath(projectView);
1053
+ const command = buildCdCommand(cdTarget);
916
1054
  clipboard.writeSync(command);
917
1055
  } catch (error) {
918
1056
  const message = error instanceof Error ? error.message : String(error);
@@ -1058,6 +1196,50 @@ var App = ({
1058
1196
  }
1059
1197
  }, [isRefreshing, onRefresh, sortedProjects]);
1060
1198
  useInput((input, key) => {
1199
+ if (isSortMenuOpen) {
1200
+ if (key.escape || input === "\x1B") {
1201
+ clearOverlay();
1202
+ forceFullRepaint();
1203
+ setIsSortMenuOpen(false);
1204
+ setRenderEpoch((prev) => prev + 1);
1205
+ return;
1206
+ }
1207
+ if (input === "j") {
1208
+ setSortMenuIndex((prev) => {
1209
+ const last = 2;
1210
+ const next = prev + 1;
1211
+ return next > last ? 0 : next;
1212
+ });
1213
+ return;
1214
+ }
1215
+ if (input === "k") {
1216
+ setSortMenuIndex((prev) => {
1217
+ const last = 2;
1218
+ const next = prev - 1;
1219
+ return next < 0 ? last : next;
1220
+ });
1221
+ return;
1222
+ }
1223
+ const toggleCurrent = () => {
1224
+ if (sortMenuIndex === 0) {
1225
+ setSortPreferences((prev) => ({ ...prev, primary: prev.primary === "updated" ? "name" : "updated" }));
1226
+ return;
1227
+ }
1228
+ if (sortMenuIndex === 1) {
1229
+ setSortPreferences((prev) => ({ ...prev, direction: prev.direction === "asc" ? "desc" : "asc" }));
1230
+ return;
1231
+ }
1232
+ setSortPreferences((prev) => ({ ...prev, favoritesFirst: !prev.favoritesFirst }));
1233
+ };
1234
+ if (input === " ") {
1235
+ toggleCurrent();
1236
+ }
1237
+ return;
1238
+ }
1239
+ if (input === "S" || input === "s") {
1240
+ setIsSortMenuOpen(true);
1241
+ return;
1242
+ }
1061
1243
  if (input === "j" || key.downArrow) {
1062
1244
  move(1);
1063
1245
  }
@@ -1126,7 +1308,7 @@ var App = ({
1126
1308
  return visibleProjects.map(({ project, repository, launchStatus }, offset) => {
1127
1309
  const rowIndex = startIndex + offset;
1128
1310
  const isSelected = rowIndex === index;
1129
- const arrow = isSelected ? ">" : " ";
1311
+ const selectionBar = isSelected ? "\u2503" : " ";
1130
1312
  const projectName = formatProjectName(project, repository, useGitRootName);
1131
1313
  const versionLabel = `(${project.version.value})`;
1132
1314
  const updatedText = formatUpdatedText(project.lastModified);
@@ -1151,28 +1333,23 @@ var App = ({
1151
1333
  const statusLabel = STATUS_LABELS[displayStatus];
1152
1334
  const statusColor = displayStatus === "running" ? LOCK_COLOR : displayStatus === "crashed" ? "red" : void 0;
1153
1335
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
1154
- /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [
1155
- /* @__PURE__ */ jsxs(Text, { children: [
1156
- /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : PROJECT_COLOR, bold: true, children: [
1157
- arrow,
1158
- " ",
1159
- projectName
1160
- ] }),
1161
- /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : void 0, children: [
1336
+ /* @__PURE__ */ jsxs(Box, { width: 1, flexDirection: "column", alignItems: "center", marginLeft: isSelected ? 1 : 0, children: [
1337
+ /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: selectionBar }),
1338
+ showBranch ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: selectionBar }) : null,
1339
+ showPath ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: selectionBar }) : null
1340
+ ] }),
1341
+ /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexDirection: "column", marginLeft: isSelected ? 2 : 1, children: [
1342
+ /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1343
+ /* @__PURE__ */ jsx(Text, { color: PROJECT_COLOR, bold: true, children: projectName }),
1344
+ /* @__PURE__ */ jsxs(Text, { children: [
1162
1345
  " ",
1163
1346
  versionLabel
1164
1347
  ] }),
1165
- updatedText ? /* @__PURE__ */ jsx(Text, { color: isSelected ? "green" : void 0, children: ` ${updatedText}` }) : null,
1348
+ updatedText ? /* @__PURE__ */ jsx(Text, { children: ` ${updatedText}` }) : null,
1166
1349
  statusLabel && statusColor ? /* @__PURE__ */ jsx(Text, { color: statusColor, children: ` ${statusLabel}` }) : null
1167
1350
  ] }),
1168
- showBranch ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : BRANCH_COLOR, children: [
1169
- " ",
1170
- branchLine
1171
- ] }) : null,
1172
- showPath ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : PATH_COLOR, children: [
1173
- " ",
1174
- pathLine
1175
- ] }) : null,
1351
+ showBranch ? /* @__PURE__ */ jsx(Text, { color: BRANCH_COLOR, wrap: "truncate", children: branchLine }) : null,
1352
+ showPath ? /* @__PURE__ */ jsx(Text, { color: PATH_COLOR, wrap: "truncate", children: pathLine }) : null,
1176
1353
  /* @__PURE__ */ jsx(Text, { children: " " })
1177
1354
  ] }),
1178
1355
  /* @__PURE__ */ jsxs(Box, { marginLeft: 1, width: 1, flexDirection: "column", alignItems: "center", children: [
@@ -1194,10 +1371,121 @@ var App = ({
1194
1371
  useGitRootName,
1195
1372
  visibleProjects
1196
1373
  ]);
1374
+ useEffect(() => {
1375
+ if (!isSortMenuOpen || !stdout) {
1376
+ return;
1377
+ }
1378
+ const columns = typeof stdout.columns === "number" ? stdout.columns : 80;
1379
+ const contentRows = rows.length === 0 ? 1 : visibleProjects.length * linesPerProject;
1380
+ const contentWidth = Math.max(10, columns - 2);
1381
+ const modalInnerWidth = Math.min(60, Math.max(28, contentWidth - 6));
1382
+ const modalWidth = Math.min(contentWidth, modalInnerWidth);
1383
+ const leftPadding = Math.max(0, Math.floor((contentWidth - modalWidth) / 2));
1384
+ const title = "Sort Menu (Select: j/k, Toggle: Space, Close: Esc)";
1385
+ const linePrimary = `Primary: ${sortPreferences.primary === "updated" ? "Updated" : "Name (Git root)"}`;
1386
+ const lineDirection = `Direction: ${sortPreferences.primary === "updated" ? sortPreferences.direction === "desc" ? "New to Old" : "Old to New" : sortPreferences.direction === "asc" ? "A to Z" : "Z to A"}`;
1387
+ const lineFav = `Favorites first: ${sortPreferences.favoritesFirst ? "ON" : "OFF"}`;
1388
+ const innerWidth = modalWidth - 2;
1389
+ const BORDER_ON = "\x1B[32m";
1390
+ const BORDER_OFF = "\x1B[39m";
1391
+ const fixedPrefixWidth = 2;
1392
+ const buildContentLine = (label, selected) => {
1393
+ const arrow = selected ? "> " : " ";
1394
+ const maxLabelWidth = Math.max(0, innerWidth - fixedPrefixWidth);
1395
+ const rawLabel = label;
1396
+ const limitedLabel = stringWidth(rawLabel) > maxLabelWidth ? `${truncateToWidth(rawLabel, Math.max(0, maxLabelWidth - 3))}...` : rawLabel;
1397
+ const visibleBase = `${arrow}${limitedLabel}`;
1398
+ const colored = selected ? `\x1B[32m${visibleBase}\x1B[39m` : visibleBase;
1399
+ const pad = Math.max(0, innerWidth - stringWidth(visibleBase));
1400
+ return `${BORDER_ON}\u2502${BORDER_OFF}${colored}${" ".repeat(pad)}${BORDER_ON}\u2502${BORDER_OFF}`;
1401
+ };
1402
+ const contentLines = [
1403
+ (() => {
1404
+ const prefix = " ";
1405
+ const maxTitleWidth = Math.max(0, innerWidth - fixedPrefixWidth);
1406
+ const visibleTitle = stringWidth(title) > maxTitleWidth ? `${truncateToWidth(title, Math.max(0, maxTitleWidth - 3))}...` : title;
1407
+ const content = `${prefix}${visibleTitle}`;
1408
+ const pad = Math.max(0, innerWidth - stringWidth(content));
1409
+ return `${BORDER_ON}\u2502${BORDER_OFF}${content}${" ".repeat(pad)}${BORDER_ON}\u2502${BORDER_OFF}`;
1410
+ })(),
1411
+ `${BORDER_ON}\u2502${BORDER_OFF}${" ".repeat(innerWidth)}${BORDER_ON}\u2502${BORDER_OFF}`,
1412
+ buildContentLine(linePrimary, sortMenuIndex === 0),
1413
+ buildContentLine(lineDirection, sortMenuIndex === 1),
1414
+ buildContentLine(lineFav, sortMenuIndex === 2)
1415
+ ];
1416
+ const topBorder = `${BORDER_ON}\u250C${"\u2500".repeat(modalWidth - 2)}\u2510${BORDER_OFF}`;
1417
+ const bottomBorder = `${BORDER_ON}\u2514${"\u2500".repeat(modalWidth - 2)}\u2518${BORDER_OFF}`;
1418
+ const overlayLines = [topBorder, ...contentLines, bottomBorder];
1419
+ const overlayHeight = overlayLines.length;
1420
+ const overlayTopWithinContent = Math.max(0, Math.floor((contentRows - overlayHeight) / 2));
1421
+ const overlayTopRelativeToComponent = 1 + overlayTopWithinContent;
1422
+ const bottomIndex = contentRows + 2;
1423
+ const moveUp = Math.max(0, bottomIndex - overlayTopRelativeToComponent);
1424
+ const moveRight = 1 + leftPadding;
1425
+ stdout.write("\x1B7");
1426
+ if (moveUp > 0) {
1427
+ stdout.write(`\x1B[${moveUp}A`);
1428
+ }
1429
+ stdout.write("\r");
1430
+ for (let i = 0; i < overlayLines.length; i++) {
1431
+ stdout.write("\r");
1432
+ if (moveRight > 0) {
1433
+ stdout.write(`\x1B[${moveRight}C`);
1434
+ }
1435
+ stdout.write(" ".repeat(Math.max(0, modalWidth)));
1436
+ stdout.write(`\x1B[${Math.max(0, modalWidth)}D`);
1437
+ stdout.write(overlayLines[i]);
1438
+ if (i < overlayLines.length - 1) {
1439
+ stdout.write("\r\n");
1440
+ }
1441
+ }
1442
+ stdout.write("\x1B8");
1443
+ }, [
1444
+ isSortMenuOpen,
1445
+ linesPerProject,
1446
+ rows,
1447
+ sortPreferences,
1448
+ sortMenuIndex,
1449
+ stdout,
1450
+ visibleProjects.length
1451
+ ]);
1452
+ const clearOverlay = useCallback(() => {
1453
+ if (!stdout) {
1454
+ return;
1455
+ }
1456
+ const columns = typeof stdout.columns === "number" ? stdout.columns : 80;
1457
+ const contentRows = rows.length === 0 ? 1 : visibleProjects.length * linesPerProject;
1458
+ const contentWidth = Math.max(10, columns - 2);
1459
+ const modalInnerWidth = Math.min(60, Math.max(28, contentWidth - 6));
1460
+ const modalWidth = Math.min(contentWidth, modalInnerWidth);
1461
+ const leftPadding = Math.max(0, Math.floor((contentWidth - modalWidth) / 2));
1462
+ const overlayHeight = 6;
1463
+ const overlayTopWithinContent = Math.max(0, Math.floor((contentRows - overlayHeight) / 2));
1464
+ const overlayTopRelativeToComponent = 1 + overlayTopWithinContent;
1465
+ const bottomIndex = contentRows + 2;
1466
+ const moveUp = Math.max(0, bottomIndex - overlayTopRelativeToComponent);
1467
+ const moveRight = 1 + leftPadding;
1468
+ stdout.write("\x1B7");
1469
+ if (moveUp > 0) {
1470
+ stdout.write(`\x1B[${moveUp}A`);
1471
+ }
1472
+ stdout.write("\r");
1473
+ for (let i = 0; i < overlayHeight; i++) {
1474
+ stdout.write("\r");
1475
+ if (moveRight > 0) {
1476
+ stdout.write(`\x1B[${moveRight}C`);
1477
+ }
1478
+ stdout.write(" ".repeat(Math.max(0, modalWidth)));
1479
+ if (i < overlayHeight - 1) {
1480
+ stdout.write("\r\n");
1481
+ }
1482
+ }
1483
+ stdout.write("\x1B8");
1484
+ }, [linesPerProject, rows.length, visibleProjects.length, stdout]);
1197
1485
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1198
1486
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "green", children: rows.length === 0 ? /* @__PURE__ */ jsx(Text, { children: "No Unity Hub projects were found." }) : rows }),
1199
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: hint }) })
1200
- ] });
1487
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: hint }) })
1488
+ ] }, renderEpoch);
1201
1489
  };
1202
1490
 
1203
1491
  // src/index.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "A CLI tool that reads Unity Hub's projects and launches Unity Editor with an interactive TUI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {