2121 <v-row v-else >
2222 <v-col cols =" 12" >
2323 <v-card outlined >
24- <v-card-title class =" py-2 d-flex justify-space-between align-center" >
24+ <v-card-title class =" py-2 d-flex align-center" >
2525 <span >{{ path }} {{ totalSize ? ` (${totalSize})` : '' }}</span >
26+ <v-spacer />
2627 <v-btn
2728 icon
2829 small
3132 >
3233 <v-icon >mdi-arrow-up</v-icon >
3334 </v-btn >
35+ <v-spacer />
36+ <v-btn
37+ icon
38+ small
39+ color =" red"
40+ :disabled =" selectedPaths.length === 0 || deleting"
41+ :loading =" deleting"
42+ @click =" confirmDeleteSelected"
43+ >
44+ <v-icon >mdi-delete</v-icon >
45+ </v-btn >
3446 </v-card-title >
3547 <v-divider />
3648 <v-card-text class =" pa-0" >
3749 <v-simple-table dense >
3850 <thead >
3951 <tr >
52+ <th class =" text-center select-col" >
53+ <v-checkbox
54+ :input-value =" allSelected"
55+ :indeterminate =" someSelected && !allSelected"
56+ hide-details
57+ dense
58+ @click.stop =" toggleSelectAll"
59+ />
60+ </th >
4061 <th class =" text-left" >
4162 Name
4263 </th >
43- <th class =" text-center" style = " width : 150 px ; " >
64+ <th class =" text-center usage-col " >
4465 Usage
4566 </th >
4667 <th class =" text-right" >
4768 Size
4869 </th >
49- <th class =" text-center" >
50- Type
51- </th >
52- <th class =" text-center" >
53- Actions
54- </th >
5570 </tr >
5671 </thead >
5772 <tbody >
5873 <tr v-if =" !usage || usage.root.children.length === 0" >
59- <td colspan =" 5 " class =" text-center grey--text text--darken-1" >
74+ <td colspan =" 4 " class =" text-center grey--text text--darken-1" >
6075 No entries.
6176 </td >
6277 </tr >
6681 :class =" { 'clickable-row': child.is_dir }"
6782 @click =" navigate(child)"
6883 >
84+ <td class =" text-center select-col" @click.stop >
85+ <v-checkbox
86+ :input-value =" isSelected(child.path)"
87+ hide-details
88+ dense
89+ @click.stop =" toggleSelection(child.path)"
90+ />
91+ </td >
6992 <td >
70- <v-icon left small >
93+ <v-icon
94+ left
95+ small
96+ :color =" child.is_dir ? 'green darken-2' : 'amber darken-2'"
97+ >
7198 {{ child.is_dir ? 'mdi-folder' : 'mdi-file' }}
7299 </v-icon >
73100 {{ child.name }}
74101 </td >
75- <td >
102+ <td class = " usage-col " >
76103 <v-progress-linear
77104 :value =" getPercentage(child.size_bytes)"
78105 height =" 16"
89116 <td class =" text-right" >
90117 {{ prettifySize(child.size_bytes / 1024) }}
91118 </td >
92- <td class =" text-center" >
93- {{ child.is_dir ? 'Dir' : 'File' }}
94- </td >
95- <td class =" text-center" >
96- <v-btn
97- icon
98- small
99- color =" red"
100- :loading =" deleting"
101- @click.stop =" confirmDelete(child)"
102- >
103- <v-icon small >
104- mdi-delete
105- </v-icon >
106- </v-btn >
107- </td >
108119 </tr >
109120 </tbody >
110121 </v-simple-table >
@@ -129,6 +140,7 @@ export default Vue.extend({
129140 depth: 2 ,
130141 includeFiles: true ,
131142 minSizeKb: 0 ,
143+ selectedPaths: [] as string [],
132144 }
133145 },
134146 computed: {
@@ -160,6 +172,14 @@ export default Vue.extend({
160172 }
161173 return prettifySize (this .usage .root .size_bytes / 1024 )
162174 },
175+ allSelected(): boolean {
176+ const children = this .sortedChildren
177+ return children .length > 0 && children .every ((child ) => this .selectedPaths .includes (child .path ))
178+ },
179+ someSelected(): boolean {
180+ const children = this .sortedChildren
181+ return children .some ((child ) => this .selectedPaths .includes (child .path ))
182+ },
163183 },
164184 mounted(): void {
165185 this .fetchUsage ()
@@ -184,22 +204,56 @@ export default Vue.extend({
184204 const currentPath = this .path as string
185205 const trimmed = currentPath .endsWith (' /' ) ? currentPath .slice (0 , - 1 ) : currentPath
186206 const parent = trimmed .substring (0 , trimmed .lastIndexOf (' /' )) || ' /'
207+ this .clearSelection ()
187208 this .path = parent
188209 this .fetchUsage ()
189210 },
190211 navigate(node : DiskNode ): void {
191212 if (! node .is_dir ) {
192213 return
193214 }
215+ this .clearSelection ()
194216 this .path = node .path
195217 this .fetchUsage ()
196218 },
197- async confirmDelete(node : DiskNode ): Promise <void > {
198- const confirmed = window .confirm (` Delete ${node .path }? This cannot be undone. ` )
219+ async confirmDeleteSelected(): Promise <void > {
220+ if (this .selectedPaths .length === 0 ) {
221+ return
222+ }
223+ const confirmed = window .confirm (
224+ ` Delete the following paths?\n ${this .selectedPaths .join (' \n ' )}\n This cannot be undone. ` ,
225+ )
199226 if (! confirmed ) {
200227 return
201228 }
202- await disk_store .deletePath (node .path )
229+ const targets = [... this .selectedPaths ]
230+ const { succeeded } = await disk_store .deletePaths (targets )
231+
232+ if (succeeded .length > 0 ) {
233+ this .selectedPaths = this .selectedPaths .filter ((p ) => ! succeeded .includes (p ))
234+ await this .fetchUsage ()
235+ }
236+ },
237+ toggleSelection(path : string ): void {
238+ if (this .selectedPaths .includes (path )) {
239+ this .selectedPaths = this .selectedPaths .filter ((p ) => p !== path )
240+ } else {
241+ this .selectedPaths = [... this .selectedPaths , path ]
242+ }
243+ },
244+ isSelected(path : string ): boolean {
245+ return this .selectedPaths .includes (path )
246+ },
247+ toggleSelectAll(): void {
248+ const children = this .sortedChildren .map ((child ) => child .path )
249+ if (this .allSelected ) {
250+ this .selectedPaths = []
251+ } else {
252+ this .selectedPaths = [... children ]
253+ }
254+ },
255+ clearSelection(): void {
256+ this .selectedPaths = []
203257 },
204258 getPercentage(sizeBytes : number ): number {
205259 const parentSize = this .usage ?.root ?.size_bytes ?? 0
@@ -220,4 +274,11 @@ export default Vue.extend({
220274.clickable-row :hover {
221275 background-color : rgba (0 , 0 , 0 , 0.05 );
222276}
277+ .select-col {
278+ width : 60px ;
279+ }
280+ .usage-col {
281+ width : 220px ;
282+ min-width : 220px ;
283+ }
223284 </style >
0 commit comments