import { useCallback, useEffect, useMemo, useState } from "react";import type { NativeSyntheticEvent } from "react-native";import { useNavigation } from "@react-navigation/native";import * as Arr from "effect/Array";import { pipe } from "effect/Function";import * as Result from "effect/Result"; import type { EnvironmentId, ThreadId } from "@t3tools/contracts";import { buildReviewCommentTarget, clearReviewCommentTarget, formatReviewSelectedRangeLabel, getSelectedReviewCommentLines, setReviewCommentTarget, useReviewCommentTarget,} from "./reviewCommentSelection";import type { NativeReviewDiffData, NativeReviewDiffCommentTarget,} from "./nativeReviewDiffAdapter";import type { ReviewSectionItem } from "./reviewModel"; interface PendingNativeCommentSelection extends NativeReviewDiffCommentTarget { readonly sectionId: string; readonly sectionTitle: string; readonly rowId: string;} export function useReviewCommentSelectionController(input: { readonly environmentId?: EnvironmentId; readonly threadId?: ThreadId; readonly selectedSection: ReviewSectionItem | null; readonly nativeReviewDiffData: NativeReviewDiffData;}) { const { environmentId, nativeReviewDiffData, selectedSection, threadId } = input; const navigation = useNavigation(); const activeCommentTarget = useReviewCommentTarget(); const [pendingNativeCommentSelection, setPendingNativeCommentSelection] = useState<PendingNativeCommentSelection | null>(null); const openReviewCommentSheet = useCallback(() => { if (!environmentId || !threadId) { return; } navigation.navigate("ThreadReviewComment", { environmentId, threadId, }); }, [environmentId, navigation, threadId]); const selectedRowIds = useMemo(() => { if ( activeCommentTarget && activeCommentTarget.sectionTitle === selectedSection?.title && activeCommentTarget.startIndex !== activeCommentTarget.endIndex ) { return pipe( getSelectedReviewCommentLines(activeCommentTarget), Arr.filterMap((line) => { const rowId = nativeReviewDiffData.rowIdByCommentLineId.get(line.id); return rowId ? Result.succeed(rowId) : Result.failVoid; }), ); } return pendingNativeCommentSelection ? [pendingNativeCommentSelection.rowId] : []; }, [ activeCommentTarget, nativeReviewDiffData.rowIdByCommentLineId, pendingNativeCommentSelection, selectedSection?.title, ]); const selectionAction = useMemo(() => { if ( activeCommentTarget && activeCommentTarget.sectionTitle === selectedSection?.title && activeCommentTarget.startIndex !== activeCommentTarget.endIndex ) { return { title: `Comment on ${formatReviewSelectedRangeLabel(activeCommentTarget)}`, onOpenComment: openReviewCommentSheet, }; } if ( pendingNativeCommentSelection && pendingNativeCommentSelection.sectionTitle === selectedSection?.title ) { return { title: "Select range end", onOpenComment: null, }; } return null; }, [ activeCommentTarget, openReviewCommentSheet, pendingNativeCommentSelection, selectedSection?.title, ]); useEffect(() => { clearReviewCommentTarget(); setPendingNativeCommentSelection(null); }, [selectedSection?.id]); useEffect(() => { if (activeCommentTarget === null) { setPendingNativeCommentSelection(null); } }, [activeCommentTarget]); const onPressLine = useCallback( ( event: NativeSyntheticEvent<{ readonly rowId?: string; readonly gesture?: "tap" | "longPress"; }>, ) => { if (!selectedSection) { return; } const { rowId, gesture } = event.nativeEvent; if (!rowId) { return; } const target = nativeReviewDiffData.commentTargetsByRowId.get(rowId); if (!target) { return; } if (gesture === "longPress") { clearReviewCommentTarget(); setPendingNativeCommentSelection({ ...target, sectionId: selectedSection.id, sectionTitle: selectedSection.title, rowId, }); return; } if ( pendingNativeCommentSelection && pendingNativeCommentSelection.sectionTitle === selectedSection.title && pendingNativeCommentSelection.filePath === target.filePath ) { setReviewCommentTarget( buildReviewCommentTarget( { sectionTitle: pendingNativeCommentSelection.sectionTitle, sectionId: pendingNativeCommentSelection.sectionId, filePath: pendingNativeCommentSelection.filePath, lines: pendingNativeCommentSelection.lines, }, pendingNativeCommentSelection.lineIndex, target.lineIndex, ), ); return; } setPendingNativeCommentSelection(null); setReviewCommentTarget({ sectionTitle: selectedSection.title, sectionId: selectedSection.id, filePath: target.filePath, lines: target.lines, startIndex: target.lineIndex, endIndex: target.lineIndex, }); openReviewCommentSheet(); }, [ nativeReviewDiffData.commentTargetsByRowId, openReviewCommentSheet, pendingNativeCommentSelection, selectedSection, ], ); const clearSelection = useCallback(() => { clearReviewCommentTarget(); setPendingNativeCommentSelection(null); }, []); return { selectedRowIds, selectionAction, onPressLine, clearSelection, };}
import { useCallback, useEffect, useMemo, useState } from "react";
import type { NativeSyntheticEvent } from "react-native";
import { useNavigation } from "@react-navigation/native";
import * as Arr from "effect/Array";
import { pipe } from "effect/Function";
import * as Result from "effect/Result";
import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
import {
buildReviewCommentTarget,
clearReviewCommentTarget,
formatReviewSelectedRangeLabel,
getSelectedReviewCommentLines,
setReviewCommentTarget,
useReviewCommentTarget,
} from "./reviewCommentSelection";
import type {
NativeReviewDiffData,
NativeReviewDiffCommentTarget,
} from "./nativeReviewDiffAdapter";
import type { ReviewSectionItem } from "./reviewModel";
interface PendingNativeCommentSelection extends NativeReviewDiffCommentTarget {
readonly sectionId: string;
readonly sectionTitle: string;
readonly rowId: string;
}
export function useReviewCommentSelectionController(input: {
readonly environmentId?: EnvironmentId;
readonly threadId?: ThreadId;
readonly selectedSection: ReviewSectionItem | null;
readonly nativeReviewDiffData: NativeReviewDiffData;
}) {
const { environmentId, nativeReviewDiffData, selectedSection, threadId } = input;
const navigation = useNavigation();
const activeCommentTarget = useReviewCommentTarget();
const [pendingNativeCommentSelection, setPendingNativeCommentSelection] =
useState<PendingNativeCommentSelection | null>(null);
const openReviewCommentSheet = useCallback(() => {
if (!environmentId || !threadId) {
return;
}
navigation.navigate("ThreadReviewComment", {
environmentId,
threadId,
});
}, [environmentId, navigation, threadId]);
const selectedRowIds = useMemo(() => {
if (
activeCommentTarget &&
activeCommentTarget.sectionTitle === selectedSection?.title &&
activeCommentTarget.startIndex !== activeCommentTarget.endIndex
) {
return pipe(
getSelectedReviewCommentLines(activeCommentTarget),
Arr.filterMap((line) => {
const rowId = nativeReviewDiffData.rowIdByCommentLineId.get(line.id);
return rowId ? Result.succeed(rowId) : Result.failVoid;
}),
);
}
return pendingNativeCommentSelection ? [pendingNativeCommentSelection.rowId] : [];
}, [
activeCommentTarget,
nativeReviewDiffData.rowIdByCommentLineId,
pendingNativeCommentSelection,
selectedSection?.title,
]);
const selectionAction = useMemo(() => {
if (
activeCommentTarget &&
activeCommentTarget.sectionTitle === selectedSection?.title &&
activeCommentTarget.startIndex !== activeCommentTarget.endIndex
) {
return {
title: `Comment on ${formatReviewSelectedRangeLabel(activeCommentTarget)}`,
onOpenComment: openReviewCommentSheet,
};
}
if (
pendingNativeCommentSelection &&
pendingNativeCommentSelection.sectionTitle === selectedSection?.title
) {
return {
title: "Select range end",
onOpenComment: null,
};
}
return null;
}, [
activeCommentTarget,
openReviewCommentSheet,
pendingNativeCommentSelection,
selectedSection?.title,
]);
useEffect(() => {
clearReviewCommentTarget();
setPendingNativeCommentSelection(null);
}, [selectedSection?.id]);
useEffect(() => {
if (activeCommentTarget === null) {
setPendingNativeCommentSelection(null);
}
}, [activeCommentTarget]);
const onPressLine = useCallback(
(
event: NativeSyntheticEvent<{
readonly rowId?: string;
readonly gesture?: "tap" | "longPress";
}>,
) => {
if (!selectedSection) {
return;
}
const { rowId, gesture } = event.nativeEvent;
if (!rowId) {
return;
}
const target = nativeReviewDiffData.commentTargetsByRowId.get(rowId);
if (!target) {
return;
}
if (gesture === "longPress") {
clearReviewCommentTarget();
setPendingNativeCommentSelection({
...target,
sectionId: selectedSection.id,
sectionTitle: selectedSection.title,
rowId,
});
return;
}
if (
pendingNativeCommentSelection &&
pendingNativeCommentSelection.sectionTitle === selectedSection.title &&
pendingNativeCommentSelection.filePath === target.filePath
) {
setReviewCommentTarget(
buildReviewCommentTarget(
{
sectionTitle: pendingNativeCommentSelection.sectionTitle,
sectionId: pendingNativeCommentSelection.sectionId,
filePath: pendingNativeCommentSelection.filePath,
lines: pendingNativeCommentSelection.lines,
},
pendingNativeCommentSelection.lineIndex,
target.lineIndex,
),
);
return;
}
setPendingNativeCommentSelection(null);
setReviewCommentTarget({
sectionTitle: selectedSection.title,
sectionId: selectedSection.id,
filePath: target.filePath,
lines: target.lines,
startIndex: target.lineIndex,
endIndex: target.lineIndex,
});
openReviewCommentSheet();
},
[
nativeReviewDiffData.commentTargetsByRowId,
openReviewCommentSheet,
pendingNativeCommentSelection,
selectedSection,
],
);
const clearSelection = useCallback(() => {
clearReviewCommentTarget();
setPendingNativeCommentSelection(null);
}, []);
return {
selectedRowIds,
selectionAction,
onPressLine,
clearSelection,
};
}