Add support for copying a file or folder
This commit is contained in:
parent
3970a24218
commit
5aa40800c8
8 changed files with 119 additions and 16 deletions
19
resources/assets/scripts/api/server/files/copyElement.ts
Normal file
19
resources/assets/scripts/api/server/files/copyElement.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {withCredentials} from "@/api/http";
|
||||
import {ServerApplicationCredentials} from "@/store/types";
|
||||
|
||||
/**
|
||||
* Creates a copy of the given file or directory on the Daemon. Expects a fully resolved path
|
||||
* to be passed through for both data arguments.
|
||||
*/
|
||||
export function copyElement(server: string, credentials: ServerApplicationCredentials, data: {
|
||||
currentPath: string, newPath: string
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
withCredentials(server, credentials).post('/v1/server/file/copy', {
|
||||
from: data.currentPath,
|
||||
to: data.newPath,
|
||||
})
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
|
@ -5,10 +5,10 @@ import {ServerApplicationCredentials} from "@/store/types";
|
|||
* Deletes files and/or folders from the server. You should pass through an array of
|
||||
* file or folder paths to be deleted.
|
||||
*/
|
||||
export function deleteElement(server: string, credentials: ServerApplicationCredentials, items: Array<string>): Promise<any> {
|
||||
export function deleteElement(server: string, credentials: ServerApplicationCredentials, items: Array<string>): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
withCredentials(server, credentials).post('/v1/server/file/delete', { items })
|
||||
.then(resolve)
|
||||
.then(() => resolve())
|
||||
.catch(reject);
|
||||
})
|
||||
}
|
||||
|
|
24
resources/assets/scripts/components/core/SpinnerModal.vue
Normal file
24
resources/assets/scripts/components/core/SpinnerModal.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<transition name="modal">
|
||||
<div class="modal-mask" v-show="visible">
|
||||
<div class="modal-container w-auto">
|
||||
<div class="p-8 pb-0">
|
||||
<div class="spinner spinner-thick spinner-relative blue spinner-xl"></div>
|
||||
<p class="text-neutral-700 mt-8 text-sm">
|
||||
<slot/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="context-menu">
|
||||
<div>
|
||||
<div class="context-row" v-on:click="openRenameModal">
|
||||
<div class="context-row" v-on:click="triggerAction('rename')">
|
||||
<div class="icon">
|
||||
<Icon name="edit-3"/>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
<div class="action"><span class="text-left">Move</span></div>
|
||||
</div>
|
||||
<div class="context-row">
|
||||
<div class="context-row" v-on:click="triggerAction('copy')">
|
||||
<div class="icon">
|
||||
<Icon name="copy" class="h-4"/>
|
||||
</div>
|
||||
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="context-row danger" v-on:click="openDeleteModal">
|
||||
<div class="context-row danger" v-on:click="triggerAction('delete')">
|
||||
<div class="icon">
|
||||
<Icon name="delete" class="h-4"/>
|
||||
</div>
|
||||
|
@ -73,12 +73,8 @@
|
|||
this.$emit('close');
|
||||
},
|
||||
|
||||
openRenameModal: function () {
|
||||
this.$emit('action:rename');
|
||||
},
|
||||
|
||||
openDeleteModal: function () {
|
||||
this.$emit('action:delete');
|
||||
triggerAction: function (action: string) {
|
||||
this.$emit(`action:${action}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -33,10 +33,12 @@
|
|||
v-on:close="contextMenuVisible = false"
|
||||
v-on:action:delete="showModal('delete')"
|
||||
v-on:action:rename="showModal('rename')"
|
||||
v-on:action:copy="showModal('copy')"
|
||||
ref="contextMenu"
|
||||
/>
|
||||
<CopyFileModal :file="file" v-if="modals.copy" v-on:close="$emit('list')"/>
|
||||
<DeleteFileModal :visible.sync="modals.delete" :object="file" v-on:deleted="$emit('deleted')" v-on:close="modal.delete = false"/>
|
||||
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('renamed')" v-on:close="modal.rename = false"/>
|
||||
<RenameModal :visible.sync="modals.rename" :object="file" v-on:renamed="$emit('list')" v-on:close="modal.rename = false"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -49,6 +51,7 @@
|
|||
import {DirectoryContentObject} from "@/api/server/types";
|
||||
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
|
||||
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
|
||||
import CopyFileModal from "@/components/server/components/filemanager/modals/CopyFileModal.vue";
|
||||
|
||||
type DataStructure = {
|
||||
currentDirectory: string,
|
||||
|
@ -58,7 +61,7 @@
|
|||
|
||||
export default Vue.extend({
|
||||
name: 'FileRow',
|
||||
components: {DeleteFileModal, Icon, FileContextMenu, RenameModal},
|
||||
components: {CopyFileModal, DeleteFileModal, Icon, FileContextMenu, RenameModal},
|
||||
|
||||
props: {
|
||||
file: {
|
||||
|
@ -79,6 +82,7 @@
|
|||
modals: {
|
||||
rename: false,
|
||||
delete: false,
|
||||
copy: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -102,7 +106,6 @@
|
|||
|
||||
methods: {
|
||||
showModal: function (name: string) {
|
||||
console.warn('showModal', name);
|
||||
this.contextMenuVisible = false;
|
||||
|
||||
Object.keys(this.modals).forEach(k => {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<SpinnerModal :visible="true">
|
||||
Copying {{ file.directory ? 'directory' : 'file' }}...
|
||||
</SpinnerModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import SpinnerModal from "../../../../core/SpinnerModal.vue";
|
||||
import {DirectoryContentObject} from '@/api/server/types';
|
||||
import {mapState} from "vuex";
|
||||
import {ServerState} from '@/store/types';
|
||||
import { join } from 'path';
|
||||
import {copyElement} from '@/api/server/files/copyElement';
|
||||
import {AxiosError} from "axios";
|
||||
|
||||
export default Vue.extend({
|
||||
components: { SpinnerModal },
|
||||
|
||||
computed: mapState('server', {
|
||||
server: (state: ServerState) => state.server,
|
||||
credentials: (state: ServerState) => state.credentials,
|
||||
fm: (state: ServerState) => state.fm,
|
||||
}),
|
||||
|
||||
props: {
|
||||
file: { type: Object as () => DirectoryContentObject, required: true },
|
||||
},
|
||||
|
||||
/**
|
||||
* This modal works differently than the other modals that exist for the file manager.
|
||||
* When it is mounted we will immediately show the spinner, and begin the copy operation
|
||||
* on the give file or directory. Once that operation is complete we will emit the event
|
||||
* and allow the parent to close the modal and do whatever else it thinks is needed.
|
||||
*/
|
||||
mounted: function () {
|
||||
let newPath = join(this.fm.currentDirectory, `${this.file.name} copy`);
|
||||
|
||||
if (!this.file.directory) {
|
||||
const extension = this.file.name.substring(this.file.name.lastIndexOf('.') + 1);
|
||||
|
||||
if (extension !== this.file.name && extension.length > 0) {
|
||||
const name = this.file.name.substring(0, this.file.name.lastIndexOf('.'));
|
||||
|
||||
newPath = join(this.fm.currentDirectory, `${name} copy.${extension}`)
|
||||
}
|
||||
}
|
||||
|
||||
copyElement(this.server.uuid, this.credentials, {currentPath: join(this.fm.currentDirectory, this.file.name), newPath})
|
||||
.then(() => this.$emit('close'))
|
||||
.catch((error: AxiosError) => {
|
||||
alert(`There was an error creating a copy of this item: ${error.message}`);
|
||||
console.error('Error at Server::Files::Copy', {error});
|
||||
})
|
||||
.then(() => this.$emit('close'));
|
||||
},
|
||||
})
|
||||
</script>
|
|
@ -37,7 +37,7 @@
|
|||
:file="file"
|
||||
:editable="editableFiles"
|
||||
v-on:deleted="fileRowDeleted(file, file.directory)"
|
||||
v-on:renamed="listDirectory"
|
||||
v-on:list="listDirectory"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,7 +57,6 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import {mapState} from "vuex";
|
||||
import { join } from 'path';
|
||||
import {map} from 'lodash';
|
||||
import getDirectoryContents from "@/api/server/getDirectoryContents";
|
||||
|
|
|
@ -17,4 +17,8 @@
|
|||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
& > .modal-container.w-auto {
|
||||
@apply .w-auto;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue