perf: 增加内存缓存提升性能
This commit is contained in:
478
ui/script.js
478
ui/script.js
@@ -1,478 +0,0 @@
|
||||
afi = {
|
||||
auth: {},
|
||||
consts: {},
|
||||
params: {},
|
||||
elements: {},
|
||||
utils: {},
|
||||
rpc: {},
|
||||
uploader: {},
|
||||
interface: {},
|
||||
directcall: {},
|
||||
}
|
||||
|
||||
afi.auth = {
|
||||
isLoggedIn: false,
|
||||
|
||||
checkAuth: async function () {
|
||||
try {
|
||||
const res = await fetch(location.origin + afi.params.extraPath + '/auth', {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
this.isLoggedIn = (res.status === 200);
|
||||
return this.isLoggedIn;
|
||||
} catch (e) {
|
||||
this.isLoggedIn = false;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
logout: function () {
|
||||
fetch(location.origin + afi.params.extraPath + '/auth', {
|
||||
method: 'GET',
|
||||
headers: { 'Authorization': 'Basic ' + btoa('invalid:invalid') },
|
||||
credentials: 'include'
|
||||
});
|
||||
this.isLoggedIn = false;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
afi.consts = {
|
||||
leftPageWarn: "确认放弃正在进行的传输?",
|
||||
ensureDrop: () => !confirm('确认移动此对象?')
|
||||
}
|
||||
|
||||
afi.params = {
|
||||
extraPath: extraPath
|
||||
}
|
||||
|
||||
afi.elements = {
|
||||
itemlinks: Array.from(document.querySelectorAll('a.item-links')),
|
||||
table: document.getElementById("index-table"),
|
||||
badge: document.getElementById("badge"),
|
||||
uploader: document.getElementById("clickupload"),
|
||||
path: document.getElementById("path"),
|
||||
nav: document.getElementById("nav"),
|
||||
title: document.head.querySelector("title"),
|
||||
dropGrid: document.getElementById("drop-grid"),
|
||||
barDisplay: document.getElementById("bar-display"),
|
||||
barProc: document.getElementById("bar-proc"),
|
||||
helpPanel: document.getElementById('help-panel'),
|
||||
helpToggle: document.getElementById('help-toggle'),
|
||||
}
|
||||
|
||||
afi.pulsar = {
|
||||
pulseLeft: 0,
|
||||
pulseNotificator(text) {
|
||||
afi.elements.badge.innerText = text
|
||||
this.pulseEffect(afi.elements.badge)
|
||||
},
|
||||
pulseSuccess() {
|
||||
this.pulseNotificator("操作成功")
|
||||
},
|
||||
pulseFailure(error) {
|
||||
this.pulseNotificator("操作失败 " + error)
|
||||
},
|
||||
pulseEffect: function (element) {
|
||||
element.style.display = "block"
|
||||
afi.pulsar.pulseLeft += 1
|
||||
element.classList.remove('pulse');
|
||||
void element.offsetWidth;
|
||||
element.classList.add('pulse');
|
||||
setTimeout(function () {
|
||||
afi.pulsar.pulseLeft -= 1;
|
||||
if (afi.pulsar.pulseLeft > 0) {
|
||||
console.log("[AFI] Notification stack", afi.pulsar.pulseLeft, "left, skip clear")
|
||||
} else {
|
||||
console.log("[AFI] Notification stack", afi.pulsar.pulseLeft, "left, do clear")
|
||||
element.style.display = 'none';
|
||||
}
|
||||
}, 5000);
|
||||
},
|
||||
}
|
||||
|
||||
afi.utils = {
|
||||
isDupe: function (itemname) {
|
||||
if (afi.elements.itemlinks.find(a => a.innerText.replace('/', '') === itemname)) {
|
||||
alert('名称 ' + itemname + ' 已被已存在的项目占用');
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
isFolder: function (element) {
|
||||
return element && element.href && element.innerText.endsWith('/');
|
||||
},
|
||||
|
||||
encodeURIHash: function (e) {
|
||||
return encodeURI(e).replaceAll('#', '%23');
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
afi.interface.browseTo(location.href, true);
|
||||
},
|
||||
|
||||
prependPath: function (a) {
|
||||
return a.startsWith('/') ? a : decodeURI(location.pathname) + a;
|
||||
},
|
||||
cancelDefault(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
},
|
||||
setHighlight: function (t) {
|
||||
t.classList.add('highlight')
|
||||
},
|
||||
getHighlight: function () {
|
||||
return document.querySelector('.highlight') || {}
|
||||
},
|
||||
resetHighlight: function () {
|
||||
try {
|
||||
this.getHighlight().classList.remove('highlight')
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
isHelpMode: () => afi.elements.helpPanel.style.display === 'block',
|
||||
helpOn: function () {
|
||||
afi.elements.helpPanel.style.display = 'block'
|
||||
afi.elements.table.style.display = 'none'
|
||||
afi.elements.helpToggle.innerText = "关闭帮助"
|
||||
afi.elements.helpToggle.onclick = afi.utils.helpOff
|
||||
},
|
||||
helpOff: function () {
|
||||
if (!afi.utils.isHelpMode()) return
|
||||
afi.elements.helpPanel.style.display = 'none'
|
||||
afi.elements.table.style.display = 'table'
|
||||
afi.elements.helpToggle.innerText = "帮助菜单"
|
||||
afi.elements.helpToggle.onclick = afi.utils.helpOn
|
||||
},
|
||||
updateButtons: function () {
|
||||
const isLogged = afi.auth.isLoggedIn;
|
||||
document.querySelectorAll('#operations a').forEach(link => {
|
||||
const text = link.innerText.trim();
|
||||
if (text === '登录') {
|
||||
link.style.display = isLogged ? 'none' : 'block';
|
||||
} else if (['上传文件', '新建文件', '新建目录', '移动对象', '递归删除', '登出'].includes(text)) {
|
||||
link.style.display = isLogged ? 'block' : 'none';
|
||||
if (text === '登出' && isLogged) {
|
||||
link.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
afi.auth.logout();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
afi.rpc = {
|
||||
rpc: function (call, args, cb) {
|
||||
console.log('[AFI] RPC', call, args);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', location.origin + afi.params.extraPath + '/rpc');
|
||||
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
xhr.withCredentials = true;
|
||||
xhr.send(JSON.stringify({ call, args }));
|
||||
xhr.onload = cb;
|
||||
xhr.onerror = () => afi.pulsar.pulseFailure();
|
||||
},
|
||||
|
||||
mkdirCall: function (path, cb) {
|
||||
this.rpc('mkdirp', [afi.utils.prependPath(path)], cb);
|
||||
},
|
||||
|
||||
rmCall: function (path1, cb) {
|
||||
this.rpc('rm', [afi.utils.prependPath(path1)], cb);
|
||||
},
|
||||
|
||||
mvCall: function (path1, path2, cb) {
|
||||
this.rpc('mv', [path1, path2], cb);
|
||||
},
|
||||
|
||||
sumCall: function (path, type, cb) {
|
||||
this.rpc('sum', [afi.utils.prependPath(path), type], cb);
|
||||
},
|
||||
|
||||
touchCall: function (path, cb) {
|
||||
this.rpc('touch', [afi.utils.prependPath(path)], cb);
|
||||
}
|
||||
}
|
||||
|
||||
afi.uploader = {
|
||||
totalUploads: 0,
|
||||
totalUploaded: 0,
|
||||
totalUploadsSize: 0,
|
||||
totalUploadedSize: [],
|
||||
|
||||
shouldRefresh: function () {
|
||||
this.totalUploaded += 1
|
||||
console.log('[AFI] Have uploaded ' + this.totalUploaded + ' files')
|
||||
if (this.totalUploads === this.totalUploaded) {
|
||||
window.onbeforeunload = null
|
||||
console.log('[AFI] Finished after uploaded ' + this.totalUploaded + ' files')
|
||||
this.totalUploaded = 0
|
||||
this.totalUploads = 0
|
||||
this.totalUploadsSize = 0
|
||||
this.totalUploadedSize = []
|
||||
setTimeout(afi.utils.refresh, 200)
|
||||
afi.elements.barDisplay.style.display = 'none'
|
||||
}
|
||||
},
|
||||
|
||||
updatePercent: function (event) {
|
||||
this.totalUploadedSize[event.target.id] = event.loaded
|
||||
const ttlDone = this.totalUploadedSize.reduce((s, x) => s + x)
|
||||
const percent = Math.min(Math.floor(100 * ttlDone / this.totalUploadsSize), 100) + '%'
|
||||
afi.elements.barProc.innerText = "传输: " + percent
|
||||
},
|
||||
upload: function (id, what, path, cbDone, cbErr, cbUpdate) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', location.origin + afi.params.extraPath + '/post');
|
||||
xhr.setRequestHeader('afi-path', path);
|
||||
xhr.withCredentials = true;
|
||||
xhr.upload.addEventListener('load', cbDone);
|
||||
xhr.upload.addEventListener('progress', cbUpdate);
|
||||
xhr.upload.addEventListener('error', cbErr);
|
||||
xhr.upload.id = id;
|
||||
xhr.send(what);
|
||||
},
|
||||
|
||||
uploadFile: function (file, path) {
|
||||
path = decodeURI(location.pathname).slice(0, -1) + path
|
||||
window.onbeforeunload = afi.consts.leftPageWarn
|
||||
afi.elements.barProc.style.display = afi.elements.barDisplay.style.display = 'block'
|
||||
this.totalUploads += 1
|
||||
this.totalUploadsSize += file.size
|
||||
if (typeof upBarName !== 'undefined') {
|
||||
upBarName.innerText = this.totalUploads > 1 ? this.totalUploads + ' 个文件' : file.name
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append(file.name, file)
|
||||
this.upload(this.totalUploads, formData, encodeURIComponent(path), this.shouldRefresh.bind(this), null, this.updatePercent.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
afi.interface = {
|
||||
pushEntry: function (entry) {
|
||||
if (!entry.webkitGetAsEntry && !entry.getAsEntry) {
|
||||
return alert('不兼容此浏览器')
|
||||
} else {
|
||||
entry = entry.webkitGetAsEntry() || entry.getAsEntry()
|
||||
}
|
||||
this.parseDomItem.bind(this)(entry, true)
|
||||
},
|
||||
parseDomFolder: function (f) {
|
||||
f.createReader().readEntries(e => e.forEach(i => afi.interface.parseDomItem.bind(this)(i)))
|
||||
},
|
||||
parseDomItem: function (domFile, shouldCheckDupes) {
|
||||
if (shouldCheckDupes && afi.utils.isDupe(domFile.name)) {
|
||||
return
|
||||
}
|
||||
if (domFile.isFile) {
|
||||
domFile.file(f => afi.uploader.uploadFile(f, domFile.fullPath))
|
||||
} else {
|
||||
const f = domFile.fullPath.startsWith('/') ? domFile.fullPath.slice(1) : domFile.fullPath
|
||||
afi.rpc.mkdirCall(f, () => this.parseDomFolder.bind(this)(domFile))
|
||||
}
|
||||
},
|
||||
|
||||
browseTo: async function (href, pulseEffectDone, skipHistory) {
|
||||
try {
|
||||
const r = await fetch(href, { credentials: 'include' })
|
||||
const t = await r.text()
|
||||
const parsed = new DOMParser().parseFromString(t, 'text/html')
|
||||
|
||||
afi.elements.table.innerHTML = parsed.getElementById('index-table').innerHTML
|
||||
const nav = parsed.getElementById('nav').innerText
|
||||
const path = parsed.getElementById('path').innerText
|
||||
const title = parsed.head.querySelector('title').innerText
|
||||
|
||||
if (afi.elements.path.innerText !== path) {
|
||||
if (!skipHistory) {
|
||||
const escaped = afi.utils.encodeURIHash(afi.params.extraPath + path)
|
||||
history.pushState({}, '', escaped)
|
||||
}
|
||||
afi.elements.title.innerText = title
|
||||
afi.elements.nav.innerText = nav
|
||||
afi.elements.path.innerText = path
|
||||
this.setBreadcrumbs()
|
||||
}
|
||||
init()
|
||||
if (pulseEffectDone) afi.pulsar.pulseSuccess()
|
||||
} catch (error) {
|
||||
afi.pulsar.pulseFailure(error)
|
||||
}
|
||||
},
|
||||
|
||||
setBreadcrumbs: function () {
|
||||
const parent = afi.elements.nav.parentNode;
|
||||
afi.elements.nav.outerHTML = '<span id="nav" onclick="return afi.directcall.bread_click(event)"><span class="nav">' + afi.elements.path.innerText.split('/').join('/</span><span class="nav">') + '</span></span>'
|
||||
afi.elements.nav = parent.querySelector('#nav');
|
||||
}
|
||||
}
|
||||
|
||||
afi.directcall = {
|
||||
exec_mkdir: function () {
|
||||
const folder = prompt('新建目录名称', '')
|
||||
if (folder && !afi.utils.isDupe(folder)) {
|
||||
afi.rpc.mkdirCall(folder, afi.utils.refresh)
|
||||
}
|
||||
},
|
||||
|
||||
exec_touch: function () {
|
||||
const file = prompt('新建文件名称', '')
|
||||
if (file && !afi.utils.isDupe(file)) {
|
||||
afi.rpc.touchCall(file, afi.utils.refresh)
|
||||
}
|
||||
},
|
||||
|
||||
exec_rm: function () {
|
||||
const file = prompt('删除对象名称', '')
|
||||
afi.rpc.rmCall(file, afi.utils.refresh)
|
||||
},
|
||||
|
||||
exec_mv: function () {
|
||||
const orig = prompt('源地址', '')
|
||||
const dest = prompt('目标地址', '')
|
||||
if (orig && !afi.utils.isDupe(dest)) {
|
||||
afi.rpc.mvCall(afi.utils.prependPath(orig), afi.utils.prependPath(dest), afi.utils.refresh)
|
||||
}
|
||||
},
|
||||
bread_click: function (e) {
|
||||
const p = Array.from(document.getElementById('nav').childNodes).map(k => k.innerText)
|
||||
const i = p.findIndex(s => s === e.target.innerText)
|
||||
var dst = p.slice(0, i + 1).join('').slice(1)
|
||||
if (!dst.startsWith('/')) {
|
||||
dst = "/" + dst
|
||||
}
|
||||
const target = location.origin + afi.params.extraPath + afi.utils.encodeURIHash(dst)
|
||||
afi.interface.browseTo(target, false)
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
afi.elements = {
|
||||
itemlinks: Array.from(document.querySelectorAll('a.item-links')),
|
||||
table: document.getElementById("index-table"),
|
||||
badge: document.getElementById("badge"),
|
||||
path: document.getElementById("path"),
|
||||
uploader: document.getElementById("clickupload"),
|
||||
nav: document.getElementById("nav"),
|
||||
title: document.head.querySelector("title"),
|
||||
dropGrid: document.getElementById("drop-grid"),
|
||||
barDisplay: document.getElementById("bar-display"),
|
||||
barProc: document.getElementById("bar-proc"),
|
||||
helpPanel: document.getElementById('help-panel'),
|
||||
helpToggle: document.getElementById('help-toggle'),
|
||||
}
|
||||
|
||||
afi.auth.checkAuth().then(() => {
|
||||
afi.utils.updateButtons();
|
||||
});
|
||||
|
||||
afi.interface.setBreadcrumbs()
|
||||
afi.elements.uploader.addEventListener('change', () => {
|
||||
const files = Array.from(afi.elements.uploader.files);
|
||||
|
||||
files.forEach(file => {
|
||||
if (!afi.utils.isDupe(file.name)) {
|
||||
afi.uploader.uploadFile(file, '/' + file.name);
|
||||
}
|
||||
});
|
||||
}, false);
|
||||
afi.elements.dropGrid.ondragend = afi.elements.dropGrid.ondragexit = afi.elements.dropGrid.ondragleave = e => {
|
||||
afi.utils.cancelDefault(e)
|
||||
afi.elements.dropGrid.style.display = 'none'
|
||||
}
|
||||
let draggingSrc
|
||||
document.ondragenter = e => {
|
||||
afi.utils.cancelDefault(e)
|
||||
afi.utils.resetHighlight()
|
||||
if (!draggingSrc) {
|
||||
afi.elements.dropGrid.style.display = 'flex'
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
} else if (draggingSrc) {
|
||||
const t = getClosestRow(e.target)
|
||||
afi.utils.isFolder(t.firstChild) && setHighlight(t)
|
||||
}
|
||||
}
|
||||
|
||||
afi.elements.itemlinks.forEach(link => {
|
||||
const row = link.parentElement.parentElement.parentElement.querySelector('.item-icon');
|
||||
if (!row) return;
|
||||
|
||||
let clickTimer = null;
|
||||
let isDoubleClick = false;
|
||||
|
||||
row.addEventListener('click', (e) => {
|
||||
if (link.innerText === '../') return;
|
||||
if (clickTimer) {
|
||||
clearTimeout(clickTimer);
|
||||
clickTimer = null;
|
||||
}
|
||||
|
||||
clickTimer = setTimeout(() => {
|
||||
if (!isDoubleClick) {
|
||||
e.stopPropagation();
|
||||
if (confirm('确认删除此项目?')) {
|
||||
afi.rpc.rmCall(afi.utils.prependPath(link.getAttribute('href')), afi.utils.refresh);
|
||||
}
|
||||
}
|
||||
isDoubleClick = false;
|
||||
clickTimer = null;
|
||||
}, 300);
|
||||
});
|
||||
|
||||
row.addEventListener('dblclick', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
isDoubleClick = true;
|
||||
|
||||
if (clickTimer) {
|
||||
clearTimeout(clickTimer);
|
||||
clickTimer = null;
|
||||
}
|
||||
|
||||
if (link.innerText === '../') return;
|
||||
|
||||
const newName = prompt('新名称或目标地址', link.innerText);
|
||||
if (newName && !afi.utils.isDupe(newName)) {
|
||||
afi.rpc.mvCall(
|
||||
afi.utils.prependPath(link.innerText),
|
||||
afi.utils.prependPath(newName),
|
||||
afi.utils.refresh
|
||||
);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
isDoubleClick = false;
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
document.ondragstart = e => { draggingSrc = e.target.innerHTML }
|
||||
document.ondragend = e => resetHighlight()
|
||||
document.ondragover = e => { afi.utils.cancelDefault(e); return false }
|
||||
|
||||
document.ondrop = e => {
|
||||
|
||||
afi.utils.cancelDefault(e)
|
||||
afi.elements.dropGrid.style.display = 'none'
|
||||
const t = afi.utils.getHighlight().firstChild
|
||||
|
||||
if (draggingSrc && t) {
|
||||
const dest = t.innerHTML + draggingSrc
|
||||
afi.consts.ensureDrop() || afi.rpc.mvCall(afi.utils.prependPath(draggingSrc), afi.utils.prependPath(dest), afi.utils.refresh)
|
||||
} else if (e.dataTransfer.items.length) {
|
||||
Array.from(e.dataTransfer.items).forEach(afi.interface.pushEntry.bind(afi.interface))
|
||||
}
|
||||
|
||||
afi.utils.resetHighlight()
|
||||
draggingSrc = null
|
||||
return false
|
||||
}
|
||||
console.log('[AFI] Initialized')
|
||||
}
|
||||
|
||||
window.onload = init
|
||||
Reference in New Issue
Block a user