Merge pull request #748 from lbryio/featureTesting

Add initial meme generator!
This commit is contained in:
Shawn K 2018-11-29 20:54:55 -06:00 committed by GitHub
commit b529b6a0e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 770 additions and 36 deletions

View file

@ -4,6 +4,7 @@
// be a flex container for children // be a flex container for children
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
} }
.dropzone { .dropzone {
@ -12,6 +13,7 @@
flex: 1 0 auto; flex: 1 0 auto;
// be a flex container for children // be a flex container for children
display: flex; display: flex;
padding: 1em;
-webkit-flex-direction: column; -webkit-flex-direction: column;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@ -52,8 +54,37 @@
.dropzone-preview-image { .dropzone-preview-image {
display: block; display: block;
padding: 1em; width: 100%;
width: calc(100% - 2em); }
.dropzone-preview-memeify {
margin-top: 3em;
}
.dropzone-memeify-button {
background: $primary-color;
color: #fff;
cursor: pointer;
font-size: .8em;
padding: 3px 6px;
position: absolute;
right: 0;
top: 0;
z-index: 3;
}
.dropzone-memeify-saveMessage {
padding-top: .25em;
position: relative;
top: .5em;
}
.dropzone-memeify-toolbar {
/* TODO: Cleanup `!important` */
background: $primary-color !important;
left: -1em !important;
right: -1em !important;
top: -4em !important;
} }
.dropzone-instructions-display__chooser-label { .dropzone-instructions-display__chooser-label {

View file

@ -0,0 +1,99 @@
import React, { Component } from 'react';
const DEFAULT_TEXT_RENDER = (text) => text;
export default class EditableFontface extends Component {
constructor(props) {
super(props);
this.state = {
blinkSelection: props.blinkSelection == false ? false : true,
value: props.value,
};
this.textInput = React.createRef();
}
componentDidMount() {
const textInput = this.textInput.current;
if(textInput) {
textInput.focus();
}
}
render() {
const me = this;
const {
blinkSelection,
value
} = me.state;
const {
editable = true,
fontFace,
preview,
} = me.props;
const textRender = fontFace.textRender || DEFAULT_TEXT_RENDER;
const textStyles = Object.assign({
...(blinkSelection ? {
animation: 'textBlink 1s infinite',
} : {}),
minHeight: '20px',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
}, fontFace.text, preview ? fontFace.previewOverrides : {});
const fontInput = (editable === true) ? (
<input ref={this.textInput} type="text" onKeyPress={(e) => me.onKeyPress(e)} onChange={(e) => me.onChange(e)} style={{
...{
bottom: 0,
opacity: 0,
padding: 0,
left: 0,
position: 'absolute',
top: 0,
width: '100%',
zIndex: 1,
},
...(fontFace.editorStyle || {}),
}} />
) : null;
return (
<div style={{ position: 'relative' }}>
<style scoped>{'@keyframes textBlink { 0% { opacity: 1 } 30% { opacity: 0.6 } 60% { opacity: 1 } }'}</style>
{fontInput}
<div ref={me.state.fontRender} style={textStyles} title={value}>{textRender(value)}</div>
</div>
);
}
onKeyPress(e) {
this.setState({
blinkSelection: false,
value: e.target.value
});
}
onChange(e) {
this.setState({
blinkSelection: false,
value: e.target.value
});
}
};
export const PRESETS = {
'Green Machine': require('../FontFaces/GreenMachine'),
'Inferno': require('../FontFaces/Inferno'),
'Lazer': require('../FontFaces/Lazer'),
'Neon': require('../FontFaces/Neon'),
'Old Blue': require('../FontFaces/OldBlue'),
'Retro Rainbow': require('../FontFaces/RetroRainbow'),
'The Special': require('../FontFaces/TheSpecial'),
'Vapor Wave': require('../FontFaces/VaporWave'),
}

View file

@ -0,0 +1,15 @@
module.exports = {
container: {},
editorStyle: {
fontFamily: 'courier, Courier New',
fontWeight: 'bold',
fontSize: '2em',
},
text: {
color: '#00b700',
fontFamily: 'courier, Courier New',
fontSize: '2rem',
fontWeight: 'bold',
textShadow: '1px 1px 2px #003605',
},
};

View file

@ -0,0 +1,19 @@
module.exports = {
container: {},
editorStyle: {
fontFamily: 'helvetica, Helvetica Nue',
fontWeight: 'bold',
fontSize: '2em',
},
text: {
fontFamily: 'helvetica, Helvetica Nue',
fontWeight: 'bold',
fontSize: '2em',
color: '#fff',
textShadow: '0px 0px 3px #c20000, 0px 0px 3px #e0f2ff, 0px 0px 5px #532761, 0px 0px 20px #670606, 0 0 10px rgba(0, 0, 0, .8), 0 0 10px #fefcc9, 5px -5px 15px #feec85, -10px -10px 20px #ffae34, 10px -20px 25px #ec760c, -10px -30px 30px #cd4606, 0 -40px 35px #973716, 5px -45px 40px #451b0e, 0 -2px 15px rgba(255, 200, 160, .5)',
},
previewOverrides: {
fontSize: '1.5em',
padding: '0 1rem 0 1rem',
},
};

View file

@ -0,0 +1,23 @@
module.exports = {
container: {},
editorStyle: {
fontFamily: 'helvetica, Helvetica Nue',
fontWeight: 'bold',
fontSize: '2em',
textTransform: 'uppercase',
whiteSpace: 'nowrap',
},
text: {
fontFamily: 'helvetica, Helvetica Nue',
fontWeight: 'bold',
backgroundImage: 'linear-gradient(180deg, #249bff 0%, #e1f8ff 44%, #3a006b 44%, #ff57d6 100%)',
backgroundClip: 'text',
fontSize: '2em',
color: 'transparent',
filter: 'drop-shadow(0 0 .05rem black)',
textTransform: 'uppercase',
whiteSpace: 'nowrap',
WebkitBackgroundClip: 'text',
WebkitTextStroke: '0.03em rgba(255, 255, 255, 0.6)',
},
};

View file

@ -0,0 +1,21 @@
module.exports = {
container: {},
editorStyle: {
fontFamily: 'Helvetica, Arial',
fontWeight: 'bold',
fontSize: '2em',
letterSpacing: '.1em',
},
text: {
color: '#fff',
fontFamily: 'Helvetica, Arial',
fontSize: '2rem',
fontWeight: 'bold',
letterSpacing: '.1em',
textShadow: '0 0 0.05em #fff, 0 0 0.1em #fff, 0 0 0.2em #fff, 0 0 .2em #ff1de2, 0 0 .4em #ff26e3, 0 0 .5em #ff00de, 0 0 1em #ff61eb, 0 0 1.5em #ff7cee',
},
previewOverrides: {
fontSize: '1.8em',
padding: '0 1rem 0 1rem',
},
};

View file

@ -0,0 +1,39 @@
import React from 'react';
const charToFullWidth = char => {
const c = char.charCodeAt( 0 )
return c >= 33 && c <= 126
? String.fromCharCode( ( c - 33 ) + 65281 )
: char
}
const stringToFullWidth =
module.exports = {
container: {},
editorStyle: {},
text: {
fontFamily: 'Segoe UI,Helvetica,Arial',
},
previewOverrides: {
height: '2.6rem',
},
textRender: (text) => {
const id = `curve-${text.replace(/[^A-Za-z0-9]/g, '')}-oceanwave`
return (
<svg viewBox="0 0 500 50" style={{ height: '4em', fontFamily: 'Arial', fontWeight: 'bold' }}>
<path id={id} fill="transparent" d="M 0 50 Q 50 0 100 50 Q 150 100 200 50 Q 250 0 300 50 Q 350 100 400 50 Q 450 0 500 50 Q 550 100 600 50 " transform="scale(1 0.5) translate(0 15)" />
<text x="10" style={{ fill: '#4dc2fe', fontWeight: 900, letterSpacing: '-0.15em', textShadow: '0.15em -0.1em #1c55a0' }}>
<textPath href={`#${id}`}>
{text}
</textPath>
</text>
<text x="10" style={{ fill: 'transparent', stroke: '#1c55a0', strokeWidth: '.012em', fontWeight: 900, letterSpacing: '-0.15em' }}>
<textPath href={`#${id}`}>
{text}
</textPath>
</text>
</svg>
);
},
};

View file

@ -0,0 +1,21 @@
module.exports = {
container: {},
editorStyle: {
fontFamily: 'Arial, sans-serif',
fontWeight: 'bold',
fontSize: '1.2em',
transform: 'scale(1, 1.5)',
},
text: {
fontFamily: 'Arial, sans-serif',
fontWeight: 'bold',
backgroundImage: 'linear-gradient(to right, #b306a9, #ef2667, #f42e2c, #ffa509, #fdfc00, #55ac2f, #0b13fd, #a804af)',
backgroundClip: 'text',
fontSize: '1.2em',
transform: 'scale(1, 1.5)',
color: 'transparent',
paddingBottom: '.25em',
paddingTop: '.1em',
WebkitBackgroundClip: 'text',
},
};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,43 @@
import React from 'react';
const charToFullWidth = char => {
const c = char.charCodeAt( 0 )
return c >= 33 && c <= 126
? String.fromCharCode( ( c - 33 ) + 65281 )
: char
}
const stringToFullWidth =
module.exports = {
container: {},
editorStyle: {},
text: {
fontFamily: 'Segoe UI,Helvetica,Arial',
},
previewOverrides: {
transform: 'rotate(39deg)',
height: '7rem',
paddingLeft: '2rem',
margin: '-2rem 0',
},
textRender: (text) => {
const formattedText = text.toLowerCase().split('').map((char) => {
const c = char.charCodeAt( 0 )
return (c >= 33 && c <= 126) ? String.fromCharCode(c + 65248) : char
}).join('');
// TODO: Inline the path
const id = `curve-${text.replace(/[^A-Za-z0-9]/g, '')}-oceanwave`
return (
<svg viewBox="0 0 500 160" style={{ height: '10em' }}>
<path id={id} fill="transparent" d="M6,150C49.63,93,105.79,36.65,156.2,47.55,207.89,58.74,213,131.91,264,150c40.67,14.43,108.57-6.91,229-145" />
<text x="10">
<textPath href={`#${id}`}>
{formattedText}
</textPath>
</text>
</svg>
);
},
};

View file

@ -0,0 +1,60 @@
import React, { Component } from 'react';
import Draggable from 'react-draggable';
let body;
try {
body = document.body;
} catch(e) {}
export default class RichDraggable extends Component {
constructor(props) {
super(props);
this.contents = React.createRef();
this.state = {
height: 0,
width: 0,
};
}
componentDidMount() {
const height = this.contents.current.offsetHeight;
const width = this.contents.current.offsetWidth;
this.setState({
height,
width,
});
}
render() {
const me = this;
const {
props,
state,
} = me;
const {
height: bottom,
width: right,
} = props.bounds;
const bounds = {
//top: 0,
//left: 0,
right: right - state.width,
bottom: bottom - state.height,
};
return (
<Draggable {...props} bounds={bounds} offsetParent={body} cancel=".no-drag">
<div ref={me.contents} style={{ padding: '15px', position: 'absolute', border: '4px dashed #ddd', cursor: 'move' }} className="creatifyDecor">
<div className="no-drag" style={{ position: 'relative', cursor: 'auto' }}>
{props.children}
</div>
</div>
</Draggable>
);
}
};

View file

@ -0,0 +1,236 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { Component } from 'react';
import Select from 'react-select'
import RichDraggable from './RichDraggable';
import EditableFontface, { PRESETS as FontPresets } from './EditableFontface';
import {
faFont,
faMinusCircle,
faPlusCircle,
} from '@fortawesome/free-solid-svg-icons';
const getRasterizedCanvas = (contents, width, height) => {
return new Promise((resolve) => {
// Parse to xHTML for SVG/foreignObject rendering
contents = new XMLSerializer().serializeToString(
new DOMParser().parseFromString(contents, 'text/html')
);
// Resolves a bug in Chrome where it renders correctly, but
// replaces the inline styles with an invalid `background-clip`.
if(/Chrome/.test(navigator.userAgent)) {
contents = contents.replace(/background\-clip:(\s*text\s*)[;$]/g,
(match, group) => (`-webkit-background-clip:text;${match}`)
);
}
// Attempt to match font kerning with the DOM.
const kerningAndPadding = '<style>svg{font-kerning:normal}body{padding:0;margin:0}</style>';
const svgContents = `<svg xmlns="http://www.w3.org/2000/svg" width="${width * 2}" height="${height * 2}">
<foreignObject x="0" y="0" width="${width * 2}" height="${height * 2}" externalResourcesRequired="true">
<html xmlns="http://www.w3.org/1999/xhtml"><head>${kerningAndPadding}</head><body>${contents}</body></html>
</foreignObject></svg>`;
const pixelRatio = 2;
let img = document.createElement('img');
let canvas = document.createElement('canvas');
img.height = canvas.height = height * pixelRatio;
img.width = canvas.width = width * pixelRatio;
canvas.style.height = `${height}px`;
canvas.style.width = `${width}px`;
let shadowNode = document.createElement('div');
Object.assign(shadowNode.style, {
height: 0,
overflow: 'hidden',
width: 0,
});
document.body.appendChild(shadowNode);
shadowNode.appendChild(img);
var svg64 = btoa(unescape(encodeURIComponent(svgContents)));
var b64Start = 'data:image/svg+xml;base64,';
var image64 = b64Start + svg64;
img.addEventListener('load', () => {
window.requestAnimationFrame(() => {
// We still can't trust Firefox's %$%&* engine, add another 5ms timeout
// `background-clip: text` is very broken and does not always render in time.
setTimeout(() => {
let context = canvas.getContext('2d', { alpha: false });
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'white';
context.imageSmoothingEnabled = false;
context.scale(pixelRatio, pixelRatio);
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0);
document.body.removeChild(shadowNode);
resolve(canvas);
}, 10);
});
});
img.src = image64;
});
};
export default class Creatify extends Component {
constructor(props) {
super(props);
const fontKeys = Object.keys(FontPresets);
this.contents = React.createRef();
const fontOptions = fontKeys.map(
(fontName) => (
{
value: fontName,
label: (
<div style={{ maxHeight: '150px', maxWidth: '100%', fontSize: '16px', overflow: 'hidden' }}>
<EditableFontface key={fontName} fontFace={FontPresets[fontName]} preview={true} value={fontName} editable={false} blinkSelection={false} />
</div>
),
fontName,
}
)
);
this.state = {
activeElement: false,
bounds: {},
fontName: fontKeys[0],
elements: [],
fontOptions,
};
}
componentDidMount() {
// TODO: Fix bounds
/*
const bounds = this.contents.current.getBoundingClientRect();
this.setState({
bounds,
});
console.log({
bounds
})
*/
}
setActiveElement(activeElement) {
this.setState({ activeElement });
}
addElement() {
const {
state
} = this;
const newElementKey = `element-${state.elements.length}-${Date.now()}`;
const newElement = (
<RichDraggable key={newElementKey} bounds={state.bounds} onStart={() => this.setActiveElement(newElement)}>
<EditableFontface fontFace={FontPresets[state.fontName]} value="Start Typing!" />
</RichDraggable>
);
this.setState({
elements: [...state.elements, newElement],
activeElement: newElement,
});
}
removeElement() {
const {
state
} = this;
const activeElementIndex = state.elements.indexOf(state.activeElement);
if(state.elements.length === 0 || activeElementIndex === -1) {
return;
}
const elements = [...state.elements];
elements.splice(activeElementIndex, 1)
this.setState({
activeElement: false,
elements,
});
}
async onSave() {
const renderedCanvas = await this.renderContentsToCanvas();
if(this.props.onSave) {
this.props.onSave(renderedCanvas);
}
}
async renderContentsToCanvas() {
const me = this;
const contentsElement = me.contents.current;
let contents = contentsElement.outerHTML;
// Cheap border/handles removal
contents = `<style>.creatifyDecor{border-color:transparent!important;background-color:transparent!important}</style>` + contents;
const contentsWidth = contentsElement.offsetWidth;
const contentsHeight = contentsElement.offsetHeight;
// Fix the dimensions, fixes when flex is used.
contents = `<div style="height:${contentsHeight}px;width:${contentsWidth}px">${contents}</div>`;
return await getRasterizedCanvas(contents, contentsWidth, contentsHeight);
}
render() {
const me = this;
const {
props,
state,
} = this;
// TODO: Abstract into separate package & use CSS Modules.
const spacerCss = { width: '.3em' };
return (
<div style={{ position: 'relative', flex: props.flex === true ? 1 : props.flex, display: props.flex ? 'flex' : 'block' }}>
<div className={props.toolbarClassName} style={{ alignItems: 'center', color: '#fff', display: 'flex', padding: '.3em', position: 'absolute', top: 0, left: 0, right: 0, background: '#333', flexDirection: 'row', zIndex: 2 }}>
<FontAwesomeIcon icon={faPlusCircle} size="2x" onClick={() => this.addElement()} />
<div style={spacerCss} />
<FontAwesomeIcon icon={faMinusCircle} size="2x" onClick={() => this.removeElement()} />
<div style={spacerCss} />
<div style={{ flex: 1 }}>
<Select style={{ flex: 1 }} isSearchable={false} options={state.fontOptions} onChange={(option) => this.setFont(option.fontName)} />
</div>
<div style={spacerCss} />
<div onClick={() => this.onSave()} style={{ alignItems: 'center', alignSelf: 'stretch', border: '1px solid #fff', borderRadius: '4px', color: '#fff', display: 'flex', padding: '0 0.6em' }}>
<span>Save</span>
</div>
</div>
<div ref={me.contents} style={{ fontSize: '22px', overflow: 'hidden', transform: 'translateZ(0)', flex: 1 }}>
{state.elements}
{props.children}
</div>
</div>
);
}
setFont(fontName) {
this.setState({
fontName,
});
}
};

View file

@ -1,17 +1,34 @@
import React from 'react'; import React from 'react';
import { validateFile } from '../../utils/file'; import { validateFile } from '../../utils/file';
import Creatify from '@components/Creatify';
import DropzonePreviewImage from '@components/DropzonePreviewImage'; import DropzonePreviewImage from '@components/DropzonePreviewImage';
import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay'; import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay';
import DropzoneInstructionsDisplay from '@components/DropzoneInstructionsDisplay'; import DropzoneInstructionsDisplay from '@components/DropzoneInstructionsDisplay';
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEdit } from '@fortawesome/free-solid-svg-icons';
class Dropzone extends React.Component { class Dropzone extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
this.state = { this.state = {
dragOver : false, dragOver : false,
mouseOver : false, mouseOver : false,
dimPreview : false, dimPreview : false,
filePreview: null,
memeify : false,
}; };
if(props.file) {
// No side effects allowed with `getDerivedStateFromProps`, so
// we must use `componentDidUpdate` and `constructor` routines.
// Note: `FileReader` has an `onloadend` side-effect
this.updateFilePreview();
}
this.handleDrop = this.handleDrop.bind(this); this.handleDrop = this.handleDrop.bind(this);
this.handleDragOver = this.handleDragOver.bind(this); this.handleDragOver = this.handleDragOver.bind(this);
this.handleDragEnd = this.handleDragEnd.bind(this); this.handleDragEnd = this.handleDragEnd.bind(this);
@ -23,6 +40,25 @@ class Dropzone extends React.Component {
this.handleFileInput = this.handleFileInput.bind(this); this.handleFileInput = this.handleFileInput.bind(this);
this.chooseFile = this.chooseFile.bind(this); this.chooseFile = this.chooseFile.bind(this);
} }
componentDidUpdate(prevProps) {
if(prevProps.file !== this.props.file) {
this.updateFilePreview();
}
}
updateFilePreview() {
if (this.props.file) {
const fileReader = new FileReader();
fileReader.readAsDataURL(this.props.file);
fileReader.onloadend = () => {
this.setState({
filePreview: fileReader.result
});
};
}
}
handleDrop (event) { handleDrop (event) {
event.preventDefault(); event.preventDefault();
this.setState({dragOver: false}); this.setState({dragOver: false});
@ -35,9 +71,11 @@ class Dropzone extends React.Component {
} }
} }
} }
handleDragOver (event) { handleDragOver (event) {
event.preventDefault(); event.preventDefault();
} }
handleDragEnd (event) { handleDragEnd (event) {
var dt = event.dataTransfer; var dt = event.dataTransfer;
if (dt.items) { if (dt.items) {
@ -48,27 +86,34 @@ class Dropzone extends React.Component {
event.dataTransfer.clearData(); event.dataTransfer.clearData();
} }
} }
handleDragEnter () { handleDragEnter () {
this.setState({dragOver: true, dimPreview: true}); this.setState({dragOver: true, dimPreview: true});
} }
handleDragLeave () { handleDragLeave () {
this.setState({dragOver: false, dimPreview: false}); this.setState({dragOver: false, dimPreview: false});
} }
handleMouseEnter () { handleMouseEnter () {
this.setState({mouseOver: true, dimPreview: true}); this.setState({mouseOver: true, dimPreview: true});
} }
handleMouseLeave () { handleMouseLeave () {
this.setState({mouseOver: false, dimPreview: false}); this.setState({mouseOver: false, dimPreview: false});
} }
handleClick (event) { handleClick (event) {
event.preventDefault(); event.preventDefault();
document.getElementById('file_input').click(); document.getElementById('file_input').click();
} }
handleFileInput (event) { handleFileInput (event) {
event.preventDefault(); event.preventDefault();
const fileList = event.target.files; const fileList = event.target.files;
this.chooseFile(fileList[0]); this.chooseFile(fileList[0]);
} }
chooseFile (file) { chooseFile (file) {
if (file) { if (file) {
try { try {
@ -80,15 +125,72 @@ class Dropzone extends React.Component {
this.props.selectFile(file); this.props.selectFile(file);
} }
} }
selectFileFromCanvas (canvas) {
const destinationFormat = 'image/jpeg';
canvas.toBlob((blob) => {
const file = new File([blob], `memeify-${Math.random().toString(36).substring(7)}.jpg`, {
type: destinationFormat,
});
this.props.selectFile(file);
// TODO: Add ability to reset.
this.setState({
memeify: false,
});
}, destinationFormat, 0.95);
}
render () { render () {
const { dragOver, mouseOver, dimPreview } = this.state; const { dragOver, mouseOver, dimPreview, filePreview, memeify } = this.state;
const { file, thumbnail, fileError, isUpdate, sourceUrl, fileExt } = this.props; const { file, thumbnail, fileError, isUpdate, sourceUrl, fileExt } = this.props;
const hasContent = !!(file || isUpdate);
const dropZoneHooks = file ? {} : {
onDrop: this.handleDrop,
onDragOver: this.handleDragOver,
onDragEnd: this.handleDragEnd,
onDragEnter: this.handleDragEnter,
onDragLeave: this.handleDragLeave,
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
onClick: this.handleClick,
};
const dropZonePreviewProps = file ? {
dimPreview,
file,
thumbnail,
} : {
dimPreview: true,
isUpdate: true,
sourceUrl,
};
const memeifyContent = memeify && file && filePreview ? (
<Creatify flex toolbarClassName={'dropzone-memeify-toolbar'} onSave={(canvas) => this.selectFileFromCanvas(canvas)}>
<div style={{ background: '#fff', flex: 1, pointerEvents: 'none' }}>
<img style={{ width: '100%' }} src={filePreview} />
</div>
</Creatify>
) : null;
const dropZoneClassName = 'dropzone' + (dragOver ? ' dropzone--drag-over' : '');
return ( return (
<React.Fragment> <React.Fragment>
{isUpdate && fileExt === 'mp4' ? ( {isUpdate && fileExt === 'mp4' ? (
<p>Video updates are currently disabled. This feature will be available soon. You can edit metadata.</p> <p>Video updates are currently disabled. This feature will be available soon. You can edit metadata.</p>
) : ( ) : (
<div className='dropzone-wrapper'> <div className={'dropzone-wrapper'}>
{ hasContent && !memeify && fileExt !== 'mp4' && (
<div className={'dropzone-memeify-button'} onClick={() => this.setState({ memeify: !memeify })}>
<FontAwesomeIcon icon={faEdit} /> Memeify
</div>
)}
<form> <form>
<input <input
className='input-file' className='input-file'
@ -100,31 +202,10 @@ class Dropzone extends React.Component {
encType='multipart/form-data' encType='multipart/form-data'
/> />
</form> </form>
<div <div className={dropZoneClassName} {...dropZoneHooks}>
className={'dropzone' + (dragOver ? ' dropzone--drag-over' : '')} {hasContent ? (
onDrop={this.handleDrop} <div className={'dropzone-preview-wrapper' + (memeifyContent ? ' dropzone-preview-memeify' : '')}>
onDragOver={this.handleDragOver} <DropzonePreviewImage {...dropZonePreviewProps} />
onDragEnd={this.handleDragEnd}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClick}>
{file || isUpdate ? (
<div className={'dropzone-preview-wrapper'}>
{file ? (
<DropzonePreviewImage
dimPreview={dimPreview}
file={file}
thumbnail={thumbnail}
/>
) : (
<DropzonePreviewImage
dimPreview
isUpdate
sourceUrl={sourceUrl}
/>
)}
<div className={'dropzone-preview-overlay'}> <div className={'dropzone-preview-overlay'}>
{ dragOver ? <DropzoneDropItDisplay /> : null } { dragOver ? <DropzoneDropItDisplay /> : null }
{ mouseOver ? ( { mouseOver ? (
@ -133,15 +214,15 @@ class Dropzone extends React.Component {
message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null} message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null}
/> />
) : null } ) : null }
{memeifyContent}
</div> </div>
</div> </div>
) : ( ) : (
dragOver ? <DropzoneDropItDisplay /> : ( dragOver ? <DropzoneDropItDisplay /> : (
<DropzoneInstructionsDisplay <DropzoneInstructionsDisplay fileError={fileError} />
fileError={fileError}
/>
) )
)} )}
{memeifyContent ? <div className={'dropzone-memeify-saveMessage'}>{`Don't forget to save before you publish.`}</div> : null}
</div> </div>
</div> </div>
)} )}

View file

@ -46,6 +46,9 @@
}, },
"homepage": "https://github.com/lbryio/spee.ch#readme", "homepage": "https://github.com/lbryio/spee.ch#readme",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.8",
"@fortawesome/free-solid-svg-icons": "^5.5.0",
"@fortawesome/react-fontawesome": "^0.1.3",
"axios": "^0.18.0", "axios": "^0.18.0",
"bcrypt": "^2.0.1", "bcrypt": "^2.0.1",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
@ -67,12 +70,14 @@
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.4.2", "react": "^16.4.2",
"react-feather": "^1.1.4",
"react-dom": "^16.6.1", "react-dom": "^16.6.1",
"react-draggable": "^3.0.5",
"react-feather": "^1.1.4",
"react-ga": "^2.5.3", "react-ga": "^2.5.3",
"react-helmet": "^5.2.0", "react-helmet": "^5.2.0",
"react-redux": "^5.1.1", "react-redux": "^5.1.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-select": "^2.1.1",
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-saga": "^0.16.2", "redux-saga": "^0.16.2",
"sequelize": "^4.41.1", "sequelize": "^4.41.1",