diff --git a/resources/assets/scripts/components/forms/CSRF.ts b/resources/assets/scripts/components/forms/CSRF.ts
new file mode 100644
index 000000000..91c22d4b6
--- /dev/null
+++ b/resources/assets/scripts/components/forms/CSRF.ts
@@ -0,0 +1,11 @@
+import Vue from 'vue';
+
+export default Vue.component('csrf', {
+ data: function () {
+ return {
+ X_CSRF_TOKEN: window.X_CSRF_TOKEN,
+ };
+ },
+
+ template: ``,
+});
diff --git a/resources/assets/scripts/components/forms/CSRF.vue b/resources/assets/scripts/components/forms/CSRF.vue
deleted file mode 100644
index b421a91da..000000000
--- a/resources/assets/scripts/components/forms/CSRF.vue
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/PowerButtons.ts b/resources/assets/scripts/components/server/components/PowerButtons.ts
new file mode 100644
index 000000000..9e6987ee3
--- /dev/null
+++ b/resources/assets/scripts/components/server/components/PowerButtons.ts
@@ -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: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+});
diff --git a/resources/assets/scripts/components/server/components/PowerButtons.vue b/resources/assets/scripts/components/server/components/PowerButtons.vue
deleted file mode 100644
index c2f0afadd..000000000
--- a/resources/assets/scripts/components/server/components/PowerButtons.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/ProgressBar.ts b/resources/assets/scripts/components/server/components/ProgressBar.ts
new file mode 100644
index 000000000..9c09c5dab
--- /dev/null
+++ b/resources/assets/scripts/components/server/components/ProgressBar.ts
@@ -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: `
+
+ `,
+});
diff --git a/resources/assets/scripts/components/server/components/ProgressBar.vue b/resources/assets/scripts/components/server/components/ProgressBar.vue
deleted file mode 100644
index e4a123c55..000000000
--- a/resources/assets/scripts/components/server/components/ProgressBar.vue
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts b/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts
new file mode 100644
index 000000000..ad93f6228
--- /dev/null
+++ b/resources/assets/scripts/components/server/components/filemanager/FileContextMenu.ts
@@ -0,0 +1,61 @@
+import Vue from 'vue';
+import Icon from "../../../core/Icon";
+
+export default Vue.component('file-context-menu', {
+ components: {
+ Icon,
+ },
+
+ template: `
+
+ `,
+})
diff --git a/resources/assets/scripts/components/server/components/filemanager/FileManagerContextMenu.vue b/resources/assets/scripts/components/server/components/filemanager/FileManagerContextMenu.vue
deleted file mode 100644
index 74e768103..000000000
--- a/resources/assets/scripts/components/server/components/filemanager/FileManagerContextMenu.vue
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/filemanager/FileManagerFileRow.vue b/resources/assets/scripts/components/server/components/filemanager/FileManagerFileRow.vue
deleted file mode 100644
index 7aef9b80b..000000000
--- a/resources/assets/scripts/components/server/components/filemanager/FileManagerFileRow.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
{{file.name}}
-
{{readableSize(file.size)}}
-
{{formatDate(file.modified)}}
-
-
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/filemanager/FileManagerFolderRow.vue b/resources/assets/scripts/components/server/components/filemanager/FileManagerFolderRow.vue
deleted file mode 100644
index cf7b47197..000000000
--- a/resources/assets/scripts/components/server/components/filemanager/FileManagerFolderRow.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
- {{directory.name}}
-
- {{formatDate(directory.modified)}}
-
-
-
-
-
-
diff --git a/resources/assets/scripts/components/server/components/filemanager/FileRow.ts b/resources/assets/scripts/components/server/components/filemanager/FileRow.ts
new file mode 100644
index 000000000..3ff59365b
--- /dev/null
+++ b/resources/assets/scripts/components/server/components/filemanager/FileRow.ts
@@ -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: `
+
+
+
+
+
+
+
{{file.name}}
+
{{readableSize(file.size)}}
+
{{formatDate(file.modified)}}
+
+
+
+
+ `
+});
diff --git a/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts b/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts
new file mode 100644
index 000000000..d1d3e985b
--- /dev/null
+++ b/resources/assets/scripts/components/server/components/filemanager/FolderRow.ts
@@ -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: `
+
+
+
+
+
+ {{directory.name}}
+
+ {{formatDate(directory.modified)}}
+
+
+
+ `
+});
diff --git a/resources/assets/styles/components/animations.css b/resources/assets/styles/components/animations.css
index 8312978f7..836140a0e 100644
--- a/resources/assets/styles/components/animations.css
+++ b/resources/assets/styles/components/animations.css
@@ -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;
+}