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
|
* Deletes files and/or folders from the server. You should pass through an array of
|
||||||
* file or folder paths to be deleted.
|
* 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
withCredentials(server, credentials).post('/v1/server/file/delete', { items })
|
withCredentials(server, credentials).post('/v1/server/file/delete', { items })
|
||||||
.then(resolve)
|
.then(() => resolve())
|
||||||
.catch(reject);
|
.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>
|
<template>
|
||||||
<div class="context-menu">
|
<div class="context-menu">
|
||||||
<div>
|
<div>
|
||||||
<div class="context-row" v-on:click="openRenameModal">
|
<div class="context-row" v-on:click="triggerAction('rename')">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon name="edit-3"/>
|
<Icon name="edit-3"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="action"><span class="text-left">Move</span></div>
|
<div class="action"><span class="text-left">Move</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="context-row">
|
<div class="context-row" v-on:click="triggerAction('copy')">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Icon name="copy" class="h-4"/>
|
<Icon name="copy" class="h-4"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div class="icon">
|
||||||
<Icon name="delete" class="h-4"/>
|
<Icon name="delete" class="h-4"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,12 +73,8 @@
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
|
|
||||||
openRenameModal: function () {
|
triggerAction: function (action: string) {
|
||||||
this.$emit('action:rename');
|
this.$emit(`action:${action}`);
|
||||||
},
|
|
||||||
|
|
||||||
openDeleteModal: function () {
|
|
||||||
this.$emit('action:delete');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,10 +33,12 @@
|
||||||
v-on:close="contextMenuVisible = false"
|
v-on:close="contextMenuVisible = false"
|
||||||
v-on:action:delete="showModal('delete')"
|
v-on:action:delete="showModal('delete')"
|
||||||
v-on:action:rename="showModal('rename')"
|
v-on:action:rename="showModal('rename')"
|
||||||
|
v-on:action:copy="showModal('copy')"
|
||||||
ref="contextMenu"
|
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"/>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -49,6 +51,7 @@
|
||||||
import {DirectoryContentObject} from "@/api/server/types";
|
import {DirectoryContentObject} from "@/api/server/types";
|
||||||
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
|
import DeleteFileModal from "@/components/server/components/filemanager/modals/DeleteFileModal.vue";
|
||||||
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
|
import RenameModal from "@/components/server/components/filemanager/modals/RenameModal.vue";
|
||||||
|
import CopyFileModal from "@/components/server/components/filemanager/modals/CopyFileModal.vue";
|
||||||
|
|
||||||
type DataStructure = {
|
type DataStructure = {
|
||||||
currentDirectory: string,
|
currentDirectory: string,
|
||||||
|
@ -58,7 +61,7 @@
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: 'FileRow',
|
name: 'FileRow',
|
||||||
components: {DeleteFileModal, Icon, FileContextMenu, RenameModal},
|
components: {CopyFileModal, DeleteFileModal, Icon, FileContextMenu, RenameModal},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
file: {
|
file: {
|
||||||
|
@ -79,6 +82,7 @@
|
||||||
modals: {
|
modals: {
|
||||||
rename: false,
|
rename: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
|
copy: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -102,7 +106,6 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
showModal: function (name: string) {
|
showModal: function (name: string) {
|
||||||
console.warn('showModal', name);
|
|
||||||
this.contextMenuVisible = false;
|
this.contextMenuVisible = false;
|
||||||
|
|
||||||
Object.keys(this.modals).forEach(k => {
|
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"
|
:file="file"
|
||||||
:editable="editableFiles"
|
:editable="editableFiles"
|
||||||
v-on:deleted="fileRowDeleted(file, file.directory)"
|
v-on:deleted="fileRowDeleted(file, file.directory)"
|
||||||
v-on:renamed="listDirectory"
|
v-on:list="listDirectory"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +57,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import {mapState} from "vuex";
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import {map} from 'lodash';
|
import {map} from 'lodash';
|
||||||
import getDirectoryContents from "@/api/server/getDirectoryContents";
|
import getDirectoryContents from "@/api/server/getDirectoryContents";
|
||||||
|
|
|
@ -17,4 +17,8 @@
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > .modal-container.w-auto {
|
||||||
|
@apply .w-auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue