1<script>
2import DiffFileTreeItem from './DiffFileTreeItem.vue';
3import {loadMoreFiles} from '../features/repo-diff.js';
4import {toggleElem} from '../utils/dom.js';
5import {diffTreeStore} from '../modules/stores.js';
6import {setFileFolding} from '../features/file-fold.js';
7
8const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
9
10export default {
11 components: {DiffFileTreeItem},
12 data: () => {
13 return {store: diffTreeStore()};
14 },
15 computed: {
16 fileTree() {
17 const result = [];
18 for (const file of this.store.files) {
19 // Split file into directories
20 const splits = file.Name.split('/');
21 let index = 0;
22 let parent = null;
23 let isFile = false;
24 for (const split of splits) {
25 index += 1;
26 // reached the end
27 if (index === splits.length) {
28 isFile = true;
29 }
30 let newParent = {
31 name: split,
32 children: [],
33 isFile,
34 };
35
36 if (isFile === true) {
37 newParent.file = file;
38 }
39
40 if (parent) {
41 // check if the folder already exists
42 const existingFolder = parent.children.find(
43 (x) => x.name === split,
44 );
45 if (existingFolder) {
46 newParent = existingFolder;
47 } else {
48 parent.children.push(newParent);
49 }
50 } else {
51 const existingFolder = result.find((x) => x.name === split);
52 if (existingFolder) {
53 newParent = existingFolder;
54 } else {
55 result.push(newParent);
56 }
57 }
58 parent = newParent;
59 }
60 }
61 const mergeChildIfOnlyOneDir = (entries) => {
62 for (const entry of entries) {
63 if (entry.children) {
64 mergeChildIfOnlyOneDir(entry.children);
65 }
66 if (entry.children.length === 1 && entry.children[0].isFile === false) {
67 // Merge it to the parent
68 entry.name = `${entry.name}/${entry.children[0].name}`;
69 entry.children = entry.children[0].children;
70 }
71 }
72 };
73 // Merge folders with just a folder as children in order to
74 // reduce the depth of our tree.
75 mergeChildIfOnlyOneDir(result);
76 return result;
77 },
78 },
79 mounted() {
80 // Default to true if unset
81 this.store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
82 document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', this.toggleVisibility);
83
84 this.hashChangeListener = () => {
85 this.store.selectedItem = window.location.hash;
86 this.expandSelectedFile();
87 };
88 this.hashChangeListener();
89 window.addEventListener('hashchange', this.hashChangeListener);
90 },
91 unmounted() {
92 document.querySelector('.diff-toggle-file-tree-button').removeEventListener('click', this.toggleVisibility);
93 window.removeEventListener('hashchange', this.hashChangeListener);
94 },
95 methods: {
96 expandSelectedFile() {
97 // expand file if the selected file is folded
98 if (this.store.selectedItem) {
99 const box = document.querySelector(this.store.selectedItem);
100 const folded = box?.getAttribute('data-folded') === 'true';
101 if (folded) setFileFolding(box, box.querySelector('.fold-file'), false);
102 }
103 },
104 toggleVisibility() {
105 this.updateVisibility(!this.store.fileTreeIsVisible);
106 },
107 updateVisibility(visible) {
108 this.store.fileTreeIsVisible = visible;
109 localStorage.setItem(LOCAL_STORAGE_KEY, this.store.fileTreeIsVisible);
110 this.updateState(this.store.fileTreeIsVisible);
111 },
112 updateState(visible) {
113 const btn = document.querySelector('.diff-toggle-file-tree-button');
114 const [toShow, toHide] = btn.querySelectorAll('.icon');
115 const tree = document.getElementById('diff-file-tree');
116 const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text');
117 btn.setAttribute('data-tooltip-content', newTooltip);
118 toggleElem(tree, visible);
119 toggleElem(toShow, !visible);
120 toggleElem(toHide, visible);
121 },
122 loadMoreData() {
123 loadMoreFiles(this.store.linkLoadMore);
124 },
125 },
126};
127</script>
128<template>
129 <div v-if="store.fileTreeIsVisible" class="diff-file-tree-items">
130 <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
131 <DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item"/>
132 <div v-if="store.isIncomplete" class="tw-pt-1">
133 <a :class="['ui', 'basic', 'tiny', 'button', store.isLoadingNewData ? 'disabled' : '']" @click.stop="loadMoreData">{{ store.showMoreMessage }}</a>
134 </div>
135 </div>
136</template>
137<style scoped>
138.diff-file-tree-items {
139 display: flex;
140 flex-direction: column;
141 gap: 1px;
142 margin-right: .5rem;
143}
144</style>