0.1 #1
13 changed files with 2643 additions and 4452 deletions
|
@ -23,6 +23,12 @@ GET http://localhost:8080/api/fling
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
:token
|
:token
|
||||||
|
|
||||||
|
# Add a new fling
|
||||||
|
POST http://localhost:8080/api/fling
|
||||||
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
{"name": "Shared Fling from querysheet", "expirationClicks": 12, "shared": true}
|
||||||
|
|
||||||
# Add a new fling
|
# Add a new fling
|
||||||
POST http://localhost:8080/api/fling
|
POST http://localhost:8080/api/fling
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
|
@ -9,6 +9,7 @@ import javax.persistence.GeneratedValue;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.ManyToOne;
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
import javax.persistence.Version;
|
import javax.persistence.Version;
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
import org.hibernate.annotations.UpdateTimestamp;
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
@ -16,7 +17,7 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "Artifact")
|
@Table(name = "Artifact", uniqueConstraints = @UniqueConstraint(columnNames = {"fling_id", "path"}))
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class ArtifactEntity {
|
public class ArtifactEntity {
|
||||||
|
|
|
@ -11,6 +11,6 @@ public interface FlingRepository extends JpaRepository<FlingEntity, UUID> {
|
||||||
|
|
||||||
FlingEntity findByShareId(String shareId);
|
FlingEntity findByShareId(String shareId);
|
||||||
|
|
||||||
@Query("SELECT fe FROM FlingEntity fe JOIN ArtifactEntity ae ON fe.id=ae.id WHERE ae.id=:artifactId")
|
@Query("SELECT fe FROM FlingEntity fe JOIN ArtifactEntity ae ON fe.id=ae.fling.id WHERE ae.id=:artifactId")
|
||||||
FlingEntity findByArtifactId(UUID artifactId);
|
FlingEntity findByArtifactId(UUID artifactId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,15 +109,15 @@ public class FlingWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||||
/***************************************/
|
/***************************************/
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.GET, "/api/artifacts/{artifactId}/**")
|
.antMatchers(HttpMethod.GET, "/api/artifacts/{artifactId}/**")
|
||||||
.access("@authorizationService.allowArtifactAccess(#artifactId, token)")
|
.access("@authorizationService.allowArtifactAccess(#artifactId, authentication)")
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.POST, "/api/artifacts/{artifactId}/data")
|
.antMatchers(HttpMethod.POST, "/api/artifacts/{artifactId}/data")
|
||||||
.access("@authorizationService.allowArtifactUpload(#artifactId, token)")
|
.access("@authorizationService.allowArtifactUpload(#artifactId, authentication)")
|
||||||
.and()
|
.and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.DELETE, "/api/artifacts/{artifactId}")
|
.antMatchers(HttpMethod.DELETE, "/api/artifacts/{artifactId}")
|
||||||
.access("@authorizationService.allowArtifactUpload(#artifactId, token)");
|
.access("@authorizationService.allowArtifactUpload(#artifactId, authentication)");
|
||||||
|
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ public class ArtifactService {
|
||||||
public ArtifactDto create(UUID flingId, ArtifactDto artifactDto) {
|
public ArtifactDto create(UUID flingId, ArtifactDto artifactDto) {
|
||||||
FlingEntity flingEntity = flingRepository.getOne(flingId);
|
FlingEntity flingEntity = flingRepository.getOne(flingId);
|
||||||
|
|
||||||
|
log.debug("Creating new ArtifactEntity for ArtifactDto[.path={}]", artifactDto.getPath());
|
||||||
ArtifactEntity artifactEntity = artifactMapper.map(artifactDto);
|
ArtifactEntity artifactEntity = artifactMapper.map(artifactDto);
|
||||||
artifactEntity.setFling(flingEntity);
|
artifactEntity.setFling(flingEntity);
|
||||||
artifactEntity = artifactRepository.save(artifactEntity);
|
artifactEntity = artifactRepository.save(artifactEntity);
|
||||||
|
|
|
@ -89,14 +89,16 @@ public class FileSystemArchive implements ArchiveService {
|
||||||
public void storeArtifact(UUID artifactId, InputStream artifactStream) throws IOException {
|
public void storeArtifact(UUID artifactId, InputStream artifactStream) throws IOException {
|
||||||
log.debug("Storing artifact {}", artifactId);
|
log.debug("Storing artifact {}", artifactId);
|
||||||
|
|
||||||
setArchived(artifactId, false);
|
synchronized (filesystems) {
|
||||||
FileSystem zipDisk = getZipDisk(artifactId);
|
setArchived(artifactId, false);
|
||||||
Files.copy(artifactStream, getZipDiskPath(artifactId, zipDisk),
|
FileSystem zipDisk = getZipDisk(artifactId);
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(artifactStream, getZipDiskPath(artifactId, zipDisk),
|
||||||
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
// we need to close the zipDisk in order to flush it to disk
|
// we need to close the zipDisk in order to flush it to disk
|
||||||
closeZipDisk(artifactId);
|
closeZipDisk(artifactId);
|
||||||
setArchived(artifactId, true);
|
setArchived(artifactId, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,7 +131,7 @@ public class FileSystemArchive implements ArchiveService {
|
||||||
|
|
||||||
Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip");
|
Path zipDiskPath = archivePath.resolve(flingId.toString() + ".zip");
|
||||||
log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath);
|
log.debug("Deleting fling [.id={}] at {}", flingId, zipDiskPath);
|
||||||
if(Files.exists(zipDiskPath)) {
|
if (Files.exists(zipDiskPath)) {
|
||||||
Files.delete(zipDiskPath);
|
Files.delete(zipDiskPath);
|
||||||
} else {
|
} else {
|
||||||
log.warn("No fling disk found at {}", zipDiskPath);
|
log.warn("No fling disk found at {}", zipDiskPath);
|
||||||
|
@ -142,7 +144,6 @@ public class FileSystemArchive implements ArchiveService {
|
||||||
private void setArchived(UUID artifactId, boolean archived) {
|
private void setArchived(UUID artifactId, boolean archived) {
|
||||||
ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId);
|
ArtifactEntity artifactEntity = artifactRepository.getOne(artifactId);
|
||||||
artifactEntity.setArchived(archived);
|
artifactEntity.setArchived(archived);
|
||||||
|
|
||||||
log.debug("Artifact[.id={}] set to {} archived", artifactId, archived ? "" : "not");
|
log.debug("Artifact[.id={}] set to {} archived", artifactId, archived ? "" : "not");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6488
web/fling/package-lock.json
generated
6488
web/fling/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,42 +1,65 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Switch, Route, useLocation, Link} from "react-router-dom";
|
import { Switch, Route, useLocation, Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
import FlingArtifacts from './FlingArtifacts';
|
import FlingArtifacts from './FlingArtifacts';
|
||||||
import Upload from './Upload';
|
import Upload from './Upload';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
|
|
||||||
export default function FlingContent(props) {
|
export default function FlingContent() {
|
||||||
let location = useLocation();
|
const location = useLocation();
|
||||||
|
const activeFling = useSelector(state => state.flings.activeFling);
|
||||||
|
|
||||||
|
function Empty() {
|
||||||
|
return (
|
||||||
|
<div className="empty">
|
||||||
|
<div className="empty-icon">
|
||||||
|
<i className="icon icon-search icon-2x"></i>
|
||||||
|
</div>
|
||||||
|
<p className="empty-title h5">No Fling selected</p>
|
||||||
|
<p className="empty-subtitle">Select a fling from the list</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Content() {
|
||||||
function path(tail) {
|
function path(tail) {
|
||||||
return `/admin/${props.activeFling}/${tail}`;
|
return `/admin/${activeFling.id}/${tail}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<div className="fling-content p-2">
|
<div className="fling-content p-2">
|
||||||
{log.info("FlingContent location ", location)}
|
{log.info("FlingContent location ", location)}
|
||||||
{log.info("FlingContent active fling ", props.activeFling)}
|
{log.info("FlingContent active fling ", activeFling)}
|
||||||
<ul className="tab tab-block mt-0">
|
<ul className="tab tab-block mt-0">
|
||||||
<li className={`tab-item ${location.pathname !== path("upload") && location.pathname !== path("settings") ? "active": ""}`}>
|
<li className={`tab-item ${location.pathname !== path("upload") && location.pathname !== path("settings") ? "active" : ""}`}>
|
||||||
<Link to={path("files")}>Files</Link>
|
<Link to={path("files")}>Files</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className={`tab-item ${location.pathname === path("upload") ? "active": ""}`}>
|
<li className={`tab-item ${location.pathname === path("upload") ? "active" : ""}`}>
|
||||||
<Link to={path("upload")}>Upload</Link>
|
<Link to={path("upload")}>Upload</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className={`tab-item ${location.pathname === path("settings") ? "active": ""}`}>
|
<li className={`tab-item ${location.pathname === path("settings") ? "active" : ""}`}>
|
||||||
<Link to={path("settings")}>Settings</Link>
|
<Link to={path("settings")}>Settings</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/admin/:fling"><FlingArtifacts activeFling={props.activeFling} /></Route>
|
<Route exact path="/admin/:fling"><FlingArtifacts /></Route>
|
||||||
<Route path="/admin/:fling/files"><FlingArtifacts activeFling={props.activeFling} /></Route>
|
<Route path="/admin/:fling/files"><FlingArtifacts /></Route>
|
||||||
<Route path="/admin/:fling/upload"><Upload activeFling={props.activeFling} /></Route>
|
<Route path="/admin/:fling/upload"><Upload /></Route>
|
||||||
<Route path="/admin/:fling/settings"><Settings activeFling={props.activeFling} /></Route>
|
<Route path="/admin/:fling/settings"><Settings /></Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ activeFling ? Content() : Empty() }
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ function TileAction(props) {
|
||||||
<li className="menu-item">
|
<li className="menu-item">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-switch">
|
<label className="form-switch">
|
||||||
<input type="checkbox" checked={props.fling.shared} />
|
<input type="checkbox" disabled checked={props.fling.shared} />
|
||||||
<i className="form-icon" />
|
<i className="form-icon" />
|
||||||
{props.fling.shared ? "Shared" : "Private"}
|
{props.fling.shared ? "Shared" : "Private"}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState} from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
import {useHistory, useLocation} from 'react-router-dom';
|
import {useHistory, useLocation} from 'react-router-dom';
|
||||||
|
|
||||||
import {fc, AuthClient} from '../../util/fc';
|
import {fc, AuthClient} from '../../util/fc';
|
||||||
|
@ -11,6 +11,25 @@ export default function Login() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { from } = location.state || { from: { pathname: "/admin" } };
|
const { from } = location.state || { from: { pathname: "/admin" } };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sessionStorage.removeItem("token")
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
let authClient = new AuthClient();
|
||||||
|
let opt = {adminAuth: new fc.AdminAuth(username, password)};
|
||||||
|
|
||||||
|
authClient.authenticateOwner(opt)
|
||||||
|
.then(response => {
|
||||||
|
log.info("Login successful");
|
||||||
|
sessionStorage.setItem('token', response);
|
||||||
|
log.debug("Returning back to", from);
|
||||||
|
history.replace(from);
|
||||||
|
}).catch(log.error);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-center">
|
<div className="container-center">
|
||||||
<div>
|
<div>
|
||||||
|
@ -41,19 +60,4 @@ export default function Login() {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleSubmit(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
let authClient = new AuthClient();
|
|
||||||
let opt = {adminAuth: new fc.AdminAuth(username, password)};
|
|
||||||
|
|
||||||
authClient.authenticateOwner(opt)
|
|
||||||
.then(response => {
|
|
||||||
log.info("Login successful");
|
|
||||||
sessionStorage.setItem('token', response);
|
|
||||||
log.debug("Returning back to", from);
|
|
||||||
history.replace(from);
|
|
||||||
}).catch(log.error);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,217 +1,208 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState, useEffect, useRef} from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
import {artifactClient} from '../../util/flingclient';
|
import { ArtifactClient, FlingClient, fc } from '../../util/fc';
|
||||||
|
import { prettifyBytes, prettifyTimestamp } from '../../util/fn';
|
||||||
|
|
||||||
import upload from '../resources/upload.svg';
|
import upload from '../resources/upload.svg';
|
||||||
import drop from '../resources/drop.svg';
|
import drop from '../resources/drop.svg';
|
||||||
|
|
||||||
|
export default function Upload() {
|
||||||
|
let fileInputRef = useRef(null);
|
||||||
|
let [files, setFiles] = useState([]);
|
||||||
|
let [dragging, setDragging] = useState(false);
|
||||||
|
let [dragCount, setDragCount] = useState(0);
|
||||||
|
|
||||||
export default function Upload(props) {
|
const activeFling = useSelector(state => state.flings.activeFling);
|
||||||
let fileInputRef = useRef(null);
|
|
||||||
let [files, setFiles] = useState([]);
|
|
||||||
let [dragging, setDragging] = useState(false);
|
|
||||||
let [dragCount, setDragCount] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// prevent browser from trying to open the file when drag event
|
// prevent browser from trying to open the file when drag event not
|
||||||
// not recognized properly
|
// recognized properly
|
||||||
window.addEventListener("dragover",function(e){
|
window.addEventListener("dragover", e => e.preventDefault(), false);
|
||||||
e.preventDefault();
|
window.addEventListener("drop", e => e.preventDefault(), false);
|
||||||
},false);
|
});
|
||||||
window.addEventListener("drop",function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
},false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function fileList() {
|
function fileList() {
|
||||||
function readableBytes(bytes) {
|
let fileList = [];
|
||||||
if(bytes <= 0) return "0 KB";
|
files.forEach((file, idx) => {
|
||||||
|
if (!file.uploaded) {
|
||||||
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
|
fileList.push(
|
||||||
sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
<div key={idx} className="column col-6 col-md-12 mb-2">
|
||||||
|
<div className="card">
|
||||||
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
|
<div className="card-header">
|
||||||
}
|
<i className="icon icon-cross float-right c-hand"
|
||||||
|
onClick={ev => deleteFile(idx)} />
|
||||||
let fileList = [];
|
<div className="card-title h5">{file.name}</div>
|
||||||
files.forEach((file,idx) => {
|
<div className="card-subtitle text-gray">
|
||||||
if(!file.uploaded) {
|
{`${prettifyTimestamp(file.lastModified)}, ` +
|
||||||
fileList.push(
|
`${prettifyBytes(file.size)}`}
|
||||||
<div className="column col-6 col-md-12 mb-2">
|
|
||||||
<div className="card">
|
|
||||||
<div className="card-header">
|
|
||||||
<i className="icon icon-cross float-right c-hand" onClick={ev => deleteFile(idx)}/>
|
|
||||||
<div className="card-title h5">{file.name}</div>
|
|
||||||
<div className="card-subtitle text-gray">{(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return fileList;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteFile(idx) {
|
|
||||||
let f = [...files];
|
|
||||||
f.splice(idx, 1);
|
|
||||||
setFiles(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
function totalSize() {
|
|
||||||
function readableBytes(bytes) {
|
|
||||||
if(bytes <= 0) return "0 KB";
|
|
||||||
|
|
||||||
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
||||||
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
||||||
|
|
||||||
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalSize = 0;
|
|
||||||
for(let file of files) {
|
|
||||||
totalSize += file.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return readableBytes(totalSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClick(ev) {
|
|
||||||
fileInputRef.current.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFileInputChange(ev) {
|
|
||||||
let fileInputFiles = fileInputRef.current.files;
|
|
||||||
if (!fileInputFiles) {
|
|
||||||
console.warn("No files selected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFiles([...files, ...fileInputFiles]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDrop(ev) {
|
|
||||||
stopEvent(ev);
|
|
||||||
ev.persist();
|
|
||||||
|
|
||||||
let evFiles = ev.dataTransfer.files;
|
|
||||||
|
|
||||||
if (!evFiles) {
|
|
||||||
console.warn("Dropzone triggered without files");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFiles([...files, ...fileListToArray(evFiles)]);
|
|
||||||
setDragging(false);
|
|
||||||
setDragCount(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileListToArray(fileList) {
|
|
||||||
if(fileList === undefined || fileList === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let arr = [];
|
|
||||||
for (let i=0; i<fileList.length; i++) { arr.push(fileList[i]); }
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnDragEnter(ev) {
|
|
||||||
stopEvent(ev);
|
|
||||||
if(dragCount === 0) setDragging(true);
|
|
||||||
|
|
||||||
setDragCount(dragCount+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOnDragLeave(ev) {
|
|
||||||
stopEvent(ev);
|
|
||||||
let dc = dragCount;
|
|
||||||
|
|
||||||
dc -= 1;
|
|
||||||
setDragCount(dc);
|
|
||||||
|
|
||||||
if(dc === 0) setDragging(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopEvent(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
function logFiles() {
|
|
||||||
log.info("Files so far: ["+files.map((i) => i.name).join(',')+"]");
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFileUploaded(idx) {
|
|
||||||
let f = [...files];
|
|
||||||
f[idx].uploaded = true;
|
|
||||||
setFiles(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleUpload() {
|
|
||||||
files.forEach((file, idx) => {
|
|
||||||
artifactClient.postArtifact(props.activeFling, file)
|
|
||||||
.then(response => {
|
|
||||||
setFileUploaded(idx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function zoneContent(dragging) {
|
|
||||||
if(dragging){
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
<img className="dropzone-icon" alt="dropzone icon" src={drop} />
|
|
||||||
<h5 className="text-primary">Drop now!</h5>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}else {
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
<img className="dropzone-icon-upload" alt="dropzone icon" src={upload} />
|
|
||||||
<h5>Click or Drop</h5>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return(
|
|
||||||
<div className="container">
|
|
||||||
{logFiles()}
|
|
||||||
<div className="columns">
|
|
||||||
<div className="column col-4 col-sm-12">
|
|
||||||
<div className="dropzone c-hand py-2"
|
|
||||||
onDrop={handleDrop}
|
|
||||||
onClick={handleClick}
|
|
||||||
onDragOver={stopEvent}
|
|
||||||
onDragEnter={handleOnDragEnter}
|
|
||||||
onDragLeave={handleOnDragLeave}>
|
|
||||||
|
|
||||||
<input className="d-hide" ref={fileInputRef} type="file" multiple onChange={handleFileInputChange} />
|
|
||||||
{zoneContent(dragging)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="column col-8 col-sm-12" >
|
|
||||||
<div className="file-list">
|
|
||||||
<div className="row">
|
|
||||||
<div className="container">
|
|
||||||
<div className="columns">
|
|
||||||
{fileList()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="upload-command-line m-2">
|
|
||||||
<span className="total-upload">Total Size: {totalSize()}</span>
|
|
||||||
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteFile(idx) {
|
||||||
|
let f = [...files];
|
||||||
|
f.splice(idx, 1);
|
||||||
|
setFiles(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalSize() {
|
||||||
|
let totalSize = 0;
|
||||||
|
for (let file of files) {
|
||||||
|
totalSize += file.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prettifyBytes(totalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(ev) {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileInputChange(ev) {
|
||||||
|
let fileInputFiles = fileInputRef.current.files;
|
||||||
|
if (!fileInputFiles) {
|
||||||
|
console.warn("No files selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFiles([...files, ...fileInputFiles]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(ev) {
|
||||||
|
stopEvent(ev);
|
||||||
|
ev.persist();
|
||||||
|
|
||||||
|
let evFiles = ev.dataTransfer.files;
|
||||||
|
|
||||||
|
if (!evFiles) {
|
||||||
|
console.warn("Dropzone triggered without files");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFiles([...files, ...fileListToArray(evFiles)]);
|
||||||
|
setDragging(false);
|
||||||
|
setDragCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileListToArray(fileList) {
|
||||||
|
if (fileList === undefined || fileList === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let arr = [];
|
||||||
|
for (let i = 0; i < fileList.length; i++) { arr.push(fileList[i]); }
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnDragEnter(ev) {
|
||||||
|
stopEvent(ev);
|
||||||
|
if (dragCount === 0) setDragging(true);
|
||||||
|
|
||||||
|
setDragCount(dragCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOnDragLeave(ev) {
|
||||||
|
stopEvent(ev);
|
||||||
|
let dc = dragCount;
|
||||||
|
|
||||||
|
dc -= 1;
|
||||||
|
setDragCount(dc);
|
||||||
|
|
||||||
|
if (dc === 0) setDragging(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEvent(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function logFiles() {
|
||||||
|
log.info("Files so far: [" + files.map((i) => i.name).join(',') + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFileUploaded(idx) {
|
||||||
|
let f = [...files];
|
||||||
|
f[idx].uploaded = true;
|
||||||
|
setFiles(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpload() {
|
||||||
|
const flingClient = new FlingClient();
|
||||||
|
const artifactClient = new ArtifactClient();
|
||||||
|
|
||||||
|
files.forEach((file, idx) => {
|
||||||
|
let artifact = new fc.Artifact(file.name)
|
||||||
|
|
||||||
|
flingClient.postArtifact(activeFling.id, { artifact: artifact })
|
||||||
|
.then(artifact => {
|
||||||
|
artifactClient.uploadArtifactData(artifact.id, { body: file });
|
||||||
|
setFileUploaded(idx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneContent(dragging) {
|
||||||
|
if (dragging) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className="dropzone-icon" alt="dropzone icon" src={drop} />
|
||||||
|
<h5 className="text-primary">Drop now!</h5>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className="dropzone-icon-upload" alt="dropzone icon" src={upload} />
|
||||||
|
<h5>Click or Drop</h5>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
{logFiles()}
|
||||||
|
<div className="columns">
|
||||||
|
<div className="column col-4 col-sm-12">
|
||||||
|
<div className="dropzone c-hand py-2"
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onClick={handleClick}
|
||||||
|
onDragOver={stopEvent}
|
||||||
|
onDragEnter={handleOnDragEnter}
|
||||||
|
onDragLeave={handleOnDragLeave}>
|
||||||
|
|
||||||
|
<input className="d-hide" ref={fileInputRef} type="file" multiple onChange={handleFileInputChange} />
|
||||||
|
{zoneContent(dragging)}
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
<div className="column col-8 col-sm-12" >
|
||||||
|
<div className="file-list">
|
||||||
|
<div className="row">
|
||||||
|
<div className="container">
|
||||||
|
<div className="columns">
|
||||||
|
{fileList()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="upload-command-line m-2">
|
||||||
|
<span className="total-upload">Total Size: {totalSize()}</span>
|
||||||
|
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
39
web/fling/src/redux/reducers/artifacts.js
Normal file
39
web/fling/src/redux/reducers/artifacts.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import log from "loglevel";
|
||||||
|
import produce from "immer";
|
||||||
|
|
||||||
|
import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "../actionTypes";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
// type [fc.Artifact]
|
||||||
|
aritfacts: []
|
||||||
|
}
|
||||||
|
|
||||||
|
export default produce((draft, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_FLINGS:
|
||||||
|
draft.flings = action.payload;
|
||||||
|
break;
|
||||||
|
case ADD_FLING:
|
||||||
|
// Check storage again here, otherwise there could be a race
|
||||||
|
// condition due to async calls of SET_FLINGS and ADD_FLING
|
||||||
|
let foundFlingIdx = draft.flings.findIndex(fling =>
|
||||||
|
fling.id === action.payload.id);
|
||||||
|
|
||||||
|
if (foundFlingIdx === -1) {
|
||||||
|
log.debug(`Adding new fling with id ${action.payload.id}`)
|
||||||
|
draft.flings.push(action.payload);
|
||||||
|
} else {
|
||||||
|
log.debug(`Fling already exists. ` +
|
||||||
|
`Updating fling with id ${action.payload.id}`)
|
||||||
|
draft.flings[foundFlingIdx] = action.payload
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SET_ACTIVE_FLING:
|
||||||
|
draft.activeFling = action.payload;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return draft;
|
||||||
|
|
||||||
|
}, initialState);
|
19
web/fling/src/util/fn.js
Normal file
19
web/fling/src/util/fn.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Returns a human readable presentation of `bytes`
|
||||||
|
*/
|
||||||
|
export function prettifyBytes(bytes) {
|
||||||
|
if (bytes <= 0) return "0 KB";
|
||||||
|
|
||||||
|
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
|
||||||
|
sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a human readable date for a unix timestamp in milliseconds
|
||||||
|
*/
|
||||||
|
export function prettifyTimestamp(timestamp, withTime=false) {
|
||||||
|
let date = new Date(timestamp);
|
||||||
|
return withTime ? date.toLocaleString(): date.toLocaleDateString();
|
||||||
|
}
|
Loading…
Reference in a new issue