|
1 | 1 | <template> |
2 | 2 | <div> |
| 3 | + <div class="filemanager-breadcrumbs"> |
| 4 | + /<span class="px-1">home</span><!-- |
| 5 | + -->/<router-link :to="{ name: 'server-files' }" class="px-1">container</router-link><!-- |
| 6 | + --><span v-for="crumb in breadcrumbs" class="inline-block"> |
| 7 | + <span v-if="crumb.path"> |
| 8 | + /<router-link :to="{ name: 'server-files', params: { path: crumb.path } }" class="px-1">{{crumb.directoryName}}</router-link> |
| 9 | + </span> |
| 10 | + <span v-else> |
| 11 | + /<span class="px-1 font-semibold">{{crumb.directoryName}}</span> |
| 12 | + </span> |
| 13 | + </span> |
| 14 | + </div> |
3 | 15 | <div v-if="loading"> |
4 | 16 | <div class="spinner spinner-xl blue"></div> |
5 | 17 | </div> |
|
14 | 26 | <div class="flex-1 text-right">Modified</div> |
15 | 27 | <div class="flex-none w-1/6">Actions</div> |
16 | 28 | </div> |
17 | | - <div class="row clickable" v-for="directory in directories" v-on:click="currentDirectory = directory.name"> |
18 | | - <div class="flex-none icon"><folder-icon/></div> |
19 | | - <div class="flex-1">{{directory.name}}</div> |
20 | | - <div class="flex-1 text-right text-grey-dark"></div> |
21 | | - <div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div> |
22 | | - <div class="flex-none w-1/6"></div> |
| 29 | + <div v-if="!directories.length && !files.length"> |
| 30 | + <p class="text-grey text-sm text-center p-6 pb-4">This directory is empty.</p> |
23 | 31 | </div> |
24 | | - <div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }"> |
25 | | - <div class="flex-none icon"> |
26 | | - <file-text-icon v-if="!file.symlink"/> |
27 | | - <link2-icon v-else/> |
| 32 | + <div v-else> |
| 33 | + <router-link class="row clickable" |
| 34 | + v-for="directory in directories" |
| 35 | + :to="{ name: 'server-files', params: { path: getClickablePath(directory.name).replace(/^\//, '') }}" |
| 36 | + :key="directory.name + directory.modified" |
| 37 | + > |
| 38 | + <div class="flex-none icon"> |
| 39 | + <folder-icon/> |
| 40 | + </div> |
| 41 | + <div class="flex-1">{{directory.name}}</div> |
| 42 | + <div class="flex-1 text-right text-grey-dark"></div> |
| 43 | + <div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div> |
| 44 | + <div class="flex-none w-1/6"></div> |
| 45 | + </router-link> |
| 46 | + <div class="row" v-for="file in files" :class="{ clickable: canEdit(file) }"> |
| 47 | + <div class="flex-none icon"> |
| 48 | + <file-text-icon v-if="!file.symlink"/> |
| 49 | + <link2-icon v-else/> |
| 50 | + </div> |
| 51 | + <div class="flex-1">{{file.name}}</div> |
| 52 | + <div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div> |
| 53 | + <div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div> |
| 54 | + <div class="flex-none w-1/6"></div> |
28 | 55 | </div> |
29 | | - <div class="flex-1">{{file.name}}</div> |
30 | | - <div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div> |
31 | | - <div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div> |
32 | | - <div class="flex-none w-1/6"></div> |
33 | 56 | </div> |
34 | 57 | </div> |
35 | 58 | </div> |
36 | 59 | </template> |
37 | 60 |
|
38 | 61 | <script> |
| 62 | + import _ from 'lodash'; |
39 | 63 | import filter from 'lodash/filter'; |
40 | 64 | import isObject from 'lodash/isObject'; |
41 | 65 | import format from 'date-fns/format'; |
|
44 | 68 |
|
45 | 69 | export default { |
46 | 70 | name: 'file-manager-page', |
47 | | - components: { FileTextIcon, FolderIcon, Link2Icon }, |
| 71 | + components: {FileTextIcon, FolderIcon, Link2Icon}, |
48 | 72 |
|
49 | 73 | computed: { |
50 | 74 | ...mapState('server', ['server', 'credentials']), |
51 | 75 | ...mapState('socket', ['connected']), |
| 76 | +
|
| 77 | + /** |
| 78 | + * Configure the breadcrumbs that display on the filemanager based on the directory that the |
| 79 | + * user is currently in. |
| 80 | + */ |
| 81 | + breadcrumbs: function () { |
| 82 | + const directories = this.currentDirectory.replace(/^\/|\/$/, '').split('/'); |
| 83 | + if (directories.length < 1 || !directories[0]) { |
| 84 | + return []; |
| 85 | + } |
| 86 | +
|
| 87 | + return _.map(directories, function (value, key) { |
| 88 | + if (key === directories.length - 1) { |
| 89 | + return {directoryName: value}; |
| 90 | + } |
| 91 | +
|
| 92 | + return { |
| 93 | + directoryName: value, |
| 94 | + path: directories.slice(0, key + 1).join('/'), |
| 95 | + }; |
| 96 | + }); |
| 97 | + } |
52 | 98 | }, |
53 | 99 |
|
54 | 100 | watch: { |
| 101 | + /** |
| 102 | + * When the route changes reload the directory. |
| 103 | + */ |
| 104 | + '$route': function (to) { |
| 105 | + this.currentDirectory = to.params.path || '/'; |
| 106 | + }, |
| 107 | +
|
55 | 108 | /** |
56 | 109 | * Watch the current directory setting and when it changes update the file listing. |
57 | 110 | */ |
|
72 | 125 |
|
73 | 126 | data: function () { |
74 | 127 | return { |
75 | | - currentDirectory: '/', |
| 128 | + currentDirectory: this.$route.params.path || '/', |
76 | 129 | loading: true, |
77 | 130 | errorMessage: null, |
78 | 131 |
|
|
95 | 148 |
|
96 | 149 | window.axios.get(this.route('server.files', { |
97 | 150 | server: this.$route.params.id, |
98 | | - directory: this.currentDirectory, |
| 151 | + directory: encodeURI(this.currentDirectory.replace(/^\/|\/$/, '')), |
99 | 152 | })) |
100 | 153 | .then((response) => { |
101 | 154 | this.files = filter(response.data.contents, function (o) { |
|
110 | 163 | this.errorMessage = null; |
111 | 164 | }) |
112 | 165 | .catch(err => { |
113 | | - console.error({ err }); |
| 166 | + console.error({err}); |
| 167 | + if (err.response.status === 404) { |
| 168 | + this.errorMessage = 'The directory you requested could not be located on the server.'; |
| 169 | + return; |
| 170 | + } |
| 171 | +
|
114 | 172 | if (err.response.data && isObject(err.response.data.errors)) { |
115 | 173 | err.response.data.errors.forEach(error => { |
116 | 174 | this.errorMessage = error.detail; |
|
132 | 190 | return this.editableFiles.indexOf(file.mime) >= 0; |
133 | 191 | }, |
134 | 192 |
|
| 193 | + /** |
| 194 | + * Return a formatted directory path that is used to switch to a nested directory. |
| 195 | + * |
| 196 | + * @return {String} |
| 197 | + */ |
| 198 | + getClickablePath (directory) { |
| 199 | + return `${this.currentDirectory.replace(/\/$/, '')}/${directory}`; |
| 200 | + }, |
| 201 | +
|
135 | 202 | /** |
136 | 203 | * Return the human readable filesize for a given number of bytes. This |
137 | 204 | * uses 1024 as the base, so the response is denoted accordingly. |
|
152 | 219 | u++; |
153 | 220 | } while (Math.abs(bytes) >= 1024 && u < units.length - 1); |
154 | 221 |
|
155 | | - return `${bytes.toFixed(1)} ${units[u]}` |
| 222 | + return `${bytes.toFixed(1)} ${units[u]}`; |
156 | 223 | }, |
157 | 224 |
|
158 | 225 | /** |
|
0 commit comments