Merge pull request #2268 from serghey-rodin/feature/r-1.0.0.7

Release UI 1.0.0.7
This commit is contained in:
Anton Reutov 2022-10-11 10:59:25 +03:00 committed by GitHub
commit 94d60267a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 483 additions and 284 deletions

View file

@ -4396,9 +4396,9 @@
} }
}, },
"dayjs": { "dayjs": {
"version": "1.10.7", "version": "1.11.4",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz",
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==" "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g=="
}, },
"debug": { "debug": {
"version": "3.1.0", "version": "3.1.0",

View file

@ -13,7 +13,7 @@
"axios": "^0.21.4", "axios": "^0.21.4",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"classname": "0.0.0", "classname": "0.0.0",
"dayjs": "^1.10.7", "dayjs": "^1.11.4",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"perfect-scrollbar": "^1.5.3", "perfect-scrollbar": "^1.5.3",

View file

@ -1,49 +1,49 @@
import axios from "axios"; import axios from 'axios'
import { getAuthToken } from "src/utils/token"; import { getAuthToken } from 'src/utils/token'
const BASE_URL = window.location.origin; const BASE_URL = window.location.origin
const webApiUri = '/api/v1/list/db/index.php'; const webApiUri = '/api/v1/list/db/index.php'
const addDbApiUri = '/api/v1/add/db/index.php'; const addDbApiUri = '/api/v1/add/db/index.php'
const optionalDbInfoUri = '/api/v1/add/db/index.php'; const optionalDbInfoUri = '/api/v1/add/db/index.php'
const dbInfoUri = '/api/v1/edit/db/index.php'; const dbInfoUri = '/api/v1/edit/db/index.php'
const updateDatabaseUri = '/api/v1/edit/db/index.php'; const updateDatabaseUri = '/api/v1/edit/db/index.php'
export const getDatabaseList = () => { export const getDatabaseList = () => {
return axios.get(BASE_URL + webApiUri); return axios.get(BASE_URL + webApiUri)
} }
export const bulkAction = (action, domainNameSystems) => { export const bulkAction = (action, domainNameSystems) => {
const formData = new FormData(); const formData = new FormData()
formData.append("action", action); formData.append('action', action)
formData.append("token", getAuthToken()); formData.append('token', getAuthToken())
domainNameSystems.forEach(domainNameSystem => { domainNameSystems.forEach((domainNameSystem) => {
formData.append("database[]", domainNameSystem); formData.append('database[]', domainNameSystem)
}); })
return axios.post(BASE_URL + '/api/v1/bulk/db/', formData); return axios.post(BASE_URL + '/api/v1/bulk/db/', formData)
}; }
export const handleAction = uri => { export const handleAction = (uri) => {
return axios.get(BASE_URL + uri, { return axios.get(BASE_URL + uri, {
params: { params: {
token: getAuthToken() token: getAuthToken(),
} },
}); })
} }
export const getDbOptionalInfo = () => { export const getDbOptionalInfo = () => {
return axios.get(BASE_URL + optionalDbInfoUri); return axios.get(BASE_URL + optionalDbInfoUri)
} }
export const addDatabase = data => { export const addDatabase = (data) => {
let formDataObject = new FormData(); let formDataObject = new FormData()
for (let key in data) { for (let key in data) {
formDataObject.append(key, data[key]); formDataObject.append(key, data[key])
} }
return axios.post(BASE_URL + addDbApiUri, formDataObject); return axios.post(BASE_URL + addDbApiUri, formDataObject)
} }
export const dbCharsets = [ export const dbCharsets = [
@ -69,6 +69,7 @@ export const dbCharsets = [
'latin5', 'latin5',
'armscii8', 'armscii8',
'utf8', 'utf8',
'utf8mb4',
'ucs2', 'ucs2',
'cp866', 'cp866',
'keybcs2', 'keybcs2',
@ -82,29 +83,29 @@ export const dbCharsets = [
'binary', 'binary',
'geostd8', 'geostd8',
'cp932', 'cp932',
'eucjpms' 'eucjpms',
]; ]
export const getDatabaseInfo = database => { export const getDatabaseInfo = (database) => {
return axios.get(BASE_URL + dbInfoUri, { return axios.get(BASE_URL + dbInfoUri, {
params: { params: {
database, database,
token: getAuthToken() token: getAuthToken(),
} },
}); })
} }
export const updateDatabase = (data, database) => { export const updateDatabase = (data, database) => {
let formDataObject = new FormData(); let formDataObject = new FormData()
for (let key in data) { for (let key in data) {
formDataObject.append(key, data[key]); formDataObject.append(key, data[key])
} }
return axios.post(BASE_URL + updateDatabaseUri, formDataObject, { return axios.post(BASE_URL + updateDatabaseUri, formDataObject, {
params: { params: {
database, database,
token: getAuthToken() token: getAuthToken(),
} },
}); })
} }

View file

@ -34,10 +34,10 @@ const Hotkeys = props => {
</li> </li>
<li> <li>
<span className="name">n</span> <span className="name">n</span>
<span className="description">{i18n['New Fille']}</span> <span className="description">{i18n['New File']}</span>
</li> </li>
<li> <li>
<span className="name">F7</span> <span className="name">F6</span>
<span className="description">{i18n['New Folder']}</span> <span className="description">{i18n['New Folder']}</span>
</li> </li>
<li> <li>
@ -56,10 +56,6 @@ const Hotkeys = props => {
<span className="name">F5</span> <span className="name">F5</span>
<span className="description">{i18n['Copy']}</span> <span className="description">{i18n['Copy']}</span>
</li> </li>
<li>
<span className="name">F5</span>
<span className="description">{i18n['Copy']}</span>
</li>
<li> <li>
<span className="name">F8 / Del</span> <span className="name">F8 / Del</span>
<span className="description">{i18n['Delete']}</span> <span className="description">{i18n['Delete']}</span>
@ -107,7 +103,7 @@ const Hotkeys = props => {
<span className="description">{i18n['Open File / Enter Directory']}</span> <span className="description">{i18n['Open File / Enter Directory']}</span>
</li> </li>
<li> <li>
<span className="name">F4</span> <span className="name">F3</span>
<span className="description">{i18n['Edit File']}</span> <span className="description">{i18n['Edit File']}</span>
</li> </li>
<li> <li>

View file

@ -6,6 +6,17 @@ import Row from '../Row/Row';
import '../List.scss'; import '../List.scss';
class DirectoryList extends Component { class DirectoryList extends Component {
constructor(props) {
super(props);
this.state = {
orderType: "descending",
sortingType: "Type",
itemsSelected: [],
listingItems: [],
cursor: 0
};
}
static propTypes = { static propTypes = {
changePathAfterToggle: PropTypes.func, changePathAfterToggle: PropTypes.func,
openCertainDirectory: PropTypes.func, openCertainDirectory: PropTypes.func,
@ -27,13 +38,6 @@ class DirectoryList extends Component {
data: PropTypes.array data: PropTypes.array
} }
state = {
orderType: "descending",
sortingType: "Type",
itemsSelected: [],
cursor: 0
};
UNSAFE_componentWillMount = () => { UNSAFE_componentWillMount = () => {
if (localStorage.getItem(`${this.props.list}Sorting`) && localStorage.getItem(`${this.props.list}Order`)) { if (localStorage.getItem(`${this.props.list}Sorting`) && localStorage.getItem(`${this.props.list}Order`)) {
this.setState({ sortingType: localStorage.getItem(`${this.props.list}Sorting`), orderType: localStorage.getItem(`${this.props.list}Order`) }); this.setState({ sortingType: localStorage.getItem(`${this.props.list}Sorting`), orderType: localStorage.getItem(`${this.props.list}Order`) });
@ -117,20 +121,21 @@ class DirectoryList extends Component {
} }
handleLiSelection = (e) => { handleLiSelection = (e) => {
const { data, isActive, modalVisible, changePath, path } = this.props; const { isActive, modalVisible, changePath, path } = this.props;
const { cursor } = this.state; const { cursor } = this.state;
const { listing } = this.getDataBySortingType()
if (!isActive || modalVisible) { if (!isActive || modalVisible) {
return; return;
} }
if (e.keyCode === 40) { if (e.keyCode === 40) {
if (cursor === data.listing.length - 1) { if (cursor === listing.length - 1) {
return; return;
} }
if (e.shiftKey) { if (e.shiftKey) {
let name = data.listing[cursor].name; let name = listing[cursor].name;
this.addToSelection(name); this.addToSelection(name);
} }
@ -145,7 +150,7 @@ class DirectoryList extends Component {
} }
if (e.shiftKey) { if (e.shiftKey) {
let name = data.listing[cursor].name; let name = listing[cursor - 1].name;
this.addToSelection(name); this.addToSelection(name);
} }
@ -160,9 +165,15 @@ class DirectoryList extends Component {
} }
passData = () => { passData = () => {
const { data, passData } = this.props; const { passData: passDataToParent } = this.props;
const { name, permissions, type } = data.listing[this.state.cursor]; const { firstItem, listing } = this.getDataBySortingType()
passData(this.state.cursor, name, permissions, type); if (this.state.cursor === 0) {
const { name, permissions, type } = firstItem;
passDataToParent(this.state.cursor, name, permissions, type);
} else {
const { name, permissions, type } = listing[this.state.cursor - 1];
passDataToParent(this.state.cursor, name, permissions, type);
}
} }
openDirectory = (name) => { openDirectory = (name) => {
@ -231,50 +242,74 @@ class DirectoryList extends Component {
sortData = (a, b) => { sortData = (a, b) => {
switch (this.state.sortingType) { switch (this.state.sortingType) {
case "Type": return this.sortByType(a, b); case "Type": return this.sortByType(a, b);
case "Size": if (a.type !== "d" && b.type !== "d") { return this.sortBySize(a, b) }; break; case "Size": return this.sortBySize(a, b);
case "Date": return this.sortByDate(a, b); case "Date": return this.sortByDate(a, b);
case "Name": return this.sortByName(a, b); case "Name": return this.sortByName(a, b);
default: return this.sortByType(a, b); default: return this.sortByType(a, b);
} }
} }
getDataBySortingType = () => {
let firstItem, listing = [];
this.props.data.listing.forEach(item => {
if (item.name === '' && item.type === 'd') {
firstItem = item
} else {
listing.push(item)
}
})
if (this.state.sortingType !== 'Type') {
listing = [
...listing.filter(item => item.type === 'd').sort((a, b) => this.sortByName(a, b)),
...listing.filter(item => item.type === 'f').sort((a, b) => this.sortData(a, b))
]
} else {
listing = listing.sort((a, b) => this.sortData(a, b))
}
return { firstItem, listing }
}
rows = () => { rows = () => {
const { isActive, modalVisible, path, download } = this.props; const { isActive, modalVisible, path, download } = this.props;
const { cursor } = this.state; const { cursor } = this.state;
const data = { ...this.props.data }; const { listing, firstItem } = this.getDataBySortingType()
if (data.listing.length !== 0) { if (listing.length || firstItem) {
let sortedData = data.listing.sort((a, b) => this.sortData(a, b));
return ( return (
sortedData.map((item, key) => <>
(item.name !== "" && sortedData.length !== 0) ? <Row
(<Row key={key} selectOnClick={(cursor, name, permissions, type) => {
selectOnClick={(cursor, name, permissions, type) => { this.setState({ cursor });
this.setState({ cursor }); this.props.passData(cursor, name, permissions, type);
this.props.passData(cursor, name, permissions, type); }}
}} openDirectory={this.moveBack}
selectMultiple={() => this.addToSelection(item.name)} modalVisible={modalVisible}
selected={this.isSelected(item.name)} activeRow={0 === cursor}
openDirectory={this.openDirectory} isActiveList={isActive}
modalVisible={modalVisible} cursor={0}
activeRow={key === cursor} data={firstItem}
isActiveList={isActive} path={path} />
download={download} {
cursor={key} listing.map((item, key) => (
data={item} <Row
path={path} />) : key={key + 1}
(<Row key={key} selectOnClick={(cursor, name, permissions, type) => {
selectOnClick={(cursor, name, permissions, type) => { this.setState({ cursor });
this.setState({ cursor }); this.props.passData(cursor, name, permissions, type);
this.props.passData(cursor, name, permissions, type); }}
}} selectMultiple={() => this.addToSelection(item.name)}
openDirectory={this.moveBack} selected={this.isSelected(item.name)}
modalVisible={modalVisible} openDirectory={this.openDirectory}
activeRow={key === cursor} modalVisible={modalVisible}
isActiveList={isActive} activeRow={key + 1 === cursor}
cursor={key} isActiveList={isActive}
data={item} download={download}
path={path} />)) cursor={key + 1}
data={item}
path={path} />
))
}
</>
); );
} }
} }

View file

@ -5,6 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faJs, faCss3, faPhp, faHtml5, faSass } from '@fortawesome/free-brands-svg-icons'; import { faJs, faCss3, faPhp, faHtml5, faSass } from '@fortawesome/free-brands-svg-icons';
import './Row.scss'; import './Row.scss';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import dayjs from 'dayjs'
class Row extends Component { class Row extends Component {
static propTypes = { static propTypes = {
@ -127,6 +128,13 @@ class Row extends Component {
return (<span className="date">{getMonth} {getDay}</span>); return (<span className="date">{getMonth} {getDay}</span>);
} }
timeFormatter = (date = new Date(), time) => {
const year = dayjs(date).year()
const currentYear = dayjs().year()
if (year === currentYear) return time
return year
}
glyph = () => { glyph = () => {
const { data: { type, name } } = this.props; const { data: { type, name } } = this.props;
@ -182,7 +190,7 @@ class Row extends Component {
<span className="fOwner">{owner}</span> <span className="fOwner">{owner}</span>
<span className="fSize">{this.sizeFormatter(size)}</span> <span className="fSize">{this.sizeFormatter(size)}</span>
<span className="fDate">{this.dateFormatter(date)}</span> <span className="fDate">{this.dateFormatter(date)}</span>
<span className="fTime">{time}</span> <span className="fTime">{this.timeFormatter(date, time)}</span>
</li> </li>
); );
} }

View file

@ -87,11 +87,11 @@
margin-left: 5px; margin-left: 5px;
line-height: 34px; line-height: 34px;
font-size: 15px; font-size: 15px;
white-space: nowrap; white-space: nowrap;
transition: all ease-out .3s; transition: all ease-out 0.3s;
&:hover { &:hover {
transition: all ease-out .2s; transition: all ease-out 0.2s;
background: rgb(201, 199, 199); background: rgb(201, 199, 199);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
@ -100,7 +100,7 @@
} }
.list .list-container ul li.active .fName .name:hover { .list .list-container ul li.active .fName .name:hover {
background: #F0B607; background: #f0b607;
color: black; color: black;
} }
@ -160,20 +160,22 @@ li.inactive {
background: rgb(220, 220, 220); background: rgb(220, 220, 220);
} }
@media (max-width: 1400px){ @media (max-width: 1320px) {
.fPermissions, .fOwner { .list .list-container ul li {
display: none; .fName {
} width: 210px;
} }
.fSize {
@media (max-width: 1100px){ width: 75px;
.fDate { }
display: none; .fDate {
} width: 40px;
} }
.fTime {
@media (max-width: 970px){ width: 50px;
.fTime { }
display: none; .fPermissions {
margin: 0;
}
} }
} }

View file

@ -15,7 +15,6 @@ const Notifications = () => {
useEffect(() => { useEffect(() => {
if (!notifications.length) { if (!notifications.length) {
console.log(notifications);
fetchData(); fetchData();
} }
}, [notifications]); }, [notifications]);

View file

@ -151,7 +151,7 @@
box-shadow: rgba(200, 200, 200, 0.5) 0px 5px 3px 0px; box-shadow: rgba(200, 200, 200, 0.5) 0px 5px 3px 0px;
} }
@media (max-width: 1350px) { @media (max-width: 1390px) {
.toolbar { .toolbar {
padding: 3px 10% 1px; padding: 3px 10% 1px;
} }

View file

@ -1,148 +1,185 @@
import React, { createRef, useEffect } from 'react'; import React, { useCallback, useEffect, useRef } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import './Menu.scss'; import './Menu.scss'
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux'
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom'
const Menu = (props) => { const Menu = (props) => {
const { i18n } = useSelector(state => state.session); const { i18n } = useSelector((state) => state.session)
const inputFile = createRef(); const inputFile = useRef()
const handleUserKeyDown = useCallback((event) => hotKeys(event), [props])
useEffect(() => { useEffect(() => {
document.addEventListener("keydown", hotKeys); document.addEventListener('keydown', handleUserKeyDown)
return () => document.removeEventListener('keydown', handleUserKeyDown)
return () => document.removeEventListener("keydown", hotKeys); }, [handleUserKeyDown])
}, []);
const newFile = () => { const newFile = () => {
props.openModal("Add file"); props.openModal('Add file')
} }
const newDirectory = () => { const newDirectory = () => {
props.openModal("Add directory"); props.openModal('Add directory')
} }
const deleteFile = () => { const deleteFile = () => {
const { selection, openModal, cursor } = props; const { selection, openModal, cursor } = props
if (selection.length === 0) { if (selection.length === 0) {
if (cursor === 0) { if (cursor === 0) {
openModal("Nothing selected"); openModal('Nothing selected')
} else { } else {
openModal("Delete"); openModal('Delete')
} }
} else { } else {
openModal("Delete", selection.length); openModal('Delete', selection.length)
} }
} }
const rename = () => { const rename = () => {
console.log(props)
if (props.cursor === 0) { if (props.cursor === 0) {
props.openModal("Nothing selected"); props.openModal('Nothing selected')
} else { } else {
props.openModal("Rename"); props.openModal('Rename')
} }
} }
const permissions = () => { const permissions = () => {
if (props.cursor === 0) { if (props.cursor === 0) {
props.openModal("Nothing selected"); props.openModal('Nothing selected')
} else { } else {
props.openModal("Permissions"); props.openModal('Permissions')
} }
} }
const move = () => { const move = () => {
const { selection, openModal, cursor } = props; const { selection, openModal, cursor } = props
if (selection.length === 0) { if (selection.length === 0) {
if (cursor === 0) { if (cursor === 0) {
openModal("Nothing selected"); openModal('Nothing selected')
} else { } else {
openModal("Move"); openModal('Move')
} }
} else { } else {
openModal("Move", selection.length); openModal('Move', selection.length)
} }
} }
const archive = () => { const archive = () => {
const { selection, openModal, cursor } = props; const { selection, openModal, cursor } = props
if (selection.length === 0) { if (selection.length === 0) {
if (cursor === 0) { if (cursor === 0) {
openModal("Nothing selected"); openModal('Nothing selected')
} else { } else {
openModal("Archive"); openModal('Archive')
} }
} else { } else {
openModal("Archive", selection.length); openModal('Archive', selection.length)
} }
} }
const extract = () => { const extract = () => {
if (props.cursor === 0) { if (props.cursor === 0) {
props.openModal("Nothing selected"); props.openModal('Nothing selected')
} else { } else {
props.openModal("Extract"); props.openModal('Extract')
} }
} }
const copy = () => { const copy = () => {
const { selection, openModal, cursor } = props; const { selection, openModal, cursor } = props
if (selection.length === 0) { if (selection.length === 0) {
if (cursor === 0) { if (cursor === 0) {
openModal("Nothing selected"); openModal('Nothing selected')
} else { } else {
openModal("Copy"); openModal('Copy')
} }
} else { } else {
openModal("Copy", selection.length); openModal('Copy', selection.length)
} }
} }
const upload = (e) => { const upload = (e) => {
if (e.target.files.length === 0) { if (e.target.files.length === 0) {
return; return
} }
props.upload(e.target.files); props.upload(e.target.files)
} }
const download = () => { const download = () => {
if (props.cursor === 0) { if (props.cursor === 0) {
props.openModal("Nothing selected"); props.openModal('Nothing selected')
} else if (props.itemType === "d") { } else if (props.itemType === 'd') {
props.openModal("Nothing selected", null, true); props.openModal('Nothing selected', null, true)
} else { } else {
props.download(); props.download()
} }
} }
const hotKeys = (e) => { const hotKeys = (e) => {
let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus'); e.stopPropagation()
let isSearchInputFocused = document.querySelector('input:focus') || document.querySelector('textarea:focus')
if (props.modalVisible || isSearchInputFocused) return; if (props.modalVisible || isSearchInputFocused) return
if (e.shiftKey && e.keyCode === 118) {
if (e.shiftKey && e.keyCode === 117) { e.preventDefault()
rename(); rename()
return
} }
switch (e.keyCode) { switch (e.keyCode) {
case 46: return deleteFile(); // u
case 65: return archive(); case 85:
case 68: return download(); e.preventDefault();
case 77: return move(); return inputFile.current.click()
case 78: return newFile(); // n
case 85: return inputFile.click(); case 78:
case 113: return rename(); e.preventDefault()
case 115: return permissions(); return newFile()
case 116: return copy(); // F6
case 118: return newDirectory(); case 118:
case 119: return deleteFile(); e.preventDefault()
default: break; return newDirectory()
// d
case 68:
e.preventDefault()
return download()
// F2
case 113:
e.preventDefault()
return rename()
// m
case 77:
e.preventDefault()
return move()
// F4
case 115:
e.preventDefault()
return copy()
// a
case 65:
e.preventDefault()
return archive()
// F8
case 119:
e.preventDefault()
return deleteFile()
// Del
case 46:
e.preventDefault()
return deleteFile()
// F3
case 114:
e.preventDefault()
return permissions()
default:
break
} }
} }
let matchArchive = props.name.match(/.zip|.tgz|.tar.gz|.gzip|.tbz|.tar.bz|.gz|.zip|.tar|.rar/g); let matchArchive = props.name.match(/.zip|.tgz|.tar.gz|.gzip|.tbz|.tar.bz|.gz|.zip|.tar|.rar/g)
return ( return (
<div className="menu"> <div className="menu">
@ -153,30 +190,80 @@ const Menu = (props) => {
</div> </div>
<div className="btn-group" role="group" aria-label="First group"> <div className="btn-group" role="group" aria-label="First group">
<input type="file" className="upload" multiple onChange={upload} ref={inputFile} /> <input type="file" className="upload" multiple onChange={upload} ref={inputFile} />
<button type="button" className="btn btn-light" id="upload" onClick={() => inputFile.current.click()}>{i18n.UPLOAD}</button> <button type="button" className="btn btn-light" id="upload" onClick={() => inputFile.current.click()}>
<button type="button" className="btn btn-light big" onClick={newFile}>{i18n['NEW FILE']}</button> {i18n.UPLOAD}
<button type="button" className="btn btn-light small" onClick={newFile} title={i18n['NEW FILE']}><FontAwesomeIcon icon="file" className="icon file" /></button> </button>
<button type="button" className="btn btn-light big" onClick={newDirectory}>{i18n['NEW DIR']}</button> <button type="button" className="btn btn-light big" onClick={newFile}>
<button type="button" className="btn btn-light small" onClick={newDirectory} title={i18n['NEW DIR']}><FontAwesomeIcon icon="folder" className="icon folder-close" /></button> {i18n['NEW FILE']}
<button type="button" className="btn btn-light big" onClick={download}>{i18n.DOWNLOAD}</button> </button>
<button type="button" className="btn btn-light small" onClick={download} title={i18n.DOWNLOAD}><FontAwesomeIcon icon="download" className="icon download" /></button> <button type="button" className="btn btn-light small" onClick={newFile} title={i18n['NEW FILE']}>
<button type="button" className="btn btn-light big" onClick={rename}>{i18n.RENAME}</button> <FontAwesomeIcon icon="file" className="icon file" />
<button type="button" className="btn btn-light small" onClick={rename} title={i18n.RENAME}><FontAwesomeIcon icon="italic" className="icon italic" /></button> </button>
<button type="button" className="btn btn-light big" onClick={permissions}>{i18n.RIGHTS}</button> <button type="button" className="btn btn-light big" onClick={newDirectory}>
<button type="button" className="btn btn-light small" onClick={permissions} title={i18n.RIGHTS}><FontAwesomeIcon icon="user" className="icon user" /></button> {i18n['NEW DIR']}
<button type="button" className="btn btn-light big" onClick={copy}>{i18n.COPY}</button> </button>
<button type="button" className="btn btn-light small" onClick={copy} title={i18n.COPY}><FontAwesomeIcon icon="copy" className="icon copy" /></button> <button type="button" className="btn btn-light small" onClick={newDirectory} title={i18n['NEW DIR']}>
<button type="button" className="btn btn-light big" onClick={move}>{i18n.MOVE}</button> <FontAwesomeIcon icon="folder" className="icon folder-close" />
<button type="button" className="btn btn-light small" onClick={move} title={i18n.MOVE}><FontAwesomeIcon icon="paste" className="icon paste" /></button> </button>
{matchArchive ? null : <button type="button" className="btn btn-light big" onClick={archive}>{i18n.ARCHIVE}</button>} <button type="button" className="btn btn-light big" onClick={download}>
{matchArchive ? null : <button type="button" className="btn btn-light small" onClick={archive} title={i18n.ARCHIVE}><FontAwesomeIcon icon="book" className="icon book" /></button>} {i18n.DOWNLOAD}
{matchArchive ? <button type="button" className="btn btn-light big" onClick={extract}>{i18n.EXTRACT}</button> : null} </button>
{matchArchive ? <button type="button" className="btn btn-light small" onClick={extract} title={i18n.EXTRACT}><FontAwesomeIcon icon="box-open" className="icon open" /></button> : null} <button type="button" className="btn btn-light small" onClick={download} title={i18n.DOWNLOAD}>
<button type="button" className="btn btn-light big delete" onClick={deleteFile} >{i18n.DELETE}</button> <FontAwesomeIcon icon="download" className="icon download" />
<button type="button" className="btn btn-light small" onClick={deleteFile} title={i18n.DELETE}><FontAwesomeIcon icon="trash" className="icon trash" /></button> </button>
<button type="button" className="btn btn-light big" onClick={rename}>
{i18n.RENAME}
</button>
<button type="button" className="btn btn-light small" onClick={rename} title={i18n.RENAME}>
<FontAwesomeIcon icon="italic" className="icon italic" />
</button>
<button type="button" className="btn btn-light big" onClick={permissions}>
{i18n.RIGHTS}
</button>
<button type="button" className="btn btn-light small" onClick={permissions} title={i18n.RIGHTS}>
<FontAwesomeIcon icon="user" className="icon user" />
</button>
<button type="button" className="btn btn-light big" onClick={copy}>
{i18n.COPY}
</button>
<button type="button" className="btn btn-light small" onClick={copy} title={i18n.COPY}>
<FontAwesomeIcon icon="copy" className="icon copy" />
</button>
<button type="button" className="btn btn-light big" onClick={move}>
{i18n.MOVE}
</button>
<button type="button" className="btn btn-light small" onClick={move} title={i18n.MOVE}>
<FontAwesomeIcon icon="paste" className="icon paste" />
</button>
{matchArchive ? null : (
<button type="button" className="btn btn-light big" onClick={archive}>
{i18n.ARCHIVE}
</button>
)}
{matchArchive ? null : (
<button type="button" className="btn btn-light small" onClick={archive} title={i18n.ARCHIVE}>
<FontAwesomeIcon icon="book" className="icon book" />
</button>
)}
{matchArchive ? (
<button type="button" className="btn btn-light big" onClick={extract}>
{i18n.EXTRACT}
</button>
) : null}
{matchArchive ? (
<button type="button" className="btn btn-light small" onClick={extract} title={i18n.EXTRACT}>
<FontAwesomeIcon icon="box-open" className="icon open" />
</button>
) : null}
<button type="button" className="btn btn-light big delete" onClick={deleteFile}>
{i18n.DELETE}
</button>
<button type="button" className="btn btn-light small" onClick={deleteFile} title={i18n.DELETE}>
<FontAwesomeIcon icon="trash" className="icon trash" />
</button>
</div> </div>
</div> </div>
); )
} }
export default Menu; export default Menu

View file

@ -1,9 +1,27 @@
import React from 'react'; import React, { useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
const AddDirectory = (props) => { const AddDirectory = (props) => {
const [value, setValue] = useState(null)
const { i18n } = useSelector(state => state.session); const { i18n } = useSelector(state => state.session);
const [hasError, setHasError] = useState(value !== null && !value.length)
const onChange = (e) => {
setValue(e.target.value)
}
const save = () => {
if (!value) {
setHasError(true)
return;
}
props.save()
}
const cancel = () => {
props.close()
}
return ( return (
<div className="modal-content"> <div className="modal-content">
@ -11,11 +29,12 @@ const AddDirectory = (props) => {
<h3 className="modal-title directory" >{i18n['Create directory']}</h3> <h3 className="modal-title directory" >{i18n['Create directory']}</h3>
</div> </div>
<div className="modal-body"> <div className="modal-body">
<input type="text" ref={props.reference} autoFocus></input> <input type="text" onChange={onChange} ref={props.reference}></input>
{hasError && <small className='error'>{i18n['Directory name cannot be empty']}</small>}
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{i18n.Cancel}</button> <button type="button" className="btn btn-danger mr-auto" onClick={cancel}>{i18n.Cancel}</button>
<button type="button" className="btn btn-primary" onClick={props.save}>{i18n.Create}</button> <button type="button" className="btn btn-primary" onClick={save}>{i18n.Create}</button>
</div> </div>
</div> </div>
); );

View file

@ -1,8 +1,26 @@
import React from 'react'; import React, { useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
const AddFile = (props) => { const AddFile = (props) => {
const [value, setValue] = useState(null)
const { i18n } = useSelector(state => state.session); const { i18n } = useSelector(state => state.session);
const [hasError, setHasError] = useState(value !== null && !value.length)
const onChange = (e) => {
setValue(e.target.value)
}
const save = () => {
if (!value) {
setHasError(true)
return;
}
props.save()
}
const cancel = () => {
props.close()
}
return ( return (
<div className="modal-content"> <div className="modal-content">
@ -10,11 +28,12 @@ const AddFile = (props) => {
<h3 className="modal-title" >{i18n['Create file']}</h3> <h3 className="modal-title" >{i18n['Create file']}</h3>
</div> </div>
<div className="modal-body"> <div className="modal-body">
<input type="text" ref={props.reference}></input> <input type="text" onChange={onChange} ref={props.reference}></input>
{hasError && <small className='error'>{i18n['File name cannot be empty']}</small>}
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{i18n.Cancel}</button> <button type="button" className="btn btn-danger mr-auto" onClick={cancel}>{i18n.Cancel}</button>
<button type="button" className="btn btn-primary" onClick={props.save}>{i18n.Create}</button> <button type="button" className="btn btn-primary" onClick={save}>{i18n.Create}</button>
</div> </div>
</div> </div>
); );

View file

@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React, { useEffect } from 'react';
import AddFile from './AddFile'; import AddFile from './AddFile';
import AddDirectory from './AddDirectory'; import AddDirectory from './AddDirectory';
import Rename from './Rename'; import Rename from './Rename';
@ -9,86 +9,83 @@ import Move from './Move';
import Archive from './Archive'; import Archive from './Archive';
import Extract from './Extract'; import Extract from './Extract';
import Copy from './Copy'; import Copy from './Copy';
import './Modal.scss';
import Replace from './Replace'; import Replace from './Replace';
import './Modal.scss';
class Modal extends Component { const Modal = (props) => {
useEffect(() => {
window.addEventListener("click", closeOutside);
document.addEventListener("keydown", hotkeys);
componentDidMount = () => { return () => {
window.addEventListener("click", this.closeOutside); window.removeEventListener("click", closeOutside);
document.addEventListener("keydown", this.hotkeys); document.removeEventListener("keydown", hotkeys);
} }
}, [])
componentWillUnmount = () => { const hotkeys = (e) => {
window.removeEventListener("click", this.closeOutside);
document.removeEventListener("keydown", this.hotkeys);
}
hotkeys = (e) => {
if (e.keyCode === 27) { if (e.keyCode === 27) {
this.closeModal(); closeModal();
} else if (e.keyCode === 13) { } else if (e.keyCode === 13) {
this.saveAndClose(); saveAndClose();
} }
} }
saveAndClose = () => { const saveAndClose = () => {
this.props.onClick(); props.onClick();
this.props.onClose(); props.onClose();
} }
changePermissions = (permissions) => { const changePermissions = (permissions) => {
this.props.onChangePermissions(permissions); props.onChangePermissions(permissions);
} }
replace = (file) => { const replace = (file) => {
this.props.onClick(file); props.onClick(file);
this.props.onClose(); props.onClose();
} }
onChange = (e) => { const onChange = (e) => {
this.props.onChangeValue(e.target.value); props.onChangeValue(e.target.value);
} }
closeModal = () => { const closeModal = () => {
this.props.onClose(); props.onClose();
} }
closeOutside = (e) => { const closeOutside = (e) => {
let modal = document.getElementById("modal"); let modal = document.getElementById("modal");
if (e.target === modal) { if (e.target === modal) {
this.props.onClose(); props.onClose();
} }
} }
content = () => { const content = () => {
const { type, reference, fName, permissions, items, path, files, notAvailable } = this.props; const { type, reference, fName, permissions, items, path, files, notAvailable } = props;
switch (type) { switch (type) {
case 'Copy': return <Copy close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} items={items} path={path} />; case 'Copy': return <Copy close={closeModal} save={saveAndClose} reference={reference} onChange={onChange} name={type} fName={fName} items={items} path={path} />;
case 'Move': return <Move close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} items={items} path={path} />; case 'Move': return <Move close={closeModal} save={saveAndClose} reference={reference} onChange={onChange} name={type} fName={fName} items={items} path={path} />;
case 'Permissions': return <Permissions close={this.closeModal} save={this.saveAndClose} changePermissions={this.changePermissions} fName={fName} permissions={permissions} />; case 'Permissions': return <Permissions close={closeModal} save={saveAndClose} changePermissions={changePermissions} fName={fName} permissions={permissions} />;
case 'Extract': return <Extract close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} path={path} />; case 'Extract': return <Extract close={closeModal} save={saveAndClose} reference={reference} onChange={onChange} name={type} fName={fName} path={path} />;
case 'Archive': return <Archive close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} items={items} name={type} fName={fName} path={path} />; case 'Archive': return <Archive close={closeModal} save={saveAndClose} reference={reference} onChange={onChange} items={items} name={type} fName={fName} path={path} />;
case 'Rename': return <Rename close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} />; case 'Rename': return <Rename close={closeModal} save={saveAndClose} reference={reference} onChange={onChange} name={type} fName={fName} />;
case 'Add directory': return <AddDirectory close={this.closeModal} save={this.saveAndClose} reference={reference} />; case 'Add directory': return <AddDirectory close={closeModal} save={saveAndClose} reference={reference} />;
case 'Delete': return <Delete close={this.closeModal} save={this.saveAndClose} fName={fName} items={items} />; case 'Delete': return <Delete close={closeModal} save={saveAndClose} fName={fName} items={items} />;
case 'Add file': return <AddFile close={this.closeModal} save={this.saveAndClose} reference={reference} />; case 'Add file': return <AddFile close={closeModal} save={saveAndClose} reference={reference} />;
case 'Replace': return <Replace close={this.closeModal} replace={(files) => this.replace(files)} files={files} /> case 'Replace': return <Replace close={closeModal} replace={(files) => replace(files)} files={files} />
case 'Nothing selected': return <NothingSelected close={this.closeModal} notAvailable={notAvailable} />; case 'Nothing selected': return <NothingSelected close={closeModal} notAvailable={notAvailable} />;
default: default:
break; break;
} }
} }
render() { return (
return ( <div>
<div> <div className="modal" id="modal">
<div className="modal" id="modal"> {content()}
{this.content()}
</div>
</div> </div>
); </div>
} );
} }
export default Modal; export default Modal;

View file

@ -12,7 +12,7 @@
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: $black; background-color: $black;
background-color: rgba(0,0,0,0.4); background-color: rgba(0, 0, 0, 0.4);
.modal-content { .modal-content {
box-shadow: 0 2px 11px 0 rgba(0, 0, 0, 0.5); box-shadow: 0 2px 11px 0 rgba(0, 0, 0, 0.5);
@ -25,10 +25,23 @@
margin-top: 100px; margin-top: 100px;
.modal-body { .modal-body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow-wrap: anywhere; overflow-wrap: anywhere;
font-size: 17px; font-size: 17px;
margin-bottom: 40px; margin-bottom: 40px;
small {
margin-top: 5px;
font-size: 13px;
&.error {
color: red;
}
}
input { input {
background: #333; background: #333;
border: 1px solid #111; border: 1px solid #111;
@ -44,6 +57,7 @@
word-break: break-word; word-break: break-word;
h3 { h3 {
font-size: 20px;
color: $secondaryLight; color: $secondaryLight;
} }
@ -66,7 +80,7 @@
border-color: none; border-color: none;
background: none; background: none;
text-transform: uppercase; text-transform: uppercase;
font-size: 12px; font-size: 11px;
&:focus { &:focus {
outline: none; outline: none;
@ -98,7 +112,7 @@
border-color: $primaryLight; border-color: $primaryLight;
color: $hoverButtonText; color: $hoverButtonText;
} }
&:hover { &:hover {
background: $primaryActive; background: $primaryActive;
border-color: $primaryActive; border-color: $primaryActive;
@ -106,7 +120,7 @@
} }
} }
} }
.header .quot { .header .quot {
color: $secondaryActive; color: $secondaryActive;
} }
@ -144,17 +158,18 @@
right: 15px; right: 15px;
} }
} }
.permissions { .permissions {
height: auto; height: auto;
width: 30%; width: 30%;
margin-top: 0; margin-top: 0;
.error, &:focus { .error,
&:focus {
border: 1px solid red; border: 1px solid red;
} }
input[type="text"] { input[type='text'] {
size: 40px; size: 40px;
width: 60px; width: 60px;
margin: auto auto 10px auto; margin: auto auto 10px auto;
@ -189,4 +204,4 @@
} }
} }
} }
} }

View file

@ -21,7 +21,7 @@ const Preview = (props) => {
const hotkeys = e => { const hotkeys = e => {
if (e.keyCode === 121) { if (e.keyCode === 121) {
props.onClose(); onClose();
} }
} }

View file

@ -14,6 +14,16 @@
} }
} }
.servers-wrapper .l-col {
display: flex;
align-items: center;
height: 55px;
.checkbox {
margin: 0px;
}
}
.servers-wrapper .r-col { .servers-wrapper .r-col {
padding-left: 2rem; padding-left: 2rem;
@ -28,7 +38,7 @@
transform: translateX(-10px); transform: translateX(-10px);
} }
.stats div>span { .stats div > span {
width: 100%; width: 100%;
} }
} }

View file

@ -222,6 +222,17 @@ button {
} }
} }
} }
.content .servers-list .servers-wrapper .l-col {
display: flex;
align-items: center;
height: 55px;
margin-top: .75rem;
.checkbox {
margin: 0px;
}
}
} }
@media (max-width: 900px) { @media (max-width: 900px) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/images/favicon.ico"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Vesta</title><link href="/static/css/2.6c9f324a.chunk.css" rel="stylesheet"><link href="/static/css/main.55ab5a88.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script><script>!function(e){function r(r){for(var n,l,a=r[0],c=r[1],p=r[2],i=0,s=[];i<a.length;i++)l=a[i],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonpreact-control-panel"]=this["webpackJsonpreact-control-panel"]||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var p=0;p<a.length;p++)r(a[p]);var f=c;t()}([])</script><script src="/static/js/2.5dc90ea3.chunk.js"></script><script src="/static/js/main.57f35a42.chunk.js"></script></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/images/favicon.ico"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Vesta</title><link href="/static/css/2.6c9f324a.chunk.css" rel="stylesheet"><link href="/static/css/main.9f0c683e.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script><script>!function(e){function r(r){for(var n,l,a=r[0],c=r[1],p=r[2],i=0,s=[];i<a.length;i++)l=a[i],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var c=t[a];0!==o[c]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonpreact-control-panel"]=this["webpackJsonpreact-control-panel"]||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var p=0;p<a.length;p++)r(a[p]);var f=c;t()}([])</script><script src="/static/js/2.7cb4195c.chunk.js"></script><script src="/static/js/main.a9be926e.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long