got publish submission working
This commit is contained in:
parent
70a65a243a
commit
11a954a442
8 changed files with 137 additions and 209 deletions
|
@ -9,103 +9,7 @@ const publishFileFunctions = {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
createMetadata: function() {
|
||||
const nameInput = document.getElementById('claim-name-input');
|
||||
const titleInput = document.getElementById('publish-title');
|
||||
const descriptionInput = document.getElementById('publish-description');
|
||||
const licenseInput = document.getElementById('publish-license');
|
||||
const nsfwInput = document.getElementById('publish-nsfw');
|
||||
const thumbnailInput = document.getElementById('claim-thumbnail-input');
|
||||
const channelName = this.returnNullOrChannel();
|
||||
let metadata = {
|
||||
name: nameInput.value.trim(),
|
||||
title: titleInput.value.trim(),
|
||||
description: descriptionInput.value.trim(),
|
||||
license: licenseInput.value.trim(),
|
||||
nsfw: nsfwInput.checked,
|
||||
type: stagedFiles[0].type,
|
||||
thumbnail: thumbnailInput.value.trim(),
|
||||
};
|
||||
if (channelName) {
|
||||
metadata['channelName'] = channelName;
|
||||
}
|
||||
return metadata;
|
||||
},
|
||||
appendDataToFormData: function (file, metadata) {
|
||||
var fd = new FormData();
|
||||
fd.append('file', file)
|
||||
for (var key in metadata) {
|
||||
if (metadata.hasOwnProperty(key)) {
|
||||
console.log(key, metadata[key]);
|
||||
fd.append(key, metadata[key]);
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
},
|
||||
publishFile: function (file, metadata) {
|
||||
var uri = "/api/claim-publish";
|
||||
var xhr = new XMLHttpRequest();
|
||||
var fd = this.appendDataToFormData(file, metadata);
|
||||
var that = this;
|
||||
xhr.upload.addEventListener("loadstart", function(e) {
|
||||
that.showUploadStartedMessage();
|
||||
})
|
||||
xhr.upload.addEventListener("progress", function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var percentage = Math.round((e.loaded * 100) / e.total);
|
||||
console.log('progress:', percentage);
|
||||
that.showUploadProgressMessage(percentage);
|
||||
}
|
||||
}, false);
|
||||
xhr.upload.addEventListener("load", function(e){
|
||||
console.log('loaded 100%');
|
||||
that.showFilePublishUpdate("Your file has been loaded, and is now being published to the blockchain. Sit tight...")
|
||||
}, false);
|
||||
xhr.open("POST", uri, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
console.log('publish response:', xhr.response)
|
||||
if (xhr.status == 200) {
|
||||
console.log('publish complete!');
|
||||
that.showFilePublishComplete(JSON.parse(xhr.response).message);
|
||||
} else if (xhr.status == 502){
|
||||
that.showFilePublishFailure('Spee.ch was not able to get a response from the LBRY network.');
|
||||
} else {
|
||||
that.showFilePublishFailure(JSON.parse(xhr.response).message);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Initiate a multipart/form-data upload
|
||||
xhr.send(fd);
|
||||
},
|
||||
// Validate the publish submission and then trigger upload
|
||||
publishStagedFile: function (event) {
|
||||
event.preventDefault(); // prevent default so this script can handle submission
|
||||
const metadata = this.createMetadata();
|
||||
const that = this;
|
||||
const fileSelectionInputError = document.getElementById('input-error-file-selection');
|
||||
const claimNameError = document.getElementById('input-error-claim-name');
|
||||
const channelSelectError = document.getElementById('input-error-channel-select');
|
||||
const publishSubmitError = document.getElementById('input-error-publish-submit');
|
||||
// validate, submit, and handle response
|
||||
validationFunctions.validateFilePublishSubmission(stagedFiles, metadata)
|
||||
.then( function() {
|
||||
that.publishFile(stagedFiles[0], metadata);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.name === 'FileError') {
|
||||
validationFunctions.showError(fileSelectionInputError, error.message);
|
||||
} else if (error.name === 'NameError') {
|
||||
validationFunctions.showError(claimNameError, error.message);
|
||||
} else if (error.name === 'ChannelNameError'){
|
||||
console.log(error);
|
||||
validationFunctions.showError(channelSelectError, error.message);
|
||||
} else {
|
||||
validationFunctions.showError(publishSubmitError, error.message);
|
||||
}
|
||||
return;
|
||||
})
|
||||
},
|
||||
showUploadStartedMessage: function (){
|
||||
console.log('starting upload');
|
||||
// hide the publish tool
|
||||
|
|
|
@ -1,53 +1,5 @@
|
|||
// validation function which checks the proposed file's type, size, and name
|
||||
const validationFunctions = {
|
||||
validateFile: function (file) {
|
||||
if (!file) {
|
||||
console.log('no file found');
|
||||
throw new Error('no file provided');
|
||||
}
|
||||
if (/'/.test(file.name)) {
|
||||
console.log('file name had apostrophe in it');
|
||||
throw new Error('apostrophes are not allowed in the file name');
|
||||
}
|
||||
// validate size and type
|
||||
switch (file.type) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
case 'image/png':
|
||||
if (file.size > 10000000) {
|
||||
console.log('file was too big');
|
||||
throw new Error('Sorry, images are limited to 10 megabytes.');
|
||||
}
|
||||
break;
|
||||
case 'image/gif':
|
||||
if (file.size > 50000000) {
|
||||
console.log('file was too big');
|
||||
throw new Error('Sorry, .gifs are limited to 50 megabytes.');
|
||||
}
|
||||
break;
|
||||
case 'video/mp4':
|
||||
if (file.size > 50000000) {
|
||||
console.log('file was too big');
|
||||
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('file type is not supported');
|
||||
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.')
|
||||
}
|
||||
},
|
||||
// validation function that checks to make sure the claim name is valid
|
||||
validateClaimName: function (name) {
|
||||
// ensure a name was entered
|
||||
if (name.length < 1) {
|
||||
throw new NameError("You must enter a name for your url");
|
||||
}
|
||||
// validate the characters in the 'name' field
|
||||
const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name);
|
||||
if (invalidCharacters) {
|
||||
throw new NameError('"' + invalidCharacters + '" characters are not allowed');
|
||||
}
|
||||
},
|
||||
validateChannelName: function (name) {
|
||||
name = name.substring(name.indexOf('@') + 1);
|
||||
// ensure a name was entered
|
||||
|
@ -117,54 +69,6 @@ const validationFunctions = {
|
|||
name = `@${name}`;
|
||||
this.checkAvailability(name, successDisplayElement, errorDisplayElement, this.validateChannelName, 'Sorry, that name is already taken', '/api/channel-is-available/');
|
||||
},
|
||||
// validation function which checks all aspects of the publish submission
|
||||
validateFilePublishSubmission: function (stagedFiles, metadata) {
|
||||
const channelName = metadata.channelName;
|
||||
const claimName = metadata.name;
|
||||
var that = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
// 1. make sure 1 file was staged
|
||||
if (!stagedFiles) {
|
||||
reject(new FileError("Please select a file"));
|
||||
return;
|
||||
} else if (stagedFiles.length > 1) {
|
||||
reject(new FileError("Only one file is allowed at a time"));
|
||||
return;
|
||||
}
|
||||
// 2. validate the file's name, type, and size
|
||||
try {
|
||||
that.validateFile(stagedFiles[0]);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// 3. validate that a channel was chosen
|
||||
if (channelName === 'new' || channelName === 'login') {
|
||||
reject(new ChannelNameError("Please log in to a channel"));
|
||||
return;
|
||||
}
|
||||
;
|
||||
// 4. validate the claim name
|
||||
try {
|
||||
that.validateClaimName(claimName);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// if all validation passes, check availability of the name (note: do we need to re-validate channel name vs. credentials as well?)
|
||||
return that.isNameAvailable(claimName, '/api/claim-is-available/')
|
||||
.then(result => {
|
||||
if (result) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new NameError('Sorry, that ending is already taken'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// validation function which checks all aspects of a new channel submission
|
||||
validateNewChannelSubmission: function (userName, password) {
|
||||
const channelName = `@${userName}`;
|
||||
|
|
|
@ -5,6 +5,7 @@ export const METADATA_UPDATE = 'METADATA_UPDATE';
|
|||
export const CLAIM_UPDATE = 'CLAIM_UPDATE';
|
||||
export const CHANNEL_UPDATE = 'CHANNEL_UPDATE';
|
||||
export const SET_PUBLISH_IN_CHANNEL = 'SET_PUBLISH_IN_CHANNEL';
|
||||
export const PUBLISH_STATUS_UPDATE = 'PUBLISH_STATUS_UPDATE';
|
||||
|
||||
// export action creators
|
||||
export function selectFile (file) {
|
||||
|
@ -44,9 +45,16 @@ export function updateLoggedInChannel (name, shortId, longId) {
|
|||
};
|
||||
};
|
||||
|
||||
export function setPublishInChannel (value) {
|
||||
export function setPublishInChannel (channel) {
|
||||
return {
|
||||
type: SET_PUBLISH_IN_CHANNEL,
|
||||
value,
|
||||
channel,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export function updatePublishStatus (status) {
|
||||
return {
|
||||
type: PUBLISH_STATUS_UPDATE,
|
||||
status,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -18,7 +18,6 @@ class Preview extends React.Component {
|
|||
this.previewFile(newProps.file);
|
||||
}
|
||||
previewFile (file) {
|
||||
console.log('previewFile', file)
|
||||
const that = this;
|
||||
if (file.type !== 'video/mp4') {
|
||||
const previewReader = new FileReader();
|
||||
|
|
|
@ -6,18 +6,19 @@ import PublishUrlInput from './PublishUrlInput.jsx';
|
|||
import PublishThumbnailInput from './PublishThumbnailInput.jsx';
|
||||
import PublishMetadataInputs from './PublishMetadataInputs.jsx';
|
||||
import AnonymousOrChannelSelect from './AnonymousOrChannelSelect.jsx';
|
||||
|
||||
import { selectFile, clearFile, updateLoggedInChannel } from '../actions/index';
|
||||
import { connect } from 'react-redux';
|
||||
import { getCookie } from '../utils/cookies.js';
|
||||
import { selectFile, clearFile, updateLoggedInChannel, updatePublishStatus } from '../actions';
|
||||
|
||||
class PublishForm extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
// set defaults
|
||||
this.state = {
|
||||
error: null,
|
||||
publishRequestError: null,
|
||||
};
|
||||
this.validatePublishRequest = this.validatePublishRequest.bind(this);
|
||||
this.makePublishRequest = this.makePublishRequest.bind(this);
|
||||
this.publish = this.publish.bind(this);
|
||||
}
|
||||
componentWillMount () {
|
||||
|
@ -28,8 +29,102 @@ class PublishForm extends React.Component {
|
|||
console.log(`channel cookies found: ${loggedInChannelName} ${loggedInChannelShortId} ${loggedInChannelLongId}`);
|
||||
this.props.onChannelLogin(loggedInChannelName, loggedInChannelShortId, loggedInChannelLongId);
|
||||
}
|
||||
validatePublishRequest () {
|
||||
// make sure all required data is provided
|
||||
return new Promise((resolve, reject) => {
|
||||
// is there a file?
|
||||
if (!this.props.file) {
|
||||
return reject(new Error('Please choose a file'));
|
||||
}
|
||||
// is there a claim chosen?
|
||||
if (!this.props.claim) {
|
||||
return reject(new Error('Please enter a claim name'));
|
||||
}
|
||||
// if publishInChannel is true, is a channel logged in (or selected)
|
||||
if (this.props.publishInChannel && !this.props.loggedInChannel.name) {
|
||||
return reject(new Error('Select Anonymous or log in to a channel'));
|
||||
}
|
||||
// tbd: is the claim available?
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
makePublishRequest (file, metadata) {
|
||||
const uri = '/api/claim-publish';
|
||||
const xhr = new XMLHttpRequest();
|
||||
const fd = this.appendDataToFormData(file, metadata);
|
||||
const that = this;
|
||||
xhr.upload.addEventListener('loadstart', function () {
|
||||
that.props.onPublishStatusChange('upload started');
|
||||
});
|
||||
xhr.upload.addEventListener('progress', function (e) {
|
||||
if (e.lengthComputable) {
|
||||
const percentage = Math.round((e.loaded * 100) / e.total);
|
||||
that.props.onPublishStatusChange(`upload progress: ${percentage}%`);
|
||||
console.log('progress:', percentage);
|
||||
}
|
||||
}, false);
|
||||
xhr.upload.addEventListener('load', function () {
|
||||
console.log('loaded 100%');
|
||||
that.props.onPublishStatusChange(`Upload complete. Your file is now being published on the blockchain...`);
|
||||
}, false);
|
||||
xhr.open('POST', uri, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
console.log('publish response:', xhr.response);
|
||||
if (xhr.status === 200) {
|
||||
console.log('publish complete!');
|
||||
that.props.onPublishStatusChange(JSON.parse(xhr.response).message);
|
||||
} else if (xhr.status === 502) {
|
||||
that.props.onPublishStatusChange('Spee.ch was not able to get a response from the LBRY network.');
|
||||
} else {
|
||||
that.props.onPublishStatusChange(JSON.parse(xhr.response).message);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Initiate a multipart/form-data upload
|
||||
xhr.send(fd);
|
||||
}
|
||||
createMetadata () {
|
||||
let metadata = {
|
||||
name : this.props.claim,
|
||||
title : this.props.title,
|
||||
description: this.props.description,
|
||||
license : this.props.license,
|
||||
nsfw : this.props.nsfw,
|
||||
type : this.props.file.type,
|
||||
thumbnail : this.props.thumbnail,
|
||||
};
|
||||
if (this.props.publishInChannel) {
|
||||
metadata['channelName'] = this.props.loggedInChannel.name;
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
appendDataToFormData (file, metadata) {
|
||||
var fd = new FormData();
|
||||
fd.append('file', file);
|
||||
for (var key in metadata) {
|
||||
if (metadata.hasOwnProperty(key)) {
|
||||
console.log(key, metadata[key]);
|
||||
fd.append(key, metadata[key]);
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
publish () {
|
||||
// publish the asset
|
||||
const that = this;
|
||||
this.validatePublishRequest()
|
||||
.then(() => {
|
||||
const metadata = that.createMetadata();
|
||||
// publish the claim
|
||||
return that.makePublishRequest(this.props.file, metadata);
|
||||
})
|
||||
.then(() => {
|
||||
that.props.onPublishStatusChange('publish request made');
|
||||
})
|
||||
.catch((error) => {
|
||||
that.setState({publishRequestError: error.message});
|
||||
});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
@ -61,14 +156,18 @@ class PublishForm extends React.Component {
|
|||
<ChannelSelector />
|
||||
</div>
|
||||
|
||||
{ (this.props.fileType === 'video/mp4') && <PublishThumbnailInput /> }
|
||||
{ (this.props.file.type === 'video/mp4') && (
|
||||
<div className="row row--padded row--wide row--no-top">
|
||||
<PublishThumbnailInput />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="row row--padded row--no-top row--no-bottom row--wide">
|
||||
<PublishMetadataInputs />
|
||||
</div>
|
||||
|
||||
<div className="row row--padded row--wide">
|
||||
<div className="input-error" id="input-error-publish-submit" hidden="true">{this.state.error}</div>
|
||||
<div className="input-error" id="input-error-publish-submit" hidden="true">{this.state.publishRequestError}</div>
|
||||
<button id="publish-submit" className="button--primary button--large" onClick={this.publish}>Publish</button>
|
||||
</div>
|
||||
|
||||
|
@ -89,8 +188,15 @@ class PublishForm extends React.Component {
|
|||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
fileType: state.file.type,
|
||||
claim : state.claim,
|
||||
file : state.file,
|
||||
claim : state.claim,
|
||||
title : state.metadata.title,
|
||||
thumbnail : state.metadata.thumbnail,
|
||||
description : state.metadata.description,
|
||||
license : state.metadata.license,
|
||||
nsfw : state.metadata.nsfw,
|
||||
loggedInChannel : state.loggedInChannel,
|
||||
publishInChannel: state.publishInChannel,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -105,6 +211,9 @@ const mapDispatchToProps = dispatch => {
|
|||
onChannelLogin: (name, shortId, longId) => {
|
||||
dispatch(updateLoggedInChannel(name, shortId, longId));
|
||||
},
|
||||
onPublishStatusChange: (status) => {
|
||||
dispatch(updatePublishStatus(status));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class ThumbnailInput extends React.Component {
|
|||
}
|
||||
render () {
|
||||
return (
|
||||
<div className="row row--padded row--wide row--no-top" id="publish-thumbnail">
|
||||
<div>
|
||||
<div className="column column--3 column--sml-10">
|
||||
<label className="label">Thumbnail:</label>
|
||||
</div><div className="column column--6 column--sml-10">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
CHANNEL_UPDATE, CLAIM_UPDATE, FILE_CLEAR, FILE_SELECTED, METADATA_UPDATE,
|
||||
CHANNEL_UPDATE, CLAIM_UPDATE, FILE_CLEAR, FILE_SELECTED, METADATA_UPDATE, PUBLISH_STATUS_UPDATE,
|
||||
SET_PUBLISH_IN_CHANNEL,
|
||||
} from '../actions';
|
||||
|
||||
|
@ -55,8 +55,12 @@ export default function (state = initialState, action) {
|
|||
});
|
||||
case SET_PUBLISH_IN_CHANNEL:
|
||||
return Object.assign({}, state, {
|
||||
publishInChannel: action.value,
|
||||
publishInChannel: action.channel,
|
||||
});
|
||||
case PUBLISH_STATUS_UPDATE:
|
||||
return Object.assign({}, state, {
|
||||
publishStatus: action.status,
|
||||
})
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
<span class="nav-bar-tagline">Open-source, decentralized image and video sharing.</span>
|
||||
</div>
|
||||
<div class="nav-bar--right">
|
||||
<a class="nav-bar-link link--nav" href="/">Upload</a>
|
||||
<a class="nav-bar-link link--nav" href="/popular">Popular</a>
|
||||
<a class="nav-bar-link link--nav" href="/">Publish</a>
|
||||
<!--<a class="nav-bar-link link--nav" href="/popular">Popular</a>-->
|
||||
<a class="nav-bar-link link--nav" href="/about">About</a>
|
||||
<select type="text" id="nav-bar-channel-select" class="select select--arrow link--nav" onchange="toggleNavBarSelection(event.target.selectedOptions[0].value)" {{#unless user}}style="display:none"{{/unless}}>
|
||||
<option id="nav-bar-channel-select-channel-option">@{{user.userName}}</option>
|
||||
|
@ -48,4 +48,4 @@
|
|||
link.setAttribute('class', 'select select--arrow link--nav-active');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue