Skip to content

Commit 5aa4080

Browse files
committed
Add support for copying a file or folder
1 parent 3970a24 commit 5aa4080

File tree

8 files changed

+119
-16
lines changed

8 files changed

+119
-16
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {withCredentials} from "@/api/http";
2+
import {ServerApplicationCredentials} from "@/store/types";
3+
4+
/**
5+
* Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path
6+
* to be passed through for both data arguments.
7+
*/
8+
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: {
9+
currentPath: string, newPath: string
10+
}): Promise<void> {
11+
return new Promise((resolve, reject) => {
12+
withCredentials(server, credentials).post('/v1/server/file/copy', {
13+
from: data.currentPath,
14+
to: data.newPath,
15+
})
16+
.then(() => resolve())
17+
.catch(reject);
18+
});
19+
}

resources/assets/scripts/api/server/files/deleteElement.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {ServerApplicationCredentials} from "@/store/types";
55
* Deletes files and/or folders from the server. You should pass through an array of
66
* file or folder paths to be deleted.
77
*/
8-
export function deleteElement(server: string, credentials: ServerApplicationCredentials, items: Array<string>): Promise<any> {
8+
export function deleteElement(server: string, credentials: ServerApplicationCredentials, items: Array<string>): Promise<void> {
99
return new Promise((resolve, reject) => {
1010
withCredentials(server, credentials).post('/v1/server/file/delete', { items })
11-
.then(resolve)
11+
.then(() => resolve())
1212
.catch(reject);
1313
})
1414
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<template>
2+
<transition name="modal">
3+
<div class="modal-mask" v-show="visible">
4+
<div class="modal-container w-auto">
5+
<div class="p-8 pb-0">
6+
<div class="spinner spinner-thick spinner-relative blue spinner-xl"></div>
7+
<p class="text-neutral-700 mt-8 text-sm">
8+
<slot/>
9+
</p>
10+
</div>
11+
</div>
12+
</div>
13+
</transition>
14+
</template>
15+
16+
<script lang="ts">
17+
import Vue from 'vue';
18+
19+
export default Vue.extend({
20+
props: {
21+
visible: { type: Boolean, default: false },
22+
},
23+
});
24+
</script>

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="context-menu">
33
<div>
4-
<div class="context-row" v-on:click="openRenameModal">
4+
<div class="context-row" v-on:click="triggerAction('rename')">
55
<div class="icon">
66
<Icon name="edit-3"/>
77
</div>
@@ -13,7 +13,7 @@
1313
</div>
1414
<div class="action"><span class="text-left">Move</span></div>
1515
</div>
16-
<div class="context-row">
16+
<div class="context-row" v-on:click="triggerAction('copy')">
1717
<div class="icon">
1818
<Icon name="copy" class="h-4"/>
1919
</div>
@@ -41,7 +41,7 @@
4141
</div>
4242
</div>
4343
<div>
44-
<div class="context-row danger" v-on:click="openDeleteModal">
44+
<div class="context-row danger" v-on:click="triggerAction('delete')">
4545
<div class="icon">
4646
<Icon name="delete" class="h-4"/>
4747
</div>
@@ -73,12 +73,8 @@
7373
this.$emit('close');
7474
},
7575
76-
openRenameModal: function () {
77-
this.$emit('action:rename');
78-
},
79-
80-
openDeleteModal: function () {
81-
this.$emit('action:delete');
76+
triggerAction: function (action: string) {
77+
this.$emit(`action:${action}`);
8278
}
8379
}
8480
});

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@
3333
v-on:close="contextMenuVisible = false"
3434
v-on:action:delete="showModal('delete')"
3535
v-on:action:rename="showModal('rename')"
36+
v-on:action:copy="showModal('copy')"
3637
ref="contextMenu"
3738
/>
39+
<CopyFileModal :file="file" v-if="modals.copy" v-on:close="$emit('list')"/>
3840
<DeleteFileModal :visible.sync="modals.delete" :object="file" v-on:deleted="$emit('deleted')" v-on:close="modal.delete = false"/>
39-
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('renamed')" v-on:close="modal.rename = false"/>
41+
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('list')" v-on:close="modal.rename = false"/>
4042
</div>
4143
</template>
4244

@@ -49,6 +51,7 @@
4951
import {DirectoryContentObject} from "@/api/server/types";
5052
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
5153
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
54+
import CopyFileModal from "@/components/server/components/filemanager/modals/CopyFileModal.vue";
5255
5356
type DataStructure = {
5457
currentDirectory: string,
@@ -58,7 +61,7 @@
5861
5962
export default Vue.extend({
6063
name: 'FileRow',
61-
components: {DeleteFileModal, Icon, FileContextMenu, RenameModal},
64+
components: {CopyFileModal, DeleteFileModal, Icon, FileContextMenu, RenameModal},
6265
6366
props: {
6467
file: {
@@ -79,6 +82,7 @@
7982
modals: {
8083
rename: false,
8184
delete: false,
85+
copy: false,
8286
},
8387
};
8488
},
@@ -102,7 +106,6 @@
102106
103107
methods: {
104108
showModal: function (name: string) {
105-
console.warn('showModal', name);
106109
this.contextMenuVisible = false;
107110
108111
Object.keys(this.modals).forEach(k => {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<template>
2+
<SpinnerModal :visible="true">
3+
Copying {{ file.directory ? 'directory' : 'file' }}...
4+
</SpinnerModal>
5+
</template>
6+
7+
<script lang="ts">
8+
import Vue from 'vue';
9+
import SpinnerModal from "../../../../core/SpinnerModal.vue";
10+
import {DirectoryContentObject} from '@/api/server/types';
11+
import {mapState} from "vuex";
12+
import {ServerState} from '@/store/types';
13+
import { join } from 'path';
14+
import {copyElement} from '@/api/server/files/copyElement';
15+
import {AxiosError} from "axios";
16+
17+
export default Vue.extend({
18+
components: { SpinnerModal },
19+
20+
computed: mapState('server', {
21+
server: (state: ServerState) => state.server,
22+
credentials: (state: ServerState) => state.credentials,
23+
fm: (state: ServerState) => state.fm,
24+
}),
25+
26+
props: {
27+
file: { type: Object as () => DirectoryContentObject, required: true },
28+
},
29+
30+
/**
31+
* This modal works differently than the other modals that exist for the file manager.
32+
* When it is mounted we will immediately show the spinner, and begin the copy operation
33+
* on the give file or directory. Once that operation is complete we will emit the event
34+
* and allow the parent to close the modal and do whatever else it thinks is needed.
35+
*/
36+
mounted: function () {
37+
let newPath = join(this.fm.currentDirectory, `${this.file.name} copy`);
38+
39+
if (!this.file.directory) {
40+
const extension = this.file.name.substring(this.file.name.lastIndexOf('.') + 1);
41+
42+
if (extension !== this.file.name && extension.length > 0) {
43+
const name = this.file.name.substring(0, this.file.name.lastIndexOf('.'));
44+
45+
newPath = join(this.fm.currentDirectory, `${name} copy.${extension}`)
46+
}
47+
}
48+
49+
copyElement(this.server.uuid, this.credentials, {currentPath: join(this.fm.currentDirectory, this.file.name), newPath})
50+
.then(() => this.$emit('close'))
51+
.catch((error: AxiosError) => {
52+
alert(`There was an error creating a copy of this item: ${error.message}`);
53+
console.error('Error at Server::Files::Copy', {error});
54+
})
55+
.then(() => this.$emit('close'));
56+
},
57+
})
58+
</script>

resources/assets/scripts/components/server/subpages/FileManager.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
:file="file"
3838
:editable="editableFiles"
3939
v-on:deleted="fileRowDeleted(file, file.directory)"
40-
v-on:renamed="listDirectory"
40+
v-on:list="listDirectory"
4141
/>
4242
</div>
4343
</div>
@@ -57,7 +57,6 @@
5757

5858
<script lang="ts">
5959
import Vue from 'vue';
60-
import {mapState} from "vuex";
6160
import { join } from 'path';
6261
import {map} from 'lodash';
6362
import getDirectoryContents from "@/api/server/getDirectoryContents";

resources/assets/styles/components/modal.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@
1717
width: 90%;
1818
}
1919
}
20+
21+
& > .modal-container.w-auto {
22+
@apply .w-auto;
23+
}
2024
}

0 commit comments

Comments
 (0)