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.
- package/dist/index.js +339 -51
- 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 = "
|
|
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
|
-
|
|
734
|
-
|
|
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
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
|
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
|
|
773
|
-
const
|
|
774
|
-
if (
|
|
775
|
-
|
|
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
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
|
904
|
+
return tieBreaker(a).localeCompare(tieBreaker(b));
|
|
783
905
|
});
|
|
784
|
-
}, [projectViews,
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, {
|
|
1155
|
-
/* @__PURE__ */
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
/* @__PURE__ */
|
|
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, {
|
|
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__ */
|
|
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
|