@d3lm/pr-stats 0.2.12 → 0.2.13
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/LICENSE +21 -0
- package/README.md +51 -45
- package/dist/tui-app.mjs +1988 -1854
- package/package.json +1 -1
package/dist/tui-app.mjs
CHANGED
|
@@ -629,1997 +629,2067 @@ function hintsFor(modal, editing, tab, authoredTab, views, copyLinks2) {
|
|
|
629
629
|
return `${toggle}${expand}1-5 tabs \xB7 j/k scroll \xB7 o options \xB7 s settings \xB7 r reload \xB7 R refetch \xB7 q quit`;
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
function Spinner() {
|
|
638
|
-
const [frame, setFrame] = useState(0);
|
|
639
|
-
useEffect(() => {
|
|
640
|
-
const timer = setInterval(() => {
|
|
641
|
-
setFrame((previous) => (previous + 1) % SPINNER_FRAMES.length);
|
|
642
|
-
}, SPINNER_INTERVAL_MS);
|
|
643
|
-
return () => {
|
|
644
|
-
clearInterval(timer);
|
|
645
|
-
};
|
|
646
|
-
}, []);
|
|
647
|
-
return /* @__PURE__ */ jsx2("text", { wrapMode: "none", fg: theme.accent, children: SPINNER_FRAMES[frame] });
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// src/tui/components/Header.tsx
|
|
651
|
-
import { jsx as jsx3, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
|
|
652
|
-
function Header({
|
|
653
|
-
options,
|
|
654
|
-
raw,
|
|
655
|
-
error,
|
|
656
|
-
spinning
|
|
657
|
-
}) {
|
|
658
|
-
const context = [
|
|
659
|
-
raw ? `@${raw.user}` : options.user !== "" ? `@${options.user}` : "@...",
|
|
660
|
-
`since ${options.since}`,
|
|
661
|
-
raw && raw.repos.length > 0 ? raw.repos.join(", ") : options.repos !== "" ? options.repos : "all repos",
|
|
662
|
-
timeModeLabel(options)
|
|
663
|
-
].join(" \xB7 ");
|
|
664
|
-
const rightStatus = raw ? error !== null ? "reload failed \xB7 press r to retry" : `refreshed ${raw.fetchedAt.toLocaleTimeString()}` : "";
|
|
665
|
-
return /* @__PURE__ */ jsxs2("box", { flexDirection: "row", height: 1, paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [
|
|
666
|
-
/* @__PURE__ */ jsxs2("text", { wrapMode: "none", children: [
|
|
667
|
-
/* @__PURE__ */ jsx3("b", { fg: theme.accent, children: "pr-stats" }),
|
|
668
|
-
/* @__PURE__ */ jsxs2("span", { fg: theme.muted, children: [
|
|
669
|
-
" \xB7 ",
|
|
670
|
-
context
|
|
671
|
-
] })
|
|
672
|
-
] }),
|
|
673
|
-
spinning ? /* @__PURE__ */ jsx3(Spinner, {}) : /* @__PURE__ */ jsx3("text", { wrapMode: "none", fg: raw !== null && error !== null ? theme.error : theme.muted, children: rightStatus })
|
|
674
|
-
] });
|
|
675
|
-
}
|
|
676
|
-
function timeModeLabel(options) {
|
|
677
|
-
if (options.wallClock) {
|
|
678
|
-
return "wall-clock time";
|
|
632
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/utilities.js
|
|
633
|
+
function stringReplaceAll(string, substring, postfix) {
|
|
634
|
+
let index = string.indexOf(substring);
|
|
635
|
+
if (index === -1) {
|
|
636
|
+
return string;
|
|
679
637
|
}
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
month: "2-digit",
|
|
691
|
-
day: "2-digit",
|
|
692
|
-
hour: "2-digit",
|
|
693
|
-
minute: "2-digit",
|
|
694
|
-
second: "2-digit"
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
var timeMode = {
|
|
698
|
-
business: true,
|
|
699
|
-
workWindows: [{ startMin: 0, endMin: 24 * 60 }],
|
|
700
|
-
dayHours: 24,
|
|
701
|
-
formatter: wallFormatter("UTC")
|
|
702
|
-
};
|
|
703
|
-
function configureTimeMode({
|
|
704
|
-
business,
|
|
705
|
-
workWindows,
|
|
706
|
-
tz
|
|
707
|
-
}) {
|
|
708
|
-
const workMinutesPerDay = workWindows.reduce((sum, window) => sum + (window.endMin - window.startMin), 0);
|
|
709
|
-
timeMode.business = business;
|
|
710
|
-
timeMode.workWindows = workWindows;
|
|
711
|
-
timeMode.dayHours = business ? workMinutesPerDay / 60 : 24;
|
|
712
|
-
timeMode.formatter = wallFormatter(tz);
|
|
713
|
-
}
|
|
714
|
-
function isFullDayMode() {
|
|
715
|
-
return timeMode.business && timeMode.dayHours === 24;
|
|
716
|
-
}
|
|
717
|
-
function wallParts(instantMs) {
|
|
718
|
-
const parts = Object.fromEntries(timeMode.formatter.formatToParts(instantMs).map((part) => [part.type, part.value]));
|
|
719
|
-
return {
|
|
720
|
-
year: Number(parts.year),
|
|
721
|
-
month: Number(parts.month),
|
|
722
|
-
day: Number(parts.day),
|
|
723
|
-
hour: Number(parts.hour),
|
|
724
|
-
minute: Number(parts.minute),
|
|
725
|
-
second: Number(parts.second)
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
function zonedStamp(date) {
|
|
729
|
-
const parts = wallParts(date.getTime());
|
|
730
|
-
const dayUtcMs = Date.UTC(parts.year, parts.month - 1, parts.day);
|
|
731
|
-
return { dayUtcMs, weekday: new Date(dayUtcMs).getUTCDay(), hour: parts.hour, minute: parts.minute };
|
|
638
|
+
const substringLength = substring.length;
|
|
639
|
+
let endIndex = 0;
|
|
640
|
+
let returnValue = "";
|
|
641
|
+
do {
|
|
642
|
+
returnValue += string.slice(endIndex, index) + substring + postfix;
|
|
643
|
+
endIndex = index + substringLength;
|
|
644
|
+
index = string.indexOf(substring, endIndex);
|
|
645
|
+
} while (index !== -1);
|
|
646
|
+
returnValue += string.slice(endIndex);
|
|
647
|
+
return returnValue;
|
|
732
648
|
}
|
|
733
|
-
function
|
|
734
|
-
|
|
735
|
-
|
|
649
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
650
|
+
let endIndex = 0;
|
|
651
|
+
let returnValue = "";
|
|
652
|
+
do {
|
|
653
|
+
const isGotCR = string[index - 1] === "\r";
|
|
654
|
+
returnValue += string.slice(endIndex, isGotCR ? index - 1 : index) + prefix + (isGotCR ? "\r\n" : "\n") + postfix;
|
|
655
|
+
endIndex = index + 1;
|
|
656
|
+
index = string.indexOf("\n", endIndex);
|
|
657
|
+
} while (index !== -1);
|
|
658
|
+
returnValue += string.slice(endIndex);
|
|
659
|
+
return returnValue;
|
|
736
660
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
661
|
+
|
|
662
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
663
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
664
|
+
var ANSI_UNDERLINE_OFFSET = 20;
|
|
665
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
666
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
667
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
668
|
+
var wrapUnderlineAnsi = (code) => `\x1B[58;5;${code < 90 ? code - 30 : code - 90 + 8}m`;
|
|
669
|
+
var styles = {
|
|
670
|
+
modifier: {
|
|
671
|
+
reset: [0, 0],
|
|
672
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
673
|
+
bold: [1, 22],
|
|
674
|
+
dim: [2, 22],
|
|
675
|
+
italic: [3, 23],
|
|
676
|
+
underline: [4, 24],
|
|
677
|
+
// Extended underline styles (`SGR 4:x` sub-parameters). Not in upstream `ansi-styles`.
|
|
678
|
+
underlineDouble: ["4:2", 24],
|
|
679
|
+
underlineCurly: ["4:3", 24],
|
|
680
|
+
underlineDotted: ["4:4", 24],
|
|
681
|
+
underlineDashed: ["4:5", 24],
|
|
682
|
+
overline: [53, 55],
|
|
683
|
+
inverse: [7, 27],
|
|
684
|
+
hidden: [8, 28],
|
|
685
|
+
strikethrough: [9, 29]
|
|
686
|
+
},
|
|
687
|
+
color: {
|
|
688
|
+
black: [30, 39],
|
|
689
|
+
red: [31, 39],
|
|
690
|
+
green: [32, 39],
|
|
691
|
+
yellow: [33, 39],
|
|
692
|
+
blue: [34, 39],
|
|
693
|
+
magenta: [35, 39],
|
|
694
|
+
cyan: [36, 39],
|
|
695
|
+
white: [37, 39],
|
|
696
|
+
// Bright color
|
|
697
|
+
blackBright: [90, 39],
|
|
698
|
+
gray: [90, 39],
|
|
699
|
+
// Alias of `blackBright`
|
|
700
|
+
grey: [90, 39],
|
|
701
|
+
// Alias of `blackBright`
|
|
702
|
+
redBright: [91, 39],
|
|
703
|
+
greenBright: [92, 39],
|
|
704
|
+
yellowBright: [93, 39],
|
|
705
|
+
blueBright: [94, 39],
|
|
706
|
+
magentaBright: [95, 39],
|
|
707
|
+
cyanBright: [96, 39],
|
|
708
|
+
whiteBright: [97, 39]
|
|
709
|
+
},
|
|
710
|
+
bgColor: {
|
|
711
|
+
bgBlack: [40, 49],
|
|
712
|
+
bgRed: [41, 49],
|
|
713
|
+
bgGreen: [42, 49],
|
|
714
|
+
bgYellow: [43, 49],
|
|
715
|
+
bgBlue: [44, 49],
|
|
716
|
+
bgMagenta: [45, 49],
|
|
717
|
+
bgCyan: [46, 49],
|
|
718
|
+
bgWhite: [47, 49],
|
|
719
|
+
// Bright color
|
|
720
|
+
bgBlackBright: [100, 49],
|
|
721
|
+
bgGray: [100, 49],
|
|
722
|
+
// Alias of `bgBlackBright`
|
|
723
|
+
bgGrey: [100, 49],
|
|
724
|
+
// Alias of `bgBlackBright`
|
|
725
|
+
bgRedBright: [101, 49],
|
|
726
|
+
bgGreenBright: [102, 49],
|
|
727
|
+
bgYellowBright: [103, 49],
|
|
728
|
+
bgBlueBright: [104, 49],
|
|
729
|
+
bgMagentaBright: [105, 49],
|
|
730
|
+
bgCyanBright: [106, 49],
|
|
731
|
+
bgWhiteBright: [107, 49]
|
|
732
|
+
},
|
|
733
|
+
// Underline color (`SGR 58`/`59`). Not in upstream `ansi-styles`.
|
|
734
|
+
underlineColor: {
|
|
735
|
+
underlineBlack: ["58;5;0", 59],
|
|
736
|
+
underlineRed: ["58;5;1", 59],
|
|
737
|
+
underlineGreen: ["58;5;2", 59],
|
|
738
|
+
underlineYellow: ["58;5;3", 59],
|
|
739
|
+
underlineBlue: ["58;5;4", 59],
|
|
740
|
+
underlineMagenta: ["58;5;5", 59],
|
|
741
|
+
underlineCyan: ["58;5;6", 59],
|
|
742
|
+
underlineWhite: ["58;5;7", 59],
|
|
743
|
+
// Bright color
|
|
744
|
+
underlineBlackBright: ["58;5;8", 59],
|
|
745
|
+
underlineGray: ["58;5;8", 59],
|
|
746
|
+
// Alias of `underlineBlackBright`
|
|
747
|
+
underlineGrey: ["58;5;8", 59],
|
|
748
|
+
// Alias of `underlineBlackBright`
|
|
749
|
+
underlineRedBright: ["58;5;9", 59],
|
|
750
|
+
underlineGreenBright: ["58;5;10", 59],
|
|
751
|
+
underlineYellowBright: ["58;5;11", 59],
|
|
752
|
+
underlineBlueBright: ["58;5;12", 59],
|
|
753
|
+
underlineMagentaBright: ["58;5;13", 59],
|
|
754
|
+
underlineCyanBright: ["58;5;14", 59],
|
|
755
|
+
underlineWhiteBright: ["58;5;15", 59]
|
|
741
756
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
757
|
+
};
|
|
758
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
759
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
760
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
761
|
+
var underlineColorNames = Object.keys(styles.underlineColor);
|
|
762
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
763
|
+
function assembleStyles() {
|
|
764
|
+
const codes = /* @__PURE__ */ new Map();
|
|
765
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
766
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
767
|
+
styles[styleName] = {
|
|
768
|
+
open: `\x1B[${style[0]}m`,
|
|
769
|
+
close: `\x1B[${style[1]}m`
|
|
770
|
+
};
|
|
771
|
+
group[styleName] = styles[styleName];
|
|
772
|
+
codes.set(Number.parseInt(style[0], 10), style[1]);
|
|
773
|
+
}
|
|
774
|
+
Object.defineProperty(styles, groupName, {
|
|
775
|
+
value: group,
|
|
776
|
+
enumerable: false
|
|
777
|
+
});
|
|
751
778
|
}
|
|
752
|
-
|
|
779
|
+
Object.defineProperty(styles, "codes", {
|
|
780
|
+
value: codes,
|
|
781
|
+
enumerable: false
|
|
782
|
+
});
|
|
783
|
+
styles.color.close = "\x1B[39m";
|
|
784
|
+
styles.bgColor.close = "\x1B[49m";
|
|
785
|
+
styles.underlineColor.close = "\x1B[59m";
|
|
786
|
+
styles.color.ansi = wrapAnsi16();
|
|
787
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
788
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
789
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
790
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
791
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
792
|
+
styles.underlineColor.ansi = wrapUnderlineAnsi;
|
|
793
|
+
styles.underlineColor.ansi256 = wrapAnsi256(ANSI_UNDERLINE_OFFSET);
|
|
794
|
+
styles.underlineColor.ansi16m = wrapAnsi16m(ANSI_UNDERLINE_OFFSET);
|
|
795
|
+
Object.defineProperties(styles, {
|
|
796
|
+
rgbToAnsi256: {
|
|
797
|
+
value(red, green, blue) {
|
|
798
|
+
if (red === green && green === blue) {
|
|
799
|
+
if (red < 8) {
|
|
800
|
+
return 16;
|
|
801
|
+
}
|
|
802
|
+
if (red > 248) {
|
|
803
|
+
return 231;
|
|
804
|
+
}
|
|
805
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
806
|
+
}
|
|
807
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
808
|
+
},
|
|
809
|
+
enumerable: false
|
|
810
|
+
},
|
|
811
|
+
hexToRgb: {
|
|
812
|
+
value(hex) {
|
|
813
|
+
const matches = /[\da-f]{6}|[\da-f]{3}/i.exec(hex.toString(16));
|
|
814
|
+
if (!matches) {
|
|
815
|
+
return [0, 0, 0];
|
|
816
|
+
}
|
|
817
|
+
let [colorString] = matches;
|
|
818
|
+
if (colorString.length === 3) {
|
|
819
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
820
|
+
}
|
|
821
|
+
const integer = Number.parseInt(colorString, 16);
|
|
822
|
+
return [
|
|
823
|
+
/* eslint-disable no-bitwise -- We need the speed */
|
|
824
|
+
integer >> 16 & 255,
|
|
825
|
+
integer >> 8 & 255,
|
|
826
|
+
integer & 255
|
|
827
|
+
/* eslint-enable no-bitwise */
|
|
828
|
+
];
|
|
829
|
+
},
|
|
830
|
+
enumerable: false
|
|
831
|
+
},
|
|
832
|
+
hexToAnsi256: {
|
|
833
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
834
|
+
enumerable: false
|
|
835
|
+
},
|
|
836
|
+
ansi256ToAnsi: {
|
|
837
|
+
value(code) {
|
|
838
|
+
if (code < 8) {
|
|
839
|
+
return 30 + code;
|
|
840
|
+
}
|
|
841
|
+
if (code < 16) {
|
|
842
|
+
return 90 + (code - 8);
|
|
843
|
+
}
|
|
844
|
+
let red;
|
|
845
|
+
let green;
|
|
846
|
+
let blue;
|
|
847
|
+
if (code >= 232) {
|
|
848
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
849
|
+
green = red;
|
|
850
|
+
blue = red;
|
|
851
|
+
} else {
|
|
852
|
+
code -= 16;
|
|
853
|
+
const remainder = code % 36;
|
|
854
|
+
red = Math.floor(code / 36) / 5;
|
|
855
|
+
green = Math.floor(remainder / 6) / 5;
|
|
856
|
+
blue = remainder % 6 / 5;
|
|
857
|
+
}
|
|
858
|
+
const value2 = Math.max(red, green, blue) * 2;
|
|
859
|
+
if (value2 === 0) {
|
|
860
|
+
return 30;
|
|
861
|
+
}
|
|
862
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
863
|
+
if (value2 === 2) {
|
|
864
|
+
result += 60;
|
|
865
|
+
}
|
|
866
|
+
return result;
|
|
867
|
+
},
|
|
868
|
+
enumerable: false
|
|
869
|
+
},
|
|
870
|
+
rgbToAnsi: {
|
|
871
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
872
|
+
enumerable: false
|
|
873
|
+
},
|
|
874
|
+
hexToAnsi: {
|
|
875
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
876
|
+
enumerable: false
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
return styles;
|
|
753
880
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
881
|
+
var ansiStyles = assembleStyles();
|
|
882
|
+
var ansi_styles_default = ansiStyles;
|
|
883
|
+
|
|
884
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/supports-color/index.js
|
|
885
|
+
import process2 from "node:process";
|
|
886
|
+
import os from "node:os";
|
|
887
|
+
import tty from "node:tty";
|
|
888
|
+
function hasFlag(flag2, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
889
|
+
const prefix = flag2.startsWith("-") ? "" : flag2.length === 1 ? "-" : "--";
|
|
890
|
+
const position = argv.indexOf(prefix + flag2);
|
|
891
|
+
const terminatorPosition = argv.indexOf("--");
|
|
892
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
893
|
+
}
|
|
894
|
+
var { env } = process2;
|
|
895
|
+
var flagForceColor;
|
|
896
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
897
|
+
flagForceColor = 0;
|
|
898
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
899
|
+
flagForceColor = 1;
|
|
900
|
+
}
|
|
901
|
+
function hasNumericForceColor() {
|
|
902
|
+
return /^\d+$/.test(env.FORCE_COLOR);
|
|
903
|
+
}
|
|
904
|
+
function envForceColor() {
|
|
905
|
+
if (!("FORCE_COLOR" in env)) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (env.FORCE_COLOR === "false") {
|
|
758
909
|
return 0;
|
|
759
910
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
let localDay = Date.UTC(parts.year, parts.month - 1, parts.day);
|
|
763
|
-
while (utcFromWall(localDay) <= endMs) {
|
|
764
|
-
const weekday = new Date(localDay).getUTCDay();
|
|
765
|
-
if (weekday !== 0 && weekday !== 6) {
|
|
766
|
-
for (const window of timeMode.workWindows) {
|
|
767
|
-
const windowStart = utcFromWall(localDay + window.startMin * 6e4);
|
|
768
|
-
const windowEnd = utcFromWall(localDay + window.endMin * 6e4);
|
|
769
|
-
const overlapStart = Math.max(windowStart, startMs);
|
|
770
|
-
const overlapEnd = Math.min(windowEnd, endMs);
|
|
771
|
-
if (overlapEnd > overlapStart) {
|
|
772
|
-
total += overlapEnd - overlapStart;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
localDay += 864e5;
|
|
911
|
+
if (env.FORCE_COLOR === "true" || env.FORCE_COLOR.length === 0) {
|
|
912
|
+
return 1;
|
|
777
913
|
}
|
|
778
|
-
|
|
914
|
+
if (!hasNumericForceColor()) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
return Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
779
918
|
}
|
|
780
|
-
function
|
|
781
|
-
if (
|
|
782
|
-
return
|
|
919
|
+
function translateLevel(level) {
|
|
920
|
+
if (level === 0) {
|
|
921
|
+
return false;
|
|
783
922
|
}
|
|
784
|
-
return
|
|
923
|
+
return {
|
|
924
|
+
level,
|
|
925
|
+
hasBasic: true,
|
|
926
|
+
has256: level >= 2,
|
|
927
|
+
has16m: level >= 3
|
|
928
|
+
};
|
|
785
929
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
} else if (result.kind === "pending" && result.pr.state === "open") {
|
|
802
|
-
pending.push({ pr: result.pr, requestedAt: result.requestedAt, hours: durationHours(result.requestedAt, now) });
|
|
930
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
931
|
+
const noFlagForceColor = envForceColor();
|
|
932
|
+
if (noFlagForceColor !== void 0) {
|
|
933
|
+
flagForceColor = noFlagForceColor;
|
|
934
|
+
}
|
|
935
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
936
|
+
if (forceColor === 0) {
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
939
|
+
if (sniffFlags) {
|
|
940
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
941
|
+
return 3;
|
|
942
|
+
}
|
|
943
|
+
if (hasFlag("color=256")) {
|
|
944
|
+
return 2;
|
|
803
945
|
}
|
|
804
946
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
947
|
+
if (forceColor !== void 0 && hasNumericForceColor()) {
|
|
948
|
+
return forceColor;
|
|
949
|
+
}
|
|
950
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
951
|
+
return 1;
|
|
952
|
+
}
|
|
953
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
954
|
+
return 0;
|
|
955
|
+
}
|
|
956
|
+
const min = forceColor || 0;
|
|
957
|
+
if (env.TERM === "dumb") {
|
|
958
|
+
return min;
|
|
959
|
+
}
|
|
960
|
+
if (process2.platform === "win32") {
|
|
961
|
+
const osRelease = os.release().split(".");
|
|
962
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
963
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
811
964
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
965
|
+
return 1;
|
|
966
|
+
}
|
|
967
|
+
if ("CI" in env) {
|
|
968
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
969
|
+
return 3;
|
|
815
970
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
latestReviews.set(key, { pr: result.pr, reviewedAt: result.reviewedAt });
|
|
971
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
972
|
+
return 1;
|
|
819
973
|
}
|
|
974
|
+
return min;
|
|
820
975
|
}
|
|
821
|
-
|
|
822
|
-
return {
|
|
823
|
-
}).toSorted((a, b) => a.reviewedAt.getTime() - b.reviewedAt.getTime());
|
|
824
|
-
const expired = results.filter((result) => result.kind === "pending" && result.pr.state !== "open");
|
|
825
|
-
const unrequested = results.filter((result) => result.kind === "unrequested");
|
|
826
|
-
const allHours = reviewed.map((result) => result.hours);
|
|
827
|
-
const hoursByRepo = /* @__PURE__ */ new Map();
|
|
828
|
-
for (const result of reviewed) {
|
|
829
|
-
const hours = hoursByRepo.get(result.pr.repo) ?? [];
|
|
830
|
-
hours.push(result.hours);
|
|
831
|
-
hoursByRepo.set(result.pr.repo, hours);
|
|
976
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
977
|
+
return /^(?:9\.0*[1-9]\d*\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
832
978
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const cyclesByPr = /* @__PURE__ */ new Map();
|
|
836
|
-
for (const result of reviewed) {
|
|
837
|
-
const key = `${result.pr.repo}#${result.pr.number}`;
|
|
838
|
-
cyclesByPr.set(key, (cyclesByPr.get(key) ?? 0) + 1);
|
|
979
|
+
if (env.COLORTERM === "truecolor") {
|
|
980
|
+
return 3;
|
|
839
981
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
pending,
|
|
843
|
-
reviewing,
|
|
844
|
-
expired,
|
|
845
|
-
unrequested,
|
|
846
|
-
allHours,
|
|
847
|
-
byRepo,
|
|
848
|
-
misses,
|
|
849
|
-
cycles: [...cyclesByPr.values()]
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
function computeSizeStats(sizes, { sizeTarget } = {}) {
|
|
853
|
-
const metrics = [
|
|
854
|
-
{ label: "files changed", values: sizes.map((size) => size.files) },
|
|
855
|
-
{ label: "lines added", values: sizes.map((size) => size.additions) },
|
|
856
|
-
{ label: "lines removed", values: sizes.map((size) => size.deletions) },
|
|
857
|
-
{ label: "lines total", values: sizes.map((size) => size.total) }
|
|
858
|
-
];
|
|
859
|
-
const timelineTotals = [...sizes].toSorted((a, b) => a.pr.createdAt.getTime() - b.pr.createdAt.getTime()).map((size) => size.total);
|
|
860
|
-
if (sizeTarget === void 0) {
|
|
861
|
-
return { metrics, timelineTotals, met: void 0, misses: [], targetLabel: void 0 };
|
|
982
|
+
if (env.TERM === "xterm-kitty") {
|
|
983
|
+
return 3;
|
|
862
984
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
...sizeTarget.lines === void 0 ? [] : [`<= ${sizeTarget.lines} lines`],
|
|
866
|
-
...sizeTarget.files === void 0 ? [] : [`<= ${sizeTarget.files} files`]
|
|
867
|
-
].join(", ");
|
|
868
|
-
const met = sizes.filter((size) => meetsTarget(size)).length;
|
|
869
|
-
const misses = sizes.filter((size) => !meetsTarget(size)).toSorted((a, b) => b.total - a.total);
|
|
870
|
-
return { metrics, timelineTotals, met, misses, targetLabel };
|
|
871
|
-
}
|
|
872
|
-
function computeMergeStats(sizes) {
|
|
873
|
-
const merged = [];
|
|
874
|
-
const closed = [];
|
|
875
|
-
const open = [];
|
|
876
|
-
for (const entry of sizes) {
|
|
877
|
-
if (entry.mergedAt !== null) {
|
|
878
|
-
merged.push({ entry, mergedAt: entry.mergedAt, hours: durationHours(entry.pr.createdAt, entry.mergedAt) });
|
|
879
|
-
} else if (entry.pr.state === "open") {
|
|
880
|
-
open.push(entry);
|
|
881
|
-
} else if (entry.closedAt !== null) {
|
|
882
|
-
closed.push({ entry, closedAt: entry.closedAt, hours: durationHours(entry.pr.createdAt, entry.closedAt) });
|
|
883
|
-
}
|
|
985
|
+
if (env.TERM === "xterm-ghostty") {
|
|
986
|
+
return 3;
|
|
884
987
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
for (const entry of sizes) {
|
|
894
|
-
const others = entry.reviews.flatMap(
|
|
895
|
-
(review) => review.login === null || review.login === author ? [] : [review.login]
|
|
896
|
-
);
|
|
897
|
-
for (const login of others) {
|
|
898
|
-
const counts = byLogin.get(login) ?? { prs: 0, reviews: 0 };
|
|
899
|
-
counts.reviews += 1;
|
|
900
|
-
byLogin.set(login, counts);
|
|
901
|
-
}
|
|
902
|
-
const distinct = new Set(others);
|
|
903
|
-
for (const login of distinct) {
|
|
904
|
-
const counts = byLogin.get(login);
|
|
905
|
-
if (counts !== void 0) {
|
|
906
|
-
counts.prs += 1;
|
|
988
|
+
if (env.TERM === "wezterm") {
|
|
989
|
+
return 3;
|
|
990
|
+
}
|
|
991
|
+
if ("TERM_PROGRAM" in env) {
|
|
992
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".", 1)[0], 10);
|
|
993
|
+
switch (env.TERM_PROGRAM) {
|
|
994
|
+
case "iTerm.app": {
|
|
995
|
+
return version >= 3 ? 3 : 2;
|
|
907
996
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (others.length > 0) {
|
|
911
|
-
mergedReviewed += 1;
|
|
912
|
-
} else {
|
|
913
|
-
mergedUnreviewed += 1;
|
|
997
|
+
case "Apple_Terminal": {
|
|
998
|
+
return 2;
|
|
914
999
|
}
|
|
915
1000
|
}
|
|
916
1001
|
}
|
|
917
|
-
|
|
918
|
-
return
|
|
919
|
-
}).toSorted((a, b) => b.prs - a.prs || b.reviews - a.reviews || a.login.localeCompare(b.login));
|
|
920
|
-
return { leaderboard, mergedReviewed, mergedUnreviewed };
|
|
921
|
-
}
|
|
922
|
-
function firstReviewOf(entry, author) {
|
|
923
|
-
let earliest = null;
|
|
924
|
-
for (const review of entry.reviews) {
|
|
925
|
-
if (review.login === null || review.login === author || review.submittedAt === null) {
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
if (earliest === null || review.submittedAt < earliest) {
|
|
929
|
-
earliest = review.submittedAt;
|
|
930
|
-
}
|
|
1002
|
+
if (/-256(?:color)?$/i.test(env.TERM)) {
|
|
1003
|
+
return 2;
|
|
931
1004
|
}
|
|
932
|
-
if (
|
|
933
|
-
return
|
|
1005
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
1006
|
+
return 1;
|
|
934
1007
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
function computeFirstReviewStats(sizes, author, { now = /* @__PURE__ */ new Date() } = {}) {
|
|
938
|
-
const received = [];
|
|
939
|
-
const awaiting = [];
|
|
940
|
-
for (const entry of sizes) {
|
|
941
|
-
const first = firstReviewOf(entry, author);
|
|
942
|
-
if (first !== null) {
|
|
943
|
-
received.push({ entry, ...first });
|
|
944
|
-
} else if (entry.pr.state === "open") {
|
|
945
|
-
awaiting.push({ entry, hours: durationHours(entry.pr.createdAt, now) });
|
|
946
|
-
}
|
|
1008
|
+
if ("COLORTERM" in env) {
|
|
1009
|
+
return 1;
|
|
947
1010
|
}
|
|
948
|
-
|
|
949
|
-
return { received, awaiting, allHours: received.map((result) => result.hours) };
|
|
1011
|
+
return min;
|
|
950
1012
|
}
|
|
951
|
-
function
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
const totals = sizes.map((size) => size.comments.total);
|
|
958
|
-
const uncommented = totals.filter((total) => total === 0).length;
|
|
959
|
-
const top = sizes.filter((size) => size.comments.total > 0).toSorted((a, b) => b.comments.total - a.comments.total);
|
|
960
|
-
return { metrics, totals, uncommented, top };
|
|
1013
|
+
function createSupportsColor(stream, options = {}) {
|
|
1014
|
+
const level = _supportsColor(stream, {
|
|
1015
|
+
streamIsTTY: stream && stream.isTTY,
|
|
1016
|
+
...options
|
|
1017
|
+
});
|
|
1018
|
+
return translateLevel(level);
|
|
961
1019
|
}
|
|
1020
|
+
var supportsColor = {
|
|
1021
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
1022
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
1023
|
+
};
|
|
1024
|
+
var supports_color_default = supportsColor;
|
|
962
1025
|
|
|
963
|
-
//
|
|
964
|
-
var
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
return [
|
|
974
|
-
{ label: "< 1h", max: 1 },
|
|
975
|
-
{ label: "1-4h", max: 4 },
|
|
976
|
-
{ label: "4-8h", max: 8 },
|
|
977
|
-
{ label: "8-24h", max: 24 },
|
|
978
|
-
{ label: "1-2d", max: 48 },
|
|
979
|
-
{ label: "2-4d", max: 96 },
|
|
980
|
-
{ label: "4-7d", max: 168 },
|
|
981
|
-
{ label: "> 7d", max: Infinity }
|
|
982
|
-
];
|
|
1026
|
+
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/index.js
|
|
1027
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
1028
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
1029
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
1030
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
1031
|
+
var LEVEL = /* @__PURE__ */ Symbol("LEVEL");
|
|
1032
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
1033
|
+
var assertValidLevel = (level) => {
|
|
1034
|
+
if (!Number.isSafeInteger(level) || level < 0 || level > 3) {
|
|
1035
|
+
throw new Error("The `level` should be an integer from 0 to 3");
|
|
983
1036
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
{ label: "> 10wd", max: Infinity }
|
|
994
|
-
];
|
|
995
|
-
}
|
|
996
|
-
var LINE_BUCKETS = [
|
|
997
|
-
{ label: "< 50", max: 50 },
|
|
998
|
-
{ label: "50-100", max: 100 },
|
|
999
|
-
{ label: "100-250", max: 250 },
|
|
1000
|
-
{ label: "250-500", max: 500 },
|
|
1001
|
-
{ label: "500-1k", max: 1e3 },
|
|
1002
|
-
{ label: "1k-2.5k", max: 2500 },
|
|
1003
|
-
{ label: "2.5k-5k", max: 5e3 },
|
|
1004
|
-
{ label: "> 5k", max: Infinity }
|
|
1005
|
-
];
|
|
1006
|
-
var FILE_BUCKETS = [
|
|
1007
|
-
{ label: "1-2", max: 3 },
|
|
1008
|
-
{ label: "3-5", max: 6 },
|
|
1009
|
-
{ label: "6-10", max: 11 },
|
|
1010
|
-
{ label: "11-20", max: 21 },
|
|
1011
|
-
{ label: "21-50", max: 51 },
|
|
1012
|
-
{ label: "> 50", max: Infinity }
|
|
1013
|
-
];
|
|
1014
|
-
var CYCLE_BUCKETS = [
|
|
1015
|
-
{ label: "1", max: 2 },
|
|
1016
|
-
{ label: "2", max: 3 },
|
|
1017
|
-
{ label: "3", max: 4 },
|
|
1018
|
-
{ label: "4-5", max: 6 },
|
|
1019
|
-
{ label: "> 5", max: Infinity }
|
|
1020
|
-
];
|
|
1021
|
-
var COMMENT_BUCKETS = [
|
|
1022
|
-
{ label: "0", max: 1 },
|
|
1023
|
-
{ label: "1-2", max: 3 },
|
|
1024
|
-
{ label: "3-5", max: 6 },
|
|
1025
|
-
{ label: "6-10", max: 11 },
|
|
1026
|
-
{ label: "11-20", max: 21 },
|
|
1027
|
-
{ label: "21-50", max: 51 },
|
|
1028
|
-
{ label: "> 50", max: Infinity }
|
|
1029
|
-
];
|
|
1030
|
-
function formatHoursOnly(hours) {
|
|
1031
|
-
if (hours < 1) {
|
|
1032
|
-
return `${Math.round(hours * 60)}m`;
|
|
1037
|
+
};
|
|
1038
|
+
var levelDescriptor = {
|
|
1039
|
+
enumerable: true,
|
|
1040
|
+
get() {
|
|
1041
|
+
return this[LEVEL];
|
|
1042
|
+
},
|
|
1043
|
+
set(level) {
|
|
1044
|
+
assertValidLevel(level);
|
|
1045
|
+
this[LEVEL] = level;
|
|
1033
1046
|
}
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
if (hours < weekHours) {
|
|
1039
|
-
return "";
|
|
1047
|
+
};
|
|
1048
|
+
var applyOptions = (object, options = {}) => {
|
|
1049
|
+
if (options.level !== void 0) {
|
|
1050
|
+
assertValidLevel(options.level);
|
|
1040
1051
|
}
|
|
1041
|
-
|
|
1052
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
1053
|
+
object[LEVEL] = options.level === void 0 ? colorLevel : options.level;
|
|
1054
|
+
};
|
|
1055
|
+
var chalkFactory = (options) => {
|
|
1056
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
1057
|
+
applyOptions(chalk2, options);
|
|
1058
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
1059
|
+
return chalk2;
|
|
1060
|
+
};
|
|
1061
|
+
function createChalk(options) {
|
|
1062
|
+
return chalkFactory(options);
|
|
1042
1063
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1064
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
1065
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
1066
|
+
styles2[styleName] = {
|
|
1067
|
+
get() {
|
|
1068
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
1069
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
1070
|
+
return builder;
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
styles2.visible = {
|
|
1075
|
+
get() {
|
|
1076
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
1077
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
1078
|
+
return builder;
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
var createModelConverters = (model, type) => {
|
|
1082
|
+
const style = ansi_styles_default[type];
|
|
1083
|
+
if (model === "rgb") {
|
|
1084
|
+
const ansi2 = (red, green, blue) => style.ansi(ansi_styles_default.rgbToAnsi(red, green, blue));
|
|
1085
|
+
const ansi256 = (red, green, blue) => style.ansi256(ansi_styles_default.rgbToAnsi256(red, green, blue));
|
|
1086
|
+
return [ansi2, ansi2, ansi256, style.ansi16m];
|
|
1087
|
+
}
|
|
1088
|
+
if (model === "hex") {
|
|
1089
|
+
const ansi2 = (hex) => style.ansi(ansi_styles_default.hexToAnsi(hex));
|
|
1090
|
+
const ansi256 = (hex) => style.ansi256(ansi_styles_default.hexToAnsi256(hex));
|
|
1091
|
+
return [ansi2, ansi2, ansi256, (hex) => style.ansi16m(...ansi_styles_default.hexToRgb(hex))];
|
|
1092
|
+
}
|
|
1093
|
+
const ansi = (code) => style.ansi(ansi_styles_default.ansi256ToAnsi(code));
|
|
1094
|
+
return [ansi, ansi, style.ansi256, style.ansi256];
|
|
1095
|
+
};
|
|
1096
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
1097
|
+
for (const model of usedModels) {
|
|
1098
|
+
const capitalizedModel = model[0].toUpperCase() + model.slice(1);
|
|
1099
|
+
for (const [styleName, type] of [
|
|
1100
|
+
[model, "color"],
|
|
1101
|
+
["bg" + capitalizedModel, "bgColor"],
|
|
1102
|
+
["underline" + capitalizedModel, "underlineColor"]
|
|
1103
|
+
]) {
|
|
1104
|
+
const { close } = ansi_styles_default[type];
|
|
1105
|
+
const converters = createModelConverters(model, type);
|
|
1106
|
+
styles2[styleName] = {
|
|
1107
|
+
get() {
|
|
1108
|
+
const styleFunction = function(first, second, third) {
|
|
1109
|
+
const open = converters[this.level](first, second, third);
|
|
1110
|
+
return createBuilder(this, createStyler(open, close, this[STYLER]), this[IS_EMPTY]);
|
|
1111
|
+
};
|
|
1112
|
+
Object.defineProperty(this, styleName, { value: styleFunction });
|
|
1113
|
+
return styleFunction;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1046
1116
|
}
|
|
1047
|
-
const scaled = value2 / 1e3;
|
|
1048
|
-
return `${scaled >= 10 ? Math.round(scaled) : scaled.toFixed(1)}k`;
|
|
1049
1117
|
}
|
|
1118
|
+
var proto = Object.defineProperties(
|
|
1119
|
+
() => {
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
...styles2,
|
|
1123
|
+
level: {
|
|
1124
|
+
enumerable: true,
|
|
1125
|
+
get() {
|
|
1126
|
+
return this[GENERATOR].level;
|
|
1127
|
+
},
|
|
1128
|
+
set(level) {
|
|
1129
|
+
this[GENERATOR].level = level;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
);
|
|
1134
|
+
var createStyler = (open, close, parent) => {
|
|
1135
|
+
let openAll;
|
|
1136
|
+
let closeAll;
|
|
1137
|
+
if (parent === void 0) {
|
|
1138
|
+
openAll = open;
|
|
1139
|
+
closeAll = close;
|
|
1140
|
+
} else {
|
|
1141
|
+
openAll = parent.openAll + open;
|
|
1142
|
+
closeAll = close + parent.closeAll;
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
open,
|
|
1146
|
+
close,
|
|
1147
|
+
openAll,
|
|
1148
|
+
closeAll,
|
|
1149
|
+
parent
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1152
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
1153
|
+
const builder = (...arguments_) => {
|
|
1154
|
+
if (arguments_.length === 1) {
|
|
1155
|
+
return applyStyle(builder, "" + arguments_[0]);
|
|
1156
|
+
}
|
|
1157
|
+
if (arguments_.length === 2) {
|
|
1158
|
+
return applyStyle(builder, arguments_[0] + " " + arguments_[1]);
|
|
1159
|
+
}
|
|
1160
|
+
return applyStyle(builder, arguments_.join(" "));
|
|
1161
|
+
};
|
|
1162
|
+
Object.setPrototypeOf(builder, proto);
|
|
1163
|
+
builder[GENERATOR] = self[GENERATOR] ?? self;
|
|
1164
|
+
builder[STYLER] = _styler;
|
|
1165
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
1166
|
+
return builder;
|
|
1167
|
+
};
|
|
1168
|
+
var applyStyle = (self, string) => {
|
|
1169
|
+
if (self[GENERATOR][LEVEL] <= 0 || !string) {
|
|
1170
|
+
return self[IS_EMPTY] ? "" : string;
|
|
1171
|
+
}
|
|
1172
|
+
let styler = self[STYLER];
|
|
1173
|
+
if (styler === void 0) {
|
|
1174
|
+
return string;
|
|
1175
|
+
}
|
|
1176
|
+
const { openAll, closeAll } = styler;
|
|
1177
|
+
if (string.includes("\x1B")) {
|
|
1178
|
+
while (styler !== void 0) {
|
|
1179
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
1180
|
+
styler = styler.parent;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
const lfIndex = string.indexOf("\n");
|
|
1184
|
+
if (lfIndex !== -1) {
|
|
1185
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
1186
|
+
}
|
|
1187
|
+
return openAll + string + closeAll;
|
|
1188
|
+
};
|
|
1189
|
+
Object.defineProperties(createChalk.prototype, { ...styles2, level: levelDescriptor });
|
|
1190
|
+
var chalk = createChalk();
|
|
1191
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
1192
|
+
var source_default = chalk;
|
|
1050
1193
|
|
|
1051
|
-
// src/
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
function
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1194
|
+
// src/flags.ts
|
|
1195
|
+
import { parseArgs } from "node:util";
|
|
1196
|
+
|
|
1197
|
+
// src/time.ts
|
|
1198
|
+
function wallFormatter(tz) {
|
|
1199
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
1200
|
+
timeZone: tz,
|
|
1201
|
+
hourCycle: "h23",
|
|
1202
|
+
year: "numeric",
|
|
1203
|
+
month: "2-digit",
|
|
1204
|
+
day: "2-digit",
|
|
1205
|
+
hour: "2-digit",
|
|
1206
|
+
minute: "2-digit",
|
|
1207
|
+
second: "2-digit"
|
|
1064
1208
|
});
|
|
1065
1209
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1210
|
+
var timeMode = {
|
|
1211
|
+
business: true,
|
|
1212
|
+
workWindows: [{ startMin: 0, endMin: 24 * 60 }],
|
|
1213
|
+
workDays: /* @__PURE__ */ new Set([1, 2, 3, 4, 5]),
|
|
1214
|
+
dayHours: 24,
|
|
1215
|
+
formatter: wallFormatter("UTC")
|
|
1216
|
+
};
|
|
1217
|
+
function configureTimeMode({
|
|
1218
|
+
business,
|
|
1219
|
+
workWindows,
|
|
1220
|
+
workDays,
|
|
1221
|
+
tz
|
|
1222
|
+
}) {
|
|
1223
|
+
const workMinutesPerDay = workWindows.reduce((sum, window) => sum + (window.endMin - window.startMin), 0);
|
|
1224
|
+
timeMode.business = business;
|
|
1225
|
+
timeMode.workWindows = workWindows;
|
|
1226
|
+
timeMode.workDays = workDays;
|
|
1227
|
+
timeMode.dayHours = business ? workMinutesPerDay / 60 : 24;
|
|
1228
|
+
timeMode.formatter = wallFormatter(tz);
|
|
1070
1229
|
}
|
|
1071
|
-
function
|
|
1072
|
-
|
|
1073
|
-
for (const entry of entries) {
|
|
1074
|
-
const group = groups.get(entry.pr.repo) ?? [];
|
|
1075
|
-
group.push(entry);
|
|
1076
|
-
groups.set(entry.pr.repo, group);
|
|
1077
|
-
}
|
|
1078
|
-
return [...groups.entries()].toSorted((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])).map(([repo, group]) => {
|
|
1079
|
-
return { title: `${repo} (n=${group.length})`, rows: rowsOf2(group) };
|
|
1080
|
-
});
|
|
1230
|
+
function isFullDayMode() {
|
|
1231
|
+
return timeMode.business && timeMode.dayHours === 24;
|
|
1081
1232
|
}
|
|
1082
|
-
function
|
|
1083
|
-
const
|
|
1084
|
-
const awaiting = repo === null ? stats.pending : stats.pending.filter((entry) => entry.pr.repo === repo);
|
|
1085
|
-
const reviewing = repo === null ? stats.reviewing : stats.reviewing.filter((entry) => entry.pr.repo === repo);
|
|
1086
|
-
if (awaiting.length === 0 && reviewing.length === 0) {
|
|
1087
|
-
return { empty: "No PRs are awaiting your review, and none you reviewed are still open.", sections: [] };
|
|
1088
|
-
}
|
|
1089
|
-
const split = repo === null && grouped;
|
|
1090
|
-
const sectionOf = (title, entries) => split ? { title, rows: [], lists: groupedLists(entries, rowsOf) } : { title, rows: rowsOf(entries), lists: [] };
|
|
1233
|
+
function wallParts(instantMs) {
|
|
1234
|
+
const parts = Object.fromEntries(timeMode.formatter.formatToParts(instantMs).map((part) => [part.type, part.value]));
|
|
1091
1235
|
return {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1236
|
+
year: Number(parts.year),
|
|
1237
|
+
month: Number(parts.month),
|
|
1238
|
+
day: Number(parts.day),
|
|
1239
|
+
hour: Number(parts.hour),
|
|
1240
|
+
minute: Number(parts.minute),
|
|
1241
|
+
second: Number(parts.second)
|
|
1097
1242
|
};
|
|
1098
1243
|
}
|
|
1099
|
-
function
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1244
|
+
function zonedStamp(date) {
|
|
1245
|
+
const parts = wallParts(date.getTime());
|
|
1246
|
+
const dayUtcMs = Date.UTC(parts.year, parts.month - 1, parts.day);
|
|
1247
|
+
return { dayUtcMs, weekday: new Date(dayUtcMs).getUTCDay(), hour: parts.hour, minute: parts.minute };
|
|
1248
|
+
}
|
|
1249
|
+
function hasWorkWindows() {
|
|
1250
|
+
const covered = timeMode.workWindows.reduce((sum, window) => sum + (window.endMin - window.startMin), 0);
|
|
1251
|
+
return covered < 24 * 60;
|
|
1252
|
+
}
|
|
1253
|
+
function classifyInstant(date) {
|
|
1254
|
+
const { weekday, hour, minute } = zonedStamp(date);
|
|
1255
|
+
if (!timeMode.workDays.has(weekday)) {
|
|
1256
|
+
return "weekend";
|
|
1103
1257
|
}
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
);
|
|
1113
|
-
};
|
|
1114
|
-
const title = `Your open authored PRs (n=${open.length})`;
|
|
1115
|
-
if (repo === null && grouped) {
|
|
1116
|
-
return { empty: null, sections: [{ title, rows: [], lists: groupedLists(open, rowsOf2) }] };
|
|
1258
|
+
const minuteOfDay = hour * 60 + minute;
|
|
1259
|
+
return timeMode.workWindows.some((window) => minuteOfDay >= window.startMin && minuteOfDay < window.endMin) ? "work" : "after";
|
|
1260
|
+
}
|
|
1261
|
+
function utcFromWall(wallTargetMs) {
|
|
1262
|
+
let guess = wallTargetMs;
|
|
1263
|
+
for (let i = 0; i < 2; i++) {
|
|
1264
|
+
const parts = wallParts(guess);
|
|
1265
|
+
const wall = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
1266
|
+
guess = wallTargetMs - (wall - guess);
|
|
1117
1267
|
}
|
|
1118
|
-
return
|
|
1268
|
+
return guess;
|
|
1119
1269
|
}
|
|
1120
|
-
function
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
function useScrollbarSettle(scrollRef, mounted = true) {
|
|
1141
|
-
const renderer2 = useRenderer();
|
|
1142
|
-
useLayoutEffect(() => {
|
|
1143
|
-
if (!mounted) {
|
|
1144
|
-
return void 0;
|
|
1145
|
-
}
|
|
1146
|
-
if (!scrollRef.current?.verticalScrollBar) {
|
|
1147
|
-
return void 0;
|
|
1148
|
-
}
|
|
1149
|
-
scrollRef.current.verticalScrollBar.visible = false;
|
|
1150
|
-
const release = () => {
|
|
1151
|
-
renderer2.off(CliRenderEvents.FRAME, release);
|
|
1152
|
-
scrollRef.current?.verticalScrollBar.resetVisibilityControl();
|
|
1153
|
-
};
|
|
1154
|
-
const resyncThumb = () => {
|
|
1155
|
-
const bar = scrollRef.current?.verticalScrollBar;
|
|
1156
|
-
if (!bar) {
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
const height = bar.viewportSize;
|
|
1160
|
-
const wanted = Math.max(1, height);
|
|
1161
|
-
if (bar.scrollSize < wanted || bar.slider.viewPortSize === wanted) {
|
|
1162
|
-
return;
|
|
1270
|
+
function businessMsBetween(start, end) {
|
|
1271
|
+
const startMs = start.getTime();
|
|
1272
|
+
const endMs = end.getTime();
|
|
1273
|
+
if (endMs <= startMs) {
|
|
1274
|
+
return 0;
|
|
1275
|
+
}
|
|
1276
|
+
let total = 0;
|
|
1277
|
+
const parts = wallParts(startMs);
|
|
1278
|
+
let localDay = Date.UTC(parts.year, parts.month - 1, parts.day);
|
|
1279
|
+
while (utcFromWall(localDay) <= endMs) {
|
|
1280
|
+
const weekday = new Date(localDay).getUTCDay();
|
|
1281
|
+
if (timeMode.workDays.has(weekday)) {
|
|
1282
|
+
for (const window of timeMode.workWindows) {
|
|
1283
|
+
const windowStart = utcFromWall(localDay + window.startMin * 6e4);
|
|
1284
|
+
const windowEnd = utcFromWall(localDay + window.endMin * 6e4);
|
|
1285
|
+
const overlapStart = Math.max(windowStart, startMs);
|
|
1286
|
+
const overlapEnd = Math.min(windowEnd, endMs);
|
|
1287
|
+
if (overlapEnd > overlapStart) {
|
|
1288
|
+
total += overlapEnd - overlapStart;
|
|
1289
|
+
}
|
|
1163
1290
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
renderer2.on(CliRenderEvents.FRAME, resyncThumb);
|
|
1169
|
-
return () => {
|
|
1170
|
-
renderer2.off(CliRenderEvents.FRAME, release);
|
|
1171
|
-
renderer2.off(CliRenderEvents.FRAME, resyncThumb);
|
|
1172
|
-
};
|
|
1173
|
-
}, [scrollRef, renderer2, mounted]);
|
|
1291
|
+
}
|
|
1292
|
+
localDay += 864e5;
|
|
1293
|
+
}
|
|
1294
|
+
return total;
|
|
1174
1295
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1296
|
+
function durationHours(start, end) {
|
|
1297
|
+
if (!timeMode.business) {
|
|
1298
|
+
return (end.getTime() - start.getTime()) / 36e5;
|
|
1299
|
+
}
|
|
1300
|
+
return businessMsBetween(start, end) / 36e5;
|
|
1179
1301
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1302
|
+
|
|
1303
|
+
// src/flags.ts
|
|
1304
|
+
var flag = source_default.green;
|
|
1305
|
+
var value = source_default.cyan;
|
|
1306
|
+
var dim = source_default.dim;
|
|
1307
|
+
var OPTIONS = [
|
|
1308
|
+
{
|
|
1309
|
+
name: "since",
|
|
1310
|
+
short: "s",
|
|
1311
|
+
type: "string",
|
|
1312
|
+
default: "90d",
|
|
1313
|
+
placeholder: "<value>",
|
|
1314
|
+
help: "Only include PRs created after this point. Accepts an ISO date (`2026-01-01`) or a relative value (`30d`, `8w`, `6m`, `1y`). The default is `90d`."
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
name: "repo",
|
|
1318
|
+
short: "r",
|
|
1319
|
+
type: "string",
|
|
1320
|
+
multiple: true,
|
|
1321
|
+
default: [],
|
|
1322
|
+
placeholder: "<name>",
|
|
1323
|
+
help: 'Restrict the search to a repository. Repeat the flag for multiple repositories. Accepts "owner/name" or a bare name, which resolves against the owner of the repo in the current directory. Without this flag, all repositories you have access to are searched.'
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "user",
|
|
1327
|
+
short: "u",
|
|
1328
|
+
type: "string",
|
|
1329
|
+
placeholder: "<login>",
|
|
1330
|
+
help: "Compute stats for this user. Defaults to the authenticated user."
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
name: "token",
|
|
1334
|
+
type: "string",
|
|
1335
|
+
placeholder: "<token>",
|
|
1336
|
+
help: "GitHub access token. When set, the tool calls the GitHub API directly instead of going through the gh CLI. The `GITHUB_TOKEN` and `GH_TOKEN` environment variables work as well."
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
name: "target",
|
|
1340
|
+
short: "t",
|
|
1341
|
+
type: "string",
|
|
1342
|
+
placeholder: "<value>",
|
|
1343
|
+
help: "Report how many reviews finished within this time. Accepts hours (`24h` or plain `24`), minutes (`90m`), or days (`2d`). A day means 24 counted hours, or one working day when --work-hours is set. Open PRs that have already waited longer than the target count as misses."
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
name: "target-percentile",
|
|
1347
|
+
type: "string",
|
|
1348
|
+
placeholder: "<p>",
|
|
1349
|
+
help: "Check the --target against this percentile of your review times. Accepts a whole percentile from 1 to 100, with an optional p prefix (`90` or `p90`). The default is `90`, so the review headline reports whether your p90 review time meets the target."
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
name: "size-target",
|
|
1353
|
+
type: "string",
|
|
1354
|
+
placeholder: "<v>",
|
|
1355
|
+
help: "Report how many authored PRs fit within this size. Accepts a line budget (`400` or `400l`), a file budget (`20f`), or both (`400l,20f`). Lines count additions plus deletions."
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
name: "tz",
|
|
1359
|
+
type: "string",
|
|
1360
|
+
placeholder: "<zone>",
|
|
1361
|
+
help: "IANA timezone for the weekend and working-hours math, for example `Europe/Berlin`. Defaults to your system timezone."
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
name: "work-days",
|
|
1365
|
+
type: "string",
|
|
1366
|
+
default: "mon-fri",
|
|
1367
|
+
placeholder: "<days>",
|
|
1368
|
+
help: "Count only these days as working days. Accepts comma-separated weekday names and ranges like `mon-fri`, `sun-thu`, or `mon,wed,fri`. A range may wrap around the end of the week, so `sat-wed` works too. Days outside the set count as weekend."
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
name: "work-hours",
|
|
1372
|
+
short: "w",
|
|
1373
|
+
type: "string",
|
|
1374
|
+
default: "0-24",
|
|
1375
|
+
placeholder: "<v>",
|
|
1376
|
+
help: "Count only these working hours instead of full working days. Accepts 24-hour ranges like `9-17` or `8:30-16:30`, 12-hour ranges like `9am-6pm` or `8:30am-4:30pm`, or several comma-separated ranges like `9-18,19:30-20:30`. Without this flag, every hour on a working day counts."
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
name: "wall-clock",
|
|
1380
|
+
type: "boolean",
|
|
1381
|
+
default: false,
|
|
1382
|
+
help: "Measure raw elapsed time, including weekends."
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "include-drafts",
|
|
1386
|
+
type: "boolean",
|
|
1387
|
+
default: false,
|
|
1388
|
+
help: "Include PRs that are currently drafts. Excluded by default."
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
name: "review-types",
|
|
1392
|
+
type: "string",
|
|
1393
|
+
placeholder: "<list>",
|
|
1394
|
+
help: "Count only these review types as a review. Accepts a comma-separated list of `approve`, `comment`, and `request-changes`, for example `approve,request-changes`. A review of another type never answers a request, so the PR stays in the awaiting queue until a counted review lands. Without this flag, every submitted review counts."
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
name: "no-cache",
|
|
1398
|
+
type: "boolean",
|
|
1399
|
+
default: false,
|
|
1400
|
+
help: "Refetch every PR instead of reading the local disk cache. Closed PRs are normally served from a per-PR cache because their timelines and sizes no longer change. Fresh results still update the cache. The TUI settings dialog can save this behavior for every run, and the flag wins over the saved setting."
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
name: "json",
|
|
1404
|
+
type: "boolean",
|
|
1405
|
+
default: false,
|
|
1406
|
+
help: "Print every stat as JSON to stdout instead of starting the TUI, so the output can be piped into jq or redirected to a file. The report holds the review, size, merge, reviewer, and comment stats with one entry per PR, and every other flag applies to it the same way. Progress renders on stderr, so a piped stdout stays pure JSON."
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
name: "debug",
|
|
1410
|
+
type: "string",
|
|
1411
|
+
placeholder: "<path>",
|
|
1412
|
+
help: "Serve canned data from a fake gh binary instead of fetching from GitHub. Accepts a path to a testdata directory that contains a `gh` executable, for example `tui/testdata`, or a path to the executable itself. Tokens are ignored while this flag is set. This is useful for testing and local development."
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: "help",
|
|
1416
|
+
short: "h",
|
|
1417
|
+
type: "boolean",
|
|
1418
|
+
default: false,
|
|
1419
|
+
help: "Show this help."
|
|
1420
|
+
}
|
|
1421
|
+
];
|
|
1422
|
+
var HELP_WIDTH = Math.min(process.stdout.isTTY ? process.stdout.columns : 80, 120);
|
|
1423
|
+
function colorizeHelp(text) {
|
|
1424
|
+
return text.replaceAll(/`([^`]+)`/g, (match, span) => value(span)).replaceAll(/"[^"]*"/g, (span) => source_default.yellow(span)).replaceAll(/--[a-z][a-z-]*/g, (span) => flag(span));
|
|
1183
1425
|
}
|
|
1184
|
-
function
|
|
1185
|
-
|
|
1186
|
-
|
|
1426
|
+
function wrapMarkup(text, width) {
|
|
1427
|
+
const lines = [];
|
|
1428
|
+
let line = "";
|
|
1429
|
+
for (const word of text.split(" ")) {
|
|
1430
|
+
const candidate = line === "" ? word : `${line} ${word}`;
|
|
1431
|
+
if (line !== "" && candidate.replaceAll("`", "").length > width) {
|
|
1432
|
+
lines.push(line);
|
|
1433
|
+
line = word;
|
|
1434
|
+
} else {
|
|
1435
|
+
line = candidate;
|
|
1436
|
+
}
|
|
1187
1437
|
}
|
|
1188
|
-
if (
|
|
1189
|
-
|
|
1438
|
+
if (line !== "") {
|
|
1439
|
+
lines.push(line);
|
|
1190
1440
|
}
|
|
1191
|
-
return
|
|
1192
|
-
}
|
|
1193
|
-
function compact(value2) {
|
|
1194
|
-
return String(Math.round(value2 * 10) / 10);
|
|
1441
|
+
return lines;
|
|
1195
1442
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
scrollRef,
|
|
1202
|
-
focused,
|
|
1203
|
-
heading,
|
|
1204
|
-
warning,
|
|
1205
|
-
view
|
|
1206
|
-
}) {
|
|
1207
|
-
useScrollbarSettle(scrollRef, view.empty === null);
|
|
1208
|
-
const { width } = useTerminalDimensions();
|
|
1209
|
-
if (view.empty !== null) {
|
|
1210
|
-
return /* @__PURE__ */ jsx4("box", { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx4("text", { fg: theme.muted, children: view.empty }) });
|
|
1211
|
-
}
|
|
1212
|
-
const left = view.cards.filter((_, i) => i % 2 === 0);
|
|
1213
|
-
const right = view.cards.filter((_, i) => i % 2 === 1);
|
|
1214
|
-
const leftWidth = Math.max(0, ...left.map((card) => cardWidth(card)));
|
|
1215
|
-
const rightWidth = Math.max(0, ...right.map((card) => cardWidth(card)));
|
|
1216
|
-
const twoColumns = right.length > 0 && width - 4 >= leftWidth + COLUMN_GAP + rightWidth;
|
|
1217
|
-
const rows = left.map((card, i) => {
|
|
1218
|
-
return { key: card.title, left: card, right: right.at(i) };
|
|
1443
|
+
function renderHelp() {
|
|
1444
|
+
const labels = OPTIONS.map((option) => {
|
|
1445
|
+
const short = option.short ? `-${option.short}, ` : "";
|
|
1446
|
+
const long = option.placeholder ? `--${option.name} ${option.placeholder}` : `--${option.name}`;
|
|
1447
|
+
return short + long;
|
|
1219
1448
|
});
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
(
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
view.headline !== null && /* @__PURE__ */ jsx4(ChartLine, { line: view.headline })
|
|
1233
|
-
] }),
|
|
1234
|
-
/* @__PURE__ */ jsx4("box", { height: 1, children: /* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
1235
|
-
/* @__PURE__ */ jsxs3(
|
|
1236
|
-
"scrollbox",
|
|
1237
|
-
{
|
|
1238
|
-
ref: scrollRef,
|
|
1239
|
-
focused,
|
|
1240
|
-
flexGrow: 1,
|
|
1241
|
-
paddingLeft: 1,
|
|
1242
|
-
paddingRight: 2,
|
|
1243
|
-
verticalScrollbarOptions: overlayScrollbar,
|
|
1244
|
-
children: [
|
|
1245
|
-
warning !== null && /* @__PURE__ */ jsx4("text", { wrapMode: "word", fg: theme.warn, marginTop: 1, children: warning }),
|
|
1246
|
-
view.lists.map((list) => /* @__PURE__ */ jsxs3("box", { flexDirection: "column", marginTop: 1, children: [
|
|
1247
|
-
/* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.accent, children: list.title }),
|
|
1248
|
-
list.rows.map((row) => /* @__PURE__ */ jsxs3("text", { wrapMode: "none", fg: theme.text, children: [
|
|
1249
|
-
" ",
|
|
1250
|
-
row.lead,
|
|
1251
|
-
" ",
|
|
1252
|
-
/* @__PURE__ */ jsx4("a", { href: row.url, fg: theme.accent, children: row.ref }),
|
|
1253
|
-
" ",
|
|
1254
|
-
row.title
|
|
1255
|
-
] }, row.url))
|
|
1256
|
-
] }, list.title)),
|
|
1257
|
-
view.distribution !== null && /* @__PURE__ */ jsxs3("box", { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
|
|
1258
|
-
/* @__PURE__ */ jsxs3("box", { flexDirection: "row", justifyContent: "space-between", children: [
|
|
1259
|
-
/* @__PURE__ */ jsx4("text", { wrapMode: "none", children: /* @__PURE__ */ jsx4("b", { fg: theme.text, children: view.distributionTitle }) }),
|
|
1260
|
-
/* @__PURE__ */ jsx4(ChartLine, { line: view.distribution.stats })
|
|
1261
|
-
] }),
|
|
1262
|
-
/* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: keyed(view.distribution.lines, lineKey).map(({ key, item }) => /* @__PURE__ */ jsx4(ChartLine, { line: item }, key)) })
|
|
1263
|
-
] }),
|
|
1264
|
-
view.cards.length === 0 ? /* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.muted, marginTop: 1, children: view.noCharts }) : cards
|
|
1265
|
-
]
|
|
1266
|
-
}
|
|
1267
|
-
)
|
|
1268
|
-
] });
|
|
1449
|
+
const column = Math.max(...labels.map((label) => label.length)) + 4;
|
|
1450
|
+
const lines = [`${source_default.bold("Usage:")} ${source_default.bold("pr-stats")} ${dim("[options]")}`, "", source_default.bold("Options:")];
|
|
1451
|
+
for (const [index, option] of OPTIONS.entries()) {
|
|
1452
|
+
const short = option.short ? `${flag(`-${option.short}`)}, ` : "";
|
|
1453
|
+
const long = option.placeholder ? `${flag(`--${option.name}`)} ${dim(option.placeholder)}` : flag(`--${option.name}`);
|
|
1454
|
+
const body = wrapMarkup(option.help, HELP_WIDTH - column).map((line) => colorizeHelp(line));
|
|
1455
|
+
lines.push(` ${short}${long}${" ".repeat(column - 2 - labels[index].length)}${body[0]}`);
|
|
1456
|
+
for (const overflow of body.slice(1)) {
|
|
1457
|
+
lines.push(" ".repeat(column) + overflow);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
return lines.join("\n");
|
|
1269
1461
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1462
|
+
var HELP = renderHelp();
|
|
1463
|
+
function parseCliArgs(args) {
|
|
1464
|
+
const options = {};
|
|
1465
|
+
for (const option of OPTIONS) {
|
|
1466
|
+
options[option.name] = { type: option.type };
|
|
1467
|
+
if (option.short) {
|
|
1468
|
+
options[option.name].short = option.short;
|
|
1469
|
+
}
|
|
1470
|
+
if (option.multiple) {
|
|
1471
|
+
options[option.name].multiple = true;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const { values } = parseArgs({ options, args });
|
|
1475
|
+
const explicit = new Set(Object.keys(values));
|
|
1476
|
+
for (const option of OPTIONS) {
|
|
1477
|
+
if (option.default !== void 0 && values[option.name] === void 0) {
|
|
1478
|
+
values[option.name] = option.default;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return { values, explicit };
|
|
1278
1482
|
}
|
|
1279
|
-
function
|
|
1280
|
-
|
|
1483
|
+
function parseSince(input) {
|
|
1484
|
+
const relative = /^(\d+)([dwmy])$/.exec(input);
|
|
1485
|
+
if (relative) {
|
|
1486
|
+
const amount = Number(relative[1]);
|
|
1487
|
+
const date2 = /* @__PURE__ */ new Date();
|
|
1488
|
+
if (relative[2] === "d") {
|
|
1489
|
+
date2.setDate(date2.getDate() - amount);
|
|
1490
|
+
}
|
|
1491
|
+
if (relative[2] === "w") {
|
|
1492
|
+
date2.setDate(date2.getDate() - amount * 7);
|
|
1493
|
+
}
|
|
1494
|
+
if (relative[2] === "m") {
|
|
1495
|
+
date2.setMonth(date2.getMonth() - amount);
|
|
1496
|
+
}
|
|
1497
|
+
if (relative[2] === "y") {
|
|
1498
|
+
date2.setFullYear(date2.getFullYear() - amount);
|
|
1499
|
+
}
|
|
1500
|
+
return date2;
|
|
1501
|
+
}
|
|
1502
|
+
const date = new Date(input);
|
|
1503
|
+
if (Number.isNaN(date.getTime())) {
|
|
1504
|
+
throw new CliError(`invalid --since value "${input}", use an ISO date or 30d/8w/6m/1y`);
|
|
1505
|
+
}
|
|
1506
|
+
return date;
|
|
1281
1507
|
}
|
|
1282
|
-
function
|
|
1283
|
-
|
|
1508
|
+
function parseTarget(input) {
|
|
1509
|
+
const match = /^(\d+(?:\.\d+)?)([hdm]?)$/.exec(input);
|
|
1510
|
+
if (!match) {
|
|
1511
|
+
throw new CliError(`invalid --target value "${input}", use 24h, 2d, or 90m`);
|
|
1512
|
+
}
|
|
1513
|
+
const amount = Number(match[1]);
|
|
1514
|
+
if (match[2] === "d") {
|
|
1515
|
+
return amount * timeMode.dayHours;
|
|
1516
|
+
}
|
|
1517
|
+
if (match[2] === "m") {
|
|
1518
|
+
return amount / 60;
|
|
1519
|
+
}
|
|
1520
|
+
return amount;
|
|
1284
1521
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1522
|
+
var DEFAULT_TARGET_PERCENTILE = 90;
|
|
1523
|
+
function parseTargetPercentile(input) {
|
|
1524
|
+
const match = /^p?(\d{1,3})$/i.exec(input);
|
|
1525
|
+
const value2 = match === null ? Number.NaN : Number(match[1]);
|
|
1526
|
+
if (!Number.isInteger(value2) || value2 < 1 || value2 > 100) {
|
|
1527
|
+
throw new CliError(`invalid --target-percentile value "${input}", use a percentile from 1 to 100 like 90 or p90`);
|
|
1528
|
+
}
|
|
1529
|
+
return value2;
|
|
1289
1530
|
}
|
|
1290
|
-
function
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
]
|
|
1298
|
-
|
|
1299
|
-
|
|
1531
|
+
function parseSizeTarget(input) {
|
|
1532
|
+
const target = {};
|
|
1533
|
+
for (const part of input.split(",")) {
|
|
1534
|
+
const match = /^(\d+)([lf]?)$/.exec(part.trim());
|
|
1535
|
+
if (!match) {
|
|
1536
|
+
throw new CliError(`invalid --size-target value "${input}", use 400, 400l, 20f, or 400l,20f`);
|
|
1537
|
+
}
|
|
1538
|
+
const key = match[2] === "f" ? "files" : "lines";
|
|
1539
|
+
if (target[key] !== void 0) {
|
|
1540
|
+
throw new CliError(`--size-target sets the ${key} budget twice`);
|
|
1541
|
+
}
|
|
1542
|
+
target[key] = Number(match[1]);
|
|
1543
|
+
}
|
|
1544
|
+
return target;
|
|
1300
1545
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
function
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1546
|
+
var REVIEW_TYPES = /* @__PURE__ */ new Map([
|
|
1547
|
+
["approve", "APPROVED"],
|
|
1548
|
+
["comment", "COMMENTED"],
|
|
1549
|
+
["request-changes", "CHANGES_REQUESTED"]
|
|
1550
|
+
]);
|
|
1551
|
+
function parseReviewTypes(input) {
|
|
1552
|
+
const states = /* @__PURE__ */ new Set();
|
|
1553
|
+
for (const part of input.split(",")) {
|
|
1554
|
+
const state = REVIEW_TYPES.get(part.trim().toLowerCase());
|
|
1555
|
+
if (state === void 0) {
|
|
1556
|
+
throw new CliError(`invalid --review-types value "${part.trim()}", use approve, comment, or request-changes`);
|
|
1557
|
+
}
|
|
1558
|
+
states.add(state);
|
|
1559
|
+
}
|
|
1560
|
+
return states;
|
|
1312
1561
|
}
|
|
1313
|
-
function
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
/* @__PURE__ */ jsx5(Spinner, {})
|
|
1318
|
-
] });
|
|
1562
|
+
function toMinutesOfDay(hourText, minuteText, meridiem) {
|
|
1563
|
+
const minute = Number(minuteText ?? 0);
|
|
1564
|
+
if (minute > 59) {
|
|
1565
|
+
return null;
|
|
1319
1566
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
/* @__PURE__ */ jsx5("span", { fg: theme.muted, children: ` ${counter}` })
|
|
1332
|
-
] })
|
|
1333
|
-
] });
|
|
1567
|
+
let hour = Number(hourText);
|
|
1568
|
+
if (meridiem !== void 0) {
|
|
1569
|
+
if (hour < 1 || hour > 12) {
|
|
1570
|
+
return null;
|
|
1571
|
+
}
|
|
1572
|
+
hour %= 12;
|
|
1573
|
+
if (meridiem.toLowerCase() === "pm") {
|
|
1574
|
+
hour += 12;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return hour * 60 + minute;
|
|
1334
1578
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1579
|
+
var WEEKDAY_NAMES = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
1580
|
+
var WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
1581
|
+
function parseWorkDays(input) {
|
|
1582
|
+
const days = /* @__PURE__ */ new Set();
|
|
1583
|
+
for (const part of input.split(",")) {
|
|
1584
|
+
const match = /^([a-z]{3})(?:-([a-z]{3}))?$/i.exec(part.trim());
|
|
1585
|
+
const endName = match?.[2];
|
|
1586
|
+
const start = match === null ? -1 : WEEKDAY_NAMES.indexOf(match[1].toLowerCase());
|
|
1587
|
+
const end = match === null ? -1 : endName === void 0 ? start : WEEKDAY_NAMES.indexOf(endName.toLowerCase());
|
|
1588
|
+
if (start === -1 || end === -1) {
|
|
1589
|
+
throw new CliError(
|
|
1590
|
+
`invalid --work-days value "${part.trim()}", use weekday names and ranges like mon-fri, sun-thu, or mon,wed,fri`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
for (let day = start; ; day = (day + 1) % 7) {
|
|
1594
|
+
days.add(day);
|
|
1595
|
+
if (day === end) {
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return days;
|
|
1337
1601
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
heading,
|
|
1345
|
-
warning,
|
|
1346
|
-
empty,
|
|
1347
|
-
sections,
|
|
1348
|
-
cursor,
|
|
1349
|
-
onRefClick
|
|
1350
|
-
}) {
|
|
1351
|
-
const scrollRef = useRef(null);
|
|
1352
|
-
const { width } = useTerminalDimensions2();
|
|
1353
|
-
const lastDown = useRef(null);
|
|
1354
|
-
useScrollbarSettle(scrollRef, empty === null);
|
|
1355
|
-
useEffect2(() => {
|
|
1356
|
-
scrollRef.current?.scrollChildIntoView(`queue-row-${cursor}`);
|
|
1357
|
-
}, [cursor]);
|
|
1358
|
-
const header = heading === null ? null : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
1359
|
-
/* @__PURE__ */ jsx6("box", { height: 1, children: /* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
1360
|
-
/* @__PURE__ */ jsx6("box", { height: 1, paddingLeft: 1, paddingRight: 2, children: /* @__PURE__ */ jsxs5("text", { wrapMode: "none", children: [
|
|
1361
|
-
/* @__PURE__ */ jsx6("span", { fg: theme.accent, children: "\u25B8 " }),
|
|
1362
|
-
/* @__PURE__ */ jsx6("b", { fg: theme.text, children: heading })
|
|
1363
|
-
] }) }),
|
|
1364
|
-
/* @__PURE__ */ jsx6("box", { height: 1, children: /* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) })
|
|
1365
|
-
] });
|
|
1366
|
-
if (empty !== null) {
|
|
1367
|
-
return /* @__PURE__ */ jsxs5("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
1368
|
-
header,
|
|
1369
|
-
/* @__PURE__ */ jsx6("box", { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx6("text", { fg: theme.muted, children: empty }) })
|
|
1370
|
-
] });
|
|
1602
|
+
function workDayRunLabel(start, end) {
|
|
1603
|
+
return start === end ? WEEKDAY_LABELS[start] : `${WEEKDAY_LABELS[start]}-${WEEKDAY_LABELS[end]}`;
|
|
1604
|
+
}
|
|
1605
|
+
function formatWorkDays(days) {
|
|
1606
|
+
if (days.size === 7) {
|
|
1607
|
+
return "Mon-Sun";
|
|
1371
1608
|
}
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1609
|
+
const monFirst = [1, 2, 3, 4, 5, 6, 0];
|
|
1610
|
+
const anchor = monFirst.find((day) => days.has(day) && !days.has((day + 6) % 7)) ?? 1;
|
|
1611
|
+
const runs = [];
|
|
1612
|
+
let runStart = -1;
|
|
1613
|
+
let runEnd = -1;
|
|
1614
|
+
for (let step = 0; step < 7; step++) {
|
|
1615
|
+
const day = (anchor + step) % 7;
|
|
1616
|
+
if (days.has(day)) {
|
|
1617
|
+
runStart = runStart === -1 ? day : runStart;
|
|
1618
|
+
runEnd = day;
|
|
1619
|
+
} else if (runStart !== -1) {
|
|
1620
|
+
runs.push(workDayRunLabel(runStart, runEnd));
|
|
1621
|
+
runStart = -1;
|
|
1380
1622
|
}
|
|
1381
|
-
offsets.push(entry);
|
|
1382
1623
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
]
|
|
1421
|
-
},
|
|
1422
|
-
row.url
|
|
1423
|
-
);
|
|
1424
|
-
};
|
|
1425
|
-
return /* @__PURE__ */ jsxs5("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
1426
|
-
header,
|
|
1427
|
-
/* @__PURE__ */ jsxs5(
|
|
1428
|
-
"scrollbox",
|
|
1429
|
-
{
|
|
1430
|
-
ref: scrollRef,
|
|
1431
|
-
flexGrow: 1,
|
|
1432
|
-
paddingLeft: 1,
|
|
1433
|
-
paddingRight: 2,
|
|
1434
|
-
verticalScrollbarOptions: overlayScrollbar,
|
|
1435
|
-
children: [
|
|
1436
|
-
warning !== null && /* @__PURE__ */ jsx6("text", { wrapMode: "word", fg: theme.warn, marginTop: 1, children: warning }),
|
|
1437
|
-
sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
|
|
1438
|
-
/* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.accent, children: section.title }),
|
|
1439
|
-
section.rows.map((row, rowIndex) => renderRow(row, offsets[sectionIndex].rows + rowIndex, "")),
|
|
1440
|
-
section.lists.map((list, listIndex) => /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: listIndex > 0 ? 1 : 0, children: [
|
|
1441
|
-
/* @__PURE__ */ jsxs5("text", { wrapMode: "none", fg: theme.accent, children: [
|
|
1442
|
-
" ",
|
|
1443
|
-
list.title
|
|
1444
|
-
] }),
|
|
1445
|
-
list.rows.map(
|
|
1446
|
-
(row, rowIndex) => renderRow(row, offsets[sectionIndex].lists[listIndex] + rowIndex, " ")
|
|
1447
|
-
)
|
|
1448
|
-
] }, list.title))
|
|
1449
|
-
] }, section.title))
|
|
1450
|
-
]
|
|
1451
|
-
}
|
|
1452
|
-
)
|
|
1453
|
-
] });
|
|
1624
|
+
return runs.join(",");
|
|
1625
|
+
}
|
|
1626
|
+
function canonicalWorkDays(input) {
|
|
1627
|
+
return formatWorkDays(parseWorkDays(input));
|
|
1628
|
+
}
|
|
1629
|
+
function parseWorkHours(input) {
|
|
1630
|
+
const windows = input.split(",").map((range) => {
|
|
1631
|
+
const match = /^(\d{1,2})(?::(\d{2}))?(am|pm)?-(\d{1,2})(?::(\d{2}))?(am|pm)?$/i.exec(range.trim());
|
|
1632
|
+
if (!match) {
|
|
1633
|
+
throw new CliError(
|
|
1634
|
+
`invalid --work-hours range "${range}", use ranges like 9-17, 9am-6pm, 8:30-16:30, or 9-18,19:30-20:30`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
const startMin = toMinutesOfDay(match[1], match[2], match[3]);
|
|
1638
|
+
const end = toMinutesOfDay(match[4], match[5], match[6]);
|
|
1639
|
+
const endMin = end === 0 ? 24 * 60 : end;
|
|
1640
|
+
if (startMin === null || endMin === null || endMin <= startMin || endMin > 24 * 60) {
|
|
1641
|
+
throw new CliError(`invalid --work-hours range "${range}"`);
|
|
1642
|
+
}
|
|
1643
|
+
return { startMin, endMin };
|
|
1644
|
+
});
|
|
1645
|
+
windows.sort((a, b) => a.startMin - b.startMin);
|
|
1646
|
+
for (let i = 1; i < windows.length; i++) {
|
|
1647
|
+
if (windows[i].startMin < windows[i - 1].endMin) {
|
|
1648
|
+
throw new CliError(`--work-hours ranges overlap around ${formatMinutesOfDay(windows[i].startMin)}`);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return windows;
|
|
1652
|
+
}
|
|
1653
|
+
function resolveTimezone(input) {
|
|
1654
|
+
const tz = input ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1655
|
+
try {
|
|
1656
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
1657
|
+
} catch {
|
|
1658
|
+
throw new CliError(`invalid --tz value "${tz}", use an IANA zone like Europe/Berlin`);
|
|
1659
|
+
}
|
|
1660
|
+
return tz;
|
|
1454
1661
|
}
|
|
1455
1662
|
|
|
1456
|
-
// src/tui/components/
|
|
1457
|
-
import { useEffect
|
|
1458
|
-
import { jsx as
|
|
1459
|
-
|
|
1663
|
+
// src/tui/components/Spinner.tsx
|
|
1664
|
+
import { useEffect, useState } from "react";
|
|
1665
|
+
import { jsx as jsx2 } from "@opentui/react/jsx-runtime";
|
|
1666
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1667
|
+
var SPINNER_INTERVAL_MS = 80;
|
|
1668
|
+
function Spinner() {
|
|
1669
|
+
const [frame, setFrame] = useState(0);
|
|
1670
|
+
useEffect(() => {
|
|
1671
|
+
const timer = setInterval(() => {
|
|
1672
|
+
setFrame((previous) => (previous + 1) % SPINNER_FRAMES.length);
|
|
1673
|
+
}, SPINNER_INTERVAL_MS);
|
|
1674
|
+
return () => {
|
|
1675
|
+
clearInterval(timer);
|
|
1676
|
+
};
|
|
1677
|
+
}, []);
|
|
1678
|
+
return /* @__PURE__ */ jsx2("text", { wrapMode: "none", fg: theme.accent, children: SPINNER_FRAMES[frame] });
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// src/tui/components/Header.tsx
|
|
1682
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "@opentui/react/jsx-runtime";
|
|
1683
|
+
function Header({
|
|
1460
1684
|
options,
|
|
1461
|
-
|
|
1462
|
-
|
|
1685
|
+
raw,
|
|
1686
|
+
error,
|
|
1687
|
+
spinning
|
|
1463
1688
|
}) {
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
{
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const isSelected = i === cursor;
|
|
1482
|
-
return /* @__PURE__ */ jsxs6("text", { id: `repo-row-${i}`, wrapMode: "none", children: [
|
|
1483
|
-
/* @__PURE__ */ jsx7("span", { fg: theme.accent, children: isSelected ? "\u25B8 " : " " }),
|
|
1484
|
-
/* @__PURE__ */ jsx7("span", { fg: isSelected ? theme.text : theme.muted, bg: isSelected ? theme.selectedBg : void 0, children: ` ${option.label.padEnd(labelWidth)} ` }),
|
|
1485
|
-
/* @__PURE__ */ jsxs6("span", { fg: theme.muted, children: [
|
|
1486
|
-
" ",
|
|
1487
|
-
option.detail
|
|
1488
|
-
] })
|
|
1489
|
-
] }, option.label);
|
|
1490
|
-
}) })
|
|
1491
|
-
]
|
|
1492
|
-
}
|
|
1493
|
-
);
|
|
1689
|
+
const context = [
|
|
1690
|
+
raw ? `@${raw.user}` : options.user !== "" ? `@${options.user}` : "@...",
|
|
1691
|
+
`since ${options.since}`,
|
|
1692
|
+
raw && raw.repos.length > 0 ? raw.repos.join(", ") : options.repos !== "" ? options.repos : "all repos",
|
|
1693
|
+
timeModeLabel(options)
|
|
1694
|
+
].join(" \xB7 ");
|
|
1695
|
+
const rightStatus = raw ? error !== null ? "reload failed \xB7 press r to retry" : `refreshed ${raw.fetchedAt.toLocaleTimeString()}` : "";
|
|
1696
|
+
return /* @__PURE__ */ jsxs2("box", { flexDirection: "row", height: 1, paddingLeft: 1, paddingRight: 1, justifyContent: "space-between", children: [
|
|
1697
|
+
/* @__PURE__ */ jsxs2("text", { wrapMode: "none", children: [
|
|
1698
|
+
/* @__PURE__ */ jsx3("b", { fg: theme.accent, children: "pr-stats" }),
|
|
1699
|
+
/* @__PURE__ */ jsxs2("span", { fg: theme.muted, children: [
|
|
1700
|
+
" \xB7 ",
|
|
1701
|
+
context
|
|
1702
|
+
] })
|
|
1703
|
+
] }),
|
|
1704
|
+
spinning ? /* @__PURE__ */ jsx3(Spinner, {}) : /* @__PURE__ */ jsx3("text", { wrapMode: "none", fg: raw !== null && error !== null ? theme.error : theme.muted, children: rightStatus })
|
|
1705
|
+
] });
|
|
1494
1706
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
var initialBrowseState = {
|
|
1499
|
-
tab: 0,
|
|
1500
|
-
authoredTab: "open",
|
|
1501
|
-
scopes: {
|
|
1502
|
-
pending: { view: "list" },
|
|
1503
|
-
open: { view: "list" },
|
|
1504
|
-
review: { view: "list" },
|
|
1505
|
-
size: { view: "list" },
|
|
1506
|
-
comment: { view: "list" },
|
|
1507
|
-
merged: { view: "list" }
|
|
1508
|
-
},
|
|
1509
|
-
repoCursors: { pending: 0, open: 0, review: 0, size: 0, comment: 0, merged: 0 },
|
|
1510
|
-
rowCursors: { pending: 0, open: 0 },
|
|
1511
|
-
grouped: { pending: false, open: false },
|
|
1512
|
-
expanded: { review: false, size: false, comment: false, merged: false }
|
|
1513
|
-
};
|
|
1514
|
-
function dropVanishedRepo(scope, repos) {
|
|
1515
|
-
if (scope.view === "detail" && scope.repo !== null && !repos.some((option) => option.repo === scope.repo)) {
|
|
1516
|
-
return { view: "list" };
|
|
1707
|
+
function timeModeLabel(options) {
|
|
1708
|
+
if (options.wallClock) {
|
|
1709
|
+
return "wall-clock time";
|
|
1517
1710
|
}
|
|
1518
|
-
|
|
1711
|
+
const tz = options.tz === "" ? Intl.DateTimeFormat().resolvedOptions().timeZone : options.tz;
|
|
1712
|
+
if (options.workHours === "0-24") {
|
|
1713
|
+
return `${options.workDays} all hours ${tz}`;
|
|
1714
|
+
}
|
|
1715
|
+
return `${options.workDays} ${options.workHours} (${dailyHoursLabel(options.workHours)}) ${tz}`;
|
|
1519
1716
|
}
|
|
1520
|
-
function
|
|
1521
|
-
|
|
1717
|
+
function dailyHoursLabel(workHours) {
|
|
1718
|
+
const minutes = parseWorkHours(workHours).reduce((sum, window) => sum + (window.endMin - window.startMin), 0);
|
|
1719
|
+
const hours = Math.round(minutes / 60 * 100) / 100;
|
|
1720
|
+
return `${hours} ${hours === 1 ? "hour" : "hours"}`;
|
|
1522
1721
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1722
|
+
|
|
1723
|
+
// src/compute.ts
|
|
1724
|
+
function computeReviewStats(results, { targetHours, now = /* @__PURE__ */ new Date() } = {}) {
|
|
1725
|
+
const reviewed = [];
|
|
1726
|
+
const pending = [];
|
|
1727
|
+
for (const result of results) {
|
|
1728
|
+
if (result.kind === "reviewed") {
|
|
1729
|
+
reviewed.push({
|
|
1730
|
+
pr: result.pr,
|
|
1731
|
+
requestedAt: result.requestedAt,
|
|
1732
|
+
reviewedAt: result.reviewedAt,
|
|
1733
|
+
hours: durationHours(result.requestedAt, result.reviewedAt),
|
|
1734
|
+
verdict: result.verdict,
|
|
1735
|
+
lines: result.lines
|
|
1736
|
+
});
|
|
1737
|
+
} else if (result.kind === "pending" && result.pr.state === "open") {
|
|
1738
|
+
pending.push({ pr: result.pr, requestedAt: result.requestedAt, hours: durationHours(result.requestedAt, now) });
|
|
1527
1739
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1740
|
+
}
|
|
1741
|
+
pending.sort((a, b) => a.requestedAt.getTime() - b.requestedAt.getTime());
|
|
1742
|
+
const pendingKeys = new Set(pending.map((entry) => `${entry.pr.repo}#${entry.pr.number}`));
|
|
1743
|
+
const latestReviews = /* @__PURE__ */ new Map();
|
|
1744
|
+
for (const result of results) {
|
|
1745
|
+
if (result.kind !== "reviewed" && result.kind !== "unrequested" || result.pr.state !== "open") {
|
|
1746
|
+
continue;
|
|
1530
1747
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1748
|
+
const key = `${result.pr.repo}#${result.pr.number}`;
|
|
1749
|
+
if (pendingKeys.has(key)) {
|
|
1750
|
+
continue;
|
|
1533
1751
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
repoCursors: {
|
|
1538
|
-
...state.repoCursors,
|
|
1539
|
-
[action.tab]: cycled(state.repoCursors[action.tab], action.delta, action.count)
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1752
|
+
const latest = latestReviews.get(key);
|
|
1753
|
+
if (latest === void 0 || result.reviewedAt > latest.reviewedAt) {
|
|
1754
|
+
latestReviews.set(key, { pr: result.pr, reviewedAt: result.reviewedAt });
|
|
1542
1755
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1756
|
+
}
|
|
1757
|
+
const reviewing = [...latestReviews.values()].map(({ pr, reviewedAt }) => {
|
|
1758
|
+
return { pr, reviewedAt, hours: durationHours(reviewedAt, now) };
|
|
1759
|
+
}).toSorted((a, b) => a.reviewedAt.getTime() - b.reviewedAt.getTime());
|
|
1760
|
+
const expired = results.filter((result) => result.kind === "pending" && result.pr.state !== "open");
|
|
1761
|
+
const unrequested = results.filter((result) => result.kind === "unrequested");
|
|
1762
|
+
const allHours = reviewed.map((result) => result.hours);
|
|
1763
|
+
const hoursByRepo = /* @__PURE__ */ new Map();
|
|
1764
|
+
for (const result of reviewed) {
|
|
1765
|
+
const hours = hoursByRepo.get(result.pr.repo) ?? [];
|
|
1766
|
+
hours.push(result.hours);
|
|
1767
|
+
hoursByRepo.set(result.pr.repo, hours);
|
|
1768
|
+
}
|
|
1769
|
+
const byRepo = [...hoursByRepo.entries()].toSorted((a, b) => b[1].length - a[1].length);
|
|
1770
|
+
const misses = targetHours === void 0 ? [] : reviewed.filter((result) => result.hours > targetHours).toSorted((a, b) => b.hours - a.hours);
|
|
1771
|
+
const cyclesByPr = /* @__PURE__ */ new Map();
|
|
1772
|
+
for (const result of reviewed) {
|
|
1773
|
+
const key = `${result.pr.repo}#${result.pr.number}`;
|
|
1774
|
+
cyclesByPr.set(key, (cyclesByPr.get(key) ?? 0) + 1);
|
|
1775
|
+
}
|
|
1776
|
+
return {
|
|
1777
|
+
reviewed,
|
|
1778
|
+
pending,
|
|
1779
|
+
reviewing,
|
|
1780
|
+
expired,
|
|
1781
|
+
unrequested,
|
|
1782
|
+
allHours,
|
|
1783
|
+
byRepo,
|
|
1784
|
+
misses,
|
|
1785
|
+
cycles: [...cyclesByPr.values()]
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function computeSizeStats(sizes, { sizeTarget } = {}) {
|
|
1789
|
+
const metrics = [
|
|
1790
|
+
{ label: "files changed", values: sizes.map((size) => size.files) },
|
|
1791
|
+
{ label: "lines added", values: sizes.map((size) => size.additions) },
|
|
1792
|
+
{ label: "lines removed", values: sizes.map((size) => size.deletions) },
|
|
1793
|
+
{ label: "lines total", values: sizes.map((size) => size.total) }
|
|
1794
|
+
];
|
|
1795
|
+
const timelineTotals = [...sizes].toSorted((a, b) => a.pr.createdAt.getTime() - b.pr.createdAt.getTime()).map((size) => size.total);
|
|
1796
|
+
if (sizeTarget === void 0) {
|
|
1797
|
+
return { metrics, timelineTotals, met: void 0, misses: [], targetLabel: void 0 };
|
|
1798
|
+
}
|
|
1799
|
+
const meetsTarget = (size) => (sizeTarget.lines === void 0 || size.total <= sizeTarget.lines) && (sizeTarget.files === void 0 || size.files <= sizeTarget.files);
|
|
1800
|
+
const targetLabel = [
|
|
1801
|
+
...sizeTarget.lines === void 0 ? [] : [`<= ${sizeTarget.lines} lines`],
|
|
1802
|
+
...sizeTarget.files === void 0 ? [] : [`<= ${sizeTarget.files} files`]
|
|
1803
|
+
].join(", ");
|
|
1804
|
+
const met = sizes.filter((size) => meetsTarget(size)).length;
|
|
1805
|
+
const misses = sizes.filter((size) => !meetsTarget(size)).toSorted((a, b) => b.total - a.total);
|
|
1806
|
+
return { metrics, timelineTotals, met, misses, targetLabel };
|
|
1807
|
+
}
|
|
1808
|
+
function computeMergeStats(sizes) {
|
|
1809
|
+
const merged = [];
|
|
1810
|
+
const closed = [];
|
|
1811
|
+
const open = [];
|
|
1812
|
+
for (const entry of sizes) {
|
|
1813
|
+
if (entry.mergedAt !== null) {
|
|
1814
|
+
merged.push({ entry, mergedAt: entry.mergedAt, hours: durationHours(entry.pr.createdAt, entry.mergedAt) });
|
|
1815
|
+
} else if (entry.pr.state === "open") {
|
|
1816
|
+
open.push(entry);
|
|
1817
|
+
} else if (entry.closedAt !== null) {
|
|
1818
|
+
closed.push({ entry, closedAt: entry.closedAt, hours: durationHours(entry.pr.createdAt, entry.closedAt) });
|
|
1551
1819
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1820
|
+
}
|
|
1821
|
+
merged.sort((a, b) => b.mergedAt.getTime() - a.mergedAt.getTime());
|
|
1822
|
+
closed.sort((a, b) => b.closedAt.getTime() - a.closedAt.getTime());
|
|
1823
|
+
return { merged, closed, open, allHours: merged.map((result) => result.hours) };
|
|
1824
|
+
}
|
|
1825
|
+
function computeReviewerStats(sizes, author) {
|
|
1826
|
+
const byLogin = /* @__PURE__ */ new Map();
|
|
1827
|
+
let mergedReviewed = 0;
|
|
1828
|
+
let mergedUnreviewed = 0;
|
|
1829
|
+
for (const entry of sizes) {
|
|
1830
|
+
const others = entry.reviews.flatMap(
|
|
1831
|
+
(review) => review.login === null || review.login === author ? [] : [review.login]
|
|
1832
|
+
);
|
|
1833
|
+
for (const login of others) {
|
|
1834
|
+
const counts = byLogin.get(login) ?? { prs: 0, reviews: 0 };
|
|
1835
|
+
counts.reviews += 1;
|
|
1836
|
+
byLogin.set(login, counts);
|
|
1554
1837
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1838
|
+
const distinct = new Set(others);
|
|
1839
|
+
for (const login of distinct) {
|
|
1840
|
+
const counts = byLogin.get(login);
|
|
1841
|
+
if (counts !== void 0) {
|
|
1842
|
+
counts.prs += 1;
|
|
1843
|
+
}
|
|
1557
1844
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1845
|
+
if (entry.mergedAt !== null) {
|
|
1846
|
+
if (others.length > 0) {
|
|
1847
|
+
mergedReviewed += 1;
|
|
1848
|
+
} else {
|
|
1849
|
+
mergedUnreviewed += 1;
|
|
1850
|
+
}
|
|
1560
1851
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1852
|
+
}
|
|
1853
|
+
const leaderboard = [...byLogin.entries()].map(([login, counts]) => {
|
|
1854
|
+
return { login, ...counts };
|
|
1855
|
+
}).toSorted((a, b) => b.prs - a.prs || b.reviews - a.reviews || a.login.localeCompare(b.login));
|
|
1856
|
+
return { leaderboard, mergedReviewed, mergedUnreviewed };
|
|
1857
|
+
}
|
|
1858
|
+
function firstReviewOf(entry, author) {
|
|
1859
|
+
let earliest = null;
|
|
1860
|
+
for (const review of entry.reviews) {
|
|
1861
|
+
if (review.login === null || review.login === author || review.submittedAt === null) {
|
|
1862
|
+
continue;
|
|
1563
1863
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
...state,
|
|
1567
|
-
scopes: {
|
|
1568
|
-
pending: dropVanishedRepo(state.scopes.pending, action.repos.pending),
|
|
1569
|
-
open: dropVanishedRepo(state.scopes.open, action.repos.open),
|
|
1570
|
-
review: dropVanishedRepo(state.scopes.review, action.repos.review),
|
|
1571
|
-
size: dropVanishedRepo(state.scopes.size, action.repos.size),
|
|
1572
|
-
comment: dropVanishedRepo(state.scopes.comment, action.repos.comment),
|
|
1573
|
-
merged: dropVanishedRepo(state.scopes.merged, action.repos.merged)
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1864
|
+
if (earliest === null || review.submittedAt < earliest) {
|
|
1865
|
+
earliest = review.submittedAt;
|
|
1576
1866
|
}
|
|
1577
1867
|
}
|
|
1578
|
-
|
|
1868
|
+
if (earliest === null) {
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
return { reviewedAt: earliest, hours: durationHours(entry.pr.createdAt, earliest) };
|
|
1579
1872
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
)) });
|
|
1873
|
+
function computeFirstReviewStats(sizes, author, { now = /* @__PURE__ */ new Date() } = {}) {
|
|
1874
|
+
const received = [];
|
|
1875
|
+
const awaiting = [];
|
|
1876
|
+
for (const entry of sizes) {
|
|
1877
|
+
const first = firstReviewOf(entry, author);
|
|
1878
|
+
if (first !== null) {
|
|
1879
|
+
received.push({ entry, ...first });
|
|
1880
|
+
} else if (entry.pr.state === "open") {
|
|
1881
|
+
awaiting.push({ entry, hours: durationHours(entry.pr.createdAt, now) });
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
awaiting.sort((a, b) => a.entry.pr.createdAt.getTime() - b.entry.pr.createdAt.getTime());
|
|
1885
|
+
return { received, awaiting, allHours: received.map((result) => result.hours) };
|
|
1594
1886
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
fg: key === active ? theme.accent : theme.muted,
|
|
1606
|
-
bg: key === active ? theme.selectedBg : void 0,
|
|
1607
|
-
children: ` ${label} `
|
|
1608
|
-
},
|
|
1609
|
-
key
|
|
1610
|
-
)),
|
|
1611
|
-
/* @__PURE__ */ jsx8("text", { wrapMode: "none", fg: theme.dim, children: "t switches" })
|
|
1612
|
-
] });
|
|
1887
|
+
function computeCommentStats(sizes) {
|
|
1888
|
+
const metrics = [
|
|
1889
|
+
{ label: "discussion comments", values: sizes.map((size) => size.comments.discussion) },
|
|
1890
|
+
{ label: "review comments", values: sizes.map((size) => size.comments.review) },
|
|
1891
|
+
{ label: "all comments", values: sizes.map((size) => size.comments.total) }
|
|
1892
|
+
];
|
|
1893
|
+
const totals = sizes.map((size) => size.comments.total);
|
|
1894
|
+
const uncommented = totals.filter((total) => total === 0).length;
|
|
1895
|
+
const top = sizes.filter((size) => size.comments.total > 0).toSorted((a, b) => b.comments.total - a.comments.total);
|
|
1896
|
+
return { metrics, totals, uncommented, top };
|
|
1613
1897
|
}
|
|
1614
1898
|
|
|
1615
|
-
// src/
|
|
1616
|
-
|
|
1617
|
-
function
|
|
1618
|
-
|
|
1619
|
-
repos,
|
|
1620
|
-
scope,
|
|
1621
|
-
view,
|
|
1622
|
-
repoCursor,
|
|
1623
|
-
rowCursor,
|
|
1624
|
-
grouped,
|
|
1625
|
-
warning,
|
|
1626
|
-
onRefClick
|
|
1627
|
-
}) {
|
|
1628
|
-
if (scope.view === "list") {
|
|
1629
|
-
return /* @__PURE__ */ jsx9(RepoList, { prompt, options: repos, cursor: Math.min(repoCursor, repos.length - 1) });
|
|
1630
|
-
}
|
|
1631
|
-
if (view === null) {
|
|
1632
|
-
return null;
|
|
1633
|
-
}
|
|
1634
|
-
return /* @__PURE__ */ jsx9(
|
|
1635
|
-
QueuePanel,
|
|
1636
|
-
{
|
|
1637
|
-
heading: repos.length > 0 ? scope.repo ?? (grouped ? "All repos \xB7 grouped by repo" : "All repos") : null,
|
|
1638
|
-
warning,
|
|
1639
|
-
empty: view.empty,
|
|
1640
|
-
sections: view.sections,
|
|
1641
|
-
cursor: Math.min(rowCursor, queueRows(view).length - 1),
|
|
1642
|
-
onRefClick
|
|
1643
|
-
}
|
|
1644
|
-
);
|
|
1899
|
+
// src/report.ts
|
|
1900
|
+
var BUCKETS = [];
|
|
1901
|
+
function initBuckets() {
|
|
1902
|
+
BUCKETS = makeBuckets();
|
|
1645
1903
|
}
|
|
1646
|
-
function
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1904
|
+
function currentBuckets() {
|
|
1905
|
+
return BUCKETS;
|
|
1906
|
+
}
|
|
1907
|
+
function makeBuckets() {
|
|
1908
|
+
if (!timeMode.business || isFullDayMode()) {
|
|
1909
|
+
return [
|
|
1910
|
+
{ label: "< 1h", max: 1 },
|
|
1911
|
+
{ label: "1-4h", max: 4 },
|
|
1912
|
+
{ label: "4-8h", max: 8 },
|
|
1913
|
+
{ label: "8-24h", max: 24 },
|
|
1914
|
+
{ label: "1-2d", max: 48 },
|
|
1915
|
+
{ label: "2-4d", max: 96 },
|
|
1916
|
+
{ label: "4-7d", max: 168 },
|
|
1917
|
+
{ label: "> 7d", max: Infinity }
|
|
1918
|
+
];
|
|
1657
1919
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1920
|
+
const wd = timeMode.dayHours;
|
|
1921
|
+
return [
|
|
1922
|
+
{ label: "< 1h", max: 1 },
|
|
1923
|
+
{ label: "1-4h", max: 4 },
|
|
1924
|
+
{ label: "4h-1wd", max: wd },
|
|
1925
|
+
{ label: "1-2wd", max: 2 * wd },
|
|
1926
|
+
{ label: "2-3wd", max: 3 * wd },
|
|
1927
|
+
{ label: "3-5wd", max: 5 * wd },
|
|
1928
|
+
{ label: "5-10wd", max: 10 * wd },
|
|
1929
|
+
{ label: "> 10wd", max: Infinity }
|
|
1930
|
+
];
|
|
1931
|
+
}
|
|
1932
|
+
var LINE_BUCKETS = [
|
|
1933
|
+
{ label: "< 50", max: 50 },
|
|
1934
|
+
{ label: "50-100", max: 100 },
|
|
1935
|
+
{ label: "100-250", max: 250 },
|
|
1936
|
+
{ label: "250-500", max: 500 },
|
|
1937
|
+
{ label: "500-1k", max: 1e3 },
|
|
1938
|
+
{ label: "1k-2.5k", max: 2500 },
|
|
1939
|
+
{ label: "2.5k-5k", max: 5e3 },
|
|
1940
|
+
{ label: "> 5k", max: Infinity }
|
|
1941
|
+
];
|
|
1942
|
+
var FILE_BUCKETS = [
|
|
1943
|
+
{ label: "1-2", max: 3 },
|
|
1944
|
+
{ label: "3-5", max: 6 },
|
|
1945
|
+
{ label: "6-10", max: 11 },
|
|
1946
|
+
{ label: "11-20", max: 21 },
|
|
1947
|
+
{ label: "21-50", max: 51 },
|
|
1948
|
+
{ label: "> 50", max: Infinity }
|
|
1949
|
+
];
|
|
1950
|
+
var CYCLE_BUCKETS = [
|
|
1951
|
+
{ label: "1", max: 2 },
|
|
1952
|
+
{ label: "2", max: 3 },
|
|
1953
|
+
{ label: "3", max: 4 },
|
|
1954
|
+
{ label: "4-5", max: 6 },
|
|
1955
|
+
{ label: "> 5", max: Infinity }
|
|
1956
|
+
];
|
|
1957
|
+
var COMMENT_BUCKETS = [
|
|
1958
|
+
{ label: "0", max: 1 },
|
|
1959
|
+
{ label: "1-2", max: 3 },
|
|
1960
|
+
{ label: "3-5", max: 6 },
|
|
1961
|
+
{ label: "6-10", max: 11 },
|
|
1962
|
+
{ label: "11-20", max: 21 },
|
|
1963
|
+
{ label: "21-50", max: 51 },
|
|
1964
|
+
{ label: "> 50", max: Infinity }
|
|
1965
|
+
];
|
|
1966
|
+
function formatHoursOnly(hours) {
|
|
1967
|
+
if (hours < 1) {
|
|
1968
|
+
return `${Math.round(hours * 60)}m`;
|
|
1660
1969
|
}
|
|
1661
|
-
return
|
|
1662
|
-
ChartsPanel,
|
|
1663
|
-
{
|
|
1664
|
-
scrollRef,
|
|
1665
|
-
focused,
|
|
1666
|
-
heading: repos.length > 0 ? scope.repo ?? "All repos" : null,
|
|
1667
|
-
warning,
|
|
1668
|
-
view
|
|
1669
|
-
}
|
|
1670
|
-
);
|
|
1970
|
+
return `${hours.toFixed(1)}h`;
|
|
1671
1971
|
}
|
|
1672
|
-
function
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
error,
|
|
1679
|
-
loading,
|
|
1680
|
-
load,
|
|
1681
|
-
onRefClick
|
|
1682
|
-
}) {
|
|
1683
|
-
return /* @__PURE__ */ jsx9("box", { flexGrow: 1, flexDirection: "column", marginTop: 1, children: views === null ? /* @__PURE__ */ jsx9(Placeholder, { error, loading, load }) : browse.tab === 0 ? /* @__PURE__ */ jsx9(
|
|
1684
|
-
QueueTab,
|
|
1685
|
-
{
|
|
1686
|
-
prompt: "Select a repository and press enter to open its review queue.",
|
|
1687
|
-
repos: views.pendingRepos,
|
|
1688
|
-
scope: views.pendingScope,
|
|
1689
|
-
view: views.pending,
|
|
1690
|
-
repoCursor: browse.repoCursors.pending,
|
|
1691
|
-
rowCursor: browse.rowCursors.pending,
|
|
1692
|
-
grouped: browse.grouped.pending,
|
|
1693
|
-
warning,
|
|
1694
|
-
onRefClick
|
|
1695
|
-
}
|
|
1696
|
-
) : browse.tab === 1 ? /* @__PURE__ */ jsxs8("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
1697
|
-
/* @__PURE__ */ jsx9(SubTabBar, { active: browse.authoredTab }),
|
|
1698
|
-
browse.authoredTab === "open" ? /* @__PURE__ */ jsx9(
|
|
1699
|
-
QueueTab,
|
|
1700
|
-
{
|
|
1701
|
-
prompt: "Select a repository and press enter to list its open PRs.",
|
|
1702
|
-
repos: views.openRepos,
|
|
1703
|
-
scope: views.openScope,
|
|
1704
|
-
view: views.open,
|
|
1705
|
-
repoCursor: browse.repoCursors.open,
|
|
1706
|
-
rowCursor: browse.rowCursors.open,
|
|
1707
|
-
grouped: browse.grouped.open,
|
|
1708
|
-
warning,
|
|
1709
|
-
onRefClick
|
|
1710
|
-
}
|
|
1711
|
-
) : /* @__PURE__ */ jsx9(
|
|
1712
|
-
StatsTab,
|
|
1713
|
-
{
|
|
1714
|
-
repos: views.mergedRepos,
|
|
1715
|
-
scope: views.mergedScope,
|
|
1716
|
-
view: views.merged,
|
|
1717
|
-
cursor: browse.repoCursors.merged,
|
|
1718
|
-
scrollRef: scrollRefs.merged,
|
|
1719
|
-
focused,
|
|
1720
|
-
warning
|
|
1721
|
-
}
|
|
1722
|
-
)
|
|
1723
|
-
] }) : browse.tab === 2 ? /* @__PURE__ */ jsx9(
|
|
1724
|
-
StatsTab,
|
|
1725
|
-
{
|
|
1726
|
-
repos: views.reviewRepos,
|
|
1727
|
-
scope: views.reviewScope,
|
|
1728
|
-
view: views.review,
|
|
1729
|
-
cursor: browse.repoCursors.review,
|
|
1730
|
-
scrollRef: scrollRefs.review,
|
|
1731
|
-
focused,
|
|
1732
|
-
warning
|
|
1733
|
-
}
|
|
1734
|
-
) : browse.tab === 3 ? /* @__PURE__ */ jsx9(
|
|
1735
|
-
StatsTab,
|
|
1736
|
-
{
|
|
1737
|
-
repos: views.sizeRepos,
|
|
1738
|
-
scope: views.sizeScope,
|
|
1739
|
-
view: views.size,
|
|
1740
|
-
cursor: browse.repoCursors.size,
|
|
1741
|
-
scrollRef: scrollRefs.size,
|
|
1742
|
-
focused,
|
|
1743
|
-
warning
|
|
1744
|
-
}
|
|
1745
|
-
) : /* @__PURE__ */ jsx9(
|
|
1746
|
-
StatsTab,
|
|
1747
|
-
{
|
|
1748
|
-
repos: views.commentRepos,
|
|
1749
|
-
scope: views.commentScope,
|
|
1750
|
-
view: views.comments,
|
|
1751
|
-
cursor: browse.repoCursors.comment,
|
|
1752
|
-
scrollRef: scrollRefs.comment,
|
|
1753
|
-
focused,
|
|
1754
|
-
warning
|
|
1755
|
-
}
|
|
1756
|
-
) });
|
|
1972
|
+
function weeksSuffix(hours) {
|
|
1973
|
+
const weekHours = timeMode.business ? 5 * timeMode.dayHours : 7 * 24;
|
|
1974
|
+
if (hours < weekHours) {
|
|
1975
|
+
return "";
|
|
1976
|
+
}
|
|
1977
|
+
return ` (${(hours / weekHours).toFixed(1)} weeks)`;
|
|
1757
1978
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
let index = string.indexOf(substring);
|
|
1762
|
-
if (index === -1) {
|
|
1763
|
-
return string;
|
|
1979
|
+
function formatCount(value2) {
|
|
1980
|
+
if (value2 < 1e3) {
|
|
1981
|
+
return String(value2);
|
|
1764
1982
|
}
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
let returnValue = "";
|
|
1768
|
-
do {
|
|
1769
|
-
returnValue += string.slice(endIndex, index) + substring + postfix;
|
|
1770
|
-
endIndex = index + substringLength;
|
|
1771
|
-
index = string.indexOf(substring, endIndex);
|
|
1772
|
-
} while (index !== -1);
|
|
1773
|
-
returnValue += string.slice(endIndex);
|
|
1774
|
-
return returnValue;
|
|
1983
|
+
const scaled = value2 / 1e3;
|
|
1984
|
+
return `${scaled >= 10 ? Math.round(scaled) : scaled.toFixed(1)}k`;
|
|
1775
1985
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1986
|
+
|
|
1987
|
+
// src/tui/views/rows.ts
|
|
1988
|
+
function durationLead(result) {
|
|
1989
|
+
return `${formatHoursOnly(result.hours).padStart(8)}${weeksSuffix(result.hours)}`;
|
|
1990
|
+
}
|
|
1991
|
+
function toPrRows(entries, leads) {
|
|
1992
|
+
const width = Math.max(...leads.map((lead) => lead.length));
|
|
1993
|
+
return entries.map((entry, i) => {
|
|
1994
|
+
return {
|
|
1995
|
+
lead: leads[i].padEnd(width),
|
|
1996
|
+
ref: `${entry.pr.repo}#${entry.pr.number}`,
|
|
1997
|
+
url: entry.pr.url,
|
|
1998
|
+
title: entry.pr.title
|
|
1999
|
+
};
|
|
2000
|
+
});
|
|
1787
2001
|
}
|
|
1788
2002
|
|
|
1789
|
-
//
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
1793
|
-
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
1794
|
-
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
1795
|
-
var wrapUnderlineAnsi = (code) => `\x1B[58;5;${code < 90 ? code - 30 : code - 90 + 8}m`;
|
|
1796
|
-
var styles = {
|
|
1797
|
-
modifier: {
|
|
1798
|
-
reset: [0, 0],
|
|
1799
|
-
// 21 isn't widely supported and 22 does the same thing
|
|
1800
|
-
bold: [1, 22],
|
|
1801
|
-
dim: [2, 22],
|
|
1802
|
-
italic: [3, 23],
|
|
1803
|
-
underline: [4, 24],
|
|
1804
|
-
// Extended underline styles (`SGR 4:x` sub-parameters). Not in upstream `ansi-styles`.
|
|
1805
|
-
underlineDouble: ["4:2", 24],
|
|
1806
|
-
underlineCurly: ["4:3", 24],
|
|
1807
|
-
underlineDotted: ["4:4", 24],
|
|
1808
|
-
underlineDashed: ["4:5", 24],
|
|
1809
|
-
overline: [53, 55],
|
|
1810
|
-
inverse: [7, 27],
|
|
1811
|
-
hidden: [8, 28],
|
|
1812
|
-
strikethrough: [9, 29]
|
|
1813
|
-
},
|
|
1814
|
-
color: {
|
|
1815
|
-
black: [30, 39],
|
|
1816
|
-
red: [31, 39],
|
|
1817
|
-
green: [32, 39],
|
|
1818
|
-
yellow: [33, 39],
|
|
1819
|
-
blue: [34, 39],
|
|
1820
|
-
magenta: [35, 39],
|
|
1821
|
-
cyan: [36, 39],
|
|
1822
|
-
white: [37, 39],
|
|
1823
|
-
// Bright color
|
|
1824
|
-
blackBright: [90, 39],
|
|
1825
|
-
gray: [90, 39],
|
|
1826
|
-
// Alias of `blackBright`
|
|
1827
|
-
grey: [90, 39],
|
|
1828
|
-
// Alias of `blackBright`
|
|
1829
|
-
redBright: [91, 39],
|
|
1830
|
-
greenBright: [92, 39],
|
|
1831
|
-
yellowBright: [93, 39],
|
|
1832
|
-
blueBright: [94, 39],
|
|
1833
|
-
magentaBright: [95, 39],
|
|
1834
|
-
cyanBright: [96, 39],
|
|
1835
|
-
whiteBright: [97, 39]
|
|
1836
|
-
},
|
|
1837
|
-
bgColor: {
|
|
1838
|
-
bgBlack: [40, 49],
|
|
1839
|
-
bgRed: [41, 49],
|
|
1840
|
-
bgGreen: [42, 49],
|
|
1841
|
-
bgYellow: [43, 49],
|
|
1842
|
-
bgBlue: [44, 49],
|
|
1843
|
-
bgMagenta: [45, 49],
|
|
1844
|
-
bgCyan: [46, 49],
|
|
1845
|
-
bgWhite: [47, 49],
|
|
1846
|
-
// Bright color
|
|
1847
|
-
bgBlackBright: [100, 49],
|
|
1848
|
-
bgGray: [100, 49],
|
|
1849
|
-
// Alias of `bgBlackBright`
|
|
1850
|
-
bgGrey: [100, 49],
|
|
1851
|
-
// Alias of `bgBlackBright`
|
|
1852
|
-
bgRedBright: [101, 49],
|
|
1853
|
-
bgGreenBright: [102, 49],
|
|
1854
|
-
bgYellowBright: [103, 49],
|
|
1855
|
-
bgBlueBright: [104, 49],
|
|
1856
|
-
bgMagentaBright: [105, 49],
|
|
1857
|
-
bgCyanBright: [106, 49],
|
|
1858
|
-
bgWhiteBright: [107, 49]
|
|
1859
|
-
},
|
|
1860
|
-
// Underline color (`SGR 58`/`59`). Not in upstream `ansi-styles`.
|
|
1861
|
-
underlineColor: {
|
|
1862
|
-
underlineBlack: ["58;5;0", 59],
|
|
1863
|
-
underlineRed: ["58;5;1", 59],
|
|
1864
|
-
underlineGreen: ["58;5;2", 59],
|
|
1865
|
-
underlineYellow: ["58;5;3", 59],
|
|
1866
|
-
underlineBlue: ["58;5;4", 59],
|
|
1867
|
-
underlineMagenta: ["58;5;5", 59],
|
|
1868
|
-
underlineCyan: ["58;5;6", 59],
|
|
1869
|
-
underlineWhite: ["58;5;7", 59],
|
|
1870
|
-
// Bright color
|
|
1871
|
-
underlineBlackBright: ["58;5;8", 59],
|
|
1872
|
-
underlineGray: ["58;5;8", 59],
|
|
1873
|
-
// Alias of `underlineBlackBright`
|
|
1874
|
-
underlineGrey: ["58;5;8", 59],
|
|
1875
|
-
// Alias of `underlineBlackBright`
|
|
1876
|
-
underlineRedBright: ["58;5;9", 59],
|
|
1877
|
-
underlineGreenBright: ["58;5;10", 59],
|
|
1878
|
-
underlineYellowBright: ["58;5;11", 59],
|
|
1879
|
-
underlineBlueBright: ["58;5;12", 59],
|
|
1880
|
-
underlineMagentaBright: ["58;5;13", 59],
|
|
1881
|
-
underlineCyanBright: ["58;5;14", 59],
|
|
1882
|
-
underlineWhiteBright: ["58;5;15", 59]
|
|
1883
|
-
}
|
|
1884
|
-
};
|
|
1885
|
-
var modifierNames = Object.keys(styles.modifier);
|
|
1886
|
-
var foregroundColorNames = Object.keys(styles.color);
|
|
1887
|
-
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
1888
|
-
var underlineColorNames = Object.keys(styles.underlineColor);
|
|
1889
|
-
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
1890
|
-
function assembleStyles() {
|
|
1891
|
-
const codes = /* @__PURE__ */ new Map();
|
|
1892
|
-
for (const [groupName, group] of Object.entries(styles)) {
|
|
1893
|
-
for (const [styleName, style] of Object.entries(group)) {
|
|
1894
|
-
styles[styleName] = {
|
|
1895
|
-
open: `\x1B[${style[0]}m`,
|
|
1896
|
-
close: `\x1B[${style[1]}m`
|
|
1897
|
-
};
|
|
1898
|
-
group[styleName] = styles[styleName];
|
|
1899
|
-
codes.set(Number.parseInt(style[0], 10), style[1]);
|
|
1900
|
-
}
|
|
1901
|
-
Object.defineProperty(styles, groupName, {
|
|
1902
|
-
value: group,
|
|
1903
|
-
enumerable: false
|
|
1904
|
-
});
|
|
1905
|
-
}
|
|
1906
|
-
Object.defineProperty(styles, "codes", {
|
|
1907
|
-
value: codes,
|
|
1908
|
-
enumerable: false
|
|
1909
|
-
});
|
|
1910
|
-
styles.color.close = "\x1B[39m";
|
|
1911
|
-
styles.bgColor.close = "\x1B[49m";
|
|
1912
|
-
styles.underlineColor.close = "\x1B[59m";
|
|
1913
|
-
styles.color.ansi = wrapAnsi16();
|
|
1914
|
-
styles.color.ansi256 = wrapAnsi256();
|
|
1915
|
-
styles.color.ansi16m = wrapAnsi16m();
|
|
1916
|
-
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
1917
|
-
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
1918
|
-
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
1919
|
-
styles.underlineColor.ansi = wrapUnderlineAnsi;
|
|
1920
|
-
styles.underlineColor.ansi256 = wrapAnsi256(ANSI_UNDERLINE_OFFSET);
|
|
1921
|
-
styles.underlineColor.ansi16m = wrapAnsi16m(ANSI_UNDERLINE_OFFSET);
|
|
1922
|
-
Object.defineProperties(styles, {
|
|
1923
|
-
rgbToAnsi256: {
|
|
1924
|
-
value(red, green, blue) {
|
|
1925
|
-
if (red === green && green === blue) {
|
|
1926
|
-
if (red < 8) {
|
|
1927
|
-
return 16;
|
|
1928
|
-
}
|
|
1929
|
-
if (red > 248) {
|
|
1930
|
-
return 231;
|
|
1931
|
-
}
|
|
1932
|
-
return Math.round((red - 8) / 247 * 24) + 232;
|
|
1933
|
-
}
|
|
1934
|
-
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
1935
|
-
},
|
|
1936
|
-
enumerable: false
|
|
1937
|
-
},
|
|
1938
|
-
hexToRgb: {
|
|
1939
|
-
value(hex) {
|
|
1940
|
-
const matches = /[\da-f]{6}|[\da-f]{3}/i.exec(hex.toString(16));
|
|
1941
|
-
if (!matches) {
|
|
1942
|
-
return [0, 0, 0];
|
|
1943
|
-
}
|
|
1944
|
-
let [colorString] = matches;
|
|
1945
|
-
if (colorString.length === 3) {
|
|
1946
|
-
colorString = [...colorString].map((character) => character + character).join("");
|
|
1947
|
-
}
|
|
1948
|
-
const integer = Number.parseInt(colorString, 16);
|
|
1949
|
-
return [
|
|
1950
|
-
/* eslint-disable no-bitwise -- We need the speed */
|
|
1951
|
-
integer >> 16 & 255,
|
|
1952
|
-
integer >> 8 & 255,
|
|
1953
|
-
integer & 255
|
|
1954
|
-
/* eslint-enable no-bitwise */
|
|
1955
|
-
];
|
|
1956
|
-
},
|
|
1957
|
-
enumerable: false
|
|
1958
|
-
},
|
|
1959
|
-
hexToAnsi256: {
|
|
1960
|
-
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
1961
|
-
enumerable: false
|
|
1962
|
-
},
|
|
1963
|
-
ansi256ToAnsi: {
|
|
1964
|
-
value(code) {
|
|
1965
|
-
if (code < 8) {
|
|
1966
|
-
return 30 + code;
|
|
1967
|
-
}
|
|
1968
|
-
if (code < 16) {
|
|
1969
|
-
return 90 + (code - 8);
|
|
1970
|
-
}
|
|
1971
|
-
let red;
|
|
1972
|
-
let green;
|
|
1973
|
-
let blue;
|
|
1974
|
-
if (code >= 232) {
|
|
1975
|
-
red = ((code - 232) * 10 + 8) / 255;
|
|
1976
|
-
green = red;
|
|
1977
|
-
blue = red;
|
|
1978
|
-
} else {
|
|
1979
|
-
code -= 16;
|
|
1980
|
-
const remainder = code % 36;
|
|
1981
|
-
red = Math.floor(code / 36) / 5;
|
|
1982
|
-
green = Math.floor(remainder / 6) / 5;
|
|
1983
|
-
blue = remainder % 6 / 5;
|
|
1984
|
-
}
|
|
1985
|
-
const value2 = Math.max(red, green, blue) * 2;
|
|
1986
|
-
if (value2 === 0) {
|
|
1987
|
-
return 30;
|
|
1988
|
-
}
|
|
1989
|
-
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
1990
|
-
if (value2 === 2) {
|
|
1991
|
-
result += 60;
|
|
1992
|
-
}
|
|
1993
|
-
return result;
|
|
1994
|
-
},
|
|
1995
|
-
enumerable: false
|
|
1996
|
-
},
|
|
1997
|
-
rgbToAnsi: {
|
|
1998
|
-
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
1999
|
-
enumerable: false
|
|
2000
|
-
},
|
|
2001
|
-
hexToAnsi: {
|
|
2002
|
-
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
2003
|
-
enumerable: false
|
|
2004
|
-
}
|
|
2005
|
-
});
|
|
2006
|
-
return styles;
|
|
2007
|
-
}
|
|
2008
|
-
var ansiStyles = assembleStyles();
|
|
2009
|
-
var ansi_styles_default = ansiStyles;
|
|
2010
|
-
|
|
2011
|
-
// node_modules/.pnpm/chalk@6.0.0/node_modules/chalk/source/vendor/supports-color/index.js
|
|
2012
|
-
import process2 from "node:process";
|
|
2013
|
-
import os from "node:os";
|
|
2014
|
-
import tty from "node:tty";
|
|
2015
|
-
function hasFlag(flag2, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
|
|
2016
|
-
const prefix = flag2.startsWith("-") ? "" : flag2.length === 1 ? "-" : "--";
|
|
2017
|
-
const position = argv.indexOf(prefix + flag2);
|
|
2018
|
-
const terminatorPosition = argv.indexOf("--");
|
|
2019
|
-
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
2020
|
-
}
|
|
2021
|
-
var { env } = process2;
|
|
2022
|
-
var flagForceColor;
|
|
2023
|
-
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
2024
|
-
flagForceColor = 0;
|
|
2025
|
-
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
2026
|
-
flagForceColor = 1;
|
|
2027
|
-
}
|
|
2028
|
-
function hasNumericForceColor() {
|
|
2029
|
-
return /^\d+$/.test(env.FORCE_COLOR);
|
|
2003
|
+
// src/tui/views/queue.ts
|
|
2004
|
+
function queueRows(view) {
|
|
2005
|
+
return view.sections.flatMap((section) => [...section.rows, ...section.lists.flatMap((list) => list.rows)]);
|
|
2030
2006
|
}
|
|
2031
|
-
function
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2038
|
-
if (env.FORCE_COLOR === "true" || env.FORCE_COLOR.length === 0) {
|
|
2039
|
-
return 1;
|
|
2040
|
-
}
|
|
2041
|
-
if (!hasNumericForceColor()) {
|
|
2042
|
-
return;
|
|
2007
|
+
function groupedLists(entries, rowsOf2) {
|
|
2008
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2009
|
+
for (const entry of entries) {
|
|
2010
|
+
const group = groups.get(entry.pr.repo) ?? [];
|
|
2011
|
+
group.push(entry);
|
|
2012
|
+
groups.set(entry.pr.repo, group);
|
|
2043
2013
|
}
|
|
2044
|
-
return
|
|
2014
|
+
return [...groups.entries()].toSorted((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0])).map(([repo, group]) => {
|
|
2015
|
+
return { title: `${repo} (n=${group.length})`, rows: rowsOf2(group) };
|
|
2016
|
+
});
|
|
2045
2017
|
}
|
|
2046
|
-
function
|
|
2047
|
-
|
|
2048
|
-
|
|
2018
|
+
function buildPendingReviewView(raw, repo = null, grouped = false) {
|
|
2019
|
+
const stats = computeReviewStats(raw.reviewResults, { now: raw.fetchedAt });
|
|
2020
|
+
const awaiting = repo === null ? stats.pending : stats.pending.filter((entry) => entry.pr.repo === repo);
|
|
2021
|
+
const reviewing = repo === null ? stats.reviewing : stats.reviewing.filter((entry) => entry.pr.repo === repo);
|
|
2022
|
+
if (awaiting.length === 0 && reviewing.length === 0) {
|
|
2023
|
+
return { empty: "No PRs are awaiting your review, and none you reviewed are still open.", sections: [] };
|
|
2049
2024
|
}
|
|
2025
|
+
const split = repo === null && grouped;
|
|
2026
|
+
const sectionOf = (title, entries) => split ? { title, rows: [], lists: groupedLists(entries, rowsOf) } : { title, rows: rowsOf(entries), lists: [] };
|
|
2050
2027
|
return {
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2028
|
+
empty: null,
|
|
2029
|
+
sections: [
|
|
2030
|
+
...awaiting.length === 0 ? [] : [sectionOf(`Awaiting your review (n=${awaiting.length})`, awaiting)],
|
|
2031
|
+
...reviewing.length === 0 ? [] : [sectionOf(`Reviewing (n=${reviewing.length})`, reviewing)]
|
|
2032
|
+
]
|
|
2055
2033
|
};
|
|
2056
2034
|
}
|
|
2057
|
-
function
|
|
2058
|
-
const
|
|
2059
|
-
if (
|
|
2060
|
-
|
|
2061
|
-
}
|
|
2062
|
-
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
2063
|
-
if (forceColor === 0) {
|
|
2064
|
-
return 0;
|
|
2065
|
-
}
|
|
2066
|
-
if (sniffFlags) {
|
|
2067
|
-
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
2068
|
-
return 3;
|
|
2069
|
-
}
|
|
2070
|
-
if (hasFlag("color=256")) {
|
|
2071
|
-
return 2;
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
if (forceColor !== void 0 && hasNumericForceColor()) {
|
|
2075
|
-
return forceColor;
|
|
2076
|
-
}
|
|
2077
|
-
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
2078
|
-
return 1;
|
|
2079
|
-
}
|
|
2080
|
-
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
2081
|
-
return 0;
|
|
2082
|
-
}
|
|
2083
|
-
const min = forceColor || 0;
|
|
2084
|
-
if (env.TERM === "dumb") {
|
|
2085
|
-
return min;
|
|
2035
|
+
function buildOpenAuthoredView(raw, repo = null, grouped = false) {
|
|
2036
|
+
const open = raw.sizes.filter((entry) => entry.pr.state === "open" && (repo === null || entry.pr.repo === repo)).toSorted((a, b) => a.pr.createdAt.getTime() - b.pr.createdAt.getTime());
|
|
2037
|
+
if (open.length === 0) {
|
|
2038
|
+
return { empty: "No open authored PRs found.", sections: [] };
|
|
2086
2039
|
}
|
|
2087
|
-
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2040
|
+
const rowsOf2 = (group) => {
|
|
2041
|
+
const ages = group.map((entry) => durationLead({ hours: durationHours(entry.pr.createdAt, raw.fetchedAt) }));
|
|
2042
|
+
const ageWidth = Math.max(...ages.map((age) => age.length));
|
|
2043
|
+
return toPrRows(
|
|
2044
|
+
group,
|
|
2045
|
+
group.map(
|
|
2046
|
+
(entry, i) => `${ages[i].padEnd(ageWidth)} +${entry.additions}/-${entry.deletions}, ${entry.files} files`
|
|
2047
|
+
)
|
|
2048
|
+
);
|
|
2049
|
+
};
|
|
2050
|
+
const title = `Your open authored PRs (n=${open.length})`;
|
|
2051
|
+
if (repo === null && grouped) {
|
|
2052
|
+
return { empty: null, sections: [{ title, rows: [], lists: groupedLists(open, rowsOf2) }] };
|
|
2093
2053
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2054
|
+
return { empty: null, sections: [{ title, rows: rowsOf2(open), lists: [] }] };
|
|
2055
|
+
}
|
|
2056
|
+
function rowsOf(group) {
|
|
2057
|
+
return toPrRows(
|
|
2058
|
+
group,
|
|
2059
|
+
group.map((entry) => durationLead(entry))
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/tui/components/ChartsPanel.tsx
|
|
2064
|
+
import { useTerminalDimensions } from "@opentui/react";
|
|
2065
|
+
|
|
2066
|
+
// src/tui/hooks/scrollbar.ts
|
|
2067
|
+
import { CliRenderEvents } from "@opentui/core";
|
|
2068
|
+
import { useRenderer } from "@opentui/react";
|
|
2069
|
+
import { useLayoutEffect } from "react";
|
|
2070
|
+
var overlayScrollbar = {
|
|
2071
|
+
position: "absolute",
|
|
2072
|
+
top: 0,
|
|
2073
|
+
right: 0,
|
|
2074
|
+
bottom: 0
|
|
2075
|
+
};
|
|
2076
|
+
function useScrollbarSettle(scrollRef, mounted = true) {
|
|
2077
|
+
const renderer2 = useRenderer();
|
|
2078
|
+
useLayoutEffect(() => {
|
|
2079
|
+
if (!mounted) {
|
|
2080
|
+
return void 0;
|
|
2097
2081
|
}
|
|
2098
|
-
if (
|
|
2099
|
-
return
|
|
2082
|
+
if (!scrollRef.current?.verticalScrollBar) {
|
|
2083
|
+
return void 0;
|
|
2100
2084
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
return 3;
|
|
2111
|
-
}
|
|
2112
|
-
if (env.TERM === "xterm-ghostty") {
|
|
2113
|
-
return 3;
|
|
2114
|
-
}
|
|
2115
|
-
if (env.TERM === "wezterm") {
|
|
2116
|
-
return 3;
|
|
2117
|
-
}
|
|
2118
|
-
if ("TERM_PROGRAM" in env) {
|
|
2119
|
-
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".", 1)[0], 10);
|
|
2120
|
-
switch (env.TERM_PROGRAM) {
|
|
2121
|
-
case "iTerm.app": {
|
|
2122
|
-
return version >= 3 ? 3 : 2;
|
|
2085
|
+
scrollRef.current.verticalScrollBar.visible = false;
|
|
2086
|
+
const release = () => {
|
|
2087
|
+
renderer2.off(CliRenderEvents.FRAME, release);
|
|
2088
|
+
scrollRef.current?.verticalScrollBar.resetVisibilityControl();
|
|
2089
|
+
};
|
|
2090
|
+
const resyncThumb = () => {
|
|
2091
|
+
const bar = scrollRef.current?.verticalScrollBar;
|
|
2092
|
+
if (!bar) {
|
|
2093
|
+
return;
|
|
2123
2094
|
}
|
|
2124
|
-
|
|
2125
|
-
|
|
2095
|
+
const height = bar.viewportSize;
|
|
2096
|
+
const wanted = Math.max(1, height);
|
|
2097
|
+
if (bar.scrollSize < wanted || bar.slider.viewPortSize === wanted) {
|
|
2098
|
+
return;
|
|
2126
2099
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
return min;
|
|
2139
|
-
}
|
|
2140
|
-
function createSupportsColor(stream, options = {}) {
|
|
2141
|
-
const level = _supportsColor(stream, {
|
|
2142
|
-
streamIsTTY: stream && stream.isTTY,
|
|
2143
|
-
...options
|
|
2144
|
-
});
|
|
2145
|
-
return translateLevel(level);
|
|
2100
|
+
bar.viewportSize = 0;
|
|
2101
|
+
bar.viewportSize = height;
|
|
2102
|
+
};
|
|
2103
|
+
renderer2.on(CliRenderEvents.FRAME, release);
|
|
2104
|
+
renderer2.on(CliRenderEvents.FRAME, resyncThumb);
|
|
2105
|
+
return () => {
|
|
2106
|
+
renderer2.off(CliRenderEvents.FRAME, release);
|
|
2107
|
+
renderer2.off(CliRenderEvents.FRAME, resyncThumb);
|
|
2108
|
+
};
|
|
2109
|
+
}, [scrollRef, renderer2, mounted]);
|
|
2146
2110
|
}
|
|
2147
|
-
var supportsColor = {
|
|
2148
|
-
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
2149
|
-
stderr: createSupportsColor({ isTTY: tty.isatty(2) })
|
|
2150
|
-
};
|
|
2151
|
-
var supports_color_default = supportsColor;
|
|
2152
2111
|
|
|
2153
|
-
//
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
2157
|
-
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
2158
|
-
var LEVEL = /* @__PURE__ */ Symbol("LEVEL");
|
|
2159
|
-
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
2160
|
-
var assertValidLevel = (level) => {
|
|
2161
|
-
if (!Number.isSafeInteger(level) || level < 0 || level > 3) {
|
|
2162
|
-
throw new Error("The `level` should be an integer from 0 to 3");
|
|
2163
|
-
}
|
|
2164
|
-
};
|
|
2165
|
-
var levelDescriptor = {
|
|
2166
|
-
enumerable: true,
|
|
2167
|
-
get() {
|
|
2168
|
-
return this[LEVEL];
|
|
2169
|
-
},
|
|
2170
|
-
set(level) {
|
|
2171
|
-
assertValidLevel(level);
|
|
2172
|
-
this[LEVEL] = level;
|
|
2173
|
-
}
|
|
2174
|
-
};
|
|
2175
|
-
var applyOptions = (object, options = {}) => {
|
|
2176
|
-
if (options.level !== void 0) {
|
|
2177
|
-
assertValidLevel(options.level);
|
|
2178
|
-
}
|
|
2179
|
-
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
2180
|
-
object[LEVEL] = options.level === void 0 ? colorLevel : options.level;
|
|
2181
|
-
};
|
|
2182
|
-
var chalkFactory = (options) => {
|
|
2183
|
-
const chalk2 = (...strings) => strings.join(" ");
|
|
2184
|
-
applyOptions(chalk2, options);
|
|
2185
|
-
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
2186
|
-
return chalk2;
|
|
2187
|
-
};
|
|
2188
|
-
function createChalk(options) {
|
|
2189
|
-
return chalkFactory(options);
|
|
2112
|
+
// src/tui/views/charts/model.ts
|
|
2113
|
+
function lineLength(line) {
|
|
2114
|
+
return line.reduce((sum, span) => sum + span.text.length, 0);
|
|
2190
2115
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
get() {
|
|
2195
|
-
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
2196
|
-
Object.defineProperty(this, styleName, { value: builder });
|
|
2197
|
-
return builder;
|
|
2198
|
-
}
|
|
2199
|
-
};
|
|
2116
|
+
function cardWidth(card) {
|
|
2117
|
+
const subtitleLength = typeof card.subtitle === "string" ? card.subtitle.length : lineLength(card.subtitle);
|
|
2118
|
+
return Math.max(card.title.length + 2 + subtitleLength, ...card.lines.map(lineLength));
|
|
2200
2119
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
Object.defineProperty(this, "visible", { value: builder });
|
|
2205
|
-
return builder;
|
|
2206
|
-
}
|
|
2207
|
-
};
|
|
2208
|
-
var createModelConverters = (model, type) => {
|
|
2209
|
-
const style = ansi_styles_default[type];
|
|
2210
|
-
if (model === "rgb") {
|
|
2211
|
-
const ansi2 = (red, green, blue) => style.ansi(ansi_styles_default.rgbToAnsi(red, green, blue));
|
|
2212
|
-
const ansi256 = (red, green, blue) => style.ansi256(ansi_styles_default.rgbToAnsi256(red, green, blue));
|
|
2213
|
-
return [ansi2, ansi2, ansi256, style.ansi16m];
|
|
2214
|
-
}
|
|
2215
|
-
if (model === "hex") {
|
|
2216
|
-
const ansi2 = (hex) => style.ansi(ansi_styles_default.hexToAnsi(hex));
|
|
2217
|
-
const ansi256 = (hex) => style.ansi256(ansi_styles_default.hexToAnsi256(hex));
|
|
2218
|
-
return [ansi2, ansi2, ansi256, (hex) => style.ansi16m(...ansi_styles_default.hexToRgb(hex))];
|
|
2120
|
+
function formatDuration(hours) {
|
|
2121
|
+
if (hours < 1) {
|
|
2122
|
+
return `${Math.round(hours * 60)}m`;
|
|
2219
2123
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
};
|
|
2223
|
-
var usedModels = ["rgb", "hex", "ansi256"];
|
|
2224
|
-
for (const model of usedModels) {
|
|
2225
|
-
const capitalizedModel = model[0].toUpperCase() + model.slice(1);
|
|
2226
|
-
for (const [styleName, type] of [
|
|
2227
|
-
[model, "color"],
|
|
2228
|
-
["bg" + capitalizedModel, "bgColor"],
|
|
2229
|
-
["underline" + capitalizedModel, "underlineColor"]
|
|
2230
|
-
]) {
|
|
2231
|
-
const { close } = ansi_styles_default[type];
|
|
2232
|
-
const converters = createModelConverters(model, type);
|
|
2233
|
-
styles2[styleName] = {
|
|
2234
|
-
get() {
|
|
2235
|
-
const styleFunction = function(first, second, third) {
|
|
2236
|
-
const open = converters[this.level](first, second, third);
|
|
2237
|
-
return createBuilder(this, createStyler(open, close, this[STYLER]), this[IS_EMPTY]);
|
|
2238
|
-
};
|
|
2239
|
-
Object.defineProperty(this, styleName, { value: styleFunction });
|
|
2240
|
-
return styleFunction;
|
|
2241
|
-
}
|
|
2242
|
-
};
|
|
2124
|
+
if (hours < 2 * timeMode.dayHours) {
|
|
2125
|
+
return `${compact(hours)}h`;
|
|
2243
2126
|
}
|
|
2127
|
+
return `${compact(hours / timeMode.dayHours)}${timeMode.business && !isFullDayMode() ? "wd" : "d"}`;
|
|
2244
2128
|
}
|
|
2245
|
-
|
|
2246
|
-
()
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
);
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
let closeAll;
|
|
2264
|
-
if (parent === void 0) {
|
|
2265
|
-
openAll = open;
|
|
2266
|
-
closeAll = close;
|
|
2267
|
-
} else {
|
|
2268
|
-
openAll = parent.openAll + open;
|
|
2269
|
-
closeAll = close + parent.closeAll;
|
|
2129
|
+
function compact(value2) {
|
|
2130
|
+
return String(Math.round(value2 * 10) / 10);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
// src/tui/components/ChartsPanel.tsx
|
|
2134
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "@opentui/react/jsx-runtime";
|
|
2135
|
+
var COLUMN_GAP = 4;
|
|
2136
|
+
function ChartsPanel({
|
|
2137
|
+
scrollRef,
|
|
2138
|
+
focused,
|
|
2139
|
+
heading,
|
|
2140
|
+
warning,
|
|
2141
|
+
view
|
|
2142
|
+
}) {
|
|
2143
|
+
useScrollbarSettle(scrollRef, view.empty === null);
|
|
2144
|
+
const { width } = useTerminalDimensions();
|
|
2145
|
+
if (view.empty !== null) {
|
|
2146
|
+
return /* @__PURE__ */ jsx4("box", { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx4("text", { fg: theme.muted, children: view.empty }) });
|
|
2270
2147
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
};
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2148
|
+
const left = view.cards.filter((_, i) => i % 2 === 0);
|
|
2149
|
+
const right = view.cards.filter((_, i) => i % 2 === 1);
|
|
2150
|
+
const leftWidth = Math.max(0, ...left.map((card) => cardWidth(card)));
|
|
2151
|
+
const rightWidth = Math.max(0, ...right.map((card) => cardWidth(card)));
|
|
2152
|
+
const twoColumns = right.length > 0 && width - 4 >= leftWidth + COLUMN_GAP + rightWidth;
|
|
2153
|
+
const rows = left.map((card, i) => {
|
|
2154
|
+
return { key: card.title, left: card, right: right.at(i) };
|
|
2155
|
+
});
|
|
2156
|
+
const cards = twoColumns ? /* @__PURE__ */ jsx4("box", { flexDirection: "column", rowGap: 1, children: rows.map((row) => /* @__PURE__ */ jsxs3("box", { flexDirection: "row", alignItems: "flex-start", columnGap: COLUMN_GAP, children: [
|
|
2157
|
+
/* @__PURE__ */ jsx4("box", { flexDirection: "column", width: leftWidth, flexShrink: 0, children: /* @__PURE__ */ jsx4(ChartCard, { card: row.left }) }),
|
|
2158
|
+
/* @__PURE__ */ jsx4("box", { flexDirection: "column", width: rightWidth, flexShrink: 0, children: row.right !== void 0 && /* @__PURE__ */ jsx4(ChartCard, { card: row.right }) })
|
|
2159
|
+
] }, row.key)) }) : /* @__PURE__ */ jsx4("box", { flexDirection: "column", rowGap: 1, children: view.cards.map((card) => /* @__PURE__ */ jsx4(ChartCard, { card }, card.title)) });
|
|
2160
|
+
return /* @__PURE__ */ jsxs3("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
2161
|
+
/* @__PURE__ */ jsx4("box", { height: 1, children: /* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
2162
|
+
/* @__PURE__ */ jsx4("box", { height: 1, paddingLeft: 1, paddingRight: 2, flexDirection: "row", justifyContent: "space-between", children: keyed(view.strip, lineKey).map(({ key, item }) => /* @__PURE__ */ jsx4(ChartLine, { line: item }, key)) }),
|
|
2163
|
+
(heading !== null || view.headline !== null) && /* @__PURE__ */ jsxs3("box", { height: 1, paddingLeft: 1, paddingRight: 2, flexDirection: "row", justifyContent: "space-between", children: [
|
|
2164
|
+
/* @__PURE__ */ jsx4("text", { wrapMode: "none", children: heading !== null && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
2165
|
+
/* @__PURE__ */ jsx4("span", { fg: theme.accent, children: "\u25B8 " }),
|
|
2166
|
+
/* @__PURE__ */ jsx4("b", { fg: theme.text, children: heading })
|
|
2167
|
+
] }) }),
|
|
2168
|
+
view.headline !== null && /* @__PURE__ */ jsx4(ChartLine, { line: view.headline })
|
|
2169
|
+
] }),
|
|
2170
|
+
/* @__PURE__ */ jsx4("box", { height: 1, children: /* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
2171
|
+
/* @__PURE__ */ jsxs3(
|
|
2172
|
+
"scrollbox",
|
|
2173
|
+
{
|
|
2174
|
+
ref: scrollRef,
|
|
2175
|
+
focused,
|
|
2176
|
+
flexGrow: 1,
|
|
2177
|
+
paddingLeft: 1,
|
|
2178
|
+
paddingRight: 2,
|
|
2179
|
+
verticalScrollbarOptions: overlayScrollbar,
|
|
2180
|
+
children: [
|
|
2181
|
+
warning !== null && /* @__PURE__ */ jsx4("text", { wrapMode: "word", fg: theme.warn, marginTop: 1, children: warning }),
|
|
2182
|
+
view.lists.map((list) => /* @__PURE__ */ jsxs3("box", { flexDirection: "column", marginTop: 1, children: [
|
|
2183
|
+
/* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.accent, children: list.title }),
|
|
2184
|
+
list.rows.map((row) => /* @__PURE__ */ jsxs3("text", { wrapMode: "none", fg: theme.text, children: [
|
|
2185
|
+
" ",
|
|
2186
|
+
row.lead,
|
|
2187
|
+
" ",
|
|
2188
|
+
/* @__PURE__ */ jsx4("a", { href: row.url, fg: theme.accent, children: row.ref }),
|
|
2189
|
+
" ",
|
|
2190
|
+
row.title
|
|
2191
|
+
] }, row.url))
|
|
2192
|
+
] }, list.title)),
|
|
2193
|
+
view.distribution !== null && /* @__PURE__ */ jsxs3("box", { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
|
|
2194
|
+
/* @__PURE__ */ jsxs3("box", { flexDirection: "row", justifyContent: "space-between", children: [
|
|
2195
|
+
/* @__PURE__ */ jsx4("text", { wrapMode: "none", children: /* @__PURE__ */ jsx4("b", { fg: theme.text, children: view.distributionTitle }) }),
|
|
2196
|
+
/* @__PURE__ */ jsx4(ChartLine, { line: view.distribution.stats })
|
|
2197
|
+
] }),
|
|
2198
|
+
/* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: keyed(view.distribution.lines, lineKey).map(({ key, item }) => /* @__PURE__ */ jsx4(ChartLine, { line: item }, key)) })
|
|
2199
|
+
] }),
|
|
2200
|
+
view.cards.length === 0 ? /* @__PURE__ */ jsx4("text", { wrapMode: "none", fg: theme.muted, marginTop: 1, children: view.noCharts }) : cards
|
|
2201
|
+
]
|
|
2202
|
+
}
|
|
2203
|
+
)
|
|
2204
|
+
] });
|
|
2205
|
+
}
|
|
2206
|
+
function keyed(items, contentOf) {
|
|
2207
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2208
|
+
return items.map((item) => {
|
|
2209
|
+
const content = contentOf(item);
|
|
2210
|
+
const count2 = seen.get(content) ?? 0;
|
|
2211
|
+
seen.set(content, count2 + 1);
|
|
2212
|
+
return { key: `${count2}:${content}`, item };
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
function spanKey(span) {
|
|
2216
|
+
return `${span.fg ?? ""}|${span.bg ?? ""}|${span.bold === true ? "b" : ""}|${span.text}`;
|
|
2217
|
+
}
|
|
2218
|
+
function lineKey(line) {
|
|
2219
|
+
return line.map((span) => spanKey(span)).join("&");
|
|
2220
|
+
}
|
|
2221
|
+
function ChartLine({ line }) {
|
|
2222
|
+
return /* @__PURE__ */ jsx4("text", { wrapMode: "none", children: keyed(line, spanKey).map(
|
|
2223
|
+
({ key, item }) => item.bold === true ? /* @__PURE__ */ jsx4("b", { fg: item.fg ?? theme.text, bg: item.bg, children: item.text }, key) : /* @__PURE__ */ jsx4("span", { fg: item.fg ?? theme.text, bg: item.bg, children: item.text }, key)
|
|
2224
|
+
) });
|
|
2225
|
+
}
|
|
2226
|
+
function ChartCard({ card }) {
|
|
2227
|
+
const subtitle = typeof card.subtitle === "string" ? [{ text: card.subtitle, fg: theme.muted }] : card.subtitle;
|
|
2228
|
+
return /* @__PURE__ */ jsxs3("box", { flexDirection: "column", children: [
|
|
2229
|
+
/* @__PURE__ */ jsxs3("text", { wrapMode: "none", children: [
|
|
2230
|
+
/* @__PURE__ */ jsx4("b", { fg: theme.text, children: card.title }),
|
|
2231
|
+
/* @__PURE__ */ jsx4("span", { fg: theme.muted, children: " " }),
|
|
2232
|
+
keyed(subtitle, spanKey).map(({ key, item }) => /* @__PURE__ */ jsx4("span", { fg: item.fg ?? theme.muted, children: item.text }, key))
|
|
2233
|
+
] }),
|
|
2234
|
+
/* @__PURE__ */ jsx4("box", { flexDirection: "column", marginTop: 1, children: keyed(card.lines, lineKey).map(({ key, item }) => /* @__PURE__ */ jsx4(ChartLine, { line: item }, key)) })
|
|
2235
|
+
] });
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/tui/components/Placeholder.tsx
|
|
2239
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "@opentui/react/jsx-runtime";
|
|
2240
|
+
var BAR_WIDTH = 40;
|
|
2241
|
+
var PARTIAL_BLOCKS = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"];
|
|
2242
|
+
function Placeholder({
|
|
2243
|
+
error,
|
|
2244
|
+
loading,
|
|
2245
|
+
load
|
|
2246
|
+
}) {
|
|
2247
|
+
return /* @__PURE__ */ jsx5("box", { flexGrow: 1, alignItems: "center", justifyContent: "center", flexDirection: "column", children: error !== null ? /* @__PURE__ */ jsx5("text", { fg: theme.error, children: error }) : loading && load !== null ? /* @__PURE__ */ jsx5(LoadProgress, { load }) : /* @__PURE__ */ jsx5("text", { fg: theme.muted, children: "no data yet, press r to load" }) });
|
|
2248
|
+
}
|
|
2249
|
+
function LoadProgress({ load }) {
|
|
2250
|
+
if (!load.total) {
|
|
2251
|
+
return /* @__PURE__ */ jsxs4("box", { flexDirection: "row", columnGap: 1, children: [
|
|
2252
|
+
/* @__PURE__ */ jsx5("text", { fg: theme.muted, children: loadLabel(load) }),
|
|
2253
|
+
/* @__PURE__ */ jsx5(Spinner, {})
|
|
2254
|
+
] });
|
|
2298
2255
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2256
|
+
const done = load.done ?? 0;
|
|
2257
|
+
const cells = Math.min(done / load.total, 1) * BAR_WIDTH;
|
|
2258
|
+
const whole = Math.floor(cells);
|
|
2259
|
+
const partial = whole < BAR_WIDTH ? PARTIAL_BLOCKS[Math.floor((cells - whole) * 8)] : "";
|
|
2260
|
+
const counter = `${String(done).padStart(String(load.total).length)}/${load.total}`;
|
|
2261
|
+
return /* @__PURE__ */ jsxs4("box", { flexDirection: "column", alignItems: "center", rowGap: 1, children: [
|
|
2262
|
+
/* @__PURE__ */ jsx5("text", { fg: theme.muted, children: loadLabel(load) }),
|
|
2263
|
+
/* @__PURE__ */ jsxs4("text", { wrapMode: "none", children: [
|
|
2264
|
+
/* @__PURE__ */ jsx5("span", { bg: theme.accent, children: " ".repeat(whole) }),
|
|
2265
|
+
/* @__PURE__ */ jsx5("span", { fg: theme.accent, bg: theme.selectedBg, children: partial }),
|
|
2266
|
+
/* @__PURE__ */ jsx5("span", { bg: theme.selectedBg, children: " ".repeat(BAR_WIDTH - whole - partial.length) }),
|
|
2267
|
+
/* @__PURE__ */ jsx5("span", { fg: theme.muted, children: ` ${counter}` })
|
|
2268
|
+
] })
|
|
2269
|
+
] });
|
|
2270
|
+
}
|
|
2271
|
+
function loadLabel(load) {
|
|
2272
|
+
return load.phase === "search" ? "searching PRs" : "fetching PR details";
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// src/tui/components/QueuePanel.tsx
|
|
2276
|
+
import { useTerminalDimensions as useTerminalDimensions2 } from "@opentui/react";
|
|
2277
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
2278
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "@opentui/react/jsx-runtime";
|
|
2279
|
+
function QueuePanel({
|
|
2280
|
+
heading,
|
|
2281
|
+
warning,
|
|
2282
|
+
empty,
|
|
2283
|
+
sections,
|
|
2284
|
+
cursor,
|
|
2285
|
+
onRefClick
|
|
2286
|
+
}) {
|
|
2287
|
+
const scrollRef = useRef(null);
|
|
2288
|
+
const { width } = useTerminalDimensions2();
|
|
2289
|
+
const lastDown = useRef(null);
|
|
2290
|
+
useScrollbarSettle(scrollRef, empty === null);
|
|
2291
|
+
useEffect2(() => {
|
|
2292
|
+
scrollRef.current?.scrollChildIntoView(`queue-row-${cursor}`);
|
|
2293
|
+
}, [cursor]);
|
|
2294
|
+
const header = heading === null ? null : /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2295
|
+
/* @__PURE__ */ jsx6("box", { height: 1, children: /* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) }),
|
|
2296
|
+
/* @__PURE__ */ jsx6("box", { height: 1, paddingLeft: 1, paddingRight: 2, children: /* @__PURE__ */ jsxs5("text", { wrapMode: "none", children: [
|
|
2297
|
+
/* @__PURE__ */ jsx6("span", { fg: theme.accent, children: "\u25B8 " }),
|
|
2298
|
+
/* @__PURE__ */ jsx6("b", { fg: theme.text, children: heading })
|
|
2299
|
+
] }) }),
|
|
2300
|
+
/* @__PURE__ */ jsx6("box", { height: 1, children: /* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.border, children: "\u2500".repeat(width) }) })
|
|
2301
|
+
] });
|
|
2302
|
+
if (empty !== null) {
|
|
2303
|
+
return /* @__PURE__ */ jsxs5("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
2304
|
+
header,
|
|
2305
|
+
/* @__PURE__ */ jsx6("box", { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx6("text", { fg: theme.muted, children: empty }) })
|
|
2306
|
+
] });
|
|
2302
2307
|
}
|
|
2303
|
-
const
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
+
const offsets = [];
|
|
2309
|
+
let total = 0;
|
|
2310
|
+
for (const section of sections) {
|
|
2311
|
+
const entry = { rows: total, lists: [] };
|
|
2312
|
+
total += section.rows.length;
|
|
2313
|
+
for (const list of section.lists) {
|
|
2314
|
+
entry.lists.push(total);
|
|
2315
|
+
total += list.rows.length;
|
|
2308
2316
|
}
|
|
2317
|
+
offsets.push(entry);
|
|
2309
2318
|
}
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
},
|
|
2382
|
-
{
|
|
2383
|
-
name: "work-hours",
|
|
2384
|
-
short: "w",
|
|
2385
|
-
type: "string",
|
|
2386
|
-
default: "0-24",
|
|
2387
|
-
placeholder: "<v>",
|
|
2388
|
-
help: "Count only these working hours instead of full weekdays. Accepts 24-hour ranges like `9-17` or `8:30-16:30`, 12-hour ranges like `9am-6pm` or `8:30am-4:30pm`, or several comma-separated ranges like `9-18,19:30-20:30`. Without this flag, every hour Mon-Fri counts."
|
|
2389
|
-
},
|
|
2390
|
-
{
|
|
2391
|
-
name: "wall-clock",
|
|
2392
|
-
type: "boolean",
|
|
2393
|
-
default: false,
|
|
2394
|
-
help: "Measure raw elapsed time, including weekends."
|
|
2395
|
-
},
|
|
2396
|
-
{
|
|
2397
|
-
name: "include-drafts",
|
|
2398
|
-
type: "boolean",
|
|
2399
|
-
default: false,
|
|
2400
|
-
help: "Include PRs that are currently drafts. Excluded by default."
|
|
2401
|
-
},
|
|
2402
|
-
{
|
|
2403
|
-
name: "review-types",
|
|
2404
|
-
type: "string",
|
|
2405
|
-
placeholder: "<list>",
|
|
2406
|
-
help: "Count only these review types as a review. Accepts a comma-separated list of `approve`, `comment`, and `request-changes`, for example `approve,request-changes`. A review of another type never answers a request, so the PR stays in the awaiting queue until a counted review lands. Without this flag, every submitted review counts."
|
|
2407
|
-
},
|
|
2408
|
-
{
|
|
2409
|
-
name: "no-cache",
|
|
2410
|
-
type: "boolean",
|
|
2411
|
-
default: false,
|
|
2412
|
-
help: "Refetch every PR instead of reading the local disk cache. Closed PRs are normally served from a per-PR cache because their timelines and sizes no longer change. Fresh results still update the cache. The TUI settings dialog can save this behavior for every run, and the flag wins over the saved setting."
|
|
2413
|
-
},
|
|
2414
|
-
{
|
|
2415
|
-
name: "json",
|
|
2416
|
-
type: "boolean",
|
|
2417
|
-
default: false,
|
|
2418
|
-
help: "Print every stat as JSON to stdout instead of starting the TUI, so the output can be piped into jq or redirected to a file. The report holds the review, size, merge, reviewer, and comment stats with one entry per PR, and every other flag applies to it the same way. Progress renders on stderr, so a piped stdout stays pure JSON."
|
|
2419
|
-
},
|
|
2420
|
-
{
|
|
2421
|
-
name: "debug",
|
|
2422
|
-
type: "string",
|
|
2423
|
-
placeholder: "<path>",
|
|
2424
|
-
help: "Serve canned data from a fake gh binary instead of fetching from GitHub. Accepts a path to a testdata directory that contains a `gh` executable, for example `tui/testdata`, or a path to the executable itself. Tokens are ignored while this flag is set. This is useful for testing and local development."
|
|
2425
|
-
},
|
|
2426
|
-
{
|
|
2427
|
-
name: "help",
|
|
2428
|
-
short: "h",
|
|
2429
|
-
type: "boolean",
|
|
2430
|
-
default: false,
|
|
2431
|
-
help: "Show this help."
|
|
2432
|
-
}
|
|
2433
|
-
];
|
|
2434
|
-
var HELP_WIDTH = Math.min(process.stdout.isTTY ? process.stdout.columns : 80, 120);
|
|
2435
|
-
function colorizeHelp(text) {
|
|
2436
|
-
return text.replaceAll(/`([^`]+)`/g, (match, span) => value(span)).replaceAll(/"[^"]*"/g, (span) => source_default.yellow(span)).replaceAll(/--[a-z][a-z-]*/g, (span) => flag(span));
|
|
2319
|
+
const renderRow = (row, index, indent) => {
|
|
2320
|
+
const isSelected = index === cursor;
|
|
2321
|
+
const bg = isSelected ? theme.selectedBg : void 0;
|
|
2322
|
+
const refStart = indent.length + 2 + row.lead.length + 2;
|
|
2323
|
+
return /* @__PURE__ */ jsxs5(
|
|
2324
|
+
"text",
|
|
2325
|
+
{
|
|
2326
|
+
id: `queue-row-${index}`,
|
|
2327
|
+
wrapMode: "none",
|
|
2328
|
+
onMouseDown: onRefClick === null ? void 0 : (event) => {
|
|
2329
|
+
lastDown.current = event.button === 0 ? { x: event.x, y: event.y } : null;
|
|
2330
|
+
},
|
|
2331
|
+
onMouseUp: onRefClick === null ? void 0 : (event) => {
|
|
2332
|
+
const down = lastDown.current;
|
|
2333
|
+
lastDown.current = null;
|
|
2334
|
+
if (event.button !== 0 || down?.x !== event.x || down.y !== event.y) {
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
const local = event.currentTarget === null ? -1 : event.x - event.currentTarget.x;
|
|
2338
|
+
if (local >= refStart && local < refStart + row.ref.length) {
|
|
2339
|
+
onRefClick(row);
|
|
2340
|
+
}
|
|
2341
|
+
},
|
|
2342
|
+
children: [
|
|
2343
|
+
/* @__PURE__ */ jsxs5("span", { fg: theme.accent, children: [
|
|
2344
|
+
indent,
|
|
2345
|
+
isSelected ? "\u25B8 " : " "
|
|
2346
|
+
] }),
|
|
2347
|
+
/* @__PURE__ */ jsxs5("span", { fg: theme.text, bg, children: [
|
|
2348
|
+
row.lead,
|
|
2349
|
+
" "
|
|
2350
|
+
] }),
|
|
2351
|
+
onRefClick === null ? /* @__PURE__ */ jsx6("a", { href: row.url, fg: theme.accent, bg, children: row.ref }) : /* @__PURE__ */ jsx6("span", { fg: theme.accent, bg, children: row.ref }),
|
|
2352
|
+
/* @__PURE__ */ jsxs5("span", { fg: theme.text, bg, children: [
|
|
2353
|
+
" ",
|
|
2354
|
+
row.title
|
|
2355
|
+
] })
|
|
2356
|
+
]
|
|
2357
|
+
},
|
|
2358
|
+
row.url
|
|
2359
|
+
);
|
|
2360
|
+
};
|
|
2361
|
+
return /* @__PURE__ */ jsxs5("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
2362
|
+
header,
|
|
2363
|
+
/* @__PURE__ */ jsxs5(
|
|
2364
|
+
"scrollbox",
|
|
2365
|
+
{
|
|
2366
|
+
ref: scrollRef,
|
|
2367
|
+
flexGrow: 1,
|
|
2368
|
+
paddingLeft: 1,
|
|
2369
|
+
paddingRight: 2,
|
|
2370
|
+
verticalScrollbarOptions: overlayScrollbar,
|
|
2371
|
+
children: [
|
|
2372
|
+
warning !== null && /* @__PURE__ */ jsx6("text", { wrapMode: "word", fg: theme.warn, marginTop: 1, children: warning }),
|
|
2373
|
+
sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: 1, children: [
|
|
2374
|
+
/* @__PURE__ */ jsx6("text", { wrapMode: "none", fg: theme.accent, children: section.title }),
|
|
2375
|
+
section.rows.map((row, rowIndex) => renderRow(row, offsets[sectionIndex].rows + rowIndex, "")),
|
|
2376
|
+
section.lists.map((list, listIndex) => /* @__PURE__ */ jsxs5("box", { flexDirection: "column", marginTop: listIndex > 0 ? 1 : 0, children: [
|
|
2377
|
+
/* @__PURE__ */ jsxs5("text", { wrapMode: "none", fg: theme.accent, children: [
|
|
2378
|
+
" ",
|
|
2379
|
+
list.title
|
|
2380
|
+
] }),
|
|
2381
|
+
list.rows.map(
|
|
2382
|
+
(row, rowIndex) => renderRow(row, offsets[sectionIndex].lists[listIndex] + rowIndex, " ")
|
|
2383
|
+
)
|
|
2384
|
+
] }, list.title))
|
|
2385
|
+
] }, section.title))
|
|
2386
|
+
]
|
|
2387
|
+
}
|
|
2388
|
+
)
|
|
2389
|
+
] });
|
|
2437
2390
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2391
|
+
|
|
2392
|
+
// src/tui/components/RepoList.tsx
|
|
2393
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
2394
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "@opentui/react/jsx-runtime";
|
|
2395
|
+
function RepoList({
|
|
2396
|
+
options,
|
|
2397
|
+
cursor,
|
|
2398
|
+
prompt = "Select a repository and press enter to open its charts."
|
|
2399
|
+
}) {
|
|
2400
|
+
const scrollRef = useRef2(null);
|
|
2401
|
+
useScrollbarSettle(scrollRef);
|
|
2402
|
+
useEffect3(() => {
|
|
2403
|
+
scrollRef.current?.scrollChildIntoView(`repo-row-${cursor}`);
|
|
2404
|
+
}, [cursor]);
|
|
2405
|
+
const labelWidth = Math.max(...options.map((option) => option.label.length));
|
|
2406
|
+
return /* @__PURE__ */ jsxs6(
|
|
2407
|
+
"scrollbox",
|
|
2408
|
+
{
|
|
2409
|
+
ref: scrollRef,
|
|
2410
|
+
flexGrow: 1,
|
|
2411
|
+
paddingLeft: 1,
|
|
2412
|
+
paddingRight: 2,
|
|
2413
|
+
verticalScrollbarOptions: overlayScrollbar,
|
|
2414
|
+
children: [
|
|
2415
|
+
/* @__PURE__ */ jsx7("text", { wrapMode: "none", fg: theme.muted, children: prompt }),
|
|
2416
|
+
/* @__PURE__ */ jsx7("box", { flexDirection: "column", marginTop: 1, children: options.map((option, i) => {
|
|
2417
|
+
const isSelected = i === cursor;
|
|
2418
|
+
return /* @__PURE__ */ jsxs6("text", { id: `repo-row-${i}`, wrapMode: "none", children: [
|
|
2419
|
+
/* @__PURE__ */ jsx7("span", { fg: theme.accent, children: isSelected ? "\u25B8 " : " " }),
|
|
2420
|
+
/* @__PURE__ */ jsx7("span", { fg: isSelected ? theme.text : theme.muted, bg: isSelected ? theme.selectedBg : void 0, children: ` ${option.label.padEnd(labelWidth)} ` }),
|
|
2421
|
+
/* @__PURE__ */ jsxs6("span", { fg: theme.muted, children: [
|
|
2422
|
+
" ",
|
|
2423
|
+
option.detail
|
|
2424
|
+
] })
|
|
2425
|
+
] }, option.label);
|
|
2426
|
+
}) })
|
|
2427
|
+
]
|
|
2448
2428
|
}
|
|
2449
|
-
|
|
2450
|
-
if (line !== "") {
|
|
2451
|
-
lines.push(line);
|
|
2452
|
-
}
|
|
2453
|
-
return lines;
|
|
2429
|
+
);
|
|
2454
2430
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2431
|
+
|
|
2432
|
+
// src/tui/state/browse.ts
|
|
2433
|
+
var TABS = ["1 Awaiting you", "2 Your PRs", "3 Review time", "4 PR size", "5 Comments"];
|
|
2434
|
+
var initialBrowseState = {
|
|
2435
|
+
tab: 0,
|
|
2436
|
+
authoredTab: "open",
|
|
2437
|
+
scopes: {
|
|
2438
|
+
pending: { view: "list" },
|
|
2439
|
+
open: { view: "list" },
|
|
2440
|
+
review: { view: "list" },
|
|
2441
|
+
size: { view: "list" },
|
|
2442
|
+
comment: { view: "list" },
|
|
2443
|
+
merged: { view: "list" }
|
|
2444
|
+
},
|
|
2445
|
+
repoCursors: { pending: 0, open: 0, review: 0, size: 0, comment: 0, merged: 0 },
|
|
2446
|
+
rowCursors: { pending: 0, open: 0 },
|
|
2447
|
+
grouped: { pending: false, open: false },
|
|
2448
|
+
expanded: { review: false, size: false, comment: false, merged: false }
|
|
2449
|
+
};
|
|
2450
|
+
function dropVanishedRepo(scope, repos) {
|
|
2451
|
+
if (scope.view === "detail" && scope.repo !== null && !repos.some((option) => option.repo === scope.repo)) {
|
|
2452
|
+
return { view: "list" };
|
|
2471
2453
|
}
|
|
2472
|
-
return
|
|
2454
|
+
return scope;
|
|
2473
2455
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2456
|
+
function cycled(previous, delta, count2) {
|
|
2457
|
+
return (Math.min(previous, count2 - 1) + count2 + delta) % count2;
|
|
2458
|
+
}
|
|
2459
|
+
function browseReducer(state, action) {
|
|
2460
|
+
switch (action.type) {
|
|
2461
|
+
case "tabSelected": {
|
|
2462
|
+
return { ...state, tab: action.tab };
|
|
2481
2463
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2464
|
+
case "tabCycled": {
|
|
2465
|
+
return { ...state, tab: (state.tab + TABS.length + action.delta) % TABS.length };
|
|
2484
2466
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
const explicit = new Set(Object.keys(values));
|
|
2488
|
-
for (const option of OPTIONS) {
|
|
2489
|
-
if (option.default !== void 0 && values[option.name] === void 0) {
|
|
2490
|
-
values[option.name] = option.default;
|
|
2467
|
+
case "subTabToggled": {
|
|
2468
|
+
return { ...state, authoredTab: state.authoredTab === "open" ? "merged" : "open" };
|
|
2491
2469
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
if (relative[2] === "d") {
|
|
2501
|
-
date2.setDate(date2.getDate() - amount);
|
|
2470
|
+
case "repoCursorMoved": {
|
|
2471
|
+
return {
|
|
2472
|
+
...state,
|
|
2473
|
+
repoCursors: {
|
|
2474
|
+
...state.repoCursors,
|
|
2475
|
+
[action.tab]: cycled(state.repoCursors[action.tab], action.delta, action.count)
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2502
2478
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2479
|
+
case "rowCursorMoved": {
|
|
2480
|
+
return {
|
|
2481
|
+
...state,
|
|
2482
|
+
rowCursors: {
|
|
2483
|
+
...state.rowCursors,
|
|
2484
|
+
[action.tab]: cycled(state.rowCursors[action.tab], action.delta, action.count)
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2505
2487
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2488
|
+
case "repoOpened": {
|
|
2489
|
+
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "detail", repo: action.repo } } };
|
|
2508
2490
|
}
|
|
2509
|
-
|
|
2510
|
-
|
|
2491
|
+
case "pickerReturned": {
|
|
2492
|
+
return { ...state, scopes: { ...state.scopes, [action.tab]: { view: "list" } } };
|
|
2493
|
+
}
|
|
2494
|
+
case "groupingToggled": {
|
|
2495
|
+
return { ...state, grouped: { ...state.grouped, [action.tab]: !state.grouped[action.tab] } };
|
|
2496
|
+
}
|
|
2497
|
+
case "expandToggled": {
|
|
2498
|
+
return { ...state, expanded: { ...state.expanded, [action.tab]: !state.expanded[action.tab] } };
|
|
2499
|
+
}
|
|
2500
|
+
case "dataLoaded": {
|
|
2501
|
+
return {
|
|
2502
|
+
...state,
|
|
2503
|
+
scopes: {
|
|
2504
|
+
pending: dropVanishedRepo(state.scopes.pending, action.repos.pending),
|
|
2505
|
+
open: dropVanishedRepo(state.scopes.open, action.repos.open),
|
|
2506
|
+
review: dropVanishedRepo(state.scopes.review, action.repos.review),
|
|
2507
|
+
size: dropVanishedRepo(state.scopes.size, action.repos.size),
|
|
2508
|
+
comment: dropVanishedRepo(state.scopes.comment, action.repos.comment),
|
|
2509
|
+
merged: dropVanishedRepo(state.scopes.merged, action.repos.merged)
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2511
2512
|
}
|
|
2512
|
-
return date2;
|
|
2513
|
-
}
|
|
2514
|
-
const date = new Date(input);
|
|
2515
|
-
if (Number.isNaN(date.getTime())) {
|
|
2516
|
-
throw new CliError(`invalid --since value "${input}", use an ISO date or 30d/8w/6m/1y`);
|
|
2517
2513
|
}
|
|
2518
|
-
return
|
|
2514
|
+
return state;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// src/tui/components/TabBar.tsx
|
|
2518
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "@opentui/react/jsx-runtime";
|
|
2519
|
+
function TabBar({ tab }) {
|
|
2520
|
+
return /* @__PURE__ */ jsx8("box", { flexDirection: "row", height: 1, paddingLeft: 1, marginTop: 1, columnGap: 1, children: TABS.map((label, i) => /* @__PURE__ */ jsx8(
|
|
2521
|
+
"text",
|
|
2522
|
+
{
|
|
2523
|
+
wrapMode: "none",
|
|
2524
|
+
fg: i === tab ? theme.accent : theme.muted,
|
|
2525
|
+
bg: i === tab ? theme.selectedBg : void 0,
|
|
2526
|
+
children: ` ${label} `
|
|
2527
|
+
},
|
|
2528
|
+
label
|
|
2529
|
+
)) });
|
|
2530
|
+
}
|
|
2531
|
+
var SUB_TABS = [
|
|
2532
|
+
{ key: "open", label: "Open" },
|
|
2533
|
+
{ key: "merged", label: "Merged & closed" }
|
|
2534
|
+
];
|
|
2535
|
+
function SubTabBar({ active }) {
|
|
2536
|
+
return /* @__PURE__ */ jsxs7("box", { flexDirection: "row", height: 1, paddingLeft: 1, marginBottom: 1, columnGap: 1, children: [
|
|
2537
|
+
SUB_TABS.map(({ key, label }) => /* @__PURE__ */ jsx8(
|
|
2538
|
+
"text",
|
|
2539
|
+
{
|
|
2540
|
+
wrapMode: "none",
|
|
2541
|
+
fg: key === active ? theme.accent : theme.muted,
|
|
2542
|
+
bg: key === active ? theme.selectedBg : void 0,
|
|
2543
|
+
children: ` ${label} `
|
|
2544
|
+
},
|
|
2545
|
+
key
|
|
2546
|
+
)),
|
|
2547
|
+
/* @__PURE__ */ jsx8("text", { wrapMode: "none", fg: theme.dim, children: "t switches" })
|
|
2548
|
+
] });
|
|
2519
2549
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2550
|
+
|
|
2551
|
+
// src/tui/components/MainPanel.tsx
|
|
2552
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "@opentui/react/jsx-runtime";
|
|
2553
|
+
function QueueTab({
|
|
2554
|
+
prompt,
|
|
2555
|
+
repos,
|
|
2556
|
+
scope,
|
|
2557
|
+
view,
|
|
2558
|
+
repoCursor,
|
|
2559
|
+
rowCursor,
|
|
2560
|
+
grouped,
|
|
2561
|
+
warning,
|
|
2562
|
+
onRefClick
|
|
2563
|
+
}) {
|
|
2564
|
+
if (scope.view === "list") {
|
|
2565
|
+
return /* @__PURE__ */ jsx9(RepoList, { prompt, options: repos, cursor: Math.min(repoCursor, repos.length - 1) });
|
|
2531
2566
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
var DEFAULT_TARGET_PERCENTILE = 90;
|
|
2535
|
-
function parseTargetPercentile(input) {
|
|
2536
|
-
const match = /^p?(\d{1,3})$/i.exec(input);
|
|
2537
|
-
const value2 = match === null ? Number.NaN : Number(match[1]);
|
|
2538
|
-
if (!Number.isInteger(value2) || value2 < 1 || value2 > 100) {
|
|
2539
|
-
throw new CliError(`invalid --target-percentile value "${input}", use a percentile from 1 to 100 like 90 or p90`);
|
|
2567
|
+
if (view === null) {
|
|
2568
|
+
return null;
|
|
2540
2569
|
}
|
|
2541
|
-
return
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
const key = match[2] === "f" ? "files" : "lines";
|
|
2551
|
-
if (target[key] !== void 0) {
|
|
2552
|
-
throw new CliError(`--size-target sets the ${key} budget twice`);
|
|
2570
|
+
return /* @__PURE__ */ jsx9(
|
|
2571
|
+
QueuePanel,
|
|
2572
|
+
{
|
|
2573
|
+
heading: repos.length > 0 ? scope.repo ?? (grouped ? "All repos \xB7 grouped by repo" : "All repos") : null,
|
|
2574
|
+
warning,
|
|
2575
|
+
empty: view.empty,
|
|
2576
|
+
sections: view.sections,
|
|
2577
|
+
cursor: Math.min(rowCursor, queueRows(view).length - 1),
|
|
2578
|
+
onRefClick
|
|
2553
2579
|
}
|
|
2554
|
-
|
|
2555
|
-
}
|
|
2556
|
-
return target;
|
|
2580
|
+
);
|
|
2557
2581
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
}
|
|
2570
|
-
states.add(state);
|
|
2582
|
+
function StatsTab({
|
|
2583
|
+
repos,
|
|
2584
|
+
scope,
|
|
2585
|
+
view,
|
|
2586
|
+
cursor,
|
|
2587
|
+
scrollRef,
|
|
2588
|
+
focused,
|
|
2589
|
+
warning
|
|
2590
|
+
}) {
|
|
2591
|
+
if (scope.view === "list") {
|
|
2592
|
+
return /* @__PURE__ */ jsx9(RepoList, { options: repos, cursor: Math.min(cursor, repos.length - 1) });
|
|
2571
2593
|
}
|
|
2572
|
-
|
|
2573
|
-
}
|
|
2574
|
-
function toMinutesOfDay(hourText, minuteText, meridiem) {
|
|
2575
|
-
const minute = Number(minuteText ?? 0);
|
|
2576
|
-
if (minute > 59) {
|
|
2594
|
+
if (view === null) {
|
|
2577
2595
|
return null;
|
|
2578
2596
|
}
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2597
|
+
return /* @__PURE__ */ jsx9(
|
|
2598
|
+
ChartsPanel,
|
|
2599
|
+
{
|
|
2600
|
+
scrollRef,
|
|
2601
|
+
focused,
|
|
2602
|
+
heading: repos.length > 0 ? scope.repo ?? "All repos" : null,
|
|
2603
|
+
warning,
|
|
2604
|
+
view
|
|
2587
2605
|
}
|
|
2588
|
-
|
|
2589
|
-
return hour * 60 + minute;
|
|
2606
|
+
);
|
|
2590
2607
|
}
|
|
2591
|
-
function
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2608
|
+
function MainPanel({
|
|
2609
|
+
views,
|
|
2610
|
+
browse,
|
|
2611
|
+
warning,
|
|
2612
|
+
focused,
|
|
2613
|
+
scrollRefs,
|
|
2614
|
+
error,
|
|
2615
|
+
loading,
|
|
2616
|
+
load,
|
|
2617
|
+
onRefClick
|
|
2618
|
+
}) {
|
|
2619
|
+
return /* @__PURE__ */ jsx9("box", { flexGrow: 1, flexDirection: "column", marginTop: 1, children: views === null ? /* @__PURE__ */ jsx9(Placeholder, { error, loading, load }) : browse.tab === 0 ? /* @__PURE__ */ jsx9(
|
|
2620
|
+
QueueTab,
|
|
2621
|
+
{
|
|
2622
|
+
prompt: "Select a repository and press enter to open its review queue.",
|
|
2623
|
+
repos: views.pendingRepos,
|
|
2624
|
+
scope: views.pendingScope,
|
|
2625
|
+
view: views.pending,
|
|
2626
|
+
repoCursor: browse.repoCursors.pending,
|
|
2627
|
+
rowCursor: browse.rowCursors.pending,
|
|
2628
|
+
grouped: browse.grouped.pending,
|
|
2629
|
+
warning,
|
|
2630
|
+
onRefClick
|
|
2598
2631
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2632
|
+
) : browse.tab === 1 ? /* @__PURE__ */ jsxs8("box", { flexGrow: 1, flexDirection: "column", children: [
|
|
2633
|
+
/* @__PURE__ */ jsx9(SubTabBar, { active: browse.authoredTab }),
|
|
2634
|
+
browse.authoredTab === "open" ? /* @__PURE__ */ jsx9(
|
|
2635
|
+
QueueTab,
|
|
2636
|
+
{
|
|
2637
|
+
prompt: "Select a repository and press enter to list its open PRs.",
|
|
2638
|
+
repos: views.openRepos,
|
|
2639
|
+
scope: views.openScope,
|
|
2640
|
+
view: views.open,
|
|
2641
|
+
repoCursor: browse.repoCursors.open,
|
|
2642
|
+
rowCursor: browse.rowCursors.open,
|
|
2643
|
+
grouped: browse.grouped.open,
|
|
2644
|
+
warning,
|
|
2645
|
+
onRefClick
|
|
2646
|
+
}
|
|
2647
|
+
) : /* @__PURE__ */ jsx9(
|
|
2648
|
+
StatsTab,
|
|
2649
|
+
{
|
|
2650
|
+
repos: views.mergedRepos,
|
|
2651
|
+
scope: views.mergedScope,
|
|
2652
|
+
view: views.merged,
|
|
2653
|
+
cursor: browse.repoCursors.merged,
|
|
2654
|
+
scrollRef: scrollRefs.merged,
|
|
2655
|
+
focused,
|
|
2656
|
+
warning
|
|
2657
|
+
}
|
|
2658
|
+
)
|
|
2659
|
+
] }) : browse.tab === 2 ? /* @__PURE__ */ jsx9(
|
|
2660
|
+
StatsTab,
|
|
2661
|
+
{
|
|
2662
|
+
repos: views.reviewRepos,
|
|
2663
|
+
scope: views.reviewScope,
|
|
2664
|
+
view: views.review,
|
|
2665
|
+
cursor: browse.repoCursors.review,
|
|
2666
|
+
scrollRef: scrollRefs.review,
|
|
2667
|
+
focused,
|
|
2668
|
+
warning
|
|
2604
2669
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2670
|
+
) : browse.tab === 3 ? /* @__PURE__ */ jsx9(
|
|
2671
|
+
StatsTab,
|
|
2672
|
+
{
|
|
2673
|
+
repos: views.sizeRepos,
|
|
2674
|
+
scope: views.sizeScope,
|
|
2675
|
+
view: views.size,
|
|
2676
|
+
cursor: browse.repoCursors.size,
|
|
2677
|
+
scrollRef: scrollRefs.size,
|
|
2678
|
+
focused,
|
|
2679
|
+
warning
|
|
2611
2680
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2681
|
+
) : /* @__PURE__ */ jsx9(
|
|
2682
|
+
StatsTab,
|
|
2683
|
+
{
|
|
2684
|
+
repos: views.commentRepos,
|
|
2685
|
+
scope: views.commentScope,
|
|
2686
|
+
view: views.comments,
|
|
2687
|
+
cursor: browse.repoCursors.comment,
|
|
2688
|
+
scrollRef: scrollRefs.comment,
|
|
2689
|
+
focused,
|
|
2690
|
+
warning
|
|
2691
|
+
}
|
|
2692
|
+
) });
|
|
2623
2693
|
}
|
|
2624
2694
|
|
|
2625
2695
|
// src/tui/state/options.ts
|
|
@@ -2680,6 +2750,13 @@ var FIELDS = [
|
|
|
2680
2750
|
kind: "text",
|
|
2681
2751
|
fetch: false
|
|
2682
2752
|
},
|
|
2753
|
+
{
|
|
2754
|
+
key: "workDays",
|
|
2755
|
+
label: "Work days",
|
|
2756
|
+
hint: "enter opens the day list, checked days count as working days",
|
|
2757
|
+
kind: "multi",
|
|
2758
|
+
fetch: false
|
|
2759
|
+
},
|
|
2683
2760
|
{
|
|
2684
2761
|
key: "workHours",
|
|
2685
2762
|
label: "Work hours",
|
|
@@ -2703,6 +2780,23 @@ var FIELDS = [
|
|
|
2703
2780
|
}
|
|
2704
2781
|
];
|
|
2705
2782
|
var REVIEW_TYPE_CHOICES = ["approve", "comment", "request-changes"];
|
|
2783
|
+
var WORK_DAY_CHOICES = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
2784
|
+
function checkedWorkDays(value2) {
|
|
2785
|
+
return new Set([...parseWorkDays(value2)].map((day) => WORK_DAY_CHOICES[(day + 6) % 7]));
|
|
2786
|
+
}
|
|
2787
|
+
function toggleWorkDay(value2, day) {
|
|
2788
|
+
const days = parseWorkDays(value2);
|
|
2789
|
+
const number = (WORK_DAY_CHOICES.indexOf(day) + 1) % 7;
|
|
2790
|
+
if (days.has(number)) {
|
|
2791
|
+
days.delete(number);
|
|
2792
|
+
} else {
|
|
2793
|
+
days.add(number);
|
|
2794
|
+
}
|
|
2795
|
+
if (days.size === 0) {
|
|
2796
|
+
return value2;
|
|
2797
|
+
}
|
|
2798
|
+
return formatWorkDays(days);
|
|
2799
|
+
}
|
|
2706
2800
|
function checkedReviewTypes(value2) {
|
|
2707
2801
|
if (value2.trim() === "") {
|
|
2708
2802
|
return new Set(REVIEW_TYPE_CHOICES);
|
|
@@ -2723,7 +2817,7 @@ function toggleReviewType(value2, type) {
|
|
|
2723
2817
|
return next.length === REVIEW_TYPE_CHOICES.length ? "" : next.join(",");
|
|
2724
2818
|
}
|
|
2725
2819
|
function validateField(key, value2) {
|
|
2726
|
-
if (value2 === "" && key !== "workHours" && key !== "since") {
|
|
2820
|
+
if (value2 === "" && key !== "workHours" && key !== "workDays" && key !== "since") {
|
|
2727
2821
|
return;
|
|
2728
2822
|
}
|
|
2729
2823
|
switch (key) {
|
|
@@ -2743,6 +2837,10 @@ function validateField(key, value2) {
|
|
|
2743
2837
|
parseSizeTarget(value2);
|
|
2744
2838
|
break;
|
|
2745
2839
|
}
|
|
2840
|
+
case "workDays": {
|
|
2841
|
+
parseWorkDays(value2);
|
|
2842
|
+
break;
|
|
2843
|
+
}
|
|
2746
2844
|
case "workHours": {
|
|
2747
2845
|
parseWorkHours(value2);
|
|
2748
2846
|
break;
|
|
@@ -2768,6 +2866,7 @@ function readSavedOptions() {
|
|
|
2768
2866
|
const record = value2;
|
|
2769
2867
|
record.reviewTypes ??= "";
|
|
2770
2868
|
record.targetPercentile ??= "";
|
|
2869
|
+
record.workDays ??= "Mon-Fri";
|
|
2771
2870
|
const options = {};
|
|
2772
2871
|
for (const field of FIELDS) {
|
|
2773
2872
|
const raw = record[field.key];
|
|
@@ -2817,6 +2916,9 @@ function applySavedOptions(values, explicit) {
|
|
|
2817
2916
|
if (!explicit.has("size-target") && saved2.sizeTarget !== "") {
|
|
2818
2917
|
values["size-target"] = saved2.sizeTarget;
|
|
2819
2918
|
}
|
|
2919
|
+
if (!explicit.has("work-days")) {
|
|
2920
|
+
values["work-days"] = saved2.workDays;
|
|
2921
|
+
}
|
|
2820
2922
|
if (!explicit.has("work-hours")) {
|
|
2821
2923
|
values["work-hours"] = saved2.workHours;
|
|
2822
2924
|
}
|
|
@@ -2927,7 +3029,8 @@ function OptionsModal({
|
|
|
2927
3029
|
fieldError,
|
|
2928
3030
|
onDraft,
|
|
2929
3031
|
onSubmit,
|
|
2930
|
-
onToggleReviewType
|
|
3032
|
+
onToggleReviewType,
|
|
3033
|
+
onToggleWorkDay
|
|
2931
3034
|
}) {
|
|
2932
3035
|
const savedState = savedLine(options, saved2);
|
|
2933
3036
|
return /* @__PURE__ */ jsxs10(ModalFrame, { title: "Options", children: [
|
|
@@ -2944,7 +3047,8 @@ function OptionsModal({
|
|
|
2944
3047
|
isEditing: index === selected && editing && field.kind !== "toggle",
|
|
2945
3048
|
onDraft,
|
|
2946
3049
|
onSubmit,
|
|
2947
|
-
onToggleType: onToggleReviewType
|
|
3050
|
+
onToggleType: onToggleReviewType,
|
|
3051
|
+
onToggleWorkDay
|
|
2948
3052
|
},
|
|
2949
3053
|
field.key
|
|
2950
3054
|
);
|
|
@@ -2989,14 +3093,29 @@ function FieldRow({
|
|
|
2989
3093
|
isEditing,
|
|
2990
3094
|
onDraft,
|
|
2991
3095
|
onSubmit,
|
|
2992
|
-
onToggleType
|
|
3096
|
+
onToggleType,
|
|
3097
|
+
onToggleWorkDay
|
|
2993
3098
|
}) {
|
|
2994
3099
|
const value2 = displayValue(field.key, options[field.key]);
|
|
2995
3100
|
const valueColor = value2.isPlaceholder ? isSelected ? theme.muted : theme.dim : theme.muted;
|
|
2996
3101
|
if (isEditing && field.kind === "multi") {
|
|
2997
3102
|
return /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
|
|
2998
3103
|
/* @__PURE__ */ jsx11(ModalRow, { label: field.label, isSelected, children: /* @__PURE__ */ jsx11("text", { wrapMode: "none", children: /* @__PURE__ */ jsx11("b", { fg: value2.isPlaceholder ? theme.muted : theme.text, children: value2.text }) }) }),
|
|
2999
|
-
/* @__PURE__ */ jsx11(
|
|
3104
|
+
field.key === "reviewTypes" ? /* @__PURE__ */ jsx11(
|
|
3105
|
+
Checklist,
|
|
3106
|
+
{
|
|
3107
|
+
choices: REVIEW_TYPE_CHOICES,
|
|
3108
|
+
checked: checkedReviewTypes(options[field.key]),
|
|
3109
|
+
onToggle: onToggleType
|
|
3110
|
+
}
|
|
3111
|
+
) : /* @__PURE__ */ jsx11(
|
|
3112
|
+
Checklist,
|
|
3113
|
+
{
|
|
3114
|
+
choices: WORK_DAY_CHOICES,
|
|
3115
|
+
checked: checkedWorkDays(String(options[field.key])),
|
|
3116
|
+
onToggle: onToggleWorkDay
|
|
3117
|
+
}
|
|
3118
|
+
)
|
|
3000
3119
|
] });
|
|
3001
3120
|
}
|
|
3002
3121
|
return /* @__PURE__ */ jsx11(ModalRow, { label: field.label, isSelected, children: isEditing ? /* @__PURE__ */ jsx11(
|
|
@@ -3022,15 +3141,18 @@ function FieldRow({
|
|
|
3022
3141
|
/* @__PURE__ */ jsx11("span", { fg: theme.muted, children: " \u203A" })
|
|
3023
3142
|
] }) : isSelected ? /* @__PURE__ */ jsx11("text", { wrapMode: "none", children: /* @__PURE__ */ jsx11("b", { fg: value2.isPlaceholder ? theme.muted : theme.text, children: value2.text }) }) : /* @__PURE__ */ jsx11("text", { wrapMode: "none", fg: valueColor, children: value2.text }) });
|
|
3024
3143
|
}
|
|
3025
|
-
function
|
|
3026
|
-
|
|
3027
|
-
|
|
3144
|
+
function Checklist({
|
|
3145
|
+
choices,
|
|
3146
|
+
checked,
|
|
3147
|
+
onToggle
|
|
3148
|
+
}) {
|
|
3149
|
+
return /* @__PURE__ */ jsx11("box", { alignSelf: "flex-end", width: 30, height: choices.length, marginRight: 2, children: /* @__PURE__ */ jsx11(
|
|
3028
3150
|
"select",
|
|
3029
3151
|
{
|
|
3030
3152
|
focused: true,
|
|
3031
3153
|
width: "100%",
|
|
3032
|
-
height:
|
|
3033
|
-
options:
|
|
3154
|
+
height: choices.length,
|
|
3155
|
+
options: choices.map((choice) => {
|
|
3034
3156
|
return { name: `[${checked.has(choice) ? "x" : " "}] ${choice}`, description: "", value: choice };
|
|
3035
3157
|
}),
|
|
3036
3158
|
showDescription: false,
|
|
@@ -3711,6 +3833,7 @@ function buildStatsReport(raw, options) {
|
|
|
3711
3833
|
configureTimeMode({
|
|
3712
3834
|
business: !options.wallClock,
|
|
3713
3835
|
workWindows: parseWorkHours(options.workHours),
|
|
3836
|
+
workDays: parseWorkDays(options.workDays),
|
|
3714
3837
|
tz: timezone
|
|
3715
3838
|
});
|
|
3716
3839
|
const targetHours = options.target === "" ? void 0 : parseTarget(options.target);
|
|
@@ -3745,6 +3868,7 @@ function buildStatsReport(raw, options) {
|
|
|
3745
3868
|
repos: raw.repos,
|
|
3746
3869
|
searchCapped: raw.searchCapped,
|
|
3747
3870
|
options: {
|
|
3871
|
+
workDays: options.workDays,
|
|
3748
3872
|
workHours: options.workHours,
|
|
3749
3873
|
timezone,
|
|
3750
3874
|
wallClock: options.wallClock,
|
|
@@ -4134,7 +4258,8 @@ function Modals({
|
|
|
4134
4258
|
onDraft,
|
|
4135
4259
|
onSubmitField,
|
|
4136
4260
|
onSubmitThemeColor,
|
|
4137
|
-
onToggleReviewType
|
|
4261
|
+
onToggleReviewType,
|
|
4262
|
+
onToggleWorkDay
|
|
4138
4263
|
}) {
|
|
4139
4264
|
if (ui.modal === "options") {
|
|
4140
4265
|
return /* @__PURE__ */ jsx14(
|
|
@@ -4147,7 +4272,8 @@ function Modals({
|
|
|
4147
4272
|
fieldError: ui.fieldError,
|
|
4148
4273
|
onDraft,
|
|
4149
4274
|
onSubmit: onSubmitField,
|
|
4150
|
-
onToggleReviewType
|
|
4275
|
+
onToggleReviewType,
|
|
4276
|
+
onToggleWorkDay
|
|
4151
4277
|
}
|
|
4152
4278
|
);
|
|
4153
4279
|
}
|
|
@@ -4744,7 +4870,7 @@ function buildGaugeCard({ title, subtitle, rows }) {
|
|
|
4744
4870
|
}
|
|
4745
4871
|
|
|
4746
4872
|
// src/tui/views/charts/heatmap.ts
|
|
4747
|
-
var
|
|
4873
|
+
var WEEKDAY_LABELS2 = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
4748
4874
|
function heatColor(count2) {
|
|
4749
4875
|
if (count2 === 1) {
|
|
4750
4876
|
return theme.heat[0];
|
|
@@ -4755,13 +4881,13 @@ function heatColor(count2) {
|
|
|
4755
4881
|
return count2 <= 4 ? theme.heat[2] : theme.heat[3];
|
|
4756
4882
|
}
|
|
4757
4883
|
function buildHeatmapCard({ title, subtitle, grid, columns, legend }) {
|
|
4758
|
-
const cells =
|
|
4884
|
+
const cells = WEEKDAY_LABELS2.map(() => Array.from({ length: 24 }, () => 0));
|
|
4759
4885
|
for (const date of grid) {
|
|
4760
4886
|
const { weekday, hour } = zonedStamp(date);
|
|
4761
4887
|
cells[(weekday + 6) % 7][hour] += 1;
|
|
4762
4888
|
}
|
|
4763
4889
|
const counts = columns.map((column) => {
|
|
4764
|
-
const byDay =
|
|
4890
|
+
const byDay = WEEKDAY_LABELS2.map(() => 0);
|
|
4765
4891
|
for (const date of column.dates) {
|
|
4766
4892
|
byDay[(zonedStamp(date).weekday + 6) % 7] += 1;
|
|
4767
4893
|
}
|
|
@@ -4776,7 +4902,7 @@ function buildHeatmapCard({ title, subtitle, grid, columns, legend }) {
|
|
|
4776
4902
|
{ text: columns.map((column, i) => column.label.padStart(columnWidths[i])).join(""), fg: theme.dim }
|
|
4777
4903
|
];
|
|
4778
4904
|
const lines = [header];
|
|
4779
|
-
for (const [day, label] of
|
|
4905
|
+
for (const [day, label] of WEEKDAY_LABELS2.entries()) {
|
|
4780
4906
|
const line = [{ text: `${label} `, fg: theme.muted }];
|
|
4781
4907
|
for (let hour = 0; hour < 24; hour++) {
|
|
4782
4908
|
const count2 = cells[day][hour];
|
|
@@ -5801,6 +5927,7 @@ function useViewModel(raw, options, width, scopes, grouping, expanded, themeEpoc
|
|
|
5801
5927
|
configureTimeMode({
|
|
5802
5928
|
business: !options.wallClock,
|
|
5803
5929
|
workWindows: parseWorkHours(options.workHours),
|
|
5930
|
+
workDays: parseWorkDays(options.workDays),
|
|
5804
5931
|
tz: resolveTimezone(options.tz === "" ? void 0 : options.tz)
|
|
5805
5932
|
});
|
|
5806
5933
|
initBuckets();
|
|
@@ -5849,6 +5976,7 @@ function useViewModel(raw, options, width, scopes, grouping, expanded, themeEpoc
|
|
|
5849
5976
|
};
|
|
5850
5977
|
}, [
|
|
5851
5978
|
raw,
|
|
5979
|
+
options.workDays,
|
|
5852
5980
|
options.workHours,
|
|
5853
5981
|
options.tz,
|
|
5854
5982
|
options.wallClock,
|
|
@@ -6552,6 +6680,11 @@ function App({
|
|
|
6552
6680
|
setOptions((previous) => {
|
|
6553
6681
|
return { ...previous, reviewTypes: toggleReviewType(previous.reviewTypes, type) };
|
|
6554
6682
|
});
|
|
6683
|
+
},
|
|
6684
|
+
onToggleWorkDay: (day) => {
|
|
6685
|
+
setOptions((previous) => {
|
|
6686
|
+
return { ...previous, workDays: toggleWorkDay(previous.workDays, day) };
|
|
6687
|
+
});
|
|
6555
6688
|
}
|
|
6556
6689
|
}
|
|
6557
6690
|
)
|
|
@@ -6578,6 +6711,7 @@ function bootstrap() {
|
|
|
6578
6711
|
target: values.target ?? "",
|
|
6579
6712
|
targetPercentile: values["target-percentile"] ?? "",
|
|
6580
6713
|
sizeTarget: values["size-target"] ?? "",
|
|
6714
|
+
workDays: canonicalWorkDays(values["work-days"]),
|
|
6581
6715
|
workHours: values["work-hours"],
|
|
6582
6716
|
tz: values.tz ?? "",
|
|
6583
6717
|
wallClock: values["wall-clock"],
|