Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions moon/apps/web/components/CodeView/TreeView/RepoTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { usePathname } from 'next/navigation';
import { useRouter } from 'next/router';
import { useGetTree } from '@/hooks/useGetTree';
import { legacyApiClient } from '@/utils/queryClient';
import { convertToTreeData, generateExpandedPaths, mergeTreeNodes, findNode } from './TreeUtils';
import { convertToTreeData, generateExpandedPaths, mergeTreeNodes, findNode, getDescendantIds } from './TreeUtils';
import { CustomTreeItem } from './CustomTreeItem';
import toast from 'react-hot-toast';
import { useAtom } from 'jotai';
Expand Down Expand Up @@ -50,20 +50,32 @@ const RepoTree = ({ onCommitInfoChange }: { onCommitInfoChange?:Function }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [treeItems]);

const handleNodeToggle = useCallback((_event: React.SyntheticEvent | null, nodeIds: string[]) => {
const newlyExpandedIds = nodeIds.filter(id => !expandedNodes.includes(id));

newlyExpandedIds.forEach(nodeId => {
const existingNode = findNode(treeAllData, nodeId);
const hasRealData = existingNode?.children && !existingNode?.children[0].isPlaceholder
const handleNodeToggle = useCallback((_event: React.SyntheticEvent | null, nodeIds: string[]) => {
const collapsedNodes = expandedNodes.filter(id => !nodeIds.includes(id));

let newExpandedIds = [...nodeIds];

if (collapsedNodes.length > 0) {
collapsedNodes.forEach(collapsedId => {
const descendantIds = getDescendantIds(treeAllData, collapsedId);

if (!loadingDirectories.has(nodeId) && !hasRealData) {
setLoadingDirectories(prev => new Set(prev).add(nodeId));
}
newExpandedIds = newExpandedIds.filter(id => !descendantIds.includes(id));
});
}

const newlyExpandedIds = newExpandedIds.filter(id => !expandedNodes.includes(id));

newlyExpandedIds.forEach(nodeId => {
const existingNode = findNode(treeAllData, nodeId);
const hasRealData = existingNode?.children && !existingNode?.children[0].isPlaceholder;

if (!loadingDirectories.has(nodeId) && !hasRealData) {
setLoadingDirectories(prev => new Set(prev).add(nodeId));
}
});

setExpandedNodes(nodeIds);
}, [expandedNodes, loadingDirectories, treeAllData, setLoadingDirectories, setExpandedNodes]);
setExpandedNodes(newExpandedIds);
}, [expandedNodes, loadingDirectories, treeAllData, setLoadingDirectories, setExpandedNodes]);

useEffect(() => {
loadingDirectories.forEach(path => {
Expand Down
22 changes: 22 additions & 0 deletions moon/apps/web/components/CodeView/TreeView/TreeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,26 @@ export function mergeTreeNodes(nodes1: MuiTreeNode[], nodes2: MuiTreeNode[]): Mu
return sortProjectsByType(result);
}

/**
* Recursively retrieves the IDs of all descendant nodes of a node.
* @param treeData: The complete tree data.
* @param nodeId: The ID of the parent node to be found.
* @returns: An array of the IDs of all descendant nodes under the node.
*/
export function getDescendantIds(treeData: MuiTreeNode[], nodeId: string): string[] {
const node = findNode(treeData, nodeId);

if (!node || !node.children) {
return [];
}

let ids: string[] = [];

node.children.forEach(child => {
ids.push(child.id);
ids = ids.concat(getDescendantIds(treeData, child.id));
});

return ids;
}

14 changes: 14 additions & 0 deletions moon/apps/web/components/CodeView/TreeView/codeTreeAtom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { atomWithWebStorage } from '@/utils/atomWithWebStorage'
import { MuiTreeNode } from './TreeUtils'
import { RESET } from "jotai/utils";

import { useRouter } from "next/router";
import { useEffect } from "react";

export const treeAllDataAtom = atomWithWebStorage<MuiTreeNode[]>('treeAllDataAtom', [])

export const expandedNodesAtom = atomWithWebStorage<string[]>('expandedNodes', [])

export function useClearTreeAtoms(setTreeAllData: (v: any) => void, setExpandedNodes: (v: any) => void) {
const router = useRouter();

useEffect(() => {
if (!router.asPath.startsWith(`/${router.query.org}/code`)) {
setTreeAllData(RESET);
setExpandedNodes(RESET);
}
}, [router.asPath, router.query.org, setTreeAllData, setExpandedNodes]);
}
32 changes: 16 additions & 16 deletions moon/apps/web/components/DiffView/FileDiff.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { DiffFile, DiffModeEnum, DiffView } from '@git-diff-view/react'

import { CommonResultFilesChangedList } from '@gitmono/types/generated'
import { CommonResultFilesChangedPage, DiffItem } from '@gitmono/types/generated'
import { ExpandIcon, SparklesIcon } from '@gitmono/ui/Icons'
import { cn } from '@gitmono/ui/src/utils'

import { parsedDiffs } from '@/components/DiffView/parsedDiffs'

import StableTreeView from './StableTreeView'

// import TreeView from './TreeView'

function calculateDiffStatsFromRawDiff(diffText: string): { additions: number; deletions: number } {
const lines = diffText.split('\n')

Expand All @@ -35,7 +33,7 @@ function generateParsedFiles(diffFiles: { path: string; lang: string; diff: stri
stats: { additions: number; deletions: number }
}[] {
return diffFiles.map((file) => {
if (file.lang === 'binary') {
if (file.lang === 'binary' || file.diff === 'EMPTY_DIFF_MARKER\n') {
return {
file,
instance: null,
Expand Down Expand Up @@ -64,8 +62,8 @@ export default function FileDiff({
diffs,
treeData
}: {
diffs: string
treeData: CommonResultFilesChangedList['data']
diffs: DiffItem[]
treeData: CommonResultFilesChangedPage['data']
}) {
const diffFiles = useMemo(() => parsedDiffs(diffs), [diffs])

Expand Down Expand Up @@ -94,21 +92,23 @@ export default function FileDiff({
file: { path: string; lang: string; diff: string }
instance: DiffFile | null
}) => {
if (file.lang === 'binary' || instance === null) {
if (instance){
return (
<DiffView
diffFile={instance}
diffViewFontSize={14}
diffViewWrap
diffViewMode={DiffModeEnum.Unified}
diffViewHighlight
/>
)
}else if (file.lang === 'binary') {
return <div className='p-2 text-center'>Binary file</div>
} else if (file.diff === 'EMPTY_DIFF_MARKER\n') {
return <div className='p-2 text-center'>No change</div>
}

return (
<DiffView
diffFile={instance}
diffViewFontSize={14}
diffViewWrap
diffViewMode={DiffModeEnum.Unified}
diffViewHighlight
/>
)
return null
}

return (
Expand Down
41 changes: 19 additions & 22 deletions moon/apps/web/components/DiffView/parsedDiffs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DiffItem } from "@gitmono/types/generated"

const extensionToLangMap: Record<string, string> = {
// Note that the key here is lowercase
'.ts': 'typescript',
Expand All @@ -21,15 +23,15 @@ const extensionToLangMap: Record<string, string> = {
'.html': 'html',
'.vue': 'vue',
'.toml': 'toml',
dockerfile: 'dockerfile',
'dockerfile': 'dockerfile',
'.dockerfile': 'dockerfile',
'license-mit': 'plaintext',
buck: 'plaintext',
'buck': 'plaintext',
'.gitignore': 'plaintext',
'.env': 'plaintext',
'license-third-party': 'plaintext',
'license-apache': 'plaintext',
workspace: 'plaintext',
'workspace': 'plaintext',
'.buckroot': 'plaintext',
'.buckconfig': 'plaintext'
}
Expand All @@ -50,18 +52,13 @@ function getLangFromPath(path: string): string {
return 'binary'
}

export function parsedDiffs(diffText: string): { path: string; lang: string; diff: string }[] {
if (!diffText) return []

const parts = diffText
.split(/(?=^diff --git )/gm)
.map((block) => block.trim())
.filter(Boolean)
export function parsedDiffs(diffText: DiffItem[]): { path: string; lang: string; diff: string }[] {
if (diffText.length < 1) return []

return parts.map((block) => {
return diffText.map((block) => {
let path = ''

const diffGitMatch = block.match(/^diff --git a\/[^\s]+ b\/([^\s]+)/m)
const diffGitMatch = block.data.match(/^diff --git a\/[^\s]+ b\/([^\s]+)/m)

if (diffGitMatch) {
const originalPath = diffGitMatch[1]?.trim()
Expand All @@ -78,28 +75,28 @@ export function parsedDiffs(diffText: string): { path: string; lang: string; dif
return {
path,
lang: getLangFromPath(path),
diff: block
diff: block.data
}
}

let diffWithHeader = block
const plusMatch = block.match(/^\+\+\+ b\/([^\n\r]+)/m)
const hunkIndex = block.indexOf('@@')
let diffWithHeader = block.data
const headerRegex = /^(diff|index|---|\+\+\+|new file mode|@@)/;
const hunkContent = block.data
.split('\n')
.filter(line => !headerRegex.test(line.trim()));

if (!plusMatch) {
let prefix = `--- a/${path}\n+++ b/${path}\n`
const isEmptyHunk = hunkContent.every(line => line.trim() === '');

diffWithHeader = hunkIndex >= 0 ? block.slice(0, hunkIndex) + prefix + block.slice(hunkIndex) : prefix + block
} else if (hunkIndex < 0) {
diffWithHeader = 'EMPTY_DIFF_MARKER'
if (!block.data.includes('@@') || isEmptyHunk) {
diffWithHeader = 'EMPTY_DIFF_MARKER';
}

if (!diffWithHeader.endsWith('\n')) {
diffWithHeader += '\n'
}

return {
path,
path: block.path,
lang: getLangFromPath(path),
diff: diffWithHeader
}
Expand Down
12 changes: 0 additions & 12 deletions moon/apps/web/hooks/useGetMrFilesChanged.ts

This file was deleted.

18 changes: 18 additions & 0 deletions moon/apps/web/hooks/useMrFilesChanged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/react-query'
import { legacyApiClient } from '@/utils/queryClient'
import type { PostApiMrFilesChangedData, PageParamsString, RequestParams } from '@gitmono/types'

export function useMrFilesChanged(
link: string,
data: PageParamsString,
params?: RequestParams
) {
return useQuery<PostApiMrFilesChangedData>({
queryKey: [
...legacyApiClient.v1.postApiMrFilesChanged().requestKey(link),
data,
],
queryFn: () =>
legacyApiClient.v1.postApiMrFilesChanged().request(link, data, params)
})
}
19 changes: 13 additions & 6 deletions moon/apps/web/pages/[org]/mr/[link]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { PicturePlusIcon } from '@gitmono/ui/Icons'
import { cn } from '@gitmono/ui/utils'

import { EMPTY_HTML } from '@/atoms/markdown'
import FileDiff from '@/components/DiffView/FileDiff'
import { BadgeItem } from '@/components/Issues/IssueNewPage'
import {
splitFun,
Expand All @@ -38,7 +39,7 @@ import { useScope } from '@/contexts/scope'
import { usePostMRAssignees } from '@/hooks/issues/usePostMRAssignees'
import { usePostMrTitle } from '@/hooks/MR/usePostMrTitle'
import { useGetMrDetail } from '@/hooks/useGetMrDetail'
import { useGetMrFilesChanged } from '@/hooks/useGetMrFilesChanged'
import { useMrFilesChanged } from '@/hooks/useMrFilesChanged'
import { usePostMrClose } from '@/hooks/usePostMrClose'
import { usePostMrComment } from '@/hooks/usePostMrComment'
import { usePostMRLabels } from '@/hooks/usePostMRLabels'
Expand Down Expand Up @@ -72,12 +73,19 @@ const MRDetailPage: PageWithLayout<any> = () => {
const [editTitle, setEditTitle] = useState(mrDetail?.title)
const [loading, setLoading] = useState(false)
const Checks = dynamic(() => import('@/components/MrView/components/Checks'))
const [page, _setPage] = useState(1)

if (mrDetail && typeof mrDetail.status === 'string') {
mrDetail.status = mrDetail.status.toLowerCase()
}

const { data: MrFilesChangedData, isLoading: fileChgIsLoading } = useGetMrFilesChanged(id)
const { data: MrFilesChangedData, isLoading: fileChgIsLoading } = useMrFilesChanged(id, {
additional: 'string',
pagination: {
page,
per_page: 10,
},
})
const { mutate: modifyTitle } = usePostMrTitle()

const { mutate: approveMr, isPending: mrMergeIsPending } = usePostMrMerge(id)
Expand Down Expand Up @@ -395,10 +403,9 @@ const MRDetailPage: PageWithLayout<any> = () => {
<div className='align-center container absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 justify-center'>
<LoadingSpinner />
</div>
) : MrFilesChangedData?.data?.content ? (
// <FileDiff diffs={MrFilesChangedData.data.content} treeData={MrFilesChangedData.data} />
<div>files</div>
) : (
) : MrFilesChangedData?.data?.page.items ? (
<FileDiff diffs={MrFilesChangedData.data.page.items} treeData={MrFilesChangedData.data} />
) : (
<div>No files changed</div>
)}
</>
Expand Down
7 changes: 7 additions & 0 deletions moon/apps/web/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { IS_PRODUCTION, LAST_CLIENT_JS_BUILD_ID_LS_KEY } from '@gitmono/config'
import { useClearEmptyDrafts } from '@/hooks/useClearEmptyDrafts'
import { useStoredState } from '@/hooks/useStoredState'
import { AppPropsWithLayout } from '@/utils/types'
import { useSetAtom } from "jotai";
import { treeAllDataAtom, expandedNodesAtom, useClearTreeAtoms } from "@/components/CodeView/TreeView/codeTreeAtom";

const inter = Inter({
subsets: ['latin'],
Expand All @@ -26,8 +28,13 @@ export default function App<T>({ Component, pageProps }: AppPropsWithLayout<T>):
const getProviders = Component.getProviders ?? ((page) => page)
const [_, setLsLastChecked] = useStoredState<number | null>(LAST_CLIENT_JS_BUILD_ID_LS_KEY, null)

const setTreeAllData = useSetAtom(treeAllDataAtom);
const setExpandedNodes = useSetAtom(expandedNodesAtom);

// TODO: Delete this hook and implementation after 4/30/24
useClearEmptyDrafts()
useClearTreeAtoms(setTreeAllData, setExpandedNodes);


/*
Whenever the app mounts for the first time, track the current time in local storage
Expand Down