fetch => axios, some nice upload ux
This commit is contained in:
parent
fb91b9e879
commit
5a169488af
@ -469,8 +469,10 @@ namespace Api\File {
|
|||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileKeys = array_keys($_FILES);
|
$numFilesUploaded = array_sum(array_map(function($fileEntry) {
|
||||||
$numFilesUploaded = count($fileKeys);
|
return is_array($fileEntry["name"]) ? count($fileEntry["name"]) : 1;
|
||||||
|
}, $_FILES
|
||||||
|
));
|
||||||
|
|
||||||
if (!is_null($token)) {
|
if (!is_null($token)) {
|
||||||
|
|
||||||
@ -511,30 +513,43 @@ namespace Api\File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$count = $res[0]["count"];
|
$count = $res[0]["count"];
|
||||||
if ($maxFiles > 0 && $numFilesUploaded > 0 && $numFilesUploaded + $count > $maxFiles) {
|
|
||||||
return $this->createError("File limit exceeded. Currently uploaded $count / $maxFiles files");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($maxSize > 0 || !empty($extensions)) {
|
|
||||||
foreach ($_FILES as $file) {
|
|
||||||
$name = $file["name"];
|
|
||||||
if ($maxSize > 0 && $file["size"] > $maxSize) {
|
|
||||||
return $this->createError("File Size limit of $maxSize bytes exceeded for file $name");
|
|
||||||
}
|
|
||||||
|
|
||||||
$dotPos = strrpos($name, ".");
|
|
||||||
$ext = ($dotPos !== false ? strtolower(substr($name, $dotPos + 1)) : false);
|
|
||||||
if (!empty($extensions) && $ext !== false && !in_array($ext, $extensions)) {
|
|
||||||
return $this->createError("File '$name' has prohibited extension. Allowed extensions: " . implode(",", $extensions));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$userId = $this->user->getId();
|
$userId = $this->user->getId();
|
||||||
|
$maxSize = 0;
|
||||||
|
$count = 0;
|
||||||
|
$maxFiles = 0;
|
||||||
|
$extensions = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maxSize = ($maxSize <= 0 ? $this->getMaxFileSizePHP() : min($maxSize, $this->getMaxFileSizePHP()));
|
||||||
|
$maxFiles = ($maxFiles <= 0 ? $this->getMaxFiles() : min($maxFiles, $this->getMaxFiles()));
|
||||||
|
|
||||||
if ($numFilesUploaded === 0) {
|
if ($numFilesUploaded === 0) {
|
||||||
return $this->createError("No file uploaded");
|
return $this->createError("No file uploaded");
|
||||||
|
} else if($numFilesUploaded > 1) {
|
||||||
|
return $this->createError("You can only upload one file at once");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($maxFiles > 0 && $count + 1 > $maxFiles) {
|
||||||
|
return $this->createError("File upload limit exceeded. Currently uploaded $count / $maxFiles files");
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileObject = array_shift($_FILES);
|
||||||
|
$fileName = (is_array($fileObject["name"]) ? $fileObject["name"][0] : $fileObject["name"]);
|
||||||
|
$fileSize = (is_array($fileObject["size"]) ? $fileObject["size"][0] : $fileObject["size"]);
|
||||||
|
$tmpPath = (is_array($fileObject["tmp_name"]) ? $fileObject["tmp_name"][0] : $fileObject["tmp_name"]);
|
||||||
|
$fileError = (is_array($fileObject["error"]) ? $fileObject["error"][0] : $fileObject["error"]);
|
||||||
|
|
||||||
|
if ($maxSize > 0 || !empty($extensions)) {
|
||||||
|
if ($maxSize > 0 && $fileSize > $maxSize) {
|
||||||
|
return $this->createError("File Size limit of $maxSize bytes exceeded for file $fileName");
|
||||||
|
}
|
||||||
|
|
||||||
|
$dotPos = strrpos($fileName, ".");
|
||||||
|
$ext = ($dotPos !== false ? strtolower(substr($fileName, $dotPos + 1)) : false);
|
||||||
|
if (!empty($extensions) && $ext !== false && !in_array($ext, $extensions)) {
|
||||||
|
return $this->createError("File '$fileName' has prohibited extension. Allowed extensions: " . implode(",", $extensions));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$uploadDir = realpath($_SERVER["DOCUMENT_ROOT"] . "/files/uploaded/");
|
$uploadDir = realpath($_SERVER["DOCUMENT_ROOT"] . "/files/uploaded/");
|
||||||
@ -542,19 +557,14 @@ namespace Api\File {
|
|||||||
return $this->createError("Upload directory is not writable");
|
return $this->createError("Upload directory is not writable");
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileIds = array();
|
|
||||||
foreach ($_FILES as $key => $file) {
|
|
||||||
$fileName = $file["name"];
|
|
||||||
$tmpPath = $file["tmp_name"];
|
|
||||||
if (!$tmpPath) {
|
if (!$tmpPath) {
|
||||||
return $this->createError("Error uploading file: $fileName");
|
return $this->createError("Error uploading file: $fileError");
|
||||||
}
|
}
|
||||||
|
|
||||||
$md5Hash = @hash_file('md5', $tmpPath);
|
$md5Hash = @hash_file('md5', $tmpPath);
|
||||||
$sha1Hash = @hash_file('sha1', $tmpPath);
|
$sha1Hash = @hash_file('sha1', $tmpPath);
|
||||||
$filePath = $uploadDir . "/" . $md5Hash . $sha1Hash;
|
$filePath = $uploadDir . "/" . $md5Hash . $sha1Hash;
|
||||||
if (move_uploaded_file($tmpPath, $filePath)) {
|
if (file_exists($filePath) || move_uploaded_file($tmpPath, $filePath)) {
|
||||||
|
|
||||||
$res = $sql->insert("UserFile", array("name", "directory", "path", "user_id", "parent_id"))
|
$res = $sql->insert("UserFile", array("name", "directory", "path", "user_id", "parent_id"))
|
||||||
->addRow($fileName, false, $filePath, $userId, $parentId)
|
->addRow($fileName, false, $filePath, $userId, $parentId)
|
||||||
->returning("uid")
|
->returning("uid")
|
||||||
@ -565,24 +575,22 @@ namespace Api\File {
|
|||||||
$this->success = false;
|
$this->success = false;
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
$fileIds[] = $sql->getLastInsertId();
|
$fileId = $sql->getLastInsertId();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return $this->createError("Could not create file: " . $fileName);
|
return $this->createError("Could not create file: $fileName");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($token)) {
|
if (!is_null($token)) {
|
||||||
$query = $sql->insert("UserFileTokenFile", array("file_id", "token_id"));
|
$res = $sql->insert("UserFileTokenFile", array("file_id", "token_id"))
|
||||||
foreach ($fileIds as $fileId) {
|
->addRow($fileId, $tokenId)
|
||||||
$query->addRow($fileId, $tokenId);
|
->execute();
|
||||||
}
|
|
||||||
|
|
||||||
$res = $query->execute();
|
|
||||||
$this->success = ($res !== false);
|
$this->success = ($res !== false);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->result["fileId"] = $fileId;
|
||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -651,6 +659,7 @@ namespace Api\File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: delete permanently from filesystem
|
||||||
class Delete extends FileAPI {
|
class Delete extends FileAPI {
|
||||||
|
|
||||||
public function __construct(User $user, bool $externalCall = false) {
|
public function __construct(User $user, bool $externalCall = false) {
|
||||||
|
17023
fileControlPanel/package-lock.json
generated
17023
fileControlPanel/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.21.1",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-collapse": "^5.0.1",
|
"react-collapse": "^5.0.1",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'babel-polyfill';
|
import 'babel-polyfill';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
|
|
||||||
@ -13,24 +14,18 @@ export default class API {
|
|||||||
|
|
||||||
async apiCall(method, params) {
|
async apiCall(method, params) {
|
||||||
params = params || { };
|
params = params || { };
|
||||||
|
|
||||||
const csrf_token = this.csrfToken();
|
const csrf_token = this.csrfToken();
|
||||||
if (csrf_token) params.csrf_token = csrf_token;
|
if (csrf_token) params.csrf_token = csrf_token;
|
||||||
let response = await fetch("/api/" + method, {
|
let response = await axios.post("/api/" + method, params);
|
||||||
method: 'post',
|
return response.data;
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(params)
|
|
||||||
});
|
|
||||||
|
|
||||||
return await response.json();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUser() {
|
async fetchUser() {
|
||||||
let response = await fetch("/api/user/info");
|
let response = await axios.get("/api/user/info");
|
||||||
let data = await response.json();
|
let data = response.data;
|
||||||
this.user = data["user"];
|
this.user = data["user"];
|
||||||
this.loggedIn = data["loggedIn"];
|
this.loggedIn = data["loggedIn"];
|
||||||
return data && data.success && data.loggedIn;
|
return data && data["success"] && data["loggedIn"];
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
@ -73,24 +68,20 @@ export default class API {
|
|||||||
return this.apiCall("file/getRestrictions");
|
return this.apiCall("file/getRestrictions");
|
||||||
}
|
}
|
||||||
|
|
||||||
async upload(files, token = null, parentId = null) {
|
async upload(file, token = null, parentId = null, onUploadProgress = null) {
|
||||||
const csrf_token = this.csrfToken();
|
const csrf_token = this.csrfToken();
|
||||||
|
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
for (let i = 0; i < files.length; i++) {
|
fd.append("file", file);
|
||||||
fd.append('file' + i, files[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (csrf_token) fd.append("csrf_token", csrf_token);
|
if (csrf_token) fd.append("csrf_token", csrf_token);
|
||||||
if (token) fd.append("token", token);
|
if (token) fd.append("token", token);
|
||||||
if (parentId) fd.append("parentId", parentId);
|
if (parentId) fd.append("parentId", parentId);
|
||||||
|
|
||||||
// send `POST` request
|
let response = await axios.post('/api/file/upload', fd, {
|
||||||
let response = await fetch('/api/file/upload', {
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
method: 'POST',
|
onUploadProgress: onUploadProgress || function () { }
|
||||||
body: fd
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.json();
|
return response.data;
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -40,8 +40,7 @@
|
|||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploaded-file > i:nth-child(3) {
|
.uploaded-file > .status-icon {
|
||||||
color: red;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -9px;
|
top: -9px;
|
||||||
right: 25px;
|
right: 25px;
|
||||||
|
@ -4,62 +4,64 @@ import Dropzone from "react-dropzone";
|
|||||||
import Icon from "./icon";
|
import Icon from "./icon";
|
||||||
import Alert from "./alert";
|
import Alert from "./alert";
|
||||||
import {Popup} from "./popup";
|
import {Popup} from "./popup";
|
||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
export function FileBrowser(props) {
|
export function FileBrowser(props) {
|
||||||
|
|
||||||
let files = props.files || { };
|
let files = props.files || {};
|
||||||
let api = props.api;
|
let api = props.api;
|
||||||
let tokenObj = props.token || { valid: false };
|
let tokenObj = props.token || {valid: false};
|
||||||
let onSelectFile = props.onSelectFile || function() { };
|
let onSelectFile = props.onSelectFile || function () { };
|
||||||
let onFetchFiles = props.onFetchFiles || function() { };
|
let onFetchFiles = props.onFetchFiles || function () { };
|
||||||
let directories = props.directories || {};
|
let directories = props.directories || {};
|
||||||
let restrictions = props.restrictions || { maxFiles: 0, maxSize: 0, extensions: "" };
|
let restrictions = props.restrictions || {maxFiles: 0, maxSize: 0, extensions: ""};
|
||||||
|
|
||||||
let [popup, setPopup] = useState({ visible: false, directoryName: "", directory: 0, type: "upload" });
|
let [popup, setPopup] = useState({visible: false, directoryName: "", directory: 0, type: "upload"});
|
||||||
let [alerts, setAlerts] = useState( []);
|
let [alerts, setAlerts] = useState([]);
|
||||||
let [filesToUpload, setFilesToUpload] = useState([]);
|
let [filesToUpload, setFilesToUpload] = useState([]);
|
||||||
|
|
||||||
function svgMiddle(scale=1.0) {
|
function svgMiddle(key, scale = 1.0) {
|
||||||
let width = 48 * scale;
|
let width = 48 * scale;
|
||||||
let height = 64 * scale;
|
let height = 64 * scale;
|
||||||
|
|
||||||
return <svg width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
return <svg key={key} width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
<line y2="0" x2={width/2} y1={height} x1={width/2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
<line y2="0" x2={width / 2} y1={height} x1={width / 2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
||||||
<line y2={height/2} x2={width} y1={height/2} x1={width/2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
<line y2={height / 2} x2={width} y1={height / 2} x1={width / 2} strokeWidth="1.5" stroke="#000"
|
||||||
|
fill="none"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>;
|
</svg>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function svgEnd(scale=1.0) {
|
function svgEnd(key, scale = 1.0) {
|
||||||
let width = 48 * scale;
|
let width = 48 * scale;
|
||||||
let height = 64 * scale;
|
let height = 64 * scale;
|
||||||
|
|
||||||
return <svg width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
return <svg key={key} width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
{ /* vertical line */}
|
{ /* vertical line */}
|
||||||
<line y2="0" x2={width/2} y1={height/2} x1={width/2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
<line y2="0" x2={width / 2} y1={height / 2} x1={width / 2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
||||||
{ /* horizontal line */}
|
{ /* horizontal line */}
|
||||||
<line y2={height/2} x2={width} y1={height/2} x1={width/2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
<line y2={height / 2} x2={width} y1={height / 2} x1={width / 2} strokeWidth="1.5" stroke="#000"
|
||||||
|
fill="none"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>;
|
</svg>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function svgLeft(scale=1.0) {
|
function svgLeft(key, scale = 1.0) {
|
||||||
let width = 48 * scale;
|
let width = 48 * scale;
|
||||||
let height = 64 * scale;
|
let height = 64 * scale;
|
||||||
return <svg width={width} height={height} xmlns="http://www.w3.org/2000/svg" style={{}}>
|
return <svg key={key} width={width} height={height} xmlns="http://www.w3.org/2000/svg">
|
||||||
<g>
|
<g>
|
||||||
{ /* vertical line */}
|
{ /* vertical line */}
|
||||||
<line y2="0" x2={width/2} y1={height} x1={width/2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
<line y2="0" x2={width / 2} y1={height} x1={width / 2} strokeWidth="1.5" stroke="#000" fill="none"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>;
|
</svg>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFileIcon(mimeType, size="2x") {
|
function createFileIcon(mimeType, size = "2x") {
|
||||||
let icon = "";
|
let icon = "";
|
||||||
if (mimeType !== null) {
|
if (mimeType) {
|
||||||
mimeType = mimeType.toLowerCase().trim();
|
mimeType = mimeType.toLowerCase().trim();
|
||||||
let types = ["image", "text", "audio", "video"];
|
let types = ["image", "text", "audio", "video"];
|
||||||
let languages = ["php", "java", "python", "cpp"];
|
let languages = ["php", "java", "python", "cpp"];
|
||||||
@ -92,11 +94,11 @@ export function FileBrowser(props) {
|
|||||||
icon = "file" + (icon ? ("-" + icon) : icon);
|
icon = "file" + (icon ? ("-" + icon) : icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Icon icon={icon} type={"far"} className={"p-1 align-middle fa-" + size} />
|
return <Icon icon={icon} type={"far"} className={"p-1 align-middle fa-" + size}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSize(size) {
|
function formatSize(size) {
|
||||||
const suffixes = ["B","KiB","MiB","GiB","TiB"];
|
const suffixes = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (; i < suffixes.length && size >= 1024; i++) {
|
for (; i < suffixes.length && size >= 1024; i++) {
|
||||||
size /= 1024.0;
|
size /= 1024.0;
|
||||||
@ -109,15 +111,33 @@ export function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let newFiles = filesToUpload.slice();
|
||||||
|
for (let fileIndex = 0; fileIndex < newFiles.length; fileIndex++) {
|
||||||
|
if (typeof newFiles[fileIndex].progress === 'undefined') {
|
||||||
|
onUpload(fileIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [filesToUpload]);
|
||||||
|
|
||||||
function canUpload() {
|
function canUpload() {
|
||||||
return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload");
|
return api.loggedIn || (tokenObj.valid && tokenObj.type === "upload");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAddUploadFiles(acceptedFiles) {
|
function onAddUploadFiles(acceptedFiles, rejectedFiles) {
|
||||||
|
|
||||||
|
if (rejectedFiles && rejectedFiles.length > 0) {
|
||||||
|
const filenames = rejectedFiles.map(f => f.file.name).join(", ");
|
||||||
|
pushAlert({msg: "The following files could not be uploaded due to given restrictions: " + filenames }, "Cannot upload file");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptedFiles && acceptedFiles.length > 0) {
|
||||||
let files = filesToUpload.slice();
|
let files = filesToUpload.slice();
|
||||||
files.push(...acceptedFiles);
|
files.push(...acceptedFiles);
|
||||||
setFilesToUpload(files);
|
setFilesToUpload(files);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedIds(items = null, recursive = true) {
|
function getSelectedIds(items = null, recursive = true) {
|
||||||
let ids = [];
|
let ids = [];
|
||||||
@ -158,9 +178,9 @@ export function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFileList(elements, indentation=0) {
|
function createFileList(elements, indentation = 0) {
|
||||||
let rows = [];
|
let rows = [];
|
||||||
let i = 0;
|
let rowIndex = 0;
|
||||||
|
|
||||||
const scale = 0.45;
|
const scale = 0.45;
|
||||||
const iconSize = "lg";
|
const iconSize = "lg";
|
||||||
@ -176,21 +196,21 @@ export function FileBrowser(props) {
|
|||||||
let svg = [];
|
let svg = [];
|
||||||
if (indentation > 0) {
|
if (indentation > 0) {
|
||||||
for (let i = 0; i < indentation - 1; i++) {
|
for (let i = 0; i < indentation - 1; i++) {
|
||||||
svg.push(svgLeft(scale));
|
svg.push(svgLeft(rowIndex + "-" + i, scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === values.length - 1) {
|
if (rowIndex === values.length - 1) {
|
||||||
svg.push(svgEnd(scale));
|
svg.push(svgEnd(rowIndex + "-end", scale));
|
||||||
} else {
|
} else {
|
||||||
svg.push(svgMiddle(scale));
|
svg.push(svgMiddle(rowIndex + "-middle", scale));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(
|
rows.push(
|
||||||
<tr key={"file-" + uid} data-id={uid} className={"file-row"}>
|
<tr key={"file-" + uid} data-id={uid} className={"file-row"}>
|
||||||
<td>
|
<td>
|
||||||
{ svg }
|
{svg}
|
||||||
{ createFileIcon(mimeType, iconSize) }
|
{createFileIcon(mimeType, iconSize)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{fileElement.isDirectory ? name :
|
{fileElement.isDirectory ? name :
|
||||||
@ -210,7 +230,7 @@ export function FileBrowser(props) {
|
|||||||
if (fileElement.isDirectory) {
|
if (fileElement.isDirectory) {
|
||||||
rows.push(...createFileList(fileElement.items, indentation + 1));
|
rows.push(...createFileList(fileElement.items, indentation + 1));
|
||||||
}
|
}
|
||||||
i++;
|
rowIndex++;
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
@ -226,7 +246,7 @@ export function FileBrowser(props) {
|
|||||||
for (let i = 0; i < alerts.length; i++) {
|
for (let i = 0; i < alerts.length; i++) {
|
||||||
const alert = alerts[i];
|
const alert = alerts[i];
|
||||||
alertElements.push(
|
alertElements.push(
|
||||||
<Alert key={"alert-" + i} {...alert} onClose={() => removeAlert(i)} />
|
<Alert key={"alert-" + i} {...alert} onClose={() => removeAlert(i)}/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,26 +257,61 @@ export function FileBrowser(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAllowedExtensions() {
|
||||||
|
let extensions = restrictions.extensions || "";
|
||||||
|
return extensions.split(",")
|
||||||
|
.map(ext => ext.trim())
|
||||||
|
.map(ext => !ext.startsWith(".") && ext.length > 0 ? "." + ext : ext)
|
||||||
|
.join(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRestrictions() {
|
||||||
|
return {
|
||||||
|
accept: getAllowedExtensions(),
|
||||||
|
maxFiles: restrictions.maxFiles,
|
||||||
|
maxSize: restrictions.maxSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (writePermissions) {
|
if (writePermissions) {
|
||||||
|
|
||||||
for(let i = 0; i < filesToUpload.length; i++) {
|
for (let i = 0; i < filesToUpload.length; i++) {
|
||||||
const file = filesToUpload[i];
|
const file = filesToUpload[i];
|
||||||
|
const progress = Math.round((file.progress ?? 0) * 100);
|
||||||
|
const done = progress >= 100;
|
||||||
uploadedFiles.push(
|
uploadedFiles.push(
|
||||||
<span className={"uploaded-file"} key={i}>
|
<span className={"uploaded-file"} key={i}>
|
||||||
{ createFileIcon(file.type, "3x") }
|
{createFileIcon(file.type, "3x")}
|
||||||
<span>{file.name}</span>
|
<span>{file.name}</span>
|
||||||
<Icon icon={"times"} onClick={(e) => onRemoveUploadedFile(e, i)}/>
|
{!done ?
|
||||||
|
<div className={"progress border border-primary"}>
|
||||||
|
<div className={"progress-bar progress-bar-striped progress-bar-animated"} role={"progressbar"}
|
||||||
|
aria-valuenow={progress} aria-valuemin={"0"} aria-valuemax={"100"}
|
||||||
|
style={{width: progress + "%"}}>
|
||||||
|
{ progress + "%" }
|
||||||
|
</div>
|
||||||
|
</div> : <></>
|
||||||
|
}
|
||||||
|
<Icon icon={done ? "check" : "spinner"} className={"status-icon " + (done ? "text-success" : "text-secondary")} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadZone = <><Dropzone onDrop={onAddUploadFiles}>
|
uploadZone = <>
|
||||||
|
<div className={"p-3"}>
|
||||||
|
<label><b>Destination Directory:</b></label>
|
||||||
|
<select value={popup.directory} className={"form-control"}
|
||||||
|
onChange={(e) => onPopupChange(e, "directory")}>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<Dropzone onDrop={onAddUploadFiles} {...getRestrictions()} >
|
||||||
{({getRootProps, getInputProps}) => (
|
{({getRootProps, getInputProps}) => (
|
||||||
<section className={"file-upload-container"}>
|
<section className={"file-upload-container"}>
|
||||||
<div {...getRootProps()}>
|
<div {...getRootProps()}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<p>Drag 'n' drop some files here, or click to select files</p>
|
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||||
{ uploadedFiles.length === 0 ?
|
{uploadedFiles.length === 0 ?
|
||||||
<Icon className={"mx-auto fa-3x text-black-50"} icon={"upload"}/> :
|
<Icon className={"mx-auto fa-3x text-black-50"} icon={"upload"}/> :
|
||||||
<div>{uploadedFiles}</div>
|
<div>{uploadedFiles}</div>
|
||||||
}
|
}
|
||||||
@ -283,7 +338,7 @@ export function FileBrowser(props) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ rows.length > 0 ? rows :
|
{rows.length > 0 ? rows :
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className={"text-center text-black-50"}>
|
<td colSpan={4} className={"text-center text-black-50"}>
|
||||||
No files uploaded yet
|
No files uploaded yet
|
||||||
@ -298,52 +353,51 @@ export function FileBrowser(props) {
|
|||||||
<Icon icon={"download"} className={"mr-1"}/>
|
<Icon icon={"download"} className={"mr-1"}/>
|
||||||
Download Selected Files ({selectedCount})
|
Download Selected Files ({selectedCount})
|
||||||
</button>
|
</button>
|
||||||
{ api.loggedIn ?
|
<span/>
|
||||||
<button type={"button"} className={"btn btn-info"} onClick={(e) => onPopupOpen("createDirectory")}>
|
|
||||||
<Icon icon={"plus"} className={"mr-1"}/>
|
|
||||||
Create Directory
|
|
||||||
</button> :
|
|
||||||
<></>
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
writePermissions ?
|
writePermissions ?
|
||||||
<>
|
<>
|
||||||
<button type={"button"} className={"btn btn-primary"} disabled={uploadedFiles.length === 0}
|
|
||||||
onClick={(e) => api.loggedIn ? onPopupOpen("upload") : onUpload()}>
|
|
||||||
<Icon icon={"upload"} className={"mr-1"}/>
|
|
||||||
Upload
|
|
||||||
</button>
|
|
||||||
<button type={"button"} className={"btn btn-danger"} disabled={selectedCount === 0}
|
<button type={"button"} className={"btn btn-danger"} disabled={selectedCount === 0}
|
||||||
onClick={() => deleteFiles(selectedIds)}>
|
onClick={() => deleteFiles(selectedIds)}>
|
||||||
<Icon icon={"trash"} className={"mr-1"}/>
|
<Icon icon={"trash"} className={"mr-1"}/>
|
||||||
Delete Selected Files ({selectedCount})
|
Delete Selected Files ({selectedCount})
|
||||||
</button>
|
</button>
|
||||||
|
{api.loggedIn ?
|
||||||
|
<button type={"button"} className={"btn btn-info"}
|
||||||
|
onClick={(e) => onPopupOpen("createDirectory")}>
|
||||||
|
<Icon icon={"plus"} className={"mr-1"}/>
|
||||||
|
Create Directory
|
||||||
|
</button> :
|
||||||
|
<></>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{ uploadZone }
|
{uploadZone}
|
||||||
<div className={"file-browser-restrictions px-4 mb-4"}>
|
<div className={"file-browser-restrictions px-4 mb-4"}>
|
||||||
<b>Restrictions:</b>
|
<b>Restrictions:</b>
|
||||||
<span>Max. Files: { restrictions.maxFiles }</span>
|
<span>Max. Files: {restrictions.maxFiles}</span>
|
||||||
<span>Max. Filesize: { formatSize(restrictions.maxSize) }</span>
|
<span>Max. Filesize: {formatSize(restrictions.maxSize)}</span>
|
||||||
<span>{ restrictions.extensions ? "Allowed extensions: " + restrictions.extensions : "All extensions allowed" }</span>
|
<span>{restrictions.extensions ? "Allowed extensions: " + restrictions.extensions : "All extensions allowed"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{ alertElements }
|
{alertElements}
|
||||||
</div>
|
</div>
|
||||||
<Popup title={"Create Directory"} visible={popup.visible} buttons={["Ok","Cancel"]} onClose={onPopupClose} onClick={onPopupButton}>
|
<Popup title={"Create Directory"} visible={popup.visible} buttons={["Ok", "Cancel"]} onClose={onPopupClose}
|
||||||
|
onClick={onPopupButton}>
|
||||||
<div className={"form-group"}>
|
<div className={"form-group"}>
|
||||||
<label>Destination Directory:</label>
|
<label>Destination Directory:</label>
|
||||||
<select value={popup.directory} className={"form-control"}
|
<select value={popup.directory} className={"form-control"}
|
||||||
onChange={(e) => onPopupChange(e, "directory")}>
|
onChange={(e) => onPopupChange(e, "directory")}>
|
||||||
{ options }
|
{options}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{ popup.type !== "upload" ?
|
{popup.type !== "upload" ?
|
||||||
<div className={"form-group"}>
|
<div className={"form-group"}>
|
||||||
<label>Directory Name</label>
|
<label>Directory Name</label>
|
||||||
<input type={"text"} className={"form-control"} value={popup.directoryName} maxLength={32} placeholder={"Enter name…"}
|
<input type={"text"} className={"form-control"} value={popup.directoryName} maxLength={32}
|
||||||
|
placeholder={"Enter name…"}
|
||||||
onChange={(e) => onPopupChange(e, "directoryName")}/>
|
onChange={(e) => onPopupChange(e, "directoryName")}/>
|
||||||
</div> : <></>
|
</div> : <></>
|
||||||
}
|
}
|
||||||
@ -351,15 +405,15 @@ export function FileBrowser(props) {
|
|||||||
</>;
|
</>;
|
||||||
|
|
||||||
function onPopupOpen(type) {
|
function onPopupOpen(type) {
|
||||||
setPopup({ ...popup, visible: true, type: type });
|
setPopup({...popup, visible: true, type: type});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPopupClose() {
|
function onPopupClose() {
|
||||||
setPopup({ ...popup, visible: false });
|
setPopup({...popup, visible: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPopupChange(e, key) {
|
function onPopupChange(e, key) {
|
||||||
setPopup({ ...popup, [key]: e.target.value });
|
setPopup({...popup, [key]: e.target.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPopupButton(btn) {
|
function onPopupButton(btn) {
|
||||||
@ -374,33 +428,39 @@ export function FileBrowser(props) {
|
|||||||
fetchFiles();
|
fetchFiles();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (popup.type === "upload") {
|
|
||||||
onUpload();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPopupClose();
|
onPopupClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeUploadedFiles() {
|
||||||
|
let newFiles = filesToUpload.filter(file => !file.progress || file.progress < 1.0);
|
||||||
|
if (newFiles.length !== filesToUpload.length) {
|
||||||
|
setFilesToUpload(newFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fetchFiles() {
|
function fetchFiles() {
|
||||||
|
let promise;
|
||||||
|
|
||||||
if (tokenObj.valid) {
|
if (tokenObj.valid) {
|
||||||
api.validateToken(tokenObj.value).then((res) => {
|
promise = api.validateToken(tokenObj.value);
|
||||||
if (res) {
|
|
||||||
onFetchFiles(res.files);
|
|
||||||
} else {
|
|
||||||
pushAlert(res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (api.loggedIn) {
|
} else if (api.loggedIn) {
|
||||||
api.listFiles().then((res) => {
|
promise = api.listFiles()
|
||||||
|
} else {
|
||||||
|
return; // should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
onFetchFiles(res.files);
|
onFetchFiles(res.files);
|
||||||
|
removeUploadedFiles();
|
||||||
} else {
|
} else {
|
||||||
pushAlert(res);
|
pushAlert(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function onRemoveUploadedFile(e, i) {
|
function onRemoveUploadedFile(e, i) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -411,7 +471,7 @@ export function FileBrowser(props) {
|
|||||||
|
|
||||||
function pushAlert(res, title) {
|
function pushAlert(res, title) {
|
||||||
let newAlerts = alerts.slice();
|
let newAlerts = alerts.slice();
|
||||||
newAlerts.push({ type: "danger", message: res.msg, title: title });
|
newAlerts.push({type: "danger", message: res.msg, title: title});
|
||||||
setAlerts(newAlerts);
|
setAlerts(newAlerts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,12 +496,21 @@ export function FileBrowser(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpload() {
|
function onUploadProgress(event, fileIndex) {
|
||||||
|
if (fileIndex < filesToUpload.length) {
|
||||||
|
let files = filesToUpload.slice();
|
||||||
|
files[fileIndex].progress = event.loaded >= event.total ? 1 : event.loaded / event.total;
|
||||||
|
setFilesToUpload(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpload(fileIndex) {
|
||||||
let token = (api.loggedIn ? null : tokenObj.value);
|
let token = (api.loggedIn ? null : tokenObj.value);
|
||||||
let parentId = ((!api.loggedIn || popup.directory === 0) ? null : popup.directory);
|
let parentId = ((!api.loggedIn || popup.directory === 0) ? null : popup.directory);
|
||||||
api.upload(filesToUpload, token, parentId).then((res) => {
|
const file = filesToUpload[fileIndex];
|
||||||
|
api.upload(file, token, parentId, (e) => onUploadProgress(e, fileIndex)).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setFilesToUpload([]);
|
// setFilesToUpload([]);
|
||||||
fetchFiles();
|
fetchFiles();
|
||||||
} else {
|
} else {
|
||||||
pushAlert(res);
|
pushAlert(res);
|
||||||
|
14
js/files.min.js
vendored
14
js/files.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user