0.1 #1
7 changed files with 317 additions and 263 deletions
|
@ -52,8 +52,10 @@ GET http://localhost:8080/api/auth/derive
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
:token
|
:token
|
||||||
|
|
||||||
|
:derivedToken = 56c4ff2e-7da7-4582-bd2c-9a81d9a13abb
|
||||||
|
|
||||||
#
|
#
|
||||||
:flingId = dfc208a3-5924-43b4-aa6a-c263541dca5e
|
:flingId = 9f7353a3-efaa-41af-9f93-61e02dc5e440
|
||||||
|
|
||||||
# Get one fling
|
# Get one fling
|
||||||
GET http://localhost:8080/api/fling/:flingId
|
GET http://localhost:8080/api/fling/:flingId
|
||||||
|
@ -61,8 +63,15 @@ GET http://localhost:8080/api/fling/:flingId
|
||||||
|
|
||||||
# Get all artifacts
|
# Get all artifacts
|
||||||
GET http://localhost:8080/api/fling/:flingId/artifacts
|
GET http://localhost:8080/api/fling/:flingId/artifacts
|
||||||
|
Content-Type: application/json
|
||||||
:token
|
:token
|
||||||
|
|
||||||
|
:artifactId = 01ba7fb9-9f2e-4809-9b2b-cbce12a92621
|
||||||
|
|
||||||
|
# Get artifact data by derived token
|
||||||
|
GET http://localhost:8080/api/artifacts/:artifactId/data?derivedtoken=:derivedToken
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
#
|
#
|
||||||
GET https://httpbin.org/json
|
GET https://httpbin.org/json
|
||||||
-> jq-set-var :my-var .slideshow.slides[0].title
|
-> jq-set-var :my-var .slideshow.slides[0].title
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
@ -59,6 +60,11 @@ public class FlingController {
|
||||||
return flingService.create(flingDto);
|
return flingService.create(flingDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public FlingDto putFling(@PathVariable UUID id, @RequestBody @Valid FlingDto flingDto) {
|
||||||
|
return flingService.replace(id, flingDto);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/{id}/artifacts")
|
@PostMapping("/{id}/artifacts")
|
||||||
public ArtifactDto postArtifact(@PathVariable UUID id,
|
public ArtifactDto postArtifact(@PathVariable UUID id,
|
||||||
@RequestBody @Valid ArtifactDto artifactDto) {
|
@RequestBody @Valid ArtifactDto artifactDto) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import javax.persistence.EntityNotFoundException;
|
import javax.persistence.EntityNotFoundException;
|
||||||
import javax.transaction.Transactional;
|
import javax.transaction.Transactional;
|
||||||
|
import javax.validation.Valid;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
@ -147,4 +148,23 @@ public class FlingService {
|
||||||
log.debug("Generated share id {}", shareId);
|
log.debug("Generated share id {}", shareId);
|
||||||
return shareId;
|
return shareId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FlingDto replace(UUID id, @Valid FlingDto flingDto) {
|
||||||
|
FlingEntity flingEntity = flingRepository.getOne(id);
|
||||||
|
flingEntity.setId(id);
|
||||||
|
flingEntity.setAllowUpload(flingDto.getAllowUpload());
|
||||||
|
flingEntity.setDirectDownload(flingDto.getDirectDownload());
|
||||||
|
flingEntity.setExpirationClicks(flingDto.getExpirationClicks());
|
||||||
|
flingEntity.setExpirationTime(flingDto.getExpirationTime());
|
||||||
|
flingEntity.setName(flingDto.getName());
|
||||||
|
flingEntity.setShareId(flingDto.getShareId());
|
||||||
|
|
||||||
|
if(!flingEntity.getAuthCode().equals(flingDto.getAuthCode())
|
||||||
|
&& !flingEntity.getAuthCode().equals(hashAuthCode(flingDto.getAuthCode()))) {
|
||||||
|
|
||||||
|
flingEntity.setAuthCode(hashAuthCode(flingDto.getAuthCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return flingMapper.map(flingEntity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
@ -108,6 +109,27 @@ public class FlingControllerTest {
|
||||||
.andExpect(status().isBadRequest());
|
.andExpect(status().isBadRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replaceFling_validatesBody_notOk() throws Exception {
|
||||||
|
FlingDto invalidFlingDto = new FlingDto();
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/fling/{id}", flingId)
|
||||||
|
.content(mapper.writeValueAsString(invalidFlingDto))
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replaceFling_ok() throws Exception {
|
||||||
|
FlingDto flingDto = new FlingDto(flingId, "new-name", Instant.EPOCH, "shareId", "new-authCode",
|
||||||
|
false, true, true, 1, null);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/fling/{id}", flingId)
|
||||||
|
.content(mapper.writeValueAsString(flingDto))
|
||||||
|
.contentType(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void postArtifact_ok() throws Exception {
|
public void postArtifact_ok() throws Exception {
|
||||||
mockMvc.perform(post("/api/fling/{id}/artifacts", flingId)
|
mockMvc.perform(post("/api/fling/{id}/artifacts", flingId)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.atLeast;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -155,6 +156,40 @@ public class FlingServiceTest {
|
||||||
assertThat(createdFling.getShareId(), is("test"));
|
assertThat(createdFling.getShareId(), is("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replace_newName_expirationClicks_setsNameAndExpirationClicks() {
|
||||||
|
FlingDto flingDto = FlingDto.builder()
|
||||||
|
.name("testName")
|
||||||
|
.authCode(flingEntity1.getAuthCode())
|
||||||
|
.expirationClicks(2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1);
|
||||||
|
FlingDto updatedEntity = flingService.replace(flingEntity1.getId(), flingDto);
|
||||||
|
|
||||||
|
assertThat(updatedEntity.getId(), is(flingEntity1.getId()));
|
||||||
|
assertThat(updatedEntity.getName(), is("testName"));
|
||||||
|
assertThat(updatedEntity.getAuthCode(), is(flingEntity1.getAuthCode()));
|
||||||
|
assertThat(updatedEntity.getExpirationClicks(), is(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replace_newAuthCode_setsNewHashedAuthCode() {
|
||||||
|
FlingDto flingDto = FlingDto.builder()
|
||||||
|
.name(flingEntity1.getName())
|
||||||
|
.authCode("new-secret")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
when(passwordEncoder.encode("new-secret")).thenReturn("new-secret-hash");
|
||||||
|
when(flingRepository.getOne(flingEntity1.getId())).thenReturn(flingEntity1);
|
||||||
|
FlingDto updatedEntity = flingService.replace(flingEntity1.getId(), flingDto);
|
||||||
|
|
||||||
|
verify(passwordEncoder, atLeast(1)).encode("new-secret");
|
||||||
|
assertThat(updatedEntity.getId(), is(flingEntity1.getId()));
|
||||||
|
assertThat(updatedEntity.getAuthCode(), is("new-secret-hash"));
|
||||||
|
assertThat(updatedEntity.getName(), is(flingEntity1.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getByShareId_flingDto() {
|
public void getByShareId_flingDto() {
|
||||||
when(flingRepository.findByShareId("shareId2")).thenReturn(flingEntity2);
|
when(flingRepository.findByShareId("shareId2")).thenReturn(flingEntity2);
|
||||||
|
|
|
@ -1,186 +1,140 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import React, {useState, useEffect} from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import produce from 'immer';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import {flingClient} from '../../util/flingclient';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { Fling } from '@fling/flingclient';
|
||||||
|
|
||||||
|
import { retrieveFlings } from "../../redux/actions";
|
||||||
|
import { FlingClient } from '../../util/fc';
|
||||||
|
|
||||||
export default function Settings(props) {
|
export default function Settings(props) {
|
||||||
let defaultState = () => ({name: "", authCode: "",
|
let flingClient = new FlingClient();
|
||||||
sharing: {directDownload: false, allowUpload: true, shared: true, shareUrl: ""},
|
let dispatch = useDispatch();
|
||||||
expiration: {}});
|
|
||||||
|
/**
|
||||||
|
* The active fling from the redux store. Treat this as immutable.
|
||||||
|
*/
|
||||||
|
let activeFling = useSelector(state => state.flings.activeFling);
|
||||||
|
let _clone = _.cloneDeep(_.toPlainObject(_.cloneDeep(activeFling)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep clone the active fling from redux into a draft. Changes to the
|
||||||
|
* settings will be stored in the draft until saved and pushed to the
|
||||||
|
* backend. This in turn will synchronize back to the redux store.
|
||||||
|
*
|
||||||
|
* The draft, just as the activeFling, is of type Fling
|
||||||
|
*/
|
||||||
|
let [draft, setDraft] = useState({ fling: _clone });
|
||||||
|
|
||||||
let [fling, setFling] = useState(defaultState());
|
|
||||||
let [shareUrlUnique, setShareUrlUnique] = useState(true);
|
let [shareUrlUnique, setShareUrlUnique] = useState(true);
|
||||||
let [authCodeChangeable, setAuthCodeChangeable] = useState(false);
|
let [authCodeChangeable, setAuthCodeChangeable] = useState(false);
|
||||||
let [reload, setReload] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
/**
|
||||||
if(props.activeFling && reload) {
|
* Publishes the draft to the backend and refreshes the redux store
|
||||||
flingClient.getFling(props.activeFling)
|
*/
|
||||||
.then(result => {
|
function publishDraft() {
|
||||||
let f = {...fling, ...result};
|
flingClient.putFling(draft).then(
|
||||||
let s = {...fling.sharing, ...result.sharing};
|
success => {
|
||||||
let e = {...fling.expiration, ...result.expiration};
|
log.info("Saved new settings {}", draft);
|
||||||
|
dispatch(retrieveFlings());
|
||||||
f.sharing = s;
|
},
|
||||||
f.expiration = e;
|
error => log.error("Could not save new settings for {}: {}", activeFling.id, error));
|
||||||
setFling(f);
|
|
||||||
|
|
||||||
setAuthCodeChangeable(!f.authCode);
|
|
||||||
setReload(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [props.activeFling, reload, fling]);
|
|
||||||
|
|
||||||
function reloadSettings(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
setFling(defaultState());
|
|
||||||
setReload(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAuthCode(ev) {
|
/**
|
||||||
if(fling.authCode) {
|
* Resets the draft to a new clone of the active fling. All draft
|
||||||
let f = {...fling};
|
* modifications get lost.
|
||||||
f.authCode = "";
|
*/
|
||||||
setFling(f);
|
function resetDraft() {
|
||||||
|
setDraft(produce({}, draft => { return { fling: activeFling }; }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!ev.currentTarget.checked) {
|
/**
|
||||||
setAuthCodeChangeable(true);
|
* A helper shim for persistent produce.
|
||||||
}
|
*
|
||||||
|
* Executes `fun` in immer.produce, hereby generating a new draft `newDraft`,
|
||||||
|
* and sets it into local state via `setDraft(newDraft)`
|
||||||
|
*/
|
||||||
|
let _pproduce = (fun) => (...args) => {
|
||||||
|
let x = produce(fun)(...args);
|
||||||
|
setDraft(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSharing(ev) {
|
/**
|
||||||
let f = {...fling};
|
* Sets the sharing toggles to valid combinations depending on the changed
|
||||||
let s = {...fling.sharing};
|
* setting and its new value.
|
||||||
|
*
|
||||||
if(ev.currentTarget.id === "direct-download") {
|
* Creates a new draft and sets it into the local state.
|
||||||
if(ev.currentTarget.checked) {
|
*/
|
||||||
s.directDownload = true;
|
let toggleSharing = _pproduce((newDraft, setting, enabled) => {
|
||||||
s.shared = true;
|
switch (setting) {
|
||||||
s.allowUpload = false;
|
case "direct-download":
|
||||||
|
if (enabled) {
|
||||||
|
newDraft.fling.directDownload = true;
|
||||||
|
newDraft.fling.shared = true;
|
||||||
|
newDraft.fling.allowUpload = false;
|
||||||
} else {
|
} else {
|
||||||
s.directDownload = false;
|
newDraft.fling.directDownload = false;
|
||||||
}
|
}
|
||||||
} else if(ev.currentTarget.id === "allow-upload") {
|
return newDraft.fling;
|
||||||
if(ev.currentTarget.checked) {
|
case "allow-upload":
|
||||||
s.allowUpload = true;
|
if (enabled) {
|
||||||
s.shared = true;
|
newDraft.fling.allowUpload = true;
|
||||||
s.directDownload = false;
|
newDraft.fling.shared = true;
|
||||||
|
newDraft.fling.directDownload = false;
|
||||||
} else {
|
} else {
|
||||||
s.allowUpload = false;
|
newDraft.fling.allowUpload = false;
|
||||||
}
|
}
|
||||||
} else if(ev.currentTarget.id === "shared") {
|
return newDraft.fling;
|
||||||
if(!ev.currentTarget.checked) {
|
case "shared":
|
||||||
s.allowUpload = s.directDownload = s.shared = false;
|
if (enabled) {
|
||||||
|
newDraft.fling.allowUpload = false;
|
||||||
|
newDraft.fling.directDownload = false;
|
||||||
|
newDraft.fling.shared = false;
|
||||||
} else {
|
} else {
|
||||||
s.shared = true;
|
newDraft.fling.shared = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return newDraft;
|
||||||
|
default:
|
||||||
|
log.warn("Unknown action");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
/** Sets the Fling.name. Creates a new draft and sets it into the local state. */
|
||||||
|
let setName = produce((newDraft, name) => { newDraft.fling.name = name; return newDraft; });
|
||||||
|
/** Sets the Fling.expirationTime. Creates a new draft and sets it into the local state. */
|
||||||
|
let setExpirationTime = _pproduce((newDraft, time) => newDraft.fling.expirationTime = time);
|
||||||
|
/** Sets the Fling.expirationClicks. Creates a new draft and sets it into the local state. */
|
||||||
|
let setExpirationClicks = _pproduce((newDraft, clicks) => newDraft.fling.clicks = clicks);
|
||||||
|
/** Sets the Fling.shareId. Creates a new draft and sets it into the local state. */
|
||||||
|
let setShareId = _pproduce((newDraft, shareId) => newDraft.fling.shareId = shareId);
|
||||||
|
/** Sets the Fling.authCode. Creates a new draft and sets it into the local state. */
|
||||||
|
let setAuthCode = _pproduce((newDraft, authCode) => newDraft.fling.authCode = authCode);
|
||||||
|
|
||||||
f.sharing = s;
|
let resetAuthCode = _pproduce((newDraft) => newDraft.fling.authCode = activeFling.authCode);
|
||||||
|
|
||||||
setFling(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setShareUrl(ev) {
|
|
||||||
let f = {...fling};
|
|
||||||
let s = {...fling.sharing}; //TODO: expiration is not cloned
|
|
||||||
let value = ev.currentTarget.value;
|
|
||||||
|
|
||||||
if(!value) {
|
|
||||||
setShareUrlUnique(false);
|
|
||||||
s.shareUrl = value;
|
|
||||||
f.sharing = s;
|
|
||||||
setFling(f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
flingClient.getFlingByShareId(ev.currentTarget.value)
|
|
||||||
.then(result => {
|
|
||||||
if(!result) {
|
|
||||||
setShareUrlUnique(true);
|
|
||||||
} else if(props.activeFling === result.id) { // share url didn't change
|
|
||||||
setShareUrlUnique(true);
|
|
||||||
} else {
|
|
||||||
setShareUrlUnique(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
s.shareUrl = value;
|
|
||||||
f.sharing = s;
|
|
||||||
setFling(f);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 value = ev.currentTarget.value;
|
|
||||||
|
|
||||||
f.authCode = value;
|
|
||||||
setFling(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
log.info(fling);
|
|
||||||
flingClient.putFling(props.activeFling, fling);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="p-centered column col-xl-9 col-sm-12 col-6">
|
<div className="p-centered column col-xl-9 col-sm-12 col-6">
|
||||||
<form className="form-horizontal" onSubmit={handleSubmit}>
|
<form className="form-horizontal" onSubmit={publishDraft}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-3 col-sm-12">
|
<div className="col-3 col-sm-12">
|
||||||
<label className="form-label" htmlFor="input-name">Name</label>
|
<label className="form-label" htmlFor="input-name">Name</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 col-sm-12">
|
<div className="col-9 col-sm-12">
|
||||||
<input className="form-input" type="text" id="input-name" value={fling.name} onChange={setName}/>
|
<input className="form-input" type="text" id="input-name"
|
||||||
|
value={draft.fling.name}
|
||||||
|
onChange={(ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
let nd = produce(draft, newDraft => {
|
||||||
|
newDraft.fling.name = ev.target.value;
|
||||||
|
return newDraft;
|
||||||
|
})
|
||||||
|
setDraft(nd);
|
||||||
|
}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
|
@ -188,7 +142,7 @@ export default function Settings(props) {
|
||||||
<label className="form-label" htmlFor="input-share-url">Share URL</label>
|
<label className="form-label" htmlFor="input-share-url">Share URL</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 col-sm-12">
|
<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-share-url" onChange={ev => setShareId(ev.target.value)} />
|
||||||
<i className={`icon icon-cross text-error ${shareUrlUnique ? "d-hide" : "d-visible"}`} />
|
<i className={`icon icon-cross text-error ${shareUrlUnique ? "d-hide" : "d-visible"}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -199,13 +153,13 @@ export default function Settings(props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 col-sm-12">
|
<div className="col-9 col-sm-12">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<input className={`form-input ${authCodeChangeable ? "d-visible":"d-hide"}`} type="text" readOnly={!authCodeChangeable} value={fling.authCode} onChange={setAuthCode} />
|
<input className={`form-input ${authCodeChangeable ? "d-visible" : "d-hide"}`} type="text" readOnly={!authCodeChangeable} onChange={ev => setAuthCode(ev.target.value)} />
|
||||||
<label className="form-switch ml-2 popover popover-bottom">
|
<label className="form-switch ml-2 popover popover-bottom">
|
||||||
<input type="checkbox" checked={!!fling.authCode} onChange={resetAuthCode} />
|
<input type="checkbox" checked={!!draft.fling.authCode} onChange={resetAuthCode} />
|
||||||
<i className="form-icon" /> Protected
|
<i className="form-icon" /> Protected
|
||||||
<div className="popover-container card">
|
<div className="popover-container card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{fling.authCode ? "Click to reset passcode": "Set passcode to enable protection"}
|
{draft.fling.authCode ? "Click to reset passcode" : "Set passcode to enable protection"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -220,25 +174,25 @@ export default function Settings(props) {
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 col-sm-12">
|
<div className="col-9 col-sm-12">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<select className="form-select" value={fling.expiration.type} onChange={setExpirationType}>
|
<select className="form-select" >
|
||||||
<option value="never">Never</option>
|
<option value="never">Never</option>
|
||||||
<option value="time">Date</option>
|
<option value="time">Date</option>
|
||||||
<option value="clicks">Clicks</option>
|
<option value="clicks">Clicks</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={fling.expiration.type === "clicks" ? "d-visible": "d-hide"}>
|
<div className={"clicks" === "clicks" ? "d-visible" : "d-hide"}>
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<span className="input-group-addon">Expire after</span>
|
<span className="input-group-addon">Expire after</span>
|
||||||
<input className="form-input" type="number" value={fling.expiration.value || ""} onChange={setExpirationValue} />
|
<input className="form-input" type="number" />
|
||||||
<span className="input-group-addon">Clicks</span>
|
<span className="input-group-addon">Clicks</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={fling.expiration.type === "time" ? "d-visible": "d-hide"}>
|
<div className={"clicks" === "time" ? "d-visible" : "d-hide"}>
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<span className="input-group-addon">Expire after</span>
|
<span className="input-group-addon">Expire after</span>
|
||||||
<input className="form-input" type="date" value={formatExpirationTime()} onChange={setExpirationValue} />
|
<input className="form-input" type="date" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -251,22 +205,22 @@ export default function Settings(props) {
|
||||||
<div className="col-9 col-sm-12">
|
<div className="col-9 col-sm-12">
|
||||||
|
|
||||||
<label className="form-switch form-inline">
|
<label className="form-switch form-inline">
|
||||||
<input type="checkbox" id="shared" checked={fling.sharing.shared} onChange={toggleSharing}/>
|
<input type="checkbox" id="shared" checked={draft.fling.shared} onChange={toggleSharing} />
|
||||||
<i className="form-icon" /> Shared
|
<i className="form-icon" /> Shared
|
||||||
</label>
|
</label>
|
||||||
<label className="form-switch form-inline">
|
<label className="form-switch form-inline">
|
||||||
<input type="checkbox" id="allow-upload" checked={fling.sharing.allowUpload} onChange={toggleSharing}/>
|
<input type="checkbox" id="allow-upload" checked={draft.fling.allowUpload} onChange={toggleSharing} />
|
||||||
<i className="form-icon" /> Uploads
|
<i className="form-icon" /> Uploads
|
||||||
</label>
|
</label>
|
||||||
<label className="form-switch form-inline">
|
<label className="form-switch form-inline">
|
||||||
<input type="checkbox" id="direct-download" checked={fling.sharing.directDownload} onChange={toggleSharing}/>
|
<input type="checkbox" id="direct-download" checked={draft.fling.directDownload} onChange={toggleSharing} />
|
||||||
<i className="form-icon" /> Direct Download
|
<i className="form-icon" /> Direct Download
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="float-right">
|
<div className="float-right">
|
||||||
<button className="btn btn-secondary mr-2" onClick={reloadSettings}>Cancel</button>
|
<button className="btn btn-secondary mr-2" onClick={resetDraft}>Cancel</button>
|
||||||
<input type="submit" className="btn btn-primary" value="Save" />
|
<input type="submit" className="btn btn-primary" value="Save" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
import { cloneDeep, toPlainObject } from 'lodash';
|
||||||
|
|
||||||
import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "./actionTypes";
|
import { SET_FLINGS, SET_ACTIVE_FLING, ADD_FLING } from "./actionTypes";
|
||||||
import { FlingClient } from "../util/fc";
|
import { FlingClient } from "../util/fc";
|
||||||
|
@ -6,21 +7,21 @@ import { FlingClient } from "../util/fc";
|
||||||
function setFlingsAction(flings) {
|
function setFlingsAction(flings) {
|
||||||
return {
|
return {
|
||||||
type: SET_FLINGS,
|
type: SET_FLINGS,
|
||||||
payload: flings
|
payload: _purify(flings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFlingAction(fling) {
|
function addFlingAction(fling) {
|
||||||
return {
|
return {
|
||||||
type: ADD_FLING,
|
type: ADD_FLING,
|
||||||
payload: fling
|
payload: _purify(fling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setActiveFlingAction(fling) {
|
function setActiveFlingAction(fling) {
|
||||||
return {
|
return {
|
||||||
type: SET_ACTIVE_FLING,
|
type: SET_ACTIVE_FLING,
|
||||||
payload: fling
|
payload: _purify(fling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +56,13 @@ function setActiveFling(id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _purify(o) {
|
||||||
|
if(Array.isArray(o)) {
|
||||||
|
return o.map(e => toPlainObject(cloneDeep(e)));
|
||||||
|
}
|
||||||
|
return toPlainObject(cloneDeep(o));
|
||||||
|
}
|
||||||
|
|
||||||
function retrieveFlings() {
|
function retrieveFlings() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { flings: { activeFling } } = getState();
|
const { flings: { activeFling } } = getState();
|
||||||
|
|
Loading…
Reference in a new issue