unity-hub-cli 0.7.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 +382 -59
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -72,9 +72,10 @@ var LaunchProjectUseCase = class {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
var TerminateProjectUseCase = class {
|
|
75
|
-
constructor(unityProcessReader, unityProcessTerminator) {
|
|
75
|
+
constructor(unityProcessReader, unityProcessTerminator, unityTempDirectoryCleaner) {
|
|
76
76
|
this.unityProcessReader = unityProcessReader;
|
|
77
77
|
this.unityProcessTerminator = unityProcessTerminator;
|
|
78
|
+
this.unityTempDirectoryCleaner = unityTempDirectoryCleaner;
|
|
78
79
|
}
|
|
79
80
|
async execute(project) {
|
|
80
81
|
const unityProcess = await this.unityProcessReader.findByProjectPath(project.path);
|
|
@@ -84,13 +85,21 @@ var TerminateProjectUseCase = class {
|
|
|
84
85
|
message: "No Unity process is running for this project."
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
|
-
const
|
|
88
|
-
if (!terminated) {
|
|
88
|
+
const termination = await this.unityProcessTerminator.terminate(unityProcess);
|
|
89
|
+
if (!termination.terminated) {
|
|
89
90
|
return {
|
|
90
91
|
terminated: false,
|
|
91
92
|
message: "Failed to terminate the Unity process."
|
|
92
93
|
};
|
|
93
94
|
}
|
|
95
|
+
if (termination.stage === "sigterm" || termination.stage === "sigkill") {
|
|
96
|
+
try {
|
|
97
|
+
await this.unityTempDirectoryCleaner.clean(project.path);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
console.error(`Failed to clean Temp directory after termination: ${message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
94
103
|
return {
|
|
95
104
|
terminated: true
|
|
96
105
|
};
|
|
@@ -436,6 +445,8 @@ var PROCESS_LIST_ARGS = ["-axo", "pid=,command=", "-ww"];
|
|
|
436
445
|
var PROCESS_LIST_COMMAND = "ps";
|
|
437
446
|
var TERMINATE_TIMEOUT_MILLIS = 5e3;
|
|
438
447
|
var TERMINATE_POLL_INTERVAL_MILLIS = 200;
|
|
448
|
+
var GRACEFUL_QUIT_TIMEOUT_MILLIS = 3e3;
|
|
449
|
+
var GRACEFUL_QUIT_POLL_INTERVAL_MILLIS = 200;
|
|
439
450
|
var delay = async (duration) => {
|
|
440
451
|
await new Promise((resolveDelay) => {
|
|
441
452
|
setTimeout(() => {
|
|
@@ -535,11 +546,33 @@ var MacUnityProcessReader = class {
|
|
|
535
546
|
};
|
|
536
547
|
var MacUnityProcessTerminator = class {
|
|
537
548
|
async terminate(unityProcess) {
|
|
549
|
+
let attemptedGraceful = false;
|
|
550
|
+
if (process.platform === "darwin") {
|
|
551
|
+
attemptedGraceful = true;
|
|
552
|
+
try {
|
|
553
|
+
const script = [
|
|
554
|
+
'tell application "System Events"',
|
|
555
|
+
` set frontmost of (first process whose unix id is ${unityProcess.pid}) to true`,
|
|
556
|
+
' keystroke "q" using {command down}',
|
|
557
|
+
"end tell"
|
|
558
|
+
].join("\n");
|
|
559
|
+
await execFileAsync2("osascript", ["-e", script]);
|
|
560
|
+
const deadlineGraceful = Date.now() + GRACEFUL_QUIT_TIMEOUT_MILLIS;
|
|
561
|
+
while (Date.now() < deadlineGraceful) {
|
|
562
|
+
await delay(GRACEFUL_QUIT_POLL_INTERVAL_MILLIS);
|
|
563
|
+
const alive = ensureProcessAlive(unityProcess.pid);
|
|
564
|
+
if (!alive) {
|
|
565
|
+
return { terminated: true, stage: "graceful" };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
}
|
|
538
571
|
try {
|
|
539
572
|
process.kill(unityProcess.pid, "SIGTERM");
|
|
540
573
|
} catch (error) {
|
|
541
574
|
if (isProcessMissingError(error)) {
|
|
542
|
-
return
|
|
575
|
+
return { terminated: true, stage: attemptedGraceful ? "graceful" : "sigterm" };
|
|
543
576
|
}
|
|
544
577
|
throw new Error(
|
|
545
578
|
`Failed to terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -550,21 +583,22 @@ var MacUnityProcessTerminator = class {
|
|
|
550
583
|
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
551
584
|
const alive = ensureProcessAlive(unityProcess.pid);
|
|
552
585
|
if (!alive) {
|
|
553
|
-
return true;
|
|
586
|
+
return { terminated: true, stage: "sigterm" };
|
|
554
587
|
}
|
|
555
588
|
}
|
|
556
589
|
try {
|
|
557
590
|
process.kill(unityProcess.pid, "SIGKILL");
|
|
558
591
|
} catch (error) {
|
|
559
592
|
if (isProcessMissingError(error)) {
|
|
560
|
-
return true;
|
|
593
|
+
return { terminated: true, stage: "sigkill" };
|
|
561
594
|
}
|
|
562
595
|
throw new Error(
|
|
563
596
|
`Failed to forcefully terminate the Unity process (PID: ${unityProcess.pid}): ${error instanceof Error ? error.message : String(error)}`
|
|
564
597
|
);
|
|
565
598
|
}
|
|
566
599
|
await delay(TERMINATE_POLL_INTERVAL_MILLIS);
|
|
567
|
-
|
|
600
|
+
const aliveAfterKill = ensureProcessAlive(unityProcess.pid);
|
|
601
|
+
return aliveAfterKill ? { terminated: false } : { terminated: true, stage: "sigkill" };
|
|
568
602
|
}
|
|
569
603
|
};
|
|
570
604
|
|
|
@@ -589,6 +623,52 @@ var UnityTempDirectoryCleaner = class {
|
|
|
589
623
|
import clipboard from "clipboardy";
|
|
590
624
|
import { Box, Text, useApp, useInput, useStdout } from "ink";
|
|
591
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
|
|
592
672
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
593
673
|
var extractRootFolder = (repository) => {
|
|
594
674
|
if (!repository?.root) {
|
|
@@ -673,7 +753,7 @@ var formatUpdatedText = (lastModified) => {
|
|
|
673
753
|
var homeDirectory = process.env.HOME ?? "";
|
|
674
754
|
var homePrefix = homeDirectory ? `${homeDirectory}/` : "";
|
|
675
755
|
var minimumVisibleProjectCount = 4;
|
|
676
|
-
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";
|
|
677
757
|
var PROJECT_COLOR = "#abd8e7";
|
|
678
758
|
var BRANCH_COLOR = "#e3839c";
|
|
679
759
|
var PATH_COLOR = "#719bd8";
|
|
@@ -696,8 +776,50 @@ var shortenHomePath = (targetPath) => {
|
|
|
696
776
|
return targetPath;
|
|
697
777
|
};
|
|
698
778
|
var buildCdCommand = (targetPath) => {
|
|
699
|
-
|
|
700
|
-
|
|
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;
|
|
701
823
|
};
|
|
702
824
|
var App = ({
|
|
703
825
|
projects,
|
|
@@ -718,36 +840,87 @@ var App = ({
|
|
|
718
840
|
const [releasedProjects, setReleasedProjects] = useState(/* @__PURE__ */ new Set());
|
|
719
841
|
const [launchedProjects, setLaunchedProjects] = useState(/* @__PURE__ */ new Set());
|
|
720
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);
|
|
721
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]);
|
|
722
855
|
const sortedProjects = useMemo(() => {
|
|
723
856
|
const fallbackTime = 0;
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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;
|
|
730
876
|
}
|
|
731
|
-
return
|
|
877
|
+
return direction === "desc" ? keyB.localeCompare(keyA) : keyA.localeCompare(keyB);
|
|
732
878
|
};
|
|
733
|
-
const toTieBreaker = (view) => view.project.path.toLocaleLowerCase();
|
|
734
879
|
return [...projectViews].sort((a, b) => {
|
|
735
|
-
if (a.project.favorite !== b.project.favorite) {
|
|
880
|
+
if (sortPreferences.favoritesFirst && a.project.favorite !== b.project.favorite) {
|
|
736
881
|
return a.project.favorite ? -1 : 1;
|
|
737
882
|
}
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
|
|
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));
|
|
742
895
|
}
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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;
|
|
747
903
|
}
|
|
748
|
-
return
|
|
904
|
+
return tieBreaker(a).localeCompare(tieBreaker(b));
|
|
749
905
|
});
|
|
750
|
-
}, [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]);
|
|
751
924
|
useEffect(() => {
|
|
752
925
|
const handleSigint = () => {
|
|
753
926
|
exit();
|
|
@@ -766,12 +939,9 @@ var App = ({
|
|
|
766
939
|
const borderRows = 2;
|
|
767
940
|
const hintRows = 1;
|
|
768
941
|
const reservedRows = borderRows + hintRows;
|
|
769
|
-
const availableRows = stdout.rows - reservedRows;
|
|
942
|
+
const availableRows = Math.max(0, stdout.rows - reservedRows);
|
|
770
943
|
const rowsPerProject = Math.max(linesPerProject, 1);
|
|
771
|
-
const calculatedCount = Math.max(
|
|
772
|
-
minimumVisibleProjectCount,
|
|
773
|
-
Math.floor(availableRows / rowsPerProject)
|
|
774
|
-
);
|
|
944
|
+
const calculatedCount = Math.max(1, Math.floor(availableRows / rowsPerProject));
|
|
775
945
|
setVisibleCount(calculatedCount);
|
|
776
946
|
};
|
|
777
947
|
updateVisibleCount();
|
|
@@ -780,7 +950,7 @@ var App = ({
|
|
|
780
950
|
stdout?.off("resize", updateVisibleCount);
|
|
781
951
|
};
|
|
782
952
|
}, [linesPerProject, stdout]);
|
|
783
|
-
const limit = Math.max(
|
|
953
|
+
const limit = Math.max(1, visibleCount);
|
|
784
954
|
const move = useCallback(
|
|
785
955
|
(delta) => {
|
|
786
956
|
setIndex((prev) => {
|
|
@@ -846,7 +1016,8 @@ var App = ({
|
|
|
846
1016
|
});
|
|
847
1017
|
}, [index, limit, sortedProjects.length]);
|
|
848
1018
|
const copyProjectPath = useCallback(() => {
|
|
849
|
-
const
|
|
1019
|
+
const projectView = sortedProjects[index];
|
|
1020
|
+
const projectPath = projectView ? getCopyTargetPath(projectView) : void 0;
|
|
850
1021
|
if (!projectPath) {
|
|
851
1022
|
setHint("No project to copy");
|
|
852
1023
|
setTimeout(() => {
|
|
@@ -858,7 +1029,7 @@ var App = ({
|
|
|
858
1029
|
const command = buildCdCommand(projectPath);
|
|
859
1030
|
clipboard.writeSync(command);
|
|
860
1031
|
const displayPath = shortenHomePath(projectPath);
|
|
861
|
-
setHint(`Copied command: cd
|
|
1032
|
+
setHint(`Copied command: cd ${displayPath}`);
|
|
862
1033
|
} catch (error) {
|
|
863
1034
|
const message = error instanceof Error ? error.message : String(error);
|
|
864
1035
|
setHint(`Failed to copy: ${message}`);
|
|
@@ -878,7 +1049,8 @@ var App = ({
|
|
|
878
1049
|
}
|
|
879
1050
|
const { project } = projectView;
|
|
880
1051
|
try {
|
|
881
|
-
const
|
|
1052
|
+
const cdTarget = getCopyTargetPath(projectView);
|
|
1053
|
+
const command = buildCdCommand(cdTarget);
|
|
882
1054
|
clipboard.writeSync(command);
|
|
883
1055
|
} catch (error) {
|
|
884
1056
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1024,6 +1196,50 @@ var App = ({
|
|
|
1024
1196
|
}
|
|
1025
1197
|
}, [isRefreshing, onRefresh, sortedProjects]);
|
|
1026
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
|
+
}
|
|
1027
1243
|
if (input === "j" || key.downArrow) {
|
|
1028
1244
|
move(1);
|
|
1029
1245
|
}
|
|
@@ -1092,7 +1308,7 @@ var App = ({
|
|
|
1092
1308
|
return visibleProjects.map(({ project, repository, launchStatus }, offset) => {
|
|
1093
1309
|
const rowIndex = startIndex + offset;
|
|
1094
1310
|
const isSelected = rowIndex === index;
|
|
1095
|
-
const
|
|
1311
|
+
const selectionBar = isSelected ? "\u2503" : " ";
|
|
1096
1312
|
const projectName = formatProjectName(project, repository, useGitRootName);
|
|
1097
1313
|
const versionLabel = `(${project.version.value})`;
|
|
1098
1314
|
const updatedText = formatUpdatedText(project.lastModified);
|
|
@@ -1117,28 +1333,23 @@ var App = ({
|
|
|
1117
1333
|
const statusLabel = STATUS_LABELS[displayStatus];
|
|
1118
1334
|
const statusColor = displayStatus === "running" ? LOCK_COLOR : displayStatus === "crashed" ? "red" : void 0;
|
|
1119
1335
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
1120
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
1121
|
-
/* @__PURE__ */
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
/* @__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: [
|
|
1128
1345
|
" ",
|
|
1129
1346
|
versionLabel
|
|
1130
1347
|
] }),
|
|
1131
|
-
updatedText ? /* @__PURE__ */ jsx(Text, {
|
|
1348
|
+
updatedText ? /* @__PURE__ */ jsx(Text, { children: ` ${updatedText}` }) : null,
|
|
1132
1349
|
statusLabel && statusColor ? /* @__PURE__ */ jsx(Text, { color: statusColor, children: ` ${statusLabel}` }) : null
|
|
1133
1350
|
] }),
|
|
1134
|
-
showBranch ? /* @__PURE__ */
|
|
1135
|
-
|
|
1136
|
-
branchLine
|
|
1137
|
-
] }) : null,
|
|
1138
|
-
showPath ? /* @__PURE__ */ jsxs(Text, { color: isSelected ? "green" : PATH_COLOR, children: [
|
|
1139
|
-
" ",
|
|
1140
|
-
pathLine
|
|
1141
|
-
] }) : 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,
|
|
1142
1353
|
/* @__PURE__ */ jsx(Text, { children: " " })
|
|
1143
1354
|
] }),
|
|
1144
1355
|
/* @__PURE__ */ jsxs(Box, { marginLeft: 1, width: 1, flexDirection: "column", alignItems: "center", children: [
|
|
@@ -1160,10 +1371,121 @@ var App = ({
|
|
|
1160
1371
|
useGitRootName,
|
|
1161
1372
|
visibleProjects
|
|
1162
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]);
|
|
1163
1485
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
1164
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 }),
|
|
1165
|
-
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { children: hint }) })
|
|
1166
|
-
] });
|
|
1487
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: hint }) })
|
|
1488
|
+
] }, renderEpoch);
|
|
1167
1489
|
};
|
|
1168
1490
|
|
|
1169
1491
|
// src/index.tsx
|
|
@@ -1194,7 +1516,8 @@ var bootstrap = async () => {
|
|
|
1194
1516
|
);
|
|
1195
1517
|
const terminateProjectUseCase = new TerminateProjectUseCase(
|
|
1196
1518
|
unityProcessReader,
|
|
1197
|
-
unityProcessTerminator
|
|
1519
|
+
unityProcessTerminator,
|
|
1520
|
+
unityTempDirectoryCleaner
|
|
1198
1521
|
);
|
|
1199
1522
|
const useGitRootName = !process2.argv.includes("--no-git-root-name");
|
|
1200
1523
|
const showBranch = !process2.argv.includes("--hide-branch");
|