Updates and more features

Former-commit-id: 9f1f09311813203910d5b323ba80712553ee2741 [formerly 0be00be1de305d786affc6bf0886aed9b20fbc51] [formerly 04597463117e94830b24b87faaaccf3d35284427 [formerly 3f2dc3f1c5]]
Former-commit-id: 8d26cc1d96faed73c7974ea7e5e78bf268af3ad9 [formerly a083ac8f68c90a636843c3565bd349657c0ec383]
Former-commit-id: ef10f3b3c388d65ceac40785b45dbac190a6cc99
This commit is contained in:
Henrique Dias 2017-06-30 16:04:01 +01:00
parent 3535f34c4f
commit 713e89eb68
26 changed files with 217 additions and 259 deletions

View File

@ -1,24 +1,3 @@
'use strict'
var tempID = '_fm_internal_temporary_id'
var templates = {}
var selectedItems = []
var overlay
var clickOverlay
// Sends a costum event to itself
Document.prototype.sendCostumEvent = function (text) {
this.dispatchEvent(new window.CustomEvent(text))
}
/* * * * * * * * * * * * * * * *
* *
* BUTTONS *
* *
* * * * * * * * * * * * * * * */
var buttons = {
previousState: {}
}
@ -70,77 +49,31 @@ buttons.setDone = function (name, success = true) {
return false
}
/* * * * * * * * * * * * * * * *
* *
* EVENTS *
* *
* * * * * * * * * * * * * * * */
function notImplemented (event) {
event.preventDefault()
clickOverlay.click()
let clone = document.importNode(templates.message.content, true)
clone.querySelector('h3').innerHTML = 'Not implemented'
clone.querySelector('p').innerHTML = "Sorry, but this feature wasn't implemented yet."
document.querySelector('body').appendChild(clone)
document.querySelector('.overlay').classList.add('active')
document.querySelector('.prompt').classList.add('active')
}
// Prevent Default event
var preventDefault = function (event) {
event.preventDefault()
}
function logoutEvent (event) {
let request = new window.XMLHttpRequest()
request.open('GET', window.location.pathname, true, 'data.username', 'password')
request.send()
request.onreadystatechange = function () {
if (request.readyState === 4) {
window.location = '/'
}
listing.addDoubleTapEvent = function () {
let items = document.getElementsByClassName('item'),
touches = {
id: '',
count: 0
}
Array.from(items).forEach(file => {
file.addEventListener('touchstart', event => {
if (touches.id != file.id) {
touches.id = file.id
touches.count = 1
setTimeout(() => {
touches.count = 0
}, 300)
return
}
touches.count++
if (touches.count > 1) {
window.location = file.dataset.url
}
})
})
}
/* * * * * * * * * * * * * * * *
* *
* BOOTSTRAP *
* *
* * * * * * * * * * * * * * * */
document.addEventListener('DOMContentLoaded', function (event) {
let dropdownButtons = document.querySelectorAll('.action[data-dropdown]')
Array.from(dropdownButtons).forEach(button => {
button.addEventListener('click', event => {
button.querySelector('ul').classList.toggle('active')
clickOverlay.classList.add('active')
clickOverlay.addEventListener('click', event => {
button.querySelector('ul').classList.remove('active')
clickOverlay.classList.remove('active')
})
})
})
let mainActions = document.getElementById('main-actions')
document.getElementById('more').addEventListener('click', event => {
event.preventDefault()
event.stopPropagation()
clickOverlay.classList.add('active')
mainActions.classList.add('active')
clickOverlay.addEventListener('click', event => {
mainActions.classList.remove('active')
clickOverlay.classList.remove('active')
})
})
return false
})

View File

@ -1,56 +0,0 @@
'use strict'
listing.redefineDownloadURLs = function () {
let files = ''
for (let i = 0; i < selectedItems.length; i++) {
let url = document.getElementById(selectedItems[i]).dataset.url
files += url.replace(window.location.pathname, '') + ','
}
files = files.substring(0, files.length - 1)
files = encodeURIComponent(files)
let links = document.querySelectorAll('#download ul a')
Array.from(links).forEach(link => {
link.href = '?download=' + link.dataset.format + '&files=' + files
})
}
listing.addDoubleTapEvent = function () {
let items = document.getElementsByClassName('item'),
touches = {
id: '',
count: 0
}
Array.from(items).forEach(file => {
file.addEventListener('touchstart', event => {
if (touches.id != file.id) {
touches.id = file.id
touches.count = 1
setTimeout(() => {
touches.count = 0
}, 300)
return
}
touches.count++
if (touches.count > 1) {
window.location = file.dataset.url
}
})
})
}
document.addEventListener('DOMContentLoaded', event => {
listing.addDoubleTapEvent()
if (user.AllowNew) {
buttons.new.addEventListener('click', listing.newFileButton)
}
})

View File

@ -36,7 +36,7 @@
background: #fff;
z-index: 9999;
transition: .1s ease opacity;
-webkit-transition: .5s ease opacity;
-webkit-transition: .1s ease opacity;
}
#loading.done {
@ -79,10 +79,10 @@
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}

