mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Mark parent directory as viewed when all files are viewed (#33958)
Fix #25644 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -1,21 +1,14 @@ | ||||
| <script lang="ts" setup> | ||||
| import DiffFileTreeItem from './DiffFileTreeItem.vue'; | ||||
| import {toggleElem} from '../utils/dom.ts'; | ||||
| import {diffTreeStore} from '../modules/stores.ts'; | ||||
| import {diffTreeStore} from '../modules/diff-file.ts'; | ||||
| import {setFileFolding} from '../features/file-fold.ts'; | ||||
| import {computed, onMounted, onUnmounted} from 'vue'; | ||||
| import {pathListToTree, mergeChildIfOnlyOneDir} from '../utils/filetree.ts'; | ||||
| import {onMounted, onUnmounted} from 'vue'; | ||||
|  | ||||
| const LOCAL_STORAGE_KEY = 'diff_file_tree_visible'; | ||||
|  | ||||
| const store = diffTreeStore(); | ||||
|  | ||||
| const fileTree = computed(() => { | ||||
|   const result = pathListToTree(store.files); | ||||
|   mergeChildIfOnlyOneDir(result); // mutation | ||||
|   return result; | ||||
| }); | ||||
|  | ||||
| onMounted(() => { | ||||
|   // Default to true if unset | ||||
|   store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false'; | ||||
| @@ -50,7 +43,7 @@ function toggleVisibility() { | ||||
|  | ||||
| function updateVisibility(visible: boolean) { | ||||
|   store.fileTreeIsVisible = visible; | ||||
|   localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible); | ||||
|   localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible.toString()); | ||||
|   updateState(store.fileTreeIsVisible); | ||||
| } | ||||
|  | ||||
| @@ -69,7 +62,7 @@ function updateState(visible: boolean) { | ||||
| <template> | ||||
|   <div v-if="store.fileTreeIsVisible" class="diff-file-tree-items"> | ||||
|     <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often --> | ||||
|     <DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/> | ||||
|     <DiffFileTreeItem v-for="item in store.diffFileTree.TreeRoot.Children" :key="item.FullName" :item="item"/> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| <script lang="ts" setup> | ||||
| import {SvgIcon, type SvgName} from '../svg.ts'; | ||||
| import {diffTreeStore} from '../modules/stores.ts'; | ||||
| import {ref} from 'vue'; | ||||
| import type {Item, File, FileStatus} from '../utils/filetree.ts'; | ||||
| import {type DiffStatus, type DiffTreeEntry, diffTreeStore} from '../modules/diff-file.ts'; | ||||
|  | ||||
| defineProps<{ | ||||
|   item: Item, | ||||
| const props = defineProps<{ | ||||
|   item: DiffTreeEntry, | ||||
| }>(); | ||||
|  | ||||
| const store = diffTreeStore(); | ||||
| const collapsed = ref(false); | ||||
| const collapsed = ref(props.item.IsViewed); | ||||
|  | ||||
| function getIconForDiffStatus(pType: FileStatus) { | ||||
|   const diffTypes: Record<FileStatus, { name: SvgName, classes: Array<string> }> = { | ||||
| function getIconForDiffStatus(pType: DiffStatus) { | ||||
|   const diffTypes: Record<DiffStatus, { name: SvgName, classes: Array<string> }> = { | ||||
|     '': {name: 'octicon-blocked', classes: ['text', 'red']}, // unknown case | ||||
|     'added': {name: 'octicon-diff-added', classes: ['text', 'green']}, | ||||
|     'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, | ||||
|     'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']}, | ||||
| @@ -20,11 +20,11 @@ function getIconForDiffStatus(pType: FileStatus) { | ||||
|     'copied': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, | ||||
|     'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok | ||||
|   }; | ||||
|   return diffTypes[pType]; | ||||
|   return diffTypes[pType] ?? diffTypes['']; | ||||
| } | ||||
|  | ||||
| function fileIcon(file: File) { | ||||
|   if (file.IsSubmodule) { | ||||
| function entryIcon(entry: DiffTreeEntry) { | ||||
|   if (entry.EntryMode === 'commit') { | ||||
|     return 'octicon-file-submodule'; | ||||
|   } | ||||
|   return 'octicon-file'; | ||||
| @@ -32,37 +32,36 @@ function fileIcon(file: File) { | ||||
| </script> | ||||
|  | ||||
| <template> | ||||
|   <!--title instead of tooltip above as the tooltip needs too much work with the current methods, i.e. not being loaded or staying open for "too long"--> | ||||
|   <a | ||||
|     v-if="item.isFile" class="item-file" | ||||
|     :class="{ 'selected': store.selectedItem === '#diff-' + item.file.NameHash, 'viewed': item.file.IsViewed }" | ||||
|     :title="item.name" :href="'#diff-' + item.file.NameHash" | ||||
|   > | ||||
|     <!-- file --> | ||||
|     <SvgIcon :name="fileIcon(item.file)"/> | ||||
|     <span class="gt-ellipsis tw-flex-1">{{ item.name }}</span> | ||||
|     <SvgIcon | ||||
|       :name="getIconForDiffStatus(item.file.Status).name" | ||||
|       :class="getIconForDiffStatus(item.file.Status).classes" | ||||
|     /> | ||||
|   </a> | ||||
|  | ||||
|   <template v-else-if="item.isFile === false"> | ||||
|     <div class="item-directory" :title="item.name" @click.stop="collapsed = !collapsed"> | ||||
|   <template v-if="item.EntryMode === 'tree'"> | ||||
|     <div class="item-directory" :class="{ 'viewed': item.IsViewed }" :title="item.DisplayName" @click.stop="collapsed = !collapsed"> | ||||
|       <!-- directory --> | ||||
|       <SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/> | ||||
|       <SvgIcon | ||||
|         class="text primary" | ||||
|         :name="collapsed ? 'octicon-file-directory-fill' : 'octicon-file-directory-open-fill'" | ||||
|       /> | ||||
|       <span class="gt-ellipsis">{{ item.name }}</span> | ||||
|       <span class="gt-ellipsis">{{ item.DisplayName }}</span> | ||||
|     </div> | ||||
|  | ||||
|     <div v-show="!collapsed" class="sub-items"> | ||||
|       <DiffFileTreeItem v-for="childItem in item.children" :key="childItem.name" :item="childItem"/> | ||||
|       <DiffFileTreeItem v-for="childItem in item.Children" :key="childItem.DisplayName" :item="childItem"/> | ||||
|     </div> | ||||
|   </template> | ||||
|   <a | ||||
|     v-else | ||||
|     class="item-file" :class="{ 'selected': store.selectedItem === '#diff-' + item.NameHash, 'viewed': item.IsViewed }" | ||||
|     :title="item.DisplayName" :href="'#diff-' + item.NameHash" | ||||
|   > | ||||
|     <!-- file --> | ||||
|     <SvgIcon :name="entryIcon(item)"/> | ||||
|     <span class="gt-ellipsis tw-flex-1">{{ item.DisplayName }}</span> | ||||
|     <SvgIcon | ||||
|       :name="getIconForDiffStatus(item.DiffStatus).name" | ||||
|       :class="getIconForDiffStatus(item.DiffStatus).classes" | ||||
|     /> | ||||
|   </a> | ||||
| </template> | ||||
|  | ||||
| <style scoped> | ||||
| a, | ||||
| a:hover { | ||||
| @@ -88,7 +87,8 @@ a:hover { | ||||
|   border-radius: 4px; | ||||
| } | ||||
|  | ||||
| .item-file.viewed { | ||||
| .item-file.viewed, | ||||
| .item-directory.viewed { | ||||
|   color: var(--color-text-light-3); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import {svg} from '../svg.ts'; | ||||
| // The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class. | ||||
| // The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class. | ||||
| // | ||||
| export function setFileFolding(fileContentBox: HTMLElement, foldArrow: HTMLElement, newFold: boolean) { | ||||
| export function setFileFolding(fileContentBox: Element, foldArrow: HTMLElement, newFold: boolean) { | ||||
|   foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18); | ||||
|   fileContentBox.setAttribute('data-folded', String(newFold)); | ||||
|   if (newFold && fileContentBox.getBoundingClientRect().top < 0) { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import {diffTreeStore} from '../modules/stores.ts'; | ||||
| import {diffTreeStore, diffTreeStoreSetViewed} from '../modules/diff-file.ts'; | ||||
| import {setFileFolding} from './file-fold.ts'; | ||||
| import {POST} from '../modules/fetch.ts'; | ||||
|  | ||||
| @@ -58,11 +58,8 @@ export function initViewedCheckboxListenerFor() { | ||||
|  | ||||
|       const fileName = checkbox.getAttribute('name'); | ||||
|  | ||||
|       // check if the file is in our difftreestore and if we find it -> change the IsViewed status | ||||
|       const fileInPageData = diffTreeStore().files.find((x: Record<string, any>) => x.Name === fileName); | ||||
|       if (fileInPageData) { | ||||
|         fileInPageData.IsViewed = this.checked; | ||||
|       } | ||||
|       // check if the file is in our diffTreeStore and if we find it -> change the IsViewed status | ||||
|       diffTreeStoreSetViewed(diffTreeStore(), fileName, this.checked); | ||||
|  | ||||
|       // Unfortunately, actual forms cause too many problems, hence another approach is needed | ||||
|       const files: Record<string, boolean> = {}; | ||||
|   | ||||
							
								
								
									
										47
									
								
								web_src/js/modules/diff-file.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								web_src/js/modules/diff-file.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import {diffTreeStoreSetViewed, reactiveDiffTreeStore} from './diff-file.ts'; | ||||
|  | ||||
| test('diff-tree', () => { | ||||
|   const store = reactiveDiffTreeStore({ | ||||
|     'TreeRoot': { | ||||
|       'FullName': '', | ||||
|       'DisplayName': '', | ||||
|       'EntryMode': '', | ||||
|       'IsViewed': false, | ||||
|       'NameHash': '....', | ||||
|       'DiffStatus': '', | ||||
|       'Children': [ | ||||
|         { | ||||
|           'FullName': 'dir1', | ||||
|           'DisplayName': 'dir1', | ||||
|           'EntryMode': 'tree', | ||||
|           'IsViewed': false, | ||||
|           'NameHash': '....', | ||||
|           'DiffStatus': '', | ||||
|           'Children': [ | ||||
|             { | ||||
|               'FullName': 'dir1/test.txt', | ||||
|               'DisplayName': 'test.txt', | ||||
|               'DiffStatus': 'added', | ||||
|               'NameHash': '....', | ||||
|               'EntryMode': '', | ||||
|               'IsViewed': false, | ||||
|               'Children': null, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|         { | ||||
|           'FullName': 'other.txt', | ||||
|           'DisplayName': 'other.txt', | ||||
|           'NameHash': '........', | ||||
|           'DiffStatus': 'added', | ||||
|           'EntryMode': '', | ||||
|           'IsViewed': false, | ||||
|           'Children': null, | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }); | ||||
|   diffTreeStoreSetViewed(store, 'dir1/test.txt', true); | ||||
|   expect(store.fullNameMap['dir1/test.txt'].IsViewed).toBe(true); | ||||
|   expect(store.fullNameMap['dir1'].IsViewed).toBe(true); | ||||
| }); | ||||
							
								
								
									
										78
									
								
								web_src/js/modules/diff-file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								web_src/js/modules/diff-file.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import {reactive} from 'vue'; | ||||
| import type {Reactive} from 'vue'; | ||||
|  | ||||
| const {pageData} = window.config; | ||||
|  | ||||
| export type DiffStatus = '' | 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'typechange'; | ||||
|  | ||||
| export type DiffTreeEntry = { | ||||
|   FullName: string, | ||||
|   DisplayName: string, | ||||
|   NameHash: string, | ||||
|   DiffStatus: DiffStatus, | ||||
|   EntryMode: string, | ||||
|   IsViewed: boolean, | ||||
|   Children: DiffTreeEntry[], | ||||
|  | ||||
|   ParentEntry?: DiffTreeEntry, | ||||
| } | ||||
|  | ||||
| type DiffFileTreeData = { | ||||
|   TreeRoot: DiffTreeEntry, | ||||
| }; | ||||
|  | ||||
| type DiffFileTree = { | ||||
|   diffFileTree: DiffFileTreeData; | ||||
|   fullNameMap?: Record<string, DiffTreeEntry> | ||||
|   fileTreeIsVisible: boolean; | ||||
|   selectedItem: string; | ||||
| } | ||||
|  | ||||
| let diffTreeStoreReactive: Reactive<DiffFileTree>; | ||||
| export function diffTreeStore() { | ||||
|   if (!diffTreeStoreReactive) { | ||||
|     diffTreeStoreReactive = reactiveDiffTreeStore(pageData.DiffFileTree); | ||||
|   } | ||||
|   return diffTreeStoreReactive; | ||||
| } | ||||
|  | ||||
| export function diffTreeStoreSetViewed(store: Reactive<DiffFileTree>, fullName: string, viewed: boolean) { | ||||
|   const entry = store.fullNameMap[fullName]; | ||||
|   if (!entry) return; | ||||
|   entry.IsViewed = viewed; | ||||
|   for (let parent = entry.ParentEntry; parent; parent = parent.ParentEntry) { | ||||
|     parent.IsViewed = isEntryViewed(parent); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function fillFullNameMap(map: Record<string, DiffTreeEntry>, entry: DiffTreeEntry) { | ||||
|   map[entry.FullName] = entry; | ||||
|   if (!entry.Children) return; | ||||
|   entry.IsViewed = isEntryViewed(entry); | ||||
|   for (const child of entry.Children) { | ||||
|     child.ParentEntry = entry; | ||||
|     fillFullNameMap(map, child); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function reactiveDiffTreeStore(data: DiffFileTreeData): Reactive<DiffFileTree> { | ||||
|   const store = reactive({ | ||||
|     diffFileTree: data, | ||||
|     fileTreeIsVisible: false, | ||||
|     selectedItem: '', | ||||
|     fullNameMap: {}, | ||||
|   }); | ||||
|   fillFullNameMap(store.fullNameMap, data.TreeRoot); | ||||
|   return store; | ||||
| } | ||||
|  | ||||
| function isEntryViewed(entry: DiffTreeEntry): boolean { | ||||
|   if (entry.Children) { | ||||
|     let count = 0; | ||||
|     for (const child of entry.Children) { | ||||
|       if (child.IsViewed) count++; | ||||
|     } | ||||
|     return count === entry.Children.length; | ||||
|   } | ||||
|   return entry.IsViewed; | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| import {reactive} from 'vue'; | ||||
| import type {Reactive} from 'vue'; | ||||
|  | ||||
| const {pageData} = window.config; | ||||
|  | ||||
| let diffTreeStoreReactive: Reactive<Record<string, any>>; | ||||
| export function diffTreeStore() { | ||||
|   if (!diffTreeStoreReactive) { | ||||
|     diffTreeStoreReactive = reactive({ | ||||
|       files: pageData.DiffFiles, | ||||
|       fileTreeIsVisible: false, | ||||
|       selectedItem: '', | ||||
|     }); | ||||
|   } | ||||
|   return diffTreeStoreReactive; | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| import {mergeChildIfOnlyOneDir, pathListToTree, type File} from './filetree.ts'; | ||||
|  | ||||
| const emptyList: File[] = []; | ||||
| const singleFile = [{Name: 'file1'}] as File[]; | ||||
| const singleDir = [{Name: 'dir1/file1'}] as File[]; | ||||
| const nestedDir = [{Name: 'dir1/dir2/file1'}] as File[]; | ||||
| const multiplePathsDisjoint = [{Name: 'dir1/dir2/file1'}, {Name: 'dir3/file2'}] as File[]; | ||||
| const multiplePathsShared = [{Name: 'dir1/dir2/dir3/file1'}, {Name: 'dir1/file2'}] as File[]; | ||||
|  | ||||
| test('pathListToTree', () => { | ||||
|   expect(pathListToTree(emptyList)).toEqual([]); | ||||
|   expect(pathListToTree(singleFile)).toEqual([ | ||||
|     {isFile: true, name: 'file1', path: 'file1', file: {Name: 'file1'}}, | ||||
|   ]); | ||||
|   expect(pathListToTree(singleDir)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: true, name: 'file1', path: 'dir1/file1', file: {Name: 'dir1/file1'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(pathListToTree(nestedDir)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ | ||||
|         {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, | ||||
|       ]}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(pathListToTree(multiplePathsDisjoint)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ | ||||
|         {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, | ||||
|       ]}, | ||||
|     ]}, | ||||
|     {isFile: false, name: 'dir3', path: 'dir3', children: [ | ||||
|       {isFile: true, name: 'file2', path: 'dir3/file2', file: {Name: 'dir3/file2'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(pathListToTree(multiplePathsShared)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: false, name: 'dir2', path: 'dir1/dir2', children: [ | ||||
|         {isFile: false, name: 'dir3', path: 'dir1/dir2/dir3', children: [ | ||||
|           {isFile: true, name: 'file1', path: 'dir1/dir2/dir3/file1', file: {Name: 'dir1/dir2/dir3/file1'}}, | ||||
|         ]}, | ||||
|       ]}, | ||||
|       {isFile: true, name: 'file2', path: 'dir1/file2', file: {Name: 'dir1/file2'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
| }); | ||||
|  | ||||
| const mergeChildWrapper = (testCase: File[]) => { | ||||
|   const tree = pathListToTree(testCase); | ||||
|   mergeChildIfOnlyOneDir(tree); | ||||
|   return tree; | ||||
| }; | ||||
|  | ||||
| test('mergeChildIfOnlyOneDir', () => { | ||||
|   expect(mergeChildWrapper(emptyList)).toEqual([]); | ||||
|   expect(mergeChildWrapper(singleFile)).toEqual([ | ||||
|     {isFile: true, name: 'file1', path: 'file1', file: {Name: 'file1'}}, | ||||
|   ]); | ||||
|   expect(mergeChildWrapper(singleDir)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: true, name: 'file1', path: 'dir1/file1', file: {Name: 'dir1/file1'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(mergeChildWrapper(nestedDir)).toEqual([ | ||||
|     {isFile: false, name: 'dir1/dir2', path: 'dir1/dir2', children: [ | ||||
|       {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(mergeChildWrapper(multiplePathsDisjoint)).toEqual([ | ||||
|     {isFile: false, name: 'dir1/dir2', path: 'dir1/dir2', children: [ | ||||
|       {isFile: true, name: 'file1', path: 'dir1/dir2/file1', file: {Name: 'dir1/dir2/file1'}}, | ||||
|     ]}, | ||||
|     {isFile: false, name: 'dir3', path: 'dir3', children: [ | ||||
|       {isFile: true, name: 'file2', path: 'dir3/file2', file: {Name: 'dir3/file2'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
|   expect(mergeChildWrapper(multiplePathsShared)).toEqual([ | ||||
|     {isFile: false, name: 'dir1', path: 'dir1', children: [ | ||||
|       {isFile: false, name: 'dir2/dir3', path: 'dir1/dir2/dir3', children: [ | ||||
|         {isFile: true, name: 'file1', path: 'dir1/dir2/dir3/file1', file: {Name: 'dir1/dir2/dir3/file1'}}, | ||||
|       ]}, | ||||
|       {isFile: true, name: 'file2', path: 'dir1/file2', file: {Name: 'dir1/file2'}}, | ||||
|     ]}, | ||||
|   ]); | ||||
| }); | ||||
| @@ -1,85 +0,0 @@ | ||||
| import {dirname, basename} from '../utils.ts'; | ||||
|  | ||||
| export type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'copied' | 'typechange'; | ||||
|  | ||||
| export type File = { | ||||
|   Name: string; | ||||
|   NameHash: string; | ||||
|   Status: FileStatus; | ||||
|   IsViewed: boolean; | ||||
|   IsSubmodule: boolean; | ||||
| } | ||||
|  | ||||
| type DirItem = { | ||||
|     isFile: false; | ||||
|     name: string; | ||||
|     path: string; | ||||
|  | ||||
|     children: Item[]; | ||||
| } | ||||
|  | ||||
| type FileItem = { | ||||
|     isFile: true; | ||||
|     name: string; | ||||
|     path: string; | ||||
|     file: File; | ||||
| } | ||||
|  | ||||
| export type Item = DirItem | FileItem; | ||||
|  | ||||
| export function pathListToTree(fileEntries: File[]): Item[] { | ||||
|   const pathToItem = new Map<string, DirItem>(); | ||||
|  | ||||
|     // init root node | ||||
|   const root: DirItem = {name: '', path: '', isFile: false, children: []}; | ||||
|   pathToItem.set('', root); | ||||
|  | ||||
|   for (const fileEntry of fileEntries) { | ||||
|     const [parentPath, fileName] = [dirname(fileEntry.Name), basename(fileEntry.Name)]; | ||||
|  | ||||
|     let parentItem = pathToItem.get(parentPath); | ||||
|     if (!parentItem) { | ||||
|       parentItem = constructParents(pathToItem, parentPath); | ||||
|     } | ||||
|  | ||||
|     const fileItem: FileItem = {name: fileName, path: fileEntry.Name, isFile: true, file: fileEntry}; | ||||
|  | ||||
|     parentItem.children.push(fileItem); | ||||
|   } | ||||
|  | ||||
|   return root.children; | ||||
| } | ||||
|  | ||||
| function constructParents(pathToItem: Map<string, DirItem>, dirPath: string): DirItem { | ||||
|   const [dirParentPath, dirName] = [dirname(dirPath), basename(dirPath)]; | ||||
|  | ||||
|   let parentItem = pathToItem.get(dirParentPath); | ||||
|   if (!parentItem) { | ||||
|     // if the parent node does not exist, create it | ||||
|     parentItem = constructParents(pathToItem, dirParentPath); | ||||
|   } | ||||
|  | ||||
|   const dirItem: DirItem = {name: dirName, path: dirPath, isFile: false, children: []}; | ||||
|   parentItem.children.push(dirItem); | ||||
|   pathToItem.set(dirPath, dirItem); | ||||
|  | ||||
|   return dirItem; | ||||
| } | ||||
|  | ||||
| export function mergeChildIfOnlyOneDir(nodes: Item[]): void { | ||||
|   for (const node of nodes) { | ||||
|     if (node.isFile) { | ||||
|       continue; | ||||
|     } | ||||
|     const dir = node as DirItem; | ||||
|  | ||||
|     mergeChildIfOnlyOneDir(dir.children); | ||||
|  | ||||
|     if (dir.children.length === 1 && dir.children[0].isFile === false) { | ||||
|       const child = dir.children[0]; | ||||
|       dir.name = `${dir.name}/${child.name}`; | ||||
|       dir.path = child.path; | ||||
|       dir.children = child.children; | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Kerwin Bryant
					Kerwin Bryant