Expiration settings, protection settings, fix drop area hover
- Front end for expiration time/date - Setting expiration via `PUT /fling/{flingId}` - Settings for protection code enabled in front-end (no backend) - Upload drop container used to loose focus when hovering over child elements. This is fixed now.
This commit is contained in:
parent
4ab3bf705e
commit
b0a7e8b443
5 changed files with 176 additions and 39 deletions
|
@ -54,18 +54,19 @@ public class FlingDto {
|
||||||
|
|
||||||
@JsonProperty("expiration")
|
@JsonProperty("expiration")
|
||||||
private void unpackExpiration(Map<String, Object> expiration) {
|
private void unpackExpiration(Map<String, Object> expiration) {
|
||||||
String type = (String) expiration.getOrDefault("expirationType", null);
|
String type = (String) expiration.getOrDefault("type", null);
|
||||||
if(type == null) return;
|
if(type == null) return;
|
||||||
|
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case "time":
|
case "time":
|
||||||
this.expirationClicks = null;
|
this.expirationClicks = null;
|
||||||
// json can only handle int, long must be given as string
|
// json can only handle int, long must be given as string
|
||||||
this.expirationTime = Instant.ofEpochMilli(Long.parseLong((String) expiration.get("value")));
|
// TODO: this back and forth conversion is a bit hack-ish
|
||||||
|
this.expirationTime = Instant.ofEpochMilli(Long.valueOf(expiration.get("value").toString()));
|
||||||
break;
|
break;
|
||||||
case "clicks":
|
case "clicks":
|
||||||
this.expirationTime = null;
|
this.expirationTime = null;
|
||||||
this.expirationClicks = (Integer) expiration.get("value");
|
this.expirationClicks = Integer.valueOf(expiration.get("value").toString());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unexpected value '"+type+"'");
|
throw new IllegalArgumentException("Unexpected value '"+type+"'");
|
||||||
|
|
|
@ -65,8 +65,8 @@ public class FlingService {
|
||||||
|
|
||||||
mergeNonEmpty(flingDto::getAllowUpload, flingEntity::setAllowUpload);
|
mergeNonEmpty(flingDto::getAllowUpload, flingEntity::setAllowUpload);
|
||||||
mergeNonEmpty(flingDto::getDirectDownload, flingEntity::setDirectDownload);
|
mergeNonEmpty(flingDto::getDirectDownload, flingEntity::setDirectDownload);
|
||||||
mergeNonEmpty(flingDto::getExpirationClicks, flingEntity::setExpirationClicks);
|
mergeWithEmpty(flingDto::getExpirationClicks, flingEntity::setExpirationClicks);
|
||||||
mergeNonEmpty(flingDto::getExpirationTime, flingEntity::setExpirationTime);
|
mergeWithEmpty(flingDto::getExpirationTime, flingEntity::setExpirationTime);
|
||||||
mergeNonEmpty(flingDto::getName, flingEntity::setName);
|
mergeNonEmpty(flingDto::getName, flingEntity::setName);
|
||||||
mergeNonEmpty(flingDto::getShared, flingEntity::setShared);
|
mergeNonEmpty(flingDto::getShared, flingEntity::setShared);
|
||||||
mergeNonEmpty(flingDto::getShareUrl, flingEntity::setShareUrl);
|
mergeNonEmpty(flingDto::getShareUrl, flingEntity::setShareUrl);
|
||||||
|
@ -131,4 +131,9 @@ public class FlingService {
|
||||||
T r = sup.get();
|
T r = sup.get();
|
||||||
if(r != null) con.accept(r);
|
if(r != null) con.accept(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T> void mergeWithEmpty(Supplier<T> sup, Consumer<T> con) {
|
||||||
|
T r = sup.get();
|
||||||
|
con.accept(r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,20 @@ import classNames from 'classnames';
|
||||||
import {flingClient} from '../../util/flingclient';
|
import {flingClient} from '../../util/flingclient';
|
||||||
|
|
||||||
export default function Settings(props) {
|
export default function Settings(props) {
|
||||||
let [fling, setFling] = useState({name: "", sharing: {directDownload: false, allowUpload: true, shared: true, shareUrl: ""}});
|
let [fling, setFling] = useState({name: "", sharing: {directDownload: false, allowUpload: true, shared: true, shareUrl: "", authCode: ""},
|
||||||
|
expiration: {type: "clicks", value: 0}});
|
||||||
let [shareUrlUnique, setShareUrlUnique] = useState(true);
|
let [shareUrlUnique, setShareUrlUnique] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.activeFling) {
|
if(props.activeFling) {
|
||||||
flingClient.getFling(props.activeFling)
|
flingClient.getFling(props.activeFling)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
setFling(result);
|
let f = {...fling, ...result};
|
||||||
|
let s = {...fling.sharing, ...result.sharing};
|
||||||
|
let e = {...fling.expiration, ...result.expiration};
|
||||||
|
f.sharing = s;
|
||||||
|
f.expiration = e;
|
||||||
|
setFling(f);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [props.activeFling]);
|
}, [props.activeFling]);
|
||||||
|
@ -80,6 +86,61 @@ export default function Settings(props) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setName(ev) {
|
||||||
|
let f = {...fling};
|
||||||
|
let value = ev.currentTarget.value;
|
||||||
|
|
||||||
|
f.name = value;
|
||||||
|
setFling(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setExpirationType(ev) {
|
||||||
|
let f = {...fling};
|
||||||
|
let e = {...fling.expiration}; //TODO: sharing is not cloned
|
||||||
|
let value = ev.currentTarget.value;
|
||||||
|
|
||||||
|
if(value === "never") {
|
||||||
|
e = {};
|
||||||
|
} else {
|
||||||
|
e.type = value;
|
||||||
|
e.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
f.expiration = e;
|
||||||
|
setFling(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setExpirationValue(ev) {
|
||||||
|
let f = {...fling};
|
||||||
|
let e = {...fling.expiration}; //TODO: sharing is not cloned
|
||||||
|
let value = e.type === "time" ? ev.currentTarget.valueAsNumber: ev.currentTarget.value;
|
||||||
|
|
||||||
|
e.value = value;
|
||||||
|
|
||||||
|
f.expiration = e;
|
||||||
|
setFling(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatExpirationTime() {
|
||||||
|
if (!fling.expiration || !fling.expiration.value || fling.expiration.type !== "time")
|
||||||
|
return "";
|
||||||
|
|
||||||
|
|
||||||
|
let date = new Date(fling.expiration.value);
|
||||||
|
let fmt = date.toISOString().split("T")[0];
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAuthCode(ev) {
|
||||||
|
let f = {...fling};
|
||||||
|
let s = {...fling.sharing};
|
||||||
|
let value = ev.currentTarget.value;
|
||||||
|
|
||||||
|
s.authCode = value;
|
||||||
|
f.sharing = s;
|
||||||
|
setFling(f);
|
||||||
|
}
|
||||||
|
|
||||||
function handleSubmit(ev) {
|
function handleSubmit(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
log.info(fling);
|
log.info(fling);
|
||||||
|
@ -88,37 +149,100 @@ export default function Settings(props) {
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<div className="container">
|
<div className="container">
|
||||||
{log.info(props)}
|
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column col-6">
|
<div className="p-centered column col-xl-9 col-sm-12 col-6">
|
||||||
<form onSubmit={handleSubmit}>
|
<form className="form-horizontal" onSubmit={handleSubmit}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-label" htmlFor="input-name">Name</label>
|
<div className="col-3 col-sm-12">
|
||||||
<input className="form-input" type="text" id="input-name" value={fling.name} />
|
<label className="form-label" htmlFor="input-name">Name</label>
|
||||||
|
</div>
|
||||||
<label className="form-label" htmlFor="input-share-url">Share</label>
|
<div className="col-9 col-sm-12">
|
||||||
<input className="form-input" type="text" id="input-share-url" value={fling.sharing.shareUrl} onChange={setShareUrl} />
|
<input className="form-input" type="text" id="input-name" value={fling.name} onChange={setName}/>
|
||||||
<i className={`icon icon-cross text-error ${shareUrlUnique ? "d-hide": "d-visible"}`} />
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-3 col-sm-12">
|
||||||
|
<label className="form-label" htmlFor="input-share-url">Share URL</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-9 col-sm-12">
|
||||||
|
<input className="form-input" type="text" id="input-share-url" value={fling.sharing.shareUrl} onChange={setShareUrl} />
|
||||||
|
<i className={`icon icon-cross text-error ${shareUrlUnique ? "d-hide": "d-visible"}`} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="form-switch form-inline">
|
<div className="col-3 col-sm-12">
|
||||||
<input type="checkbox" id="direct-download" checked={fling.sharing.directDownload} onChange={toggleSharing}/>
|
<label className="form-label" htmlFor="input-passcode">Passcode</label>
|
||||||
<i className="form-icon" /> Direct Download
|
</div>
|
||||||
</label>
|
<div className="col-9 col-sm-12">
|
||||||
|
<div className="input-group">
|
||||||
|
<input className="form-input" type="text" value={fling.sharing.authCode} onChange={setAuthCode} />
|
||||||
|
<label className="form-switch ml-2 popover popover-bottom">
|
||||||
|
<input type="checkbox" checked={!!fling.sharing.authCode} readOnly />
|
||||||
|
<i className="form-icon" /> Protected
|
||||||
|
<div className="popover-container card">
|
||||||
|
<div className="card-body">
|
||||||
|
{!!fling.sharing.authCode ? "Delete the passcode to disable protection": "Set a passcode to enable protection"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<label className="form-switch form-inline">
|
</div>
|
||||||
<input type="checkbox" id="allow-upload" checked={fling.sharing.allowUpload} onChange={toggleSharing}/>
|
</div>
|
||||||
<i className="form-icon" /> Allow Uploads
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label className="form-switch form-inline">
|
|
||||||
<input type="checkbox" id="shared" checked={fling.sharing.shared} onChange={toggleSharing}/>
|
|
||||||
<i className="form-icon" /> Shared
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="submit" className="btn float-right" value="Save" />
|
<div className="form-group">
|
||||||
|
<div className="col-3 col-sm-12">
|
||||||
|
<label className="form-label">Expiration</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-9 col-sm-12">
|
||||||
|
<div className="form-group">
|
||||||
|
<select className="form-select" value={fling.expiration.type} onChange={setExpirationType}>
|
||||||
|
<option value="never">Never</option>
|
||||||
|
<option value="time">Date</option>
|
||||||
|
<option value="clicks">Clicks</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={fling.expiration.type === "clicks" ? "d-visible": "d-hide"}>
|
||||||
|
<div className="input-group">
|
||||||
|
<span className="input-group-addon">Expire after</span>
|
||||||
|
<input className="form-input" type="number" value={fling.expiration.value || ""} onChange={setExpirationValue} />
|
||||||
|
<span className="input-group-addon">Clicks</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={fling.expiration.type === "time" ? "d-visible": "d-hide"}>
|
||||||
|
<div className="input-group">
|
||||||
|
<span className="input-group-addon">Expire after</span>
|
||||||
|
<input className="form-input" type="date" value={formatExpirationTime()} onChange={setExpirationValue} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<div className="col-3 col-sm-12">
|
||||||
|
<label className="form-label">Settings</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-9 col-sm-12">
|
||||||
|
|
||||||
|
<label className="form-switch form-inline">
|
||||||
|
<input type="checkbox" id="shared" checked={fling.sharing.shared} onChange={toggleSharing}/>
|
||||||
|
<i className="form-icon" /> Shared
|
||||||
|
</label>
|
||||||
|
<label className="form-switch form-inline">
|
||||||
|
<input type="checkbox" id="allow-upload" checked={fling.sharing.allowUpload} onChange={toggleSharing}/>
|
||||||
|
<i className="form-icon" /> Uploads
|
||||||
|
</label>
|
||||||
|
<label className="form-switch form-inline">
|
||||||
|
<input type="checkbox" id="direct-download" checked={fling.sharing.directDownload} onChange={toggleSharing}/>
|
||||||
|
<i className="form-icon" /> Direct Download
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" className="btn btn-primary float-right" value="Save" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -169,10 +169,9 @@ export default function Upload(props) {
|
||||||
onDragEnter={handleOnDragEnter}
|
onDragEnter={handleOnDragEnter}
|
||||||
onDragLeave={handleOnDragLeave}>
|
onDragLeave={handleOnDragLeave}>
|
||||||
|
|
||||||
<div className="dropzone c-hand py-2">
|
<div className="dropzone c-hand py-2" onDragLeave={stopEvent} >
|
||||||
<input className="d-hide" ref={fileInputRef} type="file" multiple
|
<input className="d-hide" ref={fileInputRef} type="file" multiple
|
||||||
onDragOver={stopEvent} onDragEnter={stopEvent} onDragLeave={stopEvent}
|
onDragLeave={stopEvent} onChange={handleFileInputChange} />
|
||||||
onChange={handleFileInputChange} />
|
|
||||||
{zoneContent(dragging)}
|
{zoneContent(dragging)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -186,7 +185,7 @@ export default function Upload(props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="upload-command-line m-2">
|
||||||
<span className="total-upload">Total Size: {totalSize()}</span>
|
<span className="total-upload">Total Size: {totalSize()}</span>
|
||||||
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
<button className="btn btn-primary btn-upload" onClick={handleUpload}>Upload</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -207,11 +207,6 @@ tbody td {
|
||||||
|
|
||||||
.total-upload {
|
.total-upload {
|
||||||
float: left;
|
float: left;
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-upload {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-input {
|
.my-input {
|
||||||
|
@ -221,3 +216,16 @@ tbody td {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload-command-line {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************\
|
||||||
|
| Settings |
|
||||||
|
\************/
|
||||||
|
|
||||||
|
.share-settings {
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue