Skip to content

Commit 4e66977

Browse files
committed
Add support for moving files via the file manager
1 parent 5aa4080 commit 4e66977

File tree

5 files changed

+154
-10
lines changed

5 files changed

+154
-10
lines changed
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
import {withCredentials} from "@/api/http";
22
import {ServerApplicationCredentials} from "@/store/types";
33

4+
type PathChangeObject = {
5+
currentPath: string,
6+
newPath: string,
7+
}
48
/**
59
* Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path
610
* to be passed through for both data arguments.
711
*/
8-
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: {
9-
currentPath: string, newPath: string
10-
}): Promise<void> {
12+
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject, isMove = false): Promise<void> {
1113
return new Promise((resolve, reject) => {
12-
withCredentials(server, credentials).post('/v1/server/file/copy', {
14+
withCredentials(server, credentials).post(`/v1/server/file/${isMove ? 'move' : 'copy'}`, {
1315
from: data.currentPath,
1416
to: data.newPath,
1517
})
1618
.then(() => resolve())
1719
.catch(reject);
1820
});
1921
}
22+
23+
/**
24+
* Moves a file or folder to a new location on the server. Works almost exactly the same as the copy
25+
* file logic, so it really just passes an extra argument to copy to indicate that it is a move.
26+
*/
27+
export function moveElement(server: string, credentials: ServerApplicationCredentials, data: PathChangeObject): Promise<void> {
28+
return copyElement(server, credentials, data, true);
29+
}

resources/assets/scripts/components/server/components/filemanager/FileContextMenu.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</div>
88
<div class="action"><span>Rename</span></div>
99
</div>
10-
<div class="context-row">
10+
<div class="context-row" v-on:click="triggerAction('move')">
1111
<div class="icon">
1212
<Icon name="corner-up-left" class="h-4"/>
1313
</div>

resources/assets/scripts/components/server/components/filemanager/FileRow.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@
3434
v-on:action:delete="showModal('delete')"
3535
v-on:action:rename="showModal('rename')"
3636
v-on:action:copy="showModal('copy')"
37+
v-on:action:move="showModal('move')"
3738
ref="contextMenu"
3839
/>
3940
<CopyFileModal :file="file" v-if="modals.copy" v-on:close="$emit('list')"/>
4041
<DeleteFileModal :visible.sync="modals.delete" :object="file" v-on:deleted="$emit('deleted')" v-on:close="modal.delete = false"/>
4142
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('list')" v-on:close="modal.rename = false"/>
43+
<MoveFileModal :visible.sync="modals.move" :file="file" v-on:moved="$emit('list')" v-on:close="modal.move = false"/>
4244
</div>
4345
</template>
4446

@@ -52,6 +54,7 @@
5254
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
5355
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
5456
import CopyFileModal from "@/components/server/components/filemanager/modals/CopyFileModal.vue";
57+
import MoveFileModal from "@/components/server/components/filemanager/modals/MoveFileModal.vue";
5558
5659
type DataStructure = {
5760
currentDirectory: string,
@@ -61,7 +64,7 @@
6164
6265
export default Vue.extend({
6366
name: 'FileRow',
64-
components: {CopyFileModal, DeleteFileModal, Icon, FileContextMenu, RenameModal},
67+
components: {CopyFileModal, DeleteFileModal, MoveFileModal, Icon, FileContextMenu, RenameModal},
6568
6669
props: {
6770
file: {
@@ -83,6 +86,7 @@
8386
rename: false,
8487
delete: false,
8588
copy: false,
89+
move: false,
8690
},
8791
};
8892
},
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<template>
2+
<Modal :show="visible" v-on:close="isVisible = false" :dismissable="!isLoading">
3+
<MessageBox class="alert error mb-8" title="Error" :message="error" v-if="error"/>
4+
<div class="flex items-end">
5+
<div class="flex-1">
6+
<label class="input-label">
7+
Move {{ file.name}}
8+
</label>
9+
<input
10+
type="text" class="input" name="move_to"
11+
:placeholder="file.name"
12+
ref="moveToField"
13+
v-model="moveTo"
14+
v-validate="{ required: true, regex: /(^[\w\d.\-\/]+$)/}"
15+
v-on:keyup.enter="submit"
16+
/>
17+
</div>
18+
<div class="ml-4">
19+
<button type="submit"
20+
class="btn btn-primary btn-sm"
21+
v-on:click.prevent="submit"
22+
:disabled="errors.any() || isLoading"
23+
>
24+
<span class="spinner white" v-bind:class="{ hidden: !isLoading }">&nbsp;</span>
25+
<span :class="{ hidden: isLoading }">
26+
Move {{ file.directory ? 'Folder' : 'File' }}
27+
</span>
28+
</button>
29+
</div>
30+
</div>
31+
<p class="input-help error" v-if="errors.count()">
32+
{{ errors.first('move_to') }}
33+
</p>
34+
<p class="input-help" v-else>
35+
Enter the new name and path for this {{ file.directory ? 'folder' : 'file' }} in the field above. This will be relative to the current directory.
36+
</p>
37+
</Modal>
38+
</template>
39+
40+
<script lang="ts">
41+
import Vue from 'vue';
42+
import Modal from "@/components/core/Modal.vue";
43+
import MessageBox from "@/components/MessageBox.vue";
44+
import {DirectoryContentObject} from "@/api/server/types";
45+
import {moveElement} from '@/api/server/files/copyElement';
46+
import {mapState} from "vuex";
47+
import {ApplicationState} from "@/store/types";
48+
import {join} from 'path';
49+
import {AxiosError} from "axios";
50+
51+
type DataStructure = {
52+
error: null | string,
53+
isLoading: boolean,
54+
moveTo: null | string,
55+
};
56+
57+
export default Vue.extend({
58+
name: 'MoveFileModal',
59+
60+
components: { MessageBox, Modal },
61+
62+
data: function (): DataStructure {
63+
return {
64+
error: null,
65+
isLoading: false,
66+
moveTo: null,
67+
};
68+
},
69+
70+
props: {
71+
visible: { type: Boolean, default: false },
72+
file: { type: Object as () => DirectoryContentObject, required: true }
73+
},
74+
75+
computed: {
76+
...mapState({
77+
server: (state: ApplicationState) => state.server.server,
78+
credentials: (state: ApplicationState) => state.server.credentials,
79+
fm: (state: ApplicationState) => state.server.fm,
80+
}),
81+
82+
isVisible: {
83+
get: function (): boolean {
84+
return this.visible;
85+
},
86+
set: function (value: boolean) {
87+
this.$emit('update:visible', value)
88+
},
89+
}
90+
},
91+
92+
watch: {
93+
isVisible: function (n, o): void {
94+
if (n !== o) {
95+
this.resetModal();
96+
}
97+
98+
if (n && !o) {
99+
this.$nextTick(() => (this.$refs.moveToField as HTMLElement).focus());
100+
}
101+
},
102+
},
103+
104+
methods: {
105+
submit: function () {
106+
this.isLoading = true;
107+
108+
// @ts-ignore
109+
moveElement(this.server.uuid, this.credentials, {
110+
// @ts-ignore
111+
currentPath: join(this.fm.currentDirectory, this.file.name),
112+
// @ts-ignore
113+
newPath: join(this.fm.currentDirectory, this.moveTo),
114+
})
115+
.then(() => this.$emit('moved'))
116+
.catch((error: AxiosError) => {
117+
this.error = `There was an error moving the requested ${(this.file.directory) ? 'folder' : 'file'}. Response was: ${error.message}`;
118+
console.error('Error at Server::Files::Move', {error});
119+
})
120+
.then(() => this.isLoading = false);
121+
},
122+
123+
resetModal: function () {
124+
this.isLoading = false;
125+
this.moveTo = null;
126+
this.error = null;
127+
},
128+
}
129+
});
130+
</script>

resources/assets/scripts/components/server/components/filemanager/modals/RenameModal.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
:placeholder="object.name"
1717
ref="elementNameField"
1818
v-model="newName"
19-
v-validate.disabled="'required'"
20-
v-validate="'alpha_dash'"
19+
:data-vv-as="object.directory ? 'folder name' : 'file name'"
20+
v-validate="{ required: true, regex: /(^[\w\d.\-\/]+$)/}"
2121
v-on:keyup.enter="submit"
2222
/>
2323
</div>
@@ -34,8 +34,8 @@
3434
</button>
3535
</div>
3636
</div>
37-
<p class="input-help error">
38-
{{ errors.first('folder_name') }}
37+
<p class="input-help error" v-if="errors.count()">
38+
{{ errors.first('element_name') }}
3939
</p>
4040
</Modal>
4141
</template>

0 commit comments

Comments
 (0)