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:
Armin Friedl 2020-05-21 16:54:42 +02:00
parent 4ab3bf705e
commit b0a7e8b443
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
5 changed files with 176 additions and 39 deletions

View file

@ -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+"'");

View file

@ -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);
}
} }

View file

@ -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>

View file

@ -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>

View file

@ -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 {
}