Commit 735d39eb authored by wanli's avatar wanli

update tools/frontend

parent 90d35e4c
...@@ -11,16 +11,18 @@ ...@@ -11,16 +11,18 @@
"@antv/g2": "^3.2.7", "@antv/g2": "^3.2.7",
"ant-design-vue": "^1.7.5", "ant-design-vue": "^1.7.5",
"axios": "^0.18.0", "axios": "^0.18.0",
"bootstrap": "4.6.0",
"codemirror": "^5.59.2", "codemirror": "^5.59.2",
"core-js": "^3.9.0", "core-js": "^3.9.0",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.11",
"plyr": "^3.6.4", "npm-check-updates": "^11.7.1",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"plyr": "^3.6.4",
"vue": "^2.5.17", "vue": "^2.5.17",
"vue-codemirror": "^4.0.6",
"vue-i18n": "^8.1.0", "vue-i18n": "^8.1.0",
"vue-router": "^3.0.1", "vue-router": "^3.0.1",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"vue-codemirror": "^4.0.6",
"vuex-router-sync": "^5.0.0" "vuex-router-sync": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>EVM 应用商店</title> <title>EVM 应用商店</title>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.2/css/all.css">
<style> <style>
html, html,
body { body {
......
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
'menu.system.setting.config': '配置管理', 'menu.system.setting.config': '配置管理',
'menu.system.setting.dict': '字典管理', 'menu.system.setting.dict': '字典管理',
'menu.system.setting.area': '行政区划', 'menu.system.setting.area': '行政区划',
'menu.system.setting.file-manager': '文件管理',
'menu.system.role': '权限管理', 'menu.system.role': '权限管理',
'menu.system.admin': '系统管理员', 'menu.system.admin': '系统管理员',
//--- //---
......
/* eslint-disable max-len,prefer-destructuring,object-curly-newline */ /* eslint-disable max-len,prefer-destructuring,object-curly-newline */
import GET from '../../utils/get'; import GET from '@/utils/get';
import POST from '../../utils/post'; import POST from '@/utils/post';
export default { export default {
/** /**
......
/* eslint-disable object-curly-newline */ /* eslint-disable object-curly-newline */
import GET from '../../../utils/get'; import GET from '@/utils/get';
export default { export default {
/** /**
......
...@@ -2,23 +2,23 @@ import mutations from './mutations'; ...@@ -2,23 +2,23 @@ import mutations from './mutations';
import getters from './getters'; import getters from './getters';
// languages // languages
import ru from '../../../locales/lang/ru'; import ru from '@/locales/lang/ru';
import en from '../../../locales/lang/en'; import en from '@/locales/lang/en';
import ar from '../../../locales/lang/ar'; import ar from '@/locales/lang/ar';
import sr from '../../../locales/lang/sr'; import sr from '@/locales/lang/sr';
import cs from '../../../locales/lang/cs'; import cs from '@/locales/lang/cs';
import de from '../../../locales/lang/de'; import de from '@/locales/lang/de';
import es from '../../../locales/lang/es'; import es from '@/locales/lang/es';
import nl from '../../../locales/lang/nl'; import nl from '@/locales/lang/nl';
/* eslint camelcase: 0 */ /* eslint camelcase: 0 */
import zh_CN from '../../../locales/lang/zh_CN'; import fa from '@/locales/lang/fa';
import fa from '../../../locales/lang/fa'; import it from '@/locales/lang/it';
import it from '../../../locales/lang/it'; import tr from '@/locales/lang/tr';
import tr from '../../../locales/lang/tr'; import fr from '@/locales/lang/fr';
import fr from '../../../locales/lang/fr'; import pt_BR from '@/locales/lang/pt_BR';
import pt_BR from '../../../locales/lang/pt_BR'; import zh_CN from '@/locales/lang/zh_CN';
import zh_TW from '../../../locales/lang/zh_TW'; import zh_TW from '@/locales/lang/zh_TW';
import pl from '../../../locales/lang/pl'; import pl from '@/locales/lang/pl';
export default { export default {
namespaced: true, namespaced: true,
...@@ -57,12 +57,12 @@ export default { ...@@ -57,12 +57,12 @@ export default {
de: Object.freeze(de), de: Object.freeze(de),
es: Object.freeze(es), es: Object.freeze(es),
nl: Object.freeze(nl), nl: Object.freeze(nl),
'zh-CN': Object.freeze(zh_CN),
fa: Object.freeze(fa), fa: Object.freeze(fa),
it: Object.freeze(it), it: Object.freeze(it),
tr: Object.freeze(tr), tr: Object.freeze(tr),
fr: Object.freeze(fr), fr: Object.freeze(fr),
'pt-BR': Object.freeze(pt_BR), 'pt-BR': Object.freeze(pt_BR),
'zh-CN': Object.freeze(zh_CN),
'zh-TW': Object.freeze(zh_TW), 'zh-TW': Object.freeze(zh_TW),
pl: Object.freeze(pl), pl: Object.freeze(pl),
}, },
......
import GET from '../../../utils/get'; import GET from '@/utils/get';
export default { export default {
/** /**
......
...@@ -10,13 +10,13 @@ Vue.use(Vuex) ...@@ -10,13 +10,13 @@ Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
fm,
frontend: { frontend: {
namespaced: true, namespaced: true,
modules: { modules: {
openapi: frontendOpenapi openapi: frontendOpenapi
} }
}, },
fm: fm,
global: { global: {
namespaced: true, namespaced: true,
...global, ...global,
......
.breadcrumb {
display: flex;
flex-wrap: wrap;
padding: $breadcrumb-padding-y $breadcrumb-padding-x;
margin-bottom: $breadcrumb-margin-bottom;
@include font-size($breadcrumb-font-size);
list-style: none;
background-color: $breadcrumb-bg;
@include border-radius($breadcrumb-border-radius);
}
.breadcrumb-item {
// The separator between breadcrumbs (by default, a forward-slash: "/")
+ .breadcrumb-item {
padding-left: $breadcrumb-item-padding;
&::before {
float: left; // Suppress inline spacings and underlining of the separator
padding-right: $breadcrumb-item-padding;
color: $breadcrumb-divider-color;
content: escape-svg($breadcrumb-divider);
}
}
// IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
// without `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}
import axios from 'axios';
export default axios.create();
\ No newline at end of file
import axios from 'axios'; import request from './axios';
const HTTP = axios.create();
export default { export default {
/** /**
...@@ -7,7 +6,7 @@ export default { ...@@ -7,7 +6,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
initialize() { initialize() {
return HTTP.get('initialize'); return request.get('initialize');
}, },
/** /**
...@@ -17,7 +16,7 @@ export default { ...@@ -17,7 +16,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
tree(disk, path) { tree(disk, path) {
return HTTP.get('tree', { params: { disk, path } }); return request.get('tree', { params: { disk, path } });
}, },
/** /**
...@@ -26,7 +25,7 @@ export default { ...@@ -26,7 +25,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
selectDisk(disk) { selectDisk(disk) {
return HTTP.get('select-disk', { params: { disk } }); return request.get('select-disk', { params: { disk } });
}, },
/** /**
...@@ -36,14 +35,14 @@ export default { ...@@ -36,14 +35,14 @@ export default {
* @returns {*} * @returns {*}
*/ */
content(disk, path) { content(disk, path) {
return HTTP.get('content', { params: { disk, path } }); return request.get('content', { params: { disk, path } });
}, },
/** /**
* Item properties * Item properties
*/ */
/* properties(disk, path) { /* properties(disk, path) {
return HTTP.get('properties', { params: { disk, path } }); return request.get('properties', { params: { disk, path } });
}, */ }, */
/** /**
...@@ -53,7 +52,7 @@ export default { ...@@ -53,7 +52,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
url(disk, path) { url(disk, path) {
return HTTP.get('url', { params: { disk, path } }); return request.get('url', { params: { disk, path } });
}, },
/** /**
...@@ -63,7 +62,7 @@ export default { ...@@ -63,7 +62,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
getFile(disk, path) { getFile(disk, path) {
return HTTP.get('download', { params: { disk, path } }); return request.get('download', { params: { disk, path } });
}, },
/** /**
...@@ -73,7 +72,7 @@ export default { ...@@ -73,7 +72,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
getFileArrayBuffer(disk, path) { getFileArrayBuffer(disk, path) {
return HTTP.get('download', { return request.get('download', {
responseType: 'arraybuffer', responseType: 'arraybuffer',
params: { disk, path }, params: { disk, path },
}); });
...@@ -86,7 +85,7 @@ export default { ...@@ -86,7 +85,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
thumbnail(disk, path) { thumbnail(disk, path) {
return HTTP.get('thumbnails', { return request.get('thumbnails', {
responseType: 'arraybuffer', responseType: 'arraybuffer',
params: { disk, path }, params: { disk, path },
}); });
...@@ -99,7 +98,8 @@ export default { ...@@ -99,7 +98,8 @@ export default {
* @return {*} * @return {*}
*/ */
preview(disk, path) { preview(disk, path) {
return HTTP.get('preview', { window.console.log("###############===========>", disk)
return request.get('preview', {
responseType: 'arraybuffer', responseType: 'arraybuffer',
params: { disk, path }, params: { disk, path },
}); });
...@@ -112,7 +112,7 @@ export default { ...@@ -112,7 +112,7 @@ export default {
* @return {*} * @return {*}
*/ */
download(disk, path) { download(disk, path) {
return HTTP.get('download', { return request.get('download', {
responseType: 'arraybuffer', responseType: 'arraybuffer',
params: { disk, path }, params: { disk, path },
}); });
......
...@@ -28,7 +28,7 @@ export default { ...@@ -28,7 +28,7 @@ export default {
const date = new Date(timestamp * 1000); const date = new Date(timestamp * 1000);
return date.toLocaleString(this.$store.state.fm.settings.lang); return date.toLocaleString();
}, },
/** /**
......
import axios from 'axios'; import request from './axios';
const HTTP = axios.create();
export default { export default {
/** /**
...@@ -10,7 +9,7 @@ export default { ...@@ -10,7 +9,7 @@ export default {
* @returns {AxiosPromise<any>} * @returns {AxiosPromise<any>}
*/ */
createFile(disk, path, name) { createFile(disk, path, name) {
return HTTP.post('create-file', { disk, path, name }); return request.post('create-file', { disk, path, name });
}, },
/** /**
...@@ -19,7 +18,7 @@ export default { ...@@ -19,7 +18,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
updateFile(formData) { updateFile(formData) {
return HTTP.post('update-file', formData); return request.post('update-file', formData);
}, },
/** /**
...@@ -28,7 +27,7 @@ export default { ...@@ -28,7 +27,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
createDirectory(data) { createDirectory(data) {
return HTTP.post('create-directory', data); return request.post('create-directory', data);
}, },
/** /**
...@@ -38,7 +37,7 @@ export default { ...@@ -38,7 +37,7 @@ export default {
* @returns {AxiosPromise<any>} * @returns {AxiosPromise<any>}
*/ */
upload(data, config) { upload(data, config) {
return HTTP.post('upload', data, config); return request.post('upload', data, config);
}, },
/** /**
...@@ -47,7 +46,7 @@ export default { ...@@ -47,7 +46,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
delete(data) { delete(data) {
return HTTP.post('delete', data); return request.post('delete', data);
}, },
/** /**
...@@ -56,7 +55,7 @@ export default { ...@@ -56,7 +55,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
rename(data) { rename(data) {
return HTTP.post('rename', data); return request.post('rename', data);
}, },
/** /**
...@@ -65,7 +64,7 @@ export default { ...@@ -65,7 +64,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
paste(data) { paste(data) {
return HTTP.post('paste', data); return request.post('paste', data);
}, },
/** /**
...@@ -74,7 +73,7 @@ export default { ...@@ -74,7 +73,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
zip(data) { zip(data) {
return HTTP.post('zip', data); return request.post('zip', data);
}, },
/** /**
...@@ -83,6 +82,6 @@ export default { ...@@ -83,6 +82,6 @@ export default {
* @param data * @param data
*/ */
unzip(data) { unzip(data) {
return HTTP.post('unzip', data); return request.post('unzip', data);
}, },
}; };
/*
* @Author: your name
* @Date: 2021-06-18 10:43:12
* @LastEditTime: 2021-06-19 17:00:27
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \evm-store\tools\frontend\src\utils\translate.js
*/
export default { export default {
computed: { computed: {
/** /**
......
@import 'ant-design-vue/lib/style/themes/default.less'; @import 'ant-design-vue/lib/style/themes/default.less';
@import 'assets/utils.less'; @import 'styles/utils.less';
.iconGroup { .iconGroup {
i { i {
......
@import 'ant-design-vue/lib/style/themes/default.less'; @import 'ant-design-vue/lib/style/themes/default.less';
@import 'assets/utils.less'; @import 'styles/utils.less';
.activitiesList { .activitiesList {
padding: 0 24px 8px 24px; padding: 0 24px 8px 24px;
......
...@@ -40,8 +40,7 @@ ...@@ -40,8 +40,7 @@
/* eslint-disable import/no-duplicates, no-param-reassign */ /* eslint-disable import/no-duplicates, no-param-reassign */
import { mapState } from "vuex"; import { mapState } from "vuex";
// Axios // Axios
import axios from "axios"; import request from "@/utils/axios";
const HTTP = axios.create();
import EventBus from "@/utils/eventBus"; import EventBus from "@/utils/eventBus";
// Components // Components
import Navbar from "./components/blocks/Navbar.vue"; import Navbar from "./components/blocks/Navbar.vue";
...@@ -77,19 +76,11 @@ export default { ...@@ -77,19 +76,11 @@ export default {
settings: { settings: {
headers: { // axios headers headers: { // axios headers
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
Authorization: `Bearer ${window.localStorage.getItem("user-token")}`, // Authorization: `Bearer ${window.localStorage.getItem("user-token")}`,
}, },
baseUrl: "http://test.loc/file-manager/", // overwrite base url Axios baseUrl: "/file-manager/", // overwrite base url Axios
windowsConfig: 2, // overwrite config windowsConfig: 2, // overwrite config
lang: "zh_CN", // set language lang: "zh_CN", // set language
// translation: {
// // add new translation
// name: "zh_CN",
// content: {
// about: "Über",
// back: "Zurück",
// },
// },
}, },
}; };
}, },
...@@ -125,8 +116,8 @@ export default { ...@@ -125,8 +116,8 @@ export default {
EventBus.$off(["contextMenu", "addNotification"]); EventBus.$off(["contextMenu", "addNotification"]);
// eject interceptors // eject interceptors
HTTP.interceptors.request.eject(this.interceptorIndex.request); request.interceptors.request.eject(this.interceptorIndex.request);
HTTP.interceptors.response.eject(this.interceptorIndex.response); request.interceptors.response.eject(this.interceptorIndex.response);
}, },
computed: { computed: {
...mapState("fm", { ...mapState("fm", {
...@@ -141,7 +132,7 @@ export default { ...@@ -141,7 +132,7 @@ export default {
* Add axios request interceptor * Add axios request interceptor
*/ */
requestInterceptor() { requestInterceptor() {
this.interceptorIndex.request = HTTP.interceptors.request.use( this.interceptorIndex.request = request.interceptors.request.use(
(config) => { (config) => {
// overwrite base url and headers // overwrite base url and headers
config.baseURL = this.$store.getters["fm/settings/baseUrl"]; config.baseURL = this.$store.getters["fm/settings/baseUrl"];
...@@ -164,7 +155,7 @@ export default { ...@@ -164,7 +155,7 @@ export default {
* Add axios response interceptor * Add axios response interceptor
*/ */
responseInterceptor() { responseInterceptor() {
this.interceptorIndex.response = HTTP.interceptors.response.use( this.interceptorIndex.response = request.interceptors.response.use(
(response) => { (response) => {
// loading spinner - // loading spinner -
this.$store.commit("fm/messages/subtractLoading"); this.$store.commit("fm/messages/subtractLoading");
...@@ -258,7 +249,21 @@ export default { ...@@ -258,7 +249,21 @@ export default {
}; };
</script> </script>
<style lang="scss"> <style scope lang="scss">
// https://stackpath.bootstrapcdn.com/bootstrap/scss/mixins/_text-truncate.scss
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/utilities/sizing.scss";
@import "~bootstrap/scss/utilities/text.scss";
@import "~bootstrap/scss/bootstrap-grid";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/type";
@import "~plyr/src/sass/plyr.scss"; @import "~plyr/src/sass/plyr.scss";
.fm { .fm {
position: relative; position: relative;
......
...@@ -306,7 +306,6 @@ export default { ...@@ -306,7 +306,6 @@ export default {
<style lang="scss"> <style lang="scss">
.fm-navbar { .fm-navbar {
.btn-group { .btn-group {
margin-right: 0.4rem; margin-right: 0.4rem;
} }
......
<template> <template>
<div class="fm-breadcrumb"> <div class="fm-breadcrumb">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb" <ol
v-bind:class="[manager === activeManager ? 'active-manager' : 'bg-light']"> class="breadcrumb"
v-bind:class="[
manager === activeManager ? 'active-manager' : 'bg-light',
]"
>
<li class="breadcrumb-item" v-on:click="selectMainDirectory"> <li class="breadcrumb-item" v-on:click="selectMainDirectory">
<span class="badge badge-secondary"> <span class="badge badge-secondary">
<i class="far fa-hdd"/> <i class="far fa-hdd" />
</span> </span>
</li> </li>
<li class="breadcrumb-item text-truncate" <li
class="breadcrumb-item text-truncate"
v-for="(item, index) in breadcrumb" v-for="(item, index) in breadcrumb"
v-bind:key="index" v-bind:key="index"
v-bind:class="[breadcrumb.length === index + 1 ? 'active' : '']" v-bind:class="[breadcrumb.length === index + 1 ? 'active' : '']"
v-on:click="selectDirectory(index)"> v-on:click="selectDirectory(index)"
>
<span>{{ item }}</span> <span>{{ item }}</span>
</li> </li>
</ol> </ol>
...@@ -22,7 +28,7 @@ ...@@ -22,7 +28,7 @@
<script> <script>
export default { export default {
name: 'Breadcrumb', name: "Breadcrumb",
props: { props: {
manager: { type: String, required: true }, manager: { type: String, required: true },
}, },
...@@ -65,12 +71,15 @@ export default { ...@@ -65,12 +71,15 @@ export default {
* @param index * @param index
*/ */
selectDirectory(index) { selectDirectory(index) {
const path = this.breadcrumb.slice(0, index + 1).join('/'); const path = this.breadcrumb.slice(0, index + 1).join("/");
// only if this path not selected // only if this path not selected
if (path !== this.selectedDirectory) { if (path !== this.selectedDirectory) {
// load directory // load directory
this.$store.dispatch(`fm/${this.manager}/selectDirectory`, { path, history: true }); this.$store.dispatch(`fm/${this.manager}/selectDirectory`, {
path,
history: true,
});
} }
}, },
...@@ -79,20 +88,62 @@ export default { ...@@ -79,20 +88,62 @@ export default {
*/ */
selectMainDirectory() { selectMainDirectory() {
if (this.selectedDirectory) { if (this.selectedDirectory) {
this.$store.dispatch(`fm/${this.manager}/selectDirectory`, { path: null, history: true }); this.$store.dispatch(`fm/${this.manager}/selectDirectory`, {
path: null,
history: true,
});
} }
}, },
}, },
}; };
</script> </script>
<style lang="scss"> <style scoped lang="scss">
.fm-breadcrumb { @import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
.fm-breadcrumb {
.breadcrumb { .breadcrumb {
display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
padding: 0.2rem 0.3rem; padding: 0.2rem 0.3rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
@include font-size($breadcrumb-font-size);
list-style: none;
background-color: $breadcrumb-bg;
@include border-radius($breadcrumb-border-radius);
.breadcrumb-item {
// The separator between breadcrumbs (by default, a forward-slash: "/")
+ .breadcrumb-item {
padding-left: $breadcrumb-item-padding;
&::before {
float: left; // Suppress inline spacings and underlining of the separator
padding-right: $breadcrumb-item-padding;
color: $breadcrumb-divider-color;
content: escape-svg($breadcrumb-divider);
}
}
// IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built
// without `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}
&.active-manager { &.active-manager {
background-color: #c2e5eb; background-color: #c2e5eb;
...@@ -104,5 +155,5 @@ export default { ...@@ -104,5 +155,5 @@ export default {
color: #6d757d; color: #6d757d;
} }
} }
} }
</style> </style>
...@@ -6,37 +6,53 @@ ...@@ -6,37 +6,53 @@
<th class="w-65" v-on:click="sortBy('name')"> <th class="w-65" v-on:click="sortBy('name')">
{{ lang.manager.table.name }} {{ lang.manager.table.name }}
<template v-if="sortSettings.field === 'name'"> <template v-if="sortSettings.field === 'name'">
<i class="fas fa-sort-amount-down" <i
v-show="sortSettings.direction === 'down'"/> class="fas fa-sort-amount-down"
<i class="fas fa-sort-amount-up" v-show="sortSettings.direction === 'down'"
v-show="sortSettings.direction === 'up'"/> />
<i
class="fas fa-sort-amount-up"
v-show="sortSettings.direction === 'up'"
/>
</template> </template>
</th> </th>
<th class="w-10" v-on:click="sortBy('size')"> <th class="w-10" v-on:click="sortBy('size')">
{{ lang.manager.table.size }} {{ lang.manager.table.size }}
<template v-if="sortSettings.field === 'size'"> <template v-if="sortSettings.field === 'size'">
<i class="fas fa-sort-amount-down" <i
v-show="sortSettings.direction === 'down'"/> class="fas fa-sort-amount-down"
<i class="fas fa-sort-amount-up" v-show="sortSettings.direction === 'down'"
v-show="sortSettings.direction === 'up'"/> />
<i
class="fas fa-sort-amount-up"
v-show="sortSettings.direction === 'up'"
/>
</template> </template>
</th> </th>
<th class="w-10" v-on:click="sortBy('type')"> <th class="w-10" v-on:click="sortBy('type')">
{{ lang.manager.table.type }} {{ lang.manager.table.type }}
<template v-if="sortSettings.field === 'type'"> <template v-if="sortSettings.field === 'type'">
<i class="fas fa-sort-amount-down" <i
v-show="sortSettings.direction === 'down'"/> class="fas fa-sort-amount-down"
<i class="fas fa-sort-amount-up" v-show="sortSettings.direction === 'down'"
v-show="sortSettings.direction === 'up'"/> />
<i
class="fas fa-sort-amount-up"
v-show="sortSettings.direction === 'up'"
/>
</template> </template>
</th> </th>
<th class="w-auto" v-on:click="sortBy('date')"> <th class="w-auto" v-on:click="sortBy('date')">
{{ lang.manager.table.date }} {{ lang.manager.table.date }}
<template v-if="sortSettings.field === 'date'"> <template v-if="sortSettings.field === 'date'">
<i class="fas fa-sort-amount-down" <i
v-show="sortSettings.direction === 'down'"/> class="fas fa-sort-amount-down"
<i class="fas fa-sort-amount-up" v-show="sortSettings.direction === 'down'"
v-show="sortSettings.direction === 'up'"/> />
<i
class="fas fa-sort-amount-up"
v-show="sortSettings.direction === 'up'"
/>
</template> </template>
</th> </th>
</tr> </tr>
...@@ -44,34 +60,44 @@ ...@@ -44,34 +60,44 @@
<tbody> <tbody>
<tr v-if="!isRootPath"> <tr v-if="!isRootPath">
<td colspan="4" class="fm-content-item" v-on:click="levelUp"> <td colspan="4" class="fm-content-item" v-on:click="levelUp">
<i class="fas fa-level-up-alt"/> <i class="fas fa-level-up-alt" />
</td> </td>
</tr> </tr>
<tr v-for="(directory, index) in directories" <tr
v-for="(directory, index) in directories"
v-bind:key="`d-${index}`" v-bind:key="`d-${index}`"
v-bind:class="{'table-info': checkSelect('directories', directory.path)}" v-bind:class="{
'table-info': checkSelect('directories', directory.path),
}"
v-on:click="selectItem('directories', directory.path, $event)" v-on:click="selectItem('directories', directory.path, $event)"
v-on:contextmenu.prevent="contextMenu(directory, $event)"> v-on:contextmenu.prevent="contextMenu(directory, $event)"
<td class="fm-content-item unselectable" >
v-bind:class="(acl && directory.acl === 0) ? 'text-hidden' : ''" <td
v-on:dblclick="selectDirectory(directory.path)"> class="fm-content-item unselectable"
<i class="far fa-folder"/> {{ directory.basename }} v-bind:class="acl && directory.acl === 0 ? 'text-hidden' : ''"
v-on:dblclick="selectDirectory(directory.path)"
>
<i class="far fa-folder" /> {{ directory.basename }}
</td> </td>
<td/> <td />
<td>{{ lang.manager.table.folder }}</td> <td>{{ lang.manager.table.folder }}</td>
<td> <td>
{{ timestampToDate(directory.timestamp) }} {{ timestampToDate(directory.timestamp) }}
</td> </td>
</tr> </tr>
<tr v-for="(file, index) in files" <tr
v-for="(file, index) in files"
v-bind:key="`f-${index}`" v-bind:key="`f-${index}`"
v-bind:class="{'table-info': checkSelect('files', file.path)}" v-bind:class="{ 'table-info': checkSelect('files', file.path) }"
v-on:click="selectItem('files', file.path, $event)" v-on:click="selectItem('files', file.path, $event)"
v-on:dblclick="selectAction(file.path, file.extension)" v-on:dblclick="selectAction(file.path, file.extension)"
v-on:contextmenu.prevent="contextMenu(file, $event)"> v-on:contextmenu.prevent="contextMenu(file, $event)"
<td class="fm-content-item unselectable" >
v-bind:class="(acl && file.acl === 0) ? 'text-hidden' : ''"> <td
<i class="far" v-bind:class="extensionToIcon(file.extension)"/> class="fm-content-item unselectable"
v-bind:class="acl && file.acl === 0 ? 'text-hidden' : ''"
>
<i class="far" v-bind:class="extensionToIcon(file.extension)" />
{{ file.filename ? file.filename : file.basename }} {{ file.filename ? file.filename : file.basename }}
</td> </td>
<td>{{ bytesToHuman(file.size) }}</td> <td>{{ bytesToHuman(file.size) }}</td>
...@@ -88,12 +114,12 @@ ...@@ -88,12 +114,12 @@
</template> </template>
<script> <script>
import translate from '@/utils/translate'; import translate from "@/utils/translate";
import helper from '@/utils/helper'; import helper from "@/utils/helper";
import managerHelper from './mixins/manager'; import managerHelper from "./mixins/manager";
export default { export default {
name: 'table-view', name: "table-view",
mixins: [translate, helper, managerHelper], mixins: [translate, helper, managerHelper],
props: { props: {
manager: { type: String, required: true }, manager: { type: String, required: true },
...@@ -113,16 +139,18 @@ export default { ...@@ -113,16 +139,18 @@ export default {
* @param field * @param field
*/ */
sortBy(field) { sortBy(field) {
this.$store.dispatch(`fm/${this.manager}/sortBy`, { field, direction: null }); this.$store.dispatch(`fm/${this.manager}/sortBy`, {
field,
direction: null,
});
}, },
}, },
}; };
</script> </script>
<style lang="scss"> <style lang="scss">
.fm-table { .fm-table {
thead th {
thead th{
background: white; background: white;
position: sticky; position: sticky;
top: 0; top: 0;
...@@ -165,5 +193,5 @@ export default { ...@@ -165,5 +193,5 @@ export default {
.text-hidden { .text-hidden {
color: #cdcdcd; color: #cdcdcd;
} }
} }
</style> </style>
<template> <template>
<transition name="fm-modal"> <transition name="fm-modal">
<div class="fm-modal" ref="fmModal" v-on:click="hideModal"> <div class="fm-modal" ref="fmModal" v-on:click="hideModal">
<div class="modal-dialog" <div
class="modal-dialog"
role="document" role="document"
v-bind:class="modalSize" v-bind:class="modalSize"
v-on:click.stop> v-on:click.stop
<component v-bind:is="modalName"/> >
<component v-bind:is="modalName" />
</div> </div>
</div> </div>
</transition> </transition>
</template> </template>
<script> <script>
import NewFile from './views/NewFile.vue'; import NewFile from "./views/NewFile.vue";
import NewFolder from './views/NewFolder.vue'; import NewFolder from "./views/NewFolder.vue";
import Upload from './views/Upload.vue'; import Upload from "./views/Upload.vue";
import Delete from './views/Delete.vue'; import Delete from "./views/Delete.vue";
import Clipboard from './views/Clipboard.vue'; import Clipboard from "./views/Clipboard.vue";
import Status from './views/Status.vue'; import Status from "./views/Status.vue";
import Rename from './views/Rename.vue'; import Rename from "./views/Rename.vue";
import Properties from './views/Properties.vue'; import Properties from "./views/Properties.vue";
import Preview from './views/Preview.vue'; import Preview from "./views/Preview.vue";
import TextEdit from './views/TextEdit.vue'; import TextEdit from "./views/TextEdit.vue";
import AudioPlayer from './views/AudioPlayer.vue'; import AudioPlayer from "./views/AudioPlayer.vue";
import VideoPlayer from './views/VideoPlayer.vue'; import VideoPlayer from "./views/VideoPlayer.vue";
import Zip from './views/Zip.vue'; import Zip from "./views/Zip.vue";
import Unzip from './views/Unzip.vue'; import Unzip from "./views/Unzip.vue";
import About from './views/About.vue'; import About from "./views/About.vue";
export default { export default {
name: 'Modal', name: "Modal",
components: { components: {
NewFile, NewFile,
NewFolder, NewFolder,
...@@ -49,7 +51,10 @@ export default { ...@@ -49,7 +51,10 @@ export default {
}, },
mounted() { mounted() {
// set height // set height
this.$store.commit('fm/modal/setModalBlockHeight', this.$refs.fmModal.offsetHeight); this.$store.commit(
"fm/modal/setModalBlockHeight",
this.$refs.fmModal.offsetHeight
);
}, },
computed: { computed: {
/** /**
...@@ -66,9 +71,10 @@ export default { ...@@ -66,9 +71,10 @@ export default {
*/ */
modalSize() { modalSize() {
return { return {
'modal-xl': this.modalName === 'Preview' || this.modalName === 'TextEdit', "modal-xl":
'modal-lg': this.modalName === 'VideoPlayer', this.modalName === "Preview" || this.modalName === "TextEdit",
'modal-sm': false, "modal-lg": this.modalName === "VideoPlayer",
"modal-sm": false,
}; };
}, },
}, },
...@@ -77,15 +83,18 @@ export default { ...@@ -77,15 +83,18 @@ export default {
* Hide modal window * Hide modal window
*/ */
hideModal() { hideModal() {
this.$store.commit('fm/modal/clearModal'); this.$store.commit("fm/modal/clearModal");
}, },
}, },
}; };
</script> </script>
<style lang="scss"> <style scoped lang="scss">
// @import "~bootstrap/scss/mixins";
.fm-modal { // @import "~bootstrap/scss/functions";
// @import "~bootstrap/scss/variables";
// @import "~bootstrap/scss/modal";
.fm-modal {
position: absolute; position: absolute;
z-index: 9998; z-index: 9998;
top: 0; top: 0;
...@@ -94,20 +103,22 @@ export default { ...@@ -94,20 +103,22 @@ export default {
right: 0; right: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(0, 0, 0, .35); background-color: rgba(0, 0, 0, 0.35);
display: block; display: block;
transition: opacity .4s ease; transition: opacity 0.4s ease;
overflow: auto; overflow: auto;
.modal-xl { .modal-xl {
max-width: 96%; max-width: 96%;
} }
} }
.fm-modal-enter-active, .fm-modal-leave-active { .fm-modal-enter-active,
transition: opacity .5s; .fm-modal-leave-active {
} transition: opacity 0.5s;
.fm-modal-enter, .fm-modal-leave-to { }
.fm-modal-enter,
.fm-modal-leave-to {
opacity: 0; opacity: 0;
} }
</style> </style>
...@@ -2,56 +2,74 @@ ...@@ -2,56 +2,74 @@
<div class="modal-content fm-modal-preview"> <div class="modal-content fm-modal-preview">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title w-75 text-truncate"> <h5 class="modal-title w-75 text-truncate">
{{ showCropperModule ? lang.modal.cropper.title : lang.modal.preview.title }} {{
showCropperModule
? lang.modal.cropper.title
: lang.modal.preview.title
}}
<small class="text-muted pl-3">{{ selectedItem.basename }}</small> <small class="text-muted pl-3">{{ selectedItem.basename }}</small>
</h5> </h5>
<button type="button" class="close" aria-label="Close" v-on:click="hideModal"> <button
type="button"
class="close"
aria-label="Close"
v-on:click="hideModal"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body text-center"> <div class="modal-body text-center">
<template v-if="showCropperModule"> <template v-if="showCropperModule">
<cropper-module v-bind:imgSrc="imgSrc" <cropper-module
v-bind:imgSrc="imgSrc"
v-bind:maxHeight="maxHeight" v-bind:maxHeight="maxHeight"
v-on:closeCropper="closeCropper"/> v-on:closeCropper="closeCropper"
/>
</template> </template>
<transition v-else name="fade" mode="out-in"> <transition v-else name="fade" mode="out-in">
<i v-if="!imgSrc" class="fas fa-spinner fa-spin fa-5x p-5 text-muted"/> <i v-if="!imgSrc" class="fas fa-spinner fa-spin fa-5x p-5 text-muted" />
<img v-else <img
v-else
v-bind:src="imgSrc" v-bind:src="imgSrc"
v-bind:alt="selectedItem.basename" v-bind:alt="selectedItem.basename"
v-bind:style="{'max-height': maxHeight+'px'}"> v-bind:style="{ 'max-height': maxHeight + 'px' }"
/>
</transition> </transition>
</div> </div>
<div v-if="showFooter" class="d-flex justify-content-between"> <div v-if="showFooter" class="d-flex justify-content-between">
<span class="d-block"> <span class="d-block">
<button class="btn btn-info" <button
v-bind:title="lang.modal.cropper.title" v-on:click="showCropperModule = true"> class="btn btn-info"
<i class="fas fa-crop-alt"/> v-bind:title="lang.modal.cropper.title"
v-on:click="showCropperModule = true"
>
<i class="fas fa-crop-alt" />
</button> </button>
</span> </span>
<span class="d-block"> <span class="d-block">
<button class="btn btn-light" v-on:click="hideModal">{{ lang.btn.cancel }}</button> <button class="btn btn-light" v-on:click="hideModal">
{{ lang.btn.cancel }}
</button>
</span> </span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import CropperModule from '../additions/Cropper.vue'; import CropperModule from "../additions/Cropper.vue";
import modal from '../mixins/modal'; import modal from "../mixins/modal";
import translate from '@/utils/translate'; import translate from "@/utils/translate";
import helper from '@/utils/helper'; import helper from "@/utils/helper";
import GET from '@/utils/get'; import GET from "@/utils/get";
export default { export default {
name: 'Preview', name: "Preview",
mixins: [modal, translate, helper], mixins: [modal, translate, helper],
components: { CropperModule }, components: { CropperModule },
data() { data() {
return { return {
showCropperModule: false, showCropperModule: false,
imgSrc: '', imgSrc: "",
}; };
}, },
created() { created() {
...@@ -63,7 +81,7 @@ export default { ...@@ -63,7 +81,7 @@ export default {
* @return {*} * @return {*}
*/ */
auth() { auth() {
return this.$store.getters['fm/settings/authHeader']; return this.$store.getters["fm/settings/authHeader"];
}, },
/** /**
...@@ -71,7 +89,7 @@ export default { ...@@ -71,7 +89,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
selectedDisk() { selectedDisk() {
return this.$store.getters['fm/selectedDisk']; return this.$store.getters["fm/selectedDisk"];
}, },
/** /**
...@@ -79,7 +97,7 @@ export default { ...@@ -79,7 +97,7 @@ export default {
* @returns {*} * @returns {*}
*/ */
selectedItem() { selectedItem() {
return this.$store.getters['fm/selectedItems'][0]; return this.$store.getters["fm/selectedItems"][0];
}, },
/** /**
...@@ -87,7 +105,9 @@ export default { ...@@ -87,7 +105,9 @@ export default {
* @return boolean * @return boolean
*/ */
showFooter() { showFooter() {
return this.canCrop(this.selectedItem.extension) && !this.showCropperModule; return (
this.canCrop(this.selectedItem.extension) && !this.showCropperModule
);
}, },
/** /**
...@@ -109,7 +129,9 @@ export default { ...@@ -109,7 +129,9 @@ export default {
* @returns {boolean} * @returns {boolean}
*/ */
canCrop(extension) { canCrop(extension) {
return this.$store.state.fm.settings.cropExtensions.includes(extension.toLowerCase()); return this.$store.state.fm.settings.cropExtensions.includes(
extension.toLowerCase()
);
}, },
/** /**
...@@ -126,17 +148,22 @@ export default { ...@@ -126,17 +148,22 @@ export default {
loadImage() { loadImage() {
// if authorization required // if authorization required
if (this.auth) { if (this.auth) {
GET.preview( GET.preview(this.selectedDisk, this.selectedItem.path).then(
this.selectedDisk, (response) => {
this.selectedItem.path, const mimeType = response.headers["content-type"].toLowerCase();
).then((response) => { const imgBase64 = Buffer.from(response.data, "binary").toString(
const mimeType = response.headers['content-type'].toLowerCase(); "base64"
const imgBase64 = Buffer.from(response.data, 'binary').toString('base64'); );
this.imgSrc = `data:${mimeType};base64,${imgBase64}`; this.imgSrc = `data:${mimeType};base64,${imgBase64}`;
}); }
);
} else { } else {
this.imgSrc = `${this.$store.getters['fm/settings/baseUrl']}preview?disk=${this.selectedDisk}&path=${encodeURIComponent(this.selectedItem.path)}&v=${this.selectedItem.timestamp}`; this.imgSrc = `${
this.$store.getters["fm/settings/baseUrl"]
}preview?disk=${this.selectedDisk}&path=${encodeURIComponent(
this.selectedItem.path
)}&v=${this.selectedItem.timestamp}`;
} }
}, },
}, },
...@@ -144,8 +171,7 @@ export default { ...@@ -144,8 +171,7 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.fm-modal-preview { .fm-modal-preview {
.modal-body { .modal-body {
padding: 0; padding: 0;
...@@ -158,5 +184,5 @@ export default { ...@@ -158,5 +184,5 @@ export default {
padding: 1rem; padding: 1rem;
border-top: 1px solid #e9ecef; border-top: 1px solid #e9ecef;
} }
} }
</style> </style>
<template> <template>
<ul class="list-unstyled fm-tree-branch"> <ul class="list-unstyled fm-tree-branch">
<li v-for="(directory, index) in subDirectories" v-bind:key="index"> <li v-for="(directory, index) in subDirectories" v-bind:key="index">
<p class="unselectable" <p
v-bind:class="{'selected': isDirectorySelected(directory.path)}" class="unselectable"
v-on:click="selectDirectory(directory.path)"> v-bind:class="{ selected: isDirectorySelected(directory.path) }"
<i class="far" v-on:click="selectDirectory(directory.path)"
>
<i
class="far"
v-if="directory.props.hasSubdirectories" v-if="directory.props.hasSubdirectories"
v-on:click.stop="showSubdirectories( v-on:click.stop="
showSubdirectories(
directory.path, directory.path,
directory.props.showSubdirectories directory.props.showSubdirectories
)" )
v-bind:class="[arrowState(index) "
? 'fa-minus-square' v-bind:class="[
: 'fa-plus-square' arrowState(index) ? 'fa-minus-square' : 'fa-plus-square',
]"/> ]"
<i class="fas fa-minus fa-xs" v-else/> />
<i class="fas fa-minus fa-xs" v-else />
{{ directory.basename }} {{ directory.basename }}
</p> </p>
<transition name="fade-tree"> <transition name="fade-tree">
<branch v-show="arrowState(index)" <branch
v-show="arrowState(index)"
v-if="directory.props.hasSubdirectories" v-if="directory.props.hasSubdirectories"
v-bind:parent-id="directory.id"> v-bind:parent-id="directory.id"
>
</branch> </branch>
</transition> </transition>
</li> </li>
...@@ -30,7 +37,7 @@ ...@@ -30,7 +37,7 @@
<script> <script>
export default { export default {
name: 'Branch', name: "Branch",
props: { props: {
parentId: { type: Number, required: true }, parentId: { type: Number, required: true },
}, },
...@@ -40,7 +47,9 @@ export default { ...@@ -40,7 +47,9 @@ export default {
* @returns {*} * @returns {*}
*/ */
subDirectories() { subDirectories() {
return this.$store.getters['fm/tree/directories'].filter((item) => item.parentId === this.parentId); return this.$store.getters["fm/tree/directories"].filter(
(item) => item.parentId === this.parentId
);
}, },
}, },
methods: { methods: {
...@@ -70,10 +79,10 @@ export default { ...@@ -70,10 +79,10 @@ export default {
showSubdirectories(path, showState) { showSubdirectories(path, showState) {
if (showState) { if (showState) {
// hide // hide
this.$store.dispatch('fm/tree/hideSubdirectories', path); this.$store.dispatch("fm/tree/hideSubdirectories", path);
} else { } else {
// show // show
this.$store.dispatch('fm/tree/showSubdirectories', path); this.$store.dispatch("fm/tree/showSubdirectories", path);
} }
}, },
...@@ -84,7 +93,10 @@ export default { ...@@ -84,7 +93,10 @@ export default {
selectDirectory(path) { selectDirectory(path) {
// only if this path not selected // only if this path not selected
if (!this.isDirectorySelected(path)) { if (!this.isDirectorySelected(path)) {
this.$store.dispatch('fm/left/selectDirectory', { path, history: true }); this.$store.dispatch("fm/left/selectDirectory", {
path,
history: true,
});
} }
}, },
}, },
...@@ -92,12 +104,12 @@ export default { ...@@ -92,12 +104,12 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.fm-tree-branch { .fm-tree-branch {
display: table; display: table;
width: 100%; width: 100%;
padding-left: 1.4rem; padding-left: 1.4rem!important;
li > p{ li > p {
margin-bottom: 0.1rem; margin-bottom: 0.1rem;
padding: 0.4rem 0.4rem; padding: 0.4rem 0.4rem;
white-space: nowrap; white-space: nowrap;
...@@ -109,21 +121,23 @@ export default { ...@@ -109,21 +121,23 @@ export default {
} }
} }
.fas.fa-minus{ .fas.fa-minus {
padding-left: 0.1rem; padding-left: 0.1rem;
padding-right: 0.6rem; padding-right: 0.6rem;
} }
.far{ .far {
padding-right: 0.5rem; padding-right: 0.5rem;
} }
} }
.fade-tree-enter-active, .fade-tree-leave-active { .fade-tree-enter-active,
transition: all .3s ease; .fade-tree-leave-active {
} transition: all 0.3s ease;
.fade-tree-enter, .fade-tree-leave-to { }
.fade-tree-enter,
.fade-tree-leave-to {
transform: translateX(20px); transform: translateX(20px);
opacity: 0; opacity: 0;
} }
</style> </style>
<!--
* @Author: your name
* @Date: 2021-06-18 10:39:31
* @LastEditTime: 2021-06-19 16:35:22
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \evm-store\tools\frontend\src\views\FileManager\components\tree\FolderTree.vue
-->
<template> <template>
<div class="fm-tree"> <div class="fm-tree">
<div class="fm-tree-disk sticky-top"> <div class="fm-tree-disk sticky-top">
<i class="far fa-hdd"/> {{ selectedDisk }} <i class="far fa-hdd" /> {{ selectedDisk }}
</div> </div>
<branch v-bind:parent-id="0"/> <branch v-bind:parent-id="0" />
</div> </div>
</template> </template>
<script> <script>
import Branch from './Branch.vue'; import Branch from "./Branch.vue";
export default { export default {
name: 'FolderTree', name: "FolderTree",
components: { components: {
branch: Branch, branch: Branch,
}, },
...@@ -21,14 +29,14 @@ export default { ...@@ -21,14 +29,14 @@ export default {
* @returns {*} * @returns {*}
*/ */
selectedDisk() { selectedDisk() {
return this.$store.getters['fm/selectedDisk']; return this.$store.getters["fm/selectedDisk"];
}, },
}, },
}; };
</script> </script>
<style lang="scss"> <style lang="scss">
.fm-tree { .fm-tree {
overflow: auto; overflow: auto;
border-right: 1px solid #6d757d; border-right: 1px solid #6d757d;
...@@ -46,5 +54,5 @@ export default { ...@@ -46,5 +54,5 @@ export default {
padding-left: 0.2rem; padding-left: 0.2rem;
padding-right: 0.5rem; padding-right: 0.5rem;
} }
} }
</style> </style>
...@@ -365,5 +365,5 @@ export default { ...@@ -365,5 +365,5 @@ export default {
}; };
</script> </script>
<style lang="less"> <style lang="less">
@import url("assets/tableList.less"); @import url("styles/tableList.less");
</style> </style>
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment