]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/window/FileBrowser.js
file browser: align size column to end/right
[proxmox-widget-toolkit.git] / src / window / FileBrowser.js
CommitLineData
09195cb6
SR
1Ext.define('proxmox-file-tree', {
2 extend: 'Ext.data.Model',
3
427685c6
TL
4 fields: [
5 'filepath', 'text', 'type', 'size',
f5be46bc
SS
6 {
7 name: 'sizedisplay',
8 calculate: data => {
9 if (data.size === undefined) {
10 return '';
11 } else if (data.type === 'd') {
12 let fs = data.size === 1 ? gettext('{0} item') : gettext('{0} items');
13 return Ext.String.format(fs, data.size);
14 }
15
16 return Proxmox.Utils.format_size(data.size);
17 },
18 },
09195cb6
SR
19 {
20 name: 'mtime',
21 type: 'date',
22 dateFormat: 'timestamp',
23 },
24 {
25 name: 'iconCls',
26 calculate: function(data) {
427685c6
TL
27 let icon = Proxmox.Schema.pxarFileTypes[data.type]?.icon ?? 'file-o';
28 if (data.expanded && data.type === 'd') {
29 icon = 'folder-open-o';
09195cb6 30 }
09195cb6
SR
31 return `fa fa-${icon}`;
32 },
33 },
34 ],
35 idProperty: 'filepath',
36});
37
38Ext.define("Proxmox.window.FileBrowser", {
39 extend: "Ext.window.Window",
40
41 width: 800,
42 height: 600,
43
44 modal: true,
45
fd8ed0d8
TL
46 config: {
47 // the base-URL to get the list of files. required.
48 listURL: '',
49
50 // the base download URL, e.g., something like '/api2/...'
51 downloadURL: '',
52
53 // extra parameters set as proxy paramns and for an actual download request
54 extraParams: {},
55
56 // the file types for which the download button should be enabled
57 downloadableFileTypes: {
58 'h': true, // hardlinks
59 'f': true, // "normal" files
60 'd': true, // directories
61 },
3b974606 62
a3faf027
SS
63 // enable tar download, this will add a menu to the
64 // "Download" button when the selection can be downloaded as
65 // .tar files
3b974606 66 enableTar: false,
614b3cd4
SS
67
68 // prefix to prepend to downloaded file names
69 downloadPrefix: '',
fd8ed0d8
TL
70 },
71
09195cb6
SR
72 controller: {
73 xclass: 'Ext.app.ViewController',
74
75 buildUrl: function(baseurl, params) {
76 let url = new URL(baseurl, window.location.origin);
77 for (const [key, value] of Object.entries(params)) {
78 url.searchParams.append(key, value);
79 }
80
81 return url.href;
82 },
83
3b974606
DC
84 downloadTar: function() {
85 this.downloadFile(true);
86 },
87
88 downloadZip: function() {
89 this.downloadFile(false);
90 },
91
92 downloadFile: function(tar) {
09195cb6
SR
93 let me = this;
94 let view = me.getView();
95 let tree = me.lookup('tree');
96 let selection = tree.getSelection();
97 if (!selection || selection.length < 1) return;
98
99 let data = selection[0].data;
100
68b29ade 101 let params = { ...view.extraParams };
09195cb6 102 params.filepath = data.filepath;
614b3cd4
SS
103
104 let atag = document.createElement('a');
105 atag.download = view.downloadPrefix + data.text;
09195cb6 106 if (data.type === 'd') {
3b974606
DC
107 if (tar) {
108 params.tar = 1;
109 atag.download += ".tar.zst";
110 } else {
111 atag.download += ".zip";
112 }
09195cb6 113 }
fd8ed0d8 114 atag.href = me.buildUrl(view.downloadURL, params);
09195cb6
SR
115 atag.click();
116 },
117
118 fileChanged: function() {
119 let me = this;
68b29ade 120 let view = me.getView();
09195cb6
SR
121 let tree = me.lookup('tree');
122 let selection = tree.getSelection();
123 if (!selection || selection.length < 1) return;
124
125 let data = selection[0].data;
a3faf027
SS
126 let st = Ext.String.format(gettext('Selected "{0}"'), atob(data.filepath));
127 view.lookup('selectText').setText(st);
128
fd8ed0d8 129 let canDownload = view.downloadURL && view.downloadableFileTypes[data.type];
a3faf027
SS
130 let enableMenu = view.enableTar && data.type === 'd';
131
132 let downloadBtn = view.lookup('downloadBtn');
133 downloadBtn.setDisabled(!canDownload || enableMenu);
134 downloadBtn.setHidden(!canDownload || enableMenu);
135
136 let menuBtn = view.lookup('menuBtn');
137 menuBtn.setDisabled(!canDownload || !enableMenu);
138 menuBtn.setHidden(!canDownload || !enableMenu);
09195cb6
SR
139 },
140
6f9f9c71
SR
141 errorHandler: function(error, msg) {
142 let me = this;
a69f2358
DC
143 if (error?.status === 503) {
144 return false;
145 }
6f9f9c71 146 me.lookup('downloadBtn').setDisabled(true);
a3faf027 147 me.lookup('menuBtn').setDisabled(true);
6f9f9c71
SR
148 if (me.initialLoadDone) {
149 Ext.Msg.alert(gettext('Error'), msg);
150 return true;
151 }
152 return false;
153 },
154
09195cb6
SR
155 init: function(view) {
156 let me = this;
157 let tree = me.lookup('tree');
158
fd8ed0d8 159 if (!view.listURL) {
68b29ade 160 throw "no list URL given";
09195cb6
SR
161 }
162
163 let store = tree.getStore();
164 let proxy = store.getProxy();
165
6f9f9c71 166 let errorCallback = (error, msg) => me.errorHandler(error, msg);
fd8ed0d8 167 proxy.setUrl(view.listURL);
a69f2358 168 proxy.setTimeout(60*1000);
68b29ade 169 proxy.setExtraParams(view.extraParams);
a69f2358
DC
170
171 tree.mon(store, 'beforeload', () => {
172 Proxmox.Utils.setErrorMask(tree, true);
173 });
174 tree.mon(store, 'load', (treestore, rec, success, operation, node) => {
175 if (success) {
176 Proxmox.Utils.setErrorMask(tree, false);
177 return;
178 }
179 if (!node.loadCount) {
180 node.loadCount = 0; // ensure its numeric
181 }
182 // trigger a reload if we got a 503 answer from the proxy
183 if (operation?.error?.status === 503 && node.loadCount < 10) {
184 node.collapse();
185 node.expand();
186 node.loadCount++;
187 return;
188 }
189
190 let error = operation.getError();
191 let msg = Proxmox.Utils.getResponseErrorMessage(error);
192 if (!errorCallback(error, msg)) {
193 Proxmox.Utils.setErrorMask(tree, msg);
194 } else {
195 Proxmox.Utils.setErrorMask(tree, false);
196 }
197 });
6f9f9c71 198 store.load((rec, op, success) => {
09195cb6
SR
199 let root = store.getRoot();
200 root.expand(); // always expand invisible root node
6f9f9c71
SR
201 if (view.archive === 'all') {
202 root.expandChildren(false);
203 } else if (view.archive) {
09195cb6
SR
204 let child = root.findChild('text', view.archive);
205 if (child) {
206 child.expand();
207 setTimeout(function() {
208 tree.setSelection(child);
209 tree.getView().focusRow(child);
210 }, 10);
211 }
212 } else if (root.childNodes.length === 1) {
213 root.firstChild.expand();
214 }
6f9f9c71 215 me.initialLoadDone = success;
09195cb6
SR
216 });
217 },
218
219 control: {
220 'treepanel': {
221 selectionchange: 'fileChanged',
222 },
223 },
224 },
225
226 layout: 'fit',
227 items: [
228 {
229 xtype: 'treepanel',
230 scrollable: true,
231 rootVisible: false,
232 reference: 'tree',
233 store: {
234 autoLoad: false,
235 model: 'proxmox-file-tree',
236 defaultRootId: '/',
237 nodeParam: 'filepath',
238 sorters: 'text',
239 proxy: {
240 appendId: false,
241 type: 'proxmox',
242 },
243 },
244
a69f2358
DC
245 viewConfig: {
246 loadMask: false,
247 },
248
09195cb6
SR
249 columns: [
250 {
251 text: gettext('Name'),
252 xtype: 'treecolumn',
253 flex: 1,
254 dataIndex: 'text',
255 renderer: Ext.String.htmlEncode,
256 },
257 {
258 text: gettext('Size'),
f5be46bc 259 dataIndex: 'sizedisplay',
b33c1d60 260 align: 'end',
09195cb6
SR
261 sorter: {
262 sorterFn: function(a, b) {
f5be46bc
SS
263 if (a.data.type === 'd' && b.data.type !== 'd') {
264 return -1;
265 } else if (a.data.type !== 'd' && b.data.type === 'd') {
266 return 1;
267 }
268
09195cb6
SR
269 let asize = a.data.size || 0;
270 let bsize = b.data.size || 0;
271
272 return asize - bsize;
273 },
274 },
275 },
276 {
277 text: gettext('Modified'),
278 dataIndex: 'mtime',
279 minWidth: 200,
280 },
281 {
282 text: gettext('Type'),
283 dataIndex: 'type',
427685c6 284 renderer: (v) => Proxmox.Schema.pxarFileTypes[v]?.label ?? Proxmox.Utils.unknownText,
09195cb6
SR
285 },
286 ],
287 },
288 ],
289
a3faf027 290 fbar: [
09195cb6 291 {
a3faf027
SS
292 text: '',
293 xtype: 'label',
294 reference: 'selectText',
3b974606
DC
295 },
296 {
a3faf027
SS
297 text: gettext('Download'),
298 xtype: 'button',
3b974606 299 handler: 'downloadZip',
09195cb6
SR
300 reference: 'downloadBtn',
301 disabled: true,
a3faf027
SS
302 hidden: true,
303 },
304 {
305 text: gettext('Download as'),
306 xtype: 'button',
307 reference: 'menuBtn',
308 menu: {
309 items: [
310 {
311 iconCls: 'fa fa-fw fa-file-zip-o',
312 text: gettext('.zip'),
313 handler: 'downloadZip',
314 reference: 'downloadZip',
315 },
316 {
317 iconCls: 'fa fa-fw fa-archive',
318 text: gettext('.tar.zst'),
319 handler: 'downloadTar',
320 reference: 'downloadTar',
321 },
322 ],
323 },
09195cb6
SR
324 },
325 ],
326});