Merge pull request #748 from lbryio/featureTesting
Add initial meme generator!
This commit is contained in:
commit
b529b6a0e0
15 changed files with 770 additions and 36 deletions
|
@ -4,6 +4,7 @@
|
|||
// be a flex container for children
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
|
@ -12,6 +13,7 @@
|
|||
flex: 1 0 auto;
|
||||
// be a flex container for children
|
||||
display: flex;
|
||||
padding: 1em;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
@ -52,8 +54,37 @@
|
|||
|
||||
.dropzone-preview-image {
|
||||
display: block;
|
||||
padding: 1em;
|
||||
width: calc(100% - 2em);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
|
99
client/src/components/Creatify/EditableFontface/index.js
Normal file
99
client/src/components/Creatify/EditableFontface/index.js
Normal 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'),
|
||||
}
|
0
client/src/components/Creatify/FontFaces/.gitkeep
Normal file
0
client/src/components/Creatify/FontFaces/.gitkeep
Normal file
15
client/src/components/Creatify/FontFaces/GreenMachine.js
Normal file
15
client/src/components/Creatify/FontFaces/GreenMachine.js
Normal 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',
|
||||
},
|
||||
};
|
19
client/src/components/Creatify/FontFaces/Inferno.js
Normal file
19
client/src/components/Creatify/FontFaces/Inferno.js
Normal 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',
|
||||
},
|
||||
};
|
23
client/src/components/Creatify/FontFaces/Lazer.js
Normal file
23
client/src/components/Creatify/FontFaces/Lazer.js
Normal 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)',
|
||||
},
|
||||
};
|
21
client/src/components/Creatify/FontFaces/Neon.js
Normal file
21
client/src/components/Creatify/FontFaces/Neon.js
Normal 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',
|
||||
},
|
||||
};
|
39
client/src/components/Creatify/FontFaces/OldBlue.js
Normal file
39
client/src/components/Creatify/FontFaces/OldBlue.js
Normal 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>
|
||||
);
|
||||
},
|
||||
};
|
21
client/src/components/Creatify/FontFaces/RetroRainbow.js
Normal file
21
client/src/components/Creatify/FontFaces/RetroRainbow.js
Normal 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',
|
||||
},
|
||||
};
|
41
client/src/components/Creatify/FontFaces/TheSpecial.js
Normal file
41
client/src/components/Creatify/FontFaces/TheSpecial.js
Normal file
File diff suppressed because one or more lines are too long
43
client/src/components/Creatify/FontFaces/VaporWave.js
Normal file
43
client/src/components/Creatify/FontFaces/VaporWave.js
Normal 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>
|
||||
);
|
||||
},
|
||||
};
|
60
client/src/components/Creatify/RichDraggable/index.js
Normal file
60
client/src/components/Creatify/RichDraggable/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
};
|
236
client/src/components/Creatify/index.js
Normal file
236
client/src/components/Creatify/index.js
Normal 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,
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,17 +1,34 @@
|
|||
import React from 'react';
|
||||
|
||||
import { validateFile } from '../../utils/file';
|
||||
import Creatify from '@components/Creatify';
|
||||
import DropzonePreviewImage from '@components/DropzonePreviewImage';
|
||||
import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay';
|
||||
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 {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
dragOver : false,
|
||||
mouseOver : false,
|
||||
dimPreview: false,
|
||||
dragOver : false,
|
||||
mouseOver : 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.handleDragOver = this.handleDragOver.bind(this);
|
||||
this.handleDragEnd = this.handleDragEnd.bind(this);
|
||||
|
@ -23,6 +40,25 @@ class Dropzone extends React.Component {
|
|||
this.handleFileInput = this.handleFileInput.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) {
|
||||
event.preventDefault();
|
||||
this.setState({dragOver: false});
|
||||
|
@ -35,9 +71,11 @@ class Dropzone extends React.Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDragOver (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
handleDragEnd (event) {
|
||||
var dt = event.dataTransfer;
|
||||
if (dt.items) {
|
||||
|
@ -48,27 +86,34 @@ class Dropzone extends React.Component {
|
|||
event.dataTransfer.clearData();
|
||||
}
|
||||
}
|
||||
|
||||
handleDragEnter () {
|
||||
this.setState({dragOver: true, dimPreview: true});
|
||||
}
|
||||
|
||||
handleDragLeave () {
|
||||
this.setState({dragOver: false, dimPreview: false});
|
||||
}
|
||||
|
||||
handleMouseEnter () {
|
||||
this.setState({mouseOver: true, dimPreview: true});
|
||||
}
|
||||
|
||||
handleMouseLeave () {
|
||||
this.setState({mouseOver: false, dimPreview: false});
|
||||
}
|
||||
|
||||
handleClick (event) {
|
||||
event.preventDefault();
|
||||
document.getElementById('file_input').click();
|
||||
}
|
||||
|
||||
handleFileInput (event) {
|
||||
event.preventDefault();
|
||||
const fileList = event.target.files;
|
||||
this.chooseFile(fileList[0]);
|
||||
}
|
||||
|
||||
chooseFile (file) {
|
||||
if (file) {
|
||||
try {
|
||||
|
@ -80,15 +125,72 @@ class Dropzone extends React.Component {
|
|||
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 () {
|
||||
const { dragOver, mouseOver, dimPreview } = this.state;
|
||||
const { dragOver, mouseOver, dimPreview, filePreview, memeify } = this.state;
|
||||
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 (
|
||||
<React.Fragment>
|
||||
{isUpdate && fileExt === 'mp4' ? (
|
||||
<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>
|
||||
<input
|
||||
className='input-file'
|
||||
|
@ -100,31 +202,10 @@ class Dropzone extends React.Component {
|
|||
encType='multipart/form-data'
|
||||
/>
|
||||
</form>
|
||||
<div
|
||||
className={'dropzone' + (dragOver ? ' dropzone--drag-over' : '')}
|
||||
onDrop={this.handleDrop}
|
||||
onDragOver={this.handleDragOver}
|
||||
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={dropZoneClassName} {...dropZoneHooks}>
|
||||
{hasContent ? (
|
||||
<div className={'dropzone-preview-wrapper' + (memeifyContent ? ' dropzone-preview-memeify' : '')}>
|
||||
<DropzonePreviewImage {...dropZonePreviewProps} />
|
||||
<div className={'dropzone-preview-overlay'}>
|
||||
{ dragOver ? <DropzoneDropItDisplay /> : null }
|
||||
{ mouseOver ? (
|
||||
|
@ -133,15 +214,15 @@ class Dropzone extends React.Component {
|
|||
message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null}
|
||||
/>
|
||||
) : null }
|
||||
{memeifyContent}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
dragOver ? <DropzoneDropItDisplay /> : (
|
||||
<DropzoneInstructionsDisplay
|
||||
fileError={fileError}
|
||||
/>
|
||||
<DropzoneInstructionsDisplay fileError={fileError} />
|
||||
)
|
||||
)}
|
||||
{memeifyContent ? <div className={'dropzone-memeify-saveMessage'}>{`Don't forget to save before you publish.`}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
},
|
||||
"homepage": "https://github.com/lbryio/spee.ch#readme",
|
||||
"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",
|
||||
"bcrypt": "^2.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
|
@ -67,12 +70,14 @@
|
|||
"passport-local": "^1.0.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.4.2",
|
||||
"react-feather": "^1.1.4",
|
||||
"react-dom": "^16.6.1",
|
||||
"react-draggable": "^3.0.5",
|
||||
"react-feather": "^1.1.4",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-select": "^2.1.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-saga": "^0.16.2",
|
||||
"sequelize": "^4.41.1",
|
||||
|
|
Loading…
Reference in a new issue