View File

@ -1,5 +1,5 @@
<template>
<div id="app" :class="{ multiple: $store.state.multiple }">
<div id="app" :class="{ multiple }">
<header>
<div>
<img src="./assets/logo.svg" alt="File Manager">
@ -9,7 +9,7 @@
<rename-button v-show="showRenameButton()"></rename-button>
<move-button v-show="showMoveButton()"></move-button>
<delete-button v-show="showDeleteButton()"></delete-button>
<switch-button v-show="$store.state.req.kind !== 'editor'"></switch-button>
<switch-button v-show="req.kind !== 'editor'"></switch-button>
<download-button></download-button>
<upload-button v-show="showUpload()"></upload-button>
<info-button></info-button>
@ -17,11 +17,11 @@
</header>
<nav>
<a class="action" :href="$store.state.baseURL + '/'">
<a class="action" :href="baseURL + '/'">
<i class="material-icons">folder</i>
<span>My Files</span>
</a>
<div v-if="$store.state.user.allowNew">
<div v-if="user.allowNew">
<button @click="$store.commit('showNewDir', true)" aria-label="New directory" title="New directory" class="action">
<i class="material-icons">create_new_folder</i>
<span>New folder</span>
@ -38,18 +38,19 @@
</nav>
<main>
<editor v-if="$store.state.req.kind === 'editor'"></editor>
<listing v-if="$store.state.req.kind === 'listing'"></listing>
<preview v-if="$store.state.req.kind === 'preview'"></preview>
<editor v-if="req.kind === 'editor'"></editor>
<listing v-if="req.kind === 'listing'"></listing>
<preview v-if="req.kind === 'preview'"></preview>
</main>
<new-file-prompt v-if="$store.state.showNewFile" :class="{ active: $store.state.showNewFile }"></new-file-prompt>
<new-dir-prompt v-if="$store.state.showNewDir" :class="{ active: $store.state.showNewDir }"></new-dir-prompt>
<rename-prompt v-if="$store.state.showRename" :class="{ active: $store.state.showRename }"></rename-prompt>
<delete-prompt v-if="$store.state.showDelete" :class="{ active: $store.state.showDelete }"></delete-prompt>
<info-prompt v-if="$store.state.showInfo" :class="{ active: $store.state.showInfo }"></info-prompt>
<move-prompt v-if="$store.state.showMove" :class="{ active: $store.state.showMove }"></move-prompt>
<help v-show="$store.state.showHelp" :class="{ active: $store.state.showHelp }"></help>
<download-prompt v-if="showDownload" :class="{ active: showDownload }"></download-prompt>
<new-file-prompt v-if="showNewFile" :class="{ active: showNewFile }"></new-file-prompt>
<new-dir-prompt v-if="showNewDir" :class="{ active: showNewDir }"></new-dir-prompt>
<rename-prompt v-if="showRename" :class="{ active: showRename }"></rename-prompt>
<delete-prompt v-if="showDelete" :class="{ active: showDelete }"></delete-prompt>
<info-prompt v-if="showInfo" :class="{ active: showInfo }"></info-prompt>
<move-prompt v-if="showMove" :class="{ active: showMove }"></move-prompt>
<help v-show="showHelp" :class="{ active: showHelp }"></help>
<div v-show="$store.getters.showOverlay" @click="resetPrompts" class="overlay" :class="{ active: $store.getters.showOverlay }"></div>
<footer>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</footer>
@ -70,12 +71,14 @@ import RenameButton from './components/RenameButton'
import RenamePrompt from './components/RenamePrompt'
import UploadButton from './components/UploadButton'
import DownloadButton from './components/DownloadButton'
import DownloadPrompt from './components/DownloadPrompt'
import SwitchButton from './components/SwitchViewButton'
import MoveButton from './components/MoveButton'
import MovePrompt from './components/MovePrompt'
import NewFilePrompt from './components/NewFilePrompt'
import NewDirPrompt from './components/NewDirPrompt'
import css from './css.js'
import css from './utils/css'
import {mapGetters, mapState} from 'vuex'
function updateColumnSizes () {
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
@ -101,6 +104,7 @@ export default {
RenameButton,
RenamePrompt,
DownloadButton,
DownloadPrompt,
UploadButton,
SwitchButton,
MoveButton,
@ -108,9 +112,28 @@ export default {
NewFilePrompt,
NewDirPrompt
},
computed: {
...mapGetters(['selectedCount']),
...mapState([
'req',
'user',
'baseURL',
'multiple',
'showInfo',
'showHelp',
'showDelete',
'showRename',
'showMove',
'showNewFile',
'showNewDir',
'showDownload'
])
},
mounted: function () {
updateColumnSizes()
window.addEventListener('resize', updateColumnSizes)
document.title = this.req.data.name
window.history.replaceState({
url: window.location.pathname,
name: document.title
@ -148,13 +171,13 @@ export default {
this.$store.commit('resetPrompts')
// Unselect all files and folders.
if (this.$store.state.req.kind === 'listing') {
if (this.req.kind === 'listing') {
let items = document.getElementsByClassName('item')
Array.from(items).forEach(link => {
link.setAttribute('aria-selected', false)
})
this.$store.selected = []
this.$store.commit('resetSelected')
}
return
@ -186,7 +209,7 @@ export default {
case 's':
event.preventDefault()
if (this.$store.state.req.kind !== 'editor') {
if (this.req.kind !== 'editor') {
window.location = '?download=true'
return
}
@ -201,42 +224,42 @@ export default {
setTimeout(function () {
loading.parentNode.removeChild(loading)
}, 1000)
}, 200)
},
methods: {
showUpload: function () {
if (this.$store.state.req.kind === 'editor') return false
return this.$store.state.user.allowNew
if (this.req.kind === 'editor') return false
return this.user.allowNew
},
showDeleteButton: function () {
if (this.$store.state.req.kind === 'listing') {
if (this.$store.getters.selectedCount === 0) {
if (this.req.kind === 'listing') {
if (this.selectedCount === 0) {
return false
}
return this.$store.state.user.allowEdit
return this.user.allowEdit
}
return this.$store.state.user.allowEdit
return this.user.allowEdit
},
showRenameButton: function () {
if (this.$store.state.req.kind === 'listing') {
if (this.$store.getters.selectedCount === 1) {
return this.$store.state.user.allowEdit
if (this.req.kind === 'listing') {
if (this.selectedCount === 1) {
return this.user.allowEdit
}
return false
}
return this.$store.state.user.allowEdit
return this.user.allowEdit
},
showMoveButton: function () {
if (this.$store.state.req.kind !== 'listing') {
if (this.req.kind !== 'listing') {
return false
}
if (this.$store.getters.selectedCount > 0) {
return this.$store.state.user.allowEdit
if (this.selectedCount > 0) {
return this.user.allowEdit
}
return false

View File

@ -1,8 +1,8 @@
<template>
<button @click="show" aria-label="Delete" title="Delete" class="action">
<i class="material-icons">delete</i>
<span>Delete</span>
</button>
<button @click="show" aria-label="Delete" title="Delete" class="action">
<i class="material-icons">delete</i>
<span>Delete</span>
</button>
</template>
<script>

View File

@ -1,30 +1,33 @@
<template>
<div class="prompt">
<h3>Delete files</h3>
<p v-show="$store.state.req.kind !== 'listing'">Are you sure you want to delete this file/folder?</p>
<p v-show="$store.state.req.kind === 'listing'">Are you sure you want to delete {{ $store.getters.selectedCount }} file(s)?</p>
<p v-show="req.kind !== 'listing'">Are you sure you want to delete this file/folder?</p>
<p v-show="req.kind === 'listing'">Are you sure you want to delete {{ selectedCount }} file(s)?</p>
<div>
<button @click="submit" autofocus>Delete</button>
<button @click="cancel" class="cancel">Cancel</button>
<button @click="showDelete(false)" class="cancel">Cancel</button>
</div>
</div>
</template>
<script>
import webdav from '../webdav'
import page from '../page'
import {mapGetters, mapMutations, mapState} from 'vuex'
import webdav from '../utils/webdav'
import page from '../utils/page'
export default {
name: 'delete-prompt',
computed: {
...mapGetters(['selectedCount']),
...mapState(['req', 'selected'])
},
methods: {
cancel: function (event) {
this.$store.commit('showDelete', false)
},
...mapMutations(['showDelete']),
submit: function (event) {
this.$store.commit('showDelete', false)
this.showDelete(false)
// buttons.setLoading('delete')
if (this.$store.state.req.kind !== 'listing') {
if (this.req.kind !== 'listing') {
webdav.trash(window.location.pathname)
.then(() => {
// buttons.setDone('delete')
@ -38,13 +41,13 @@ export default {
return
}
if (this.$store.getters.selectedCount === 0) {
if (this.selectedCount === 0) {
// This shouldn't happen...
return
}
if (this.$store.getters.selectedCount === 1) {
webdav.trash(this.$store.state.req.data.items[this.$store.state.selected[0]].url)
if (this.selectedCount === 1) {
webdav.trash(this.req.data.items[this.selected[0]].url)
.then(() => {
// buttons.setDone('delete')
page.reload()
@ -60,8 +63,8 @@ export default {
// More than one item!
let promises = []
for (let index of this.$store.state.selected) {
promises.push(webdav.trash(this.$store.state.req.data.items[index].url))
for (let index of this.selected) {
promises.push(webdav.trash(this.req.data.items[index].url))
}
Promise.all(promises)

View File

@ -1,38 +1,28 @@
<template>
<button @click="download" aria-label="Download" title="Download" class="action">
<i class="material-icons">file_download</i>
<span>Download</span>
<span v-if="count() > 0" class="counter">{{ count() }}</span>
</button>
<button @click="download" aria-label="Download" title="Download" class="action">
<i class="material-icons">file_download</i>
<span>Download</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
var $ = window.info
import {mapGetters, mapState} from 'vuex'
export default {
name: 'download-button',
computed: {
...mapState(['req']),
...mapGetters(['selectedCount'])
},
methods: {
count: function () {
return this.$store.getters.selectedCount
},
download: function (event) {
if ($.req.kind !== 'listing') {
window.location = window.location.pathname + '?download=true'
if (this.req.kind !== 'listing') {
window.open(`${window.location.pathname}?download=true`)
return
}
/*
<ul class="dropdown" id="download-drop">
<a tabindex="0" aria-label="Download as Zip" data-format="zip" href="?download=zip"><li>zip</li></a>
<a tabindex="0" aria-label="Download as Tar" data-format="tar" href="?download=tar"><li>tar</li></a>
<a tabindex="0" aria-label="Download as TarGz" data-format="targz" href="?download=targz"><li>tar.gz</li></a>
<a tabindex="0" aria-label="Download as TarBz2" data-format="tarbz2" href="?download=tarbz2"><li>tar.bz2</li></a>
<a tabindex="0" aria-label="Download as TarXz" data-format="tarbz2" href="?download=tarxz"><li>tar.xz</li></a>
</ul>
*/
// document.getElementById('upload-input').click()
// TODO
alert('Not Implemented!')
this.$store.commit('showDownload', true)
}
}
}

View File

@ -0,0 +1,42 @@
<template>
<div class="prompt" id="download">
<h3>Download files</h3>
<p>Choose the format you want to download.</p>
<button @click="download('zip')" autofocus>zip</button>
<button @click="download('tar')" autofocus>tar</button>
<button @click="download('targz')" autofocus>tar.gz</button>
<button @click="download('tarbz2')" autofocus>tar.bz2</button>
<button @click="download('tarxz')" autofocus>tar.xz</button>
</div>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
export default {
name: 'download-prompt',
computed: {
...mapState(['selected', 'req']),
...mapGetters(['selectedCount'])
},
methods: {
download: function (format) {
let uri = `${window.location.pathname}?download=${format}`
if (this.selectedCount > 0) {
let files = ''
for (let i of this.selected) {
files += this.req.data.items[i].url.replace(window.location.pathname, '') + ','
}
files = files.substring(0, files.length - 1)
files = encodeURIComponent(files)
uri += `&files=${files}`
}
window.open(uri)
}
}
}
</script>

View File

@ -27,6 +27,7 @@
</template>
<script>
import {mapState} from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
@ -35,6 +36,7 @@ export default {
data: function () {
return window.info
},
computed: mapState(['req', 'selected']),
methods: {
humanSize: function () {
if (this.selected.length === 0 || this.req.kind !== 'listing') {

View File

@ -1,8 +1,8 @@
<template>
<div id="listing"
:class="req.data.display"
<div id="listing"
:class="req.data.display"
@drop="drop"
@dragenter="dragEnter"
@dragenter="dragEnter"
@dragend="dragEnd">
<div>
<div class="item header">
@ -35,7 +35,7 @@
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
@ -51,14 +51,14 @@
v-bind:name="item.name"
v-bind:isDir="item.isDir"
v-bind:url="item.url"
v-bind:modified="item.modified"
v-bind:modified="item.modified"
v-bind:type="item.type"
v-bind:size="item.size">
</item>
</div>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" value="Upload" multiple>
<div v-show="$store.state.multiple" :class="{ active: $store.state.multiple }" id="multiple-selection">
<p>Multiple selection enabled</p>
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" title="Clear" aria-label="Clear" class="action">
@ -70,8 +70,8 @@
<script>
import Item from './ListingItem'
import webdav from '../webdav.js'
import page from '../page.js'
import webdav from '../utils/webdav'
import page from '../utils/page'
export default {
name: 'listing',

View File

@ -1,5 +1,5 @@
<template>
<div class="item"
<div class="item"
draggable="true"
@dragstart="dragStart"
@dragover="dragOver"
@ -28,8 +28,8 @@
import { mapMutations, mapGetters } from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import webdav from '../webdav.js'
import page from '../page.js'
import webdav from '../utils/webdav.js'
import page from '../utils/page.js'
export default {
name: 'item',

View File

@ -17,8 +17,8 @@
</template>
<script>
import page from '../page'
import webdav from '../webdav'
import page from '../utils/page'
import webdav from '../utils/webdav'
var $ = window.info

View File

@ -11,8 +11,8 @@
</template>
<script>
import page from '../page'
import webdav from '../webdav'
import page from '../utils/page'
import webdav from '../utils/webdav'
export default {
name: 'new-dir-prompt',

View File

@ -11,8 +11,8 @@
</template>
<script>
import page from '../page'
import webdav from '../webdav'
import page from '../utils/page'
import webdav from '../utils/webdav'
export default {
name: 'new-file-prompt',

View File

@ -4,7 +4,7 @@
<button @click="back" class="action" aria-label="Close Preview" id="close">
<i class="material-icons">close</i>
</button>
<rename-button v-if="allowEdit()"></rename-button>
<delete-button v-if="allowEdit()"></delete-button>
<download-button></download-button>
@ -27,7 +27,7 @@
</template>
<script>
import page from '../page'
import page from '../utils/page'
import InfoButton from './InfoButton'
import DeleteButton from './DeleteButton'
import RenameButton from './RenameButton'

View File

@ -11,8 +11,8 @@
</template>
<script>
import page from '../page'
import webdav from '../webdav'
import page from '../utils/page'
import webdav from '../utils/webdav'
var $ = window.info

View File

@ -7,7 +7,7 @@
v-on:blur="focus = false"
v-on:keyup="keyup"
v-on:keyup.enter="submit"
aria-label="Write here to search"
aria-label="Write here to search"
:placeholder="placeholder()">
<div v-on:mouseover="hover = true">
<div>
@ -26,7 +26,7 @@
<script>
import { mapState } from 'vuex'
import page from '../page'
import page from '../utils/page'
export default {
name: 'search',

View File

@ -6,7 +6,7 @@
</template>
<script>
import page from '../page'
import page from '../utils/page'
export default {
name: 'switch-button',

View File

@ -68,6 +68,7 @@
background-color: #e9eaeb;
}
/* * * * * * * * * * * * * * * *
* PROMPT - MOVE *
* * * * * * * * * * * * * * * */
@ -113,6 +114,22 @@
color: white;
}
.prompt#download {
max-width: 15em;
}
.prompt#download button {
width: 100%;
display: block;
margin: 0 0 1em;
background-color: #ECEFF1;
color: #37474F;
}
.prompt#download button:last-of-type {
margin-bottom: 0;
}
.help {
max-width: 24em;
}

View File

@ -4,10 +4,10 @@ import store from './store/store'
Vue.config.productionTip = false
var $ = (window.info || window.alert('Something is wrong, please refresh!'))
// TODO: keep this here? Maybe on app.vue?
document.title = $.req.data.name
if (window.info === undefined || window.info === null) {
window.alert('Something is wrong, please refresh!')
window.location.reload()
}
/* eslint-disable no-new */
new Vue({

View File

@ -6,7 +6,8 @@ const getters = {
state.showRename ||
state.showMove ||
state.showNewFile ||
state.showNewDir
state.showNewDir ||
state.showDownload
},
selectedCount: state => state.selected.length
}

View File

@ -6,6 +6,7 @@ const mutations = {
showMove: (state, value) => (state.showMove = value),
showNewFile: (state, value) => (state.showNewFile = value),
showNewDir: (state, value) => (state.showNewDir = value),
showDownload: (state, value) => (state.showDownload = value),
resetPrompts: (state) => {
state.showHelp = false
state.showInfo = false
@ -14,6 +15,7 @@ const mutations = {
state.showMove = false
state.showNewFile = false
state.showNewDir = false
state.showDownload = false
},
multiple: (state, value) => (state.multiple = value),
addSelected: (state, value) => (state.selected.push(value)),
@ -23,7 +25,7 @@ const mutations = {
state.selected.splice(i, 1)
},
resetSelected: (state) => {
state.selected.length = 0
state.selected = []
},
updateRequest: (state, value) => {
state.req.kind = value.kind

View File

@ -19,7 +19,8 @@ const state = {
showRename: false,
showMove: false,
showNewFile: false,
showNewDir: false
showNewDir: false,
showDownload: false
}
export default new Vuex.Store({

View File

@ -1,4 +1,4 @@
import store from './store/store'
import store from '../store/store'
function open (url, history) {
// Reset info