mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 11:06:51 +01:00 
			
		
		
		
	Migrate more components to TS
This commit is contained in:
		
							parent
							
								
									085da72934
								
							
						
					
					
						commit
						2a0d649b2a
					
				
							
								
								
									
										11
									
								
								resources/assets/scripts/components/forms/CSRF.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								resources/assets/scripts/components/forms/CSRF.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.component('csrf', {
 | 
			
		||||
    data: function () {
 | 
			
		||||
        return {
 | 
			
		||||
            X_CSRF_TOKEN: window.X_CSRF_TOKEN,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `<input type="hidden" name="_token" v-bind:value="X_CSRF_TOKEN" />`,
 | 
			
		||||
});
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <input type="hidden" name="_token" v-bind:value="X_CSRF_TOKEN" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'csrf',
 | 
			
		||||
        data: function () {
 | 
			
		||||
            return {
 | 
			
		||||
                X_CSRF_TOKEN: window.X_CSRF_TOKEN,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { mapState } from 'vuex';
 | 
			
		||||
import Status from '../../../helpers/statuses';
 | 
			
		||||
 | 
			
		||||
export default Vue.component('power-buttons', {
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapState('socket', ['connected', 'status']),
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data: function () {
 | 
			
		||||
        return {
 | 
			
		||||
            statuses: Status,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        sendPowerAction: function (action: string) {
 | 
			
		||||
            // this.$socket().instance().emit('set status', action)
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `
 | 
			
		||||
        <div>
 | 
			
		||||
            <div v-if="connected">
 | 
			
		||||
                <transition name="slide-fade" mode="out-in">
 | 
			
		||||
                    <button class="btn btn-green uppercase text-xs px-4 py-2"
 | 
			
		||||
                            v-if="status === statuses.STATUS_OFF"
 | 
			
		||||
                            v-on:click.prevent="sendPowerAction('start')"
 | 
			
		||||
                    >Start</button>
 | 
			
		||||
                    <div v-else>
 | 
			
		||||
                        <button class="btn btn-red uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('stop')">Stop</button>
 | 
			
		||||
                        <button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('restart')">Restart</button>
 | 
			
		||||
                        <button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('kill')">Kill</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </transition>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else>
 | 
			
		||||
                <div class="text-center">
 | 
			
		||||
                    <div class="spinner"></div>
 | 
			
		||||
                    <div class="pt-2 text-xs text-grey-light">Connecting to node</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    `
 | 
			
		||||
});
 | 
			
		||||
@ -1,60 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div v-if="connected">
 | 
			
		||||
            <transition name="slide-fade" mode="out-in">
 | 
			
		||||
                <button class="btn btn-green uppercase text-xs px-4 py-2"
 | 
			
		||||
                        v-if="status === statuses.STATUS_OFF"
 | 
			
		||||
                        v-on:click.prevent="sendPowerAction('start')"
 | 
			
		||||
                >Start</button>
 | 
			
		||||
                <div v-else>
 | 
			
		||||
                    <button class="btn btn-red uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('stop')">Stop</button>
 | 
			
		||||
                    <button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('restart')">Restart</button>
 | 
			
		||||
                    <button class="btn btn-secondary uppercase text-xs px-4 py-2" v-on:click.prevent="sendPowerAction('kill')">Kill</button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </transition>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else>
 | 
			
		||||
            <div class="text-center">
 | 
			
		||||
                <div class="spinner"></div>
 | 
			
		||||
                <div class="pt-2 text-xs text-grey-light">Connecting to node</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import Status from '../../../helpers/statuses';
 | 
			
		||||
    import { mapState } from 'vuex';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'power-buttons',
 | 
			
		||||
        computed: {
 | 
			
		||||
            ...mapState('socket', ['connected', 'status']),
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        data: function () {
 | 
			
		||||
            return {
 | 
			
		||||
                statuses: Status,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        methods: {
 | 
			
		||||
            sendPowerAction: function (action) {
 | 
			
		||||
                this.$socket().instance().emit('set status', action)
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
    .slide-fade-enter-active {
 | 
			
		||||
        transition: all 250ms ease;
 | 
			
		||||
    }
 | 
			
		||||
    .slide-fade-leave-active {
 | 
			
		||||
        transition: all 250ms cubic-bezier(1.0, 0.5, 0.8, 1.0);
 | 
			
		||||
    }
 | 
			
		||||
    .slide-fade-enter, .slide-fade-leave-to {
 | 
			
		||||
        transform: translateX(10px);
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
 | 
			
		||||
export default Vue.component('progress-bar', {
 | 
			
		||||
    props: {
 | 
			
		||||
        percent: {type: Number, default: 0},
 | 
			
		||||
        title: {type: String}
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        backgroundColor: function () {
 | 
			
		||||
            if (this.percent < 70) {
 | 
			
		||||
                return "bg-green-dark";
 | 
			
		||||
            } else if (this.percent >= 70 && this.percent < 90) {
 | 
			
		||||
                return "bg-yellow-dark";
 | 
			
		||||
            } else {
 | 
			
		||||
                return "bg-red-dark";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        borderColor: function () {
 | 
			
		||||
            if (this.percent < 70) {
 | 
			
		||||
                return "border-green-dark";
 | 
			
		||||
            } else if (this.percent >= 70 && this.percent < 90) {
 | 
			
		||||
                return "border-yellow-dark";
 | 
			
		||||
            } else {
 | 
			
		||||
                return "border-red-dark";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="text-right mb-1" v-if="title.length > 0">
 | 
			
		||||
                <span class="text-grey-dark text-xs uppercase">{{ title }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="w-full border rounded h-4" :class="borderColor">
 | 
			
		||||
                <div class="h-full w-1/3 text-center" :style="{ width: percent + '%' }" :class="backgroundColor">
 | 
			
		||||
                    <span class="mt-1 text-xs text-white leading-none">{{ percent }} %</span>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    `,
 | 
			
		||||
});
 | 
			
		||||
@ -1,42 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="text-right mb-1" v-if="title.length > 0">
 | 
			
		||||
            <span class="text-grey-dark text-xs uppercase">{{ title }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-full border rounded h-4" :class="borderColor">
 | 
			
		||||
            <div class="h-full w-1/3 text-center" :style="{ width: percent + '%' }" :class="backgroundColor">
 | 
			
		||||
                <span class="mt-1 text-xs text-white leading-none">{{ percent }} %</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'progress-bar',
 | 
			
		||||
        props: {
 | 
			
		||||
            percent: {type: String, default: '0'},
 | 
			
		||||
            title: {type: String}
 | 
			
		||||
        },
 | 
			
		||||
        computed: {
 | 
			
		||||
            backgroundColor: function () {
 | 
			
		||||
                if (this.percent < 70) {
 | 
			
		||||
                    return "bg-green-dark";
 | 
			
		||||
                } else if (this.percent >= 70 && this.percent < 90) {
 | 
			
		||||
                    return "bg-yellow-dark";
 | 
			
		||||
                } else {
 | 
			
		||||
                    return "bg-red-dark";
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            borderColor: function () {
 | 
			
		||||
                if (this.percent < 70) {
 | 
			
		||||
                    return "border-green-dark";
 | 
			
		||||
                } else if (this.percent >= 70 && this.percent < 90) {
 | 
			
		||||
                    return "border-yellow-dark";
 | 
			
		||||
                } else {
 | 
			
		||||
                    return "border-red-dark";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
</script>
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Icon from "../../../core/Icon";
 | 
			
		||||
 | 
			
		||||
export default Vue.component('file-context-menu', {
 | 
			
		||||
    components: {
 | 
			
		||||
        Icon,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `
 | 
			
		||||
        <div class="context-menu">
 | 
			
		||||
            <div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="edit3"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action"><span>Rename</span></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="corner-up-left" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action"><span class="text-left">Move</span></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="copy" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action">Copy</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="file-plus" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action">New File</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="folder-plus" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action">New Folder</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <div class="context-row">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="download" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action">Download</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="context-row danger">
 | 
			
		||||
                    <div class="icon">
 | 
			
		||||
                        <icon name="delete" class="h-4"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="action">Delete</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    `,
 | 
			
		||||
})
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="context-menu">
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <edit3-icon/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action"><span>Rename</span></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <corner-up-left-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action"><span class="text-left">Move</span></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <copy-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action">Copy</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <file-plus-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action">New File</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <folder-plus-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action">New Folder</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="context-row">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <download-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action">Download</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="context-row danger">
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                    <delete-icon class="h-4"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="action">Delete</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { CopyIcon, CornerUpLeftIcon, DeleteIcon, DownloadIcon, Edit3Icon, FilePlusIcon, FolderPlusIcon } from 'vue-feather-icons';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'file-manager-context-menu',
 | 
			
		||||
        components: {
 | 
			
		||||
            CopyIcon, CornerUpLeftIcon, DeleteIcon, DownloadIcon, Edit3Icon, FilePlusIcon, FolderPlusIcon,
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
</script>
 | 
			
		||||
@ -1,102 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="row" :class="{ clickable: canEdit(file), 'active-selection': contextMenuVisible }" v-on:contextmenu="showContextMenu">
 | 
			
		||||
            <div class="flex-none icon">
 | 
			
		||||
                <file-text-icon v-if="!file.symlink"/>
 | 
			
		||||
                <link2-icon v-else/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex-1">{{file.name}}</div>
 | 
			
		||||
            <div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div>
 | 
			
		||||
            <div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div>
 | 
			
		||||
            <div class="flex-none w-1/6"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <file-manager-context-menu class="context-menu" v-show="contextMenuVisible" ref="contextMenu"/>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import * as Helpers from '../../../../helpers/index';
 | 
			
		||||
    import { FileTextIcon, Link2Icon } from 'vue-feather-icons';
 | 
			
		||||
    import FileManagerContextMenu from './FileManagerContextMenu';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'file-manager-file-row',
 | 
			
		||||
        components: {
 | 
			
		||||
            FileManagerContextMenu,
 | 
			
		||||
            FileTextIcon, Link2Icon
 | 
			
		||||
        },
 | 
			
		||||
        props: {
 | 
			
		||||
            file: {type: Object, required: true},
 | 
			
		||||
            editable: {type: Array, required: true}
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        data: function () {
 | 
			
		||||
            return {
 | 
			
		||||
                contextMenuVisible: false,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        mounted: function () {
 | 
			
		||||
            document.addEventListener('click', this._clickListener);
 | 
			
		||||
 | 
			
		||||
            // If the parent component emits the collapse menu event check if the unique ID of the component
 | 
			
		||||
            // is this one. If not, collapse the menu (we right clicked into another element).
 | 
			
		||||
            this.$parent.$on('collapse-menus', (uid) => {
 | 
			
		||||
                if (this._uid !== uid) {
 | 
			
		||||
                    this.contextMenuVisible = false;
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        beforeDestroy: function () {
 | 
			
		||||
            document.removeEventListener('click', this._clickListener, false);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        methods: {
 | 
			
		||||
            /**
 | 
			
		||||
             * Handle a right-click action on a file manager row.
 | 
			
		||||
             *
 | 
			
		||||
             * @param {MouseEvent} e
 | 
			
		||||
             */
 | 
			
		||||
            showContextMenu: function (e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.$parent.$emit('collapse-menus', this._uid);
 | 
			
		||||
 | 
			
		||||
                this.contextMenuVisible = true;
 | 
			
		||||
 | 
			
		||||
                const menuWidth = this.$refs.contextMenu.$el.offsetWidth;
 | 
			
		||||
                const positionElement = e.clientX - Math.round(menuWidth / 2);
 | 
			
		||||
 | 
			
		||||
                this.$refs.contextMenu.$el.style = `left: ${positionElement}; top: ${e.clientY}`;
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Determine if a file can be edited on the Panel.
 | 
			
		||||
             *
 | 
			
		||||
             * @param {Object} file
 | 
			
		||||
             * @return {Boolean}
 | 
			
		||||
             */
 | 
			
		||||
            canEdit: function (file) {
 | 
			
		||||
                return this.editable.indexOf(file.mime) >= 0;
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Handle a click anywhere in the document and hide the context menu if that click is not
 | 
			
		||||
             * a right click and isn't occurring somewhere in the currently visible context menu.
 | 
			
		||||
             *
 | 
			
		||||
             * @param {MouseEvent} e
 | 
			
		||||
             * @private
 | 
			
		||||
             */
 | 
			
		||||
            _clickListener: function (e) {
 | 
			
		||||
                if (e.button !== 2 && this.contextMenuVisible) {
 | 
			
		||||
                    if (e.target !== this.$refs.contextMenu.$el && !this.$refs.contextMenu.$el.contains(e.target)) {
 | 
			
		||||
                        this.contextMenuVisible = false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            readableSize: Helpers.readableSize,
 | 
			
		||||
            formatDate: Helpers.formatDate,
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
</script>
 | 
			
		||||
@ -1,47 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <router-link class="row clickable"
 | 
			
		||||
                     :to="{ name: 'server-files', params: { path: getClickablePath(directory.name).replace(/^\//, '') }}"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="flex-none icon">
 | 
			
		||||
                <folder-icon/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex-1">{{directory.name}}</div>
 | 
			
		||||
            <div class="flex-1 text-right text-grey-dark"></div>
 | 
			
		||||
            <div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div>
 | 
			
		||||
            <div class="flex-none w-1/6"></div>
 | 
			
		||||
        </router-link>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
    import { FolderIcon } from 'vue-feather-icons';
 | 
			
		||||
    import { formatDate } from '../../../../helpers/index';
 | 
			
		||||
 | 
			
		||||
    export default {
 | 
			
		||||
        name: 'file-manager-folder-row',
 | 
			
		||||
        components: { FolderIcon },
 | 
			
		||||
        props: {
 | 
			
		||||
            directory: {type: Object, required: true},
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        data: function () {
 | 
			
		||||
            return {
 | 
			
		||||
                currentDirectory: this.$route.params.path || '/',
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        methods: {
 | 
			
		||||
            /**
 | 
			
		||||
             * Return a formatted directory path that is used to switch to a nested directory.
 | 
			
		||||
             *
 | 
			
		||||
             * @return {String}
 | 
			
		||||
             */
 | 
			
		||||
            getClickablePath (directory) {
 | 
			
		||||
                return `${this.currentDirectory.replace(/\/$/, '')}/${directory}`;
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            formatDate: formatDate,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import Icon from "../../../core/Icon";
 | 
			
		||||
import {Vue as VueType} from "vue/types/vue";
 | 
			
		||||
import { readableSize, formatDate } from '../../../../helpers'
 | 
			
		||||
import FileContextMenu from "./FileContextMenu";
 | 
			
		||||
 | 
			
		||||
export default Vue.component('file-row', {
 | 
			
		||||
    components: {
 | 
			
		||||
        Icon,
 | 
			
		||||
        FileContextMenu,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        file: {type: Object, required: true},
 | 
			
		||||
        editable: {type: Array, required: true}
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data: function () {
 | 
			
		||||
        return {
 | 
			
		||||
            contextMenuVisible: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted: function () {
 | 
			
		||||
        document.addEventListener('click', this._clickListener);
 | 
			
		||||
 | 
			
		||||
        // If the parent component emits the collapse menu event check if the unique ID of the component
 | 
			
		||||
        // is this one. If not, collapse the menu (we right clicked into another element).
 | 
			
		||||
        this.$parent.$on('collapse-menus', (uid: string) => {
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            if (this._uid !== uid) {
 | 
			
		||||
                this.contextMenuVisible = false;
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    beforeDestroy: function () {
 | 
			
		||||
        document.removeEventListener('click', this._clickListener, false);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle a right-click action on a file manager row.
 | 
			
		||||
         */
 | 
			
		||||
        showContextMenu: function (e: MouseEvent) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
 | 
			
		||||
            // @ts-ignore
 | 
			
		||||
            this.$parent.$emit('collapse-menus', this._uid);
 | 
			
		||||
 | 
			
		||||
            this.contextMenuVisible = true;
 | 
			
		||||
 | 
			
		||||
            const menuWidth = (this.$refs.contextMenu as VueType).$el.offsetWidth;
 | 
			
		||||
            const positionElement = e.clientX - Math.round(menuWidth / 2);
 | 
			
		||||
 | 
			
		||||
            (this.$refs.contextMenu as VueType).$el.setAttribute('style', `left: ${positionElement}; top: ${e.clientY}`);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Determine if a file can be edited on the Panel.
 | 
			
		||||
         */
 | 
			
		||||
        canEdit: function (file: any): boolean {
 | 
			
		||||
            return this.editable.indexOf(file.mime) >= 0;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle a click anywhere in the document and hide the context menu if that click is not
 | 
			
		||||
         * a right click and isn't occurring somewhere in the currently visible context menu.
 | 
			
		||||
         *
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        _clickListener: function (e: MouseEvent) {
 | 
			
		||||
            if (e.button !== 2 && this.contextMenuVisible) {
 | 
			
		||||
                if (e.target !== (this.$refs.contextMenu as VueType).$el && !(this.$refs.contextMenu as VueType).$el.contains(e.target as Node)) {
 | 
			
		||||
                    this.contextMenuVisible = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        readableSize: readableSize,
 | 
			
		||||
        formatDate: formatDate,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `
 | 
			
		||||
        <div>
 | 
			
		||||
            <div class="row" :class="{ clickable: canEdit(file), 'active-selection': contextMenuVisible }" v-on:contextmenu="showContextMenu">
 | 
			
		||||
                <div class="flex-none icon">
 | 
			
		||||
                    <icon name="file-text" v-if="!file.symlink"/>
 | 
			
		||||
                    <icon name="link2" v-else/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="flex-1">{{file.name}}</div>
 | 
			
		||||
                <div class="flex-1 text-right text-grey-dark">{{readableSize(file.size)}}</div>
 | 
			
		||||
                <div class="flex-1 text-right text-grey-dark">{{formatDate(file.modified)}}</div>
 | 
			
		||||
                <div class="flex-none w-1/6"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <file-context-menu class="context-menu" v-show="contextMenuVisible" ref="contextMenu"/>
 | 
			
		||||
        </div>
 | 
			
		||||
    `
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import { formatDate } from "../../../../helpers";
 | 
			
		||||
import Icon from "../../../core/Icon";
 | 
			
		||||
 | 
			
		||||
export default Vue.component('folder-row', {
 | 
			
		||||
    components: {
 | 
			
		||||
        Icon,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    props: {
 | 
			
		||||
        directory: {type: Object, required: true},
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    data: function () {
 | 
			
		||||
        return {
 | 
			
		||||
            currentDirectory: this.$route.params.path || '/',
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    methods: {
 | 
			
		||||
        /**
 | 
			
		||||
         * Return a formatted directory path that is used to switch to a nested directory.
 | 
			
		||||
         */
 | 
			
		||||
        getClickablePath (directory: string): string {
 | 
			
		||||
            return `${this.currentDirectory.replace(/\/$/, '')}/${directory}`;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        formatDate: formatDate,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    template: `
 | 
			
		||||
        <div>
 | 
			
		||||
            <router-link class="row clickable"
 | 
			
		||||
                         :to="{ name: 'server-files', params: { path: getClickablePath(directory.name).replace(/^\\//, '') }}"
 | 
			
		||||
            >
 | 
			
		||||
                <div class="flex-none icon">
 | 
			
		||||
                    <folder-icon/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="flex-1">{{directory.name}}</div>
 | 
			
		||||
                <div class="flex-1 text-right text-grey-dark"></div>
 | 
			
		||||
                <div class="flex-1 text-right text-grey-dark">{{formatDate(directory.modified)}}</div>
 | 
			
		||||
                <div class="flex-none w-1/6"></div>
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </div>
 | 
			
		||||
    `
 | 
			
		||||
});
 | 
			
		||||
@ -50,3 +50,19 @@
 | 
			
		||||
.modal-leave-active .modal-container {
 | 
			
		||||
  animation: opacity 250ms linear;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * name="slide-fade" mode="out-in"
 | 
			
		||||
 */
 | 
			
		||||
.slide-fade-enter-active {
 | 
			
		||||
  transition: all 250ms ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.slide-fade-leave-active {
 | 
			
		||||
  transition: all 250ms cubic-bezier(1.0, 0.5, 0.8, 1.0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.slide-fade-enter, .slide-fade-leave-to {
 | 
			
		||||
  transform: translateX(10px);
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user