diff --git a/client/scss/_asset-preview.scss b/client/scss/_asset-preview.scss index b9290fe6..f420531d 100644 --- a/client/scss/_asset-preview.scss +++ b/client/scss/_asset-preview.scss @@ -31,19 +31,19 @@ h3.asset-preview__title { padding: 0; border: 0; position: absolute; - opacity: .50; - height: 50%; - top: 25%; + opacity: 0.80; + height: 25%; + top: 37.5%; left: 0; right: 0; bottom: 0; z-index: 1000; margin: 0 auto; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='lightgray' stroke-width='2' fill='white' fill-rule='evenodd' stroke-linejoin='round'%3E %3Cpolygon points='5 21 5 3 21 12'/%3E %3C/g%3E %3C/svg%3E"); + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E %3Cg stroke='black' stroke-width='2' fill='black' fill-rule='evenodd' stroke-linejoin='round'%3E %3Ccircle cx='30' cy='30' r='28'/%3E%3C/g%3E %3Cg stroke='white' stroke-width='1' fill='white' fill-rule='evenodd' stroke-linejoin='round'%3E %3Cpolygon points='25 19 42 30 25 41'/%3E %3C/g%3E %3C/svg%3E"); background-repeat: no-repeat; background-position: center; } .asset-preview__play-wrapper:hover .asset-preview__play-overlay { - opacity: 1.0; + opacity: 0.2; } diff --git a/client/scss/_dropzone.scss b/client/scss/_dropzone.scss index 09b8b1bc..a3934edd 100644 --- a/client/scss/_dropzone.scss +++ b/client/scss/_dropzone.scss @@ -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 { diff --git a/client/src/components/Creatify/EditableFontface/index.js b/client/src/components/Creatify/EditableFontface/index.js new file mode 100644 index 00000000..2ef131e0 --- /dev/null +++ b/client/src/components/Creatify/EditableFontface/index.js @@ -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) ? ( + 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 ( +
+ + {fontInput} +
{textRender(value)}
+
+ ); + } + + 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'), +} diff --git a/client/src/components/Creatify/FontFaces/.gitkeep b/client/src/components/Creatify/FontFaces/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/client/src/components/Creatify/FontFaces/GreenMachine.js b/client/src/components/Creatify/FontFaces/GreenMachine.js new file mode 100644 index 00000000..60b0b462 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/GreenMachine.js @@ -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', + }, +}; diff --git a/client/src/components/Creatify/FontFaces/Inferno.js b/client/src/components/Creatify/FontFaces/Inferno.js new file mode 100644 index 00000000..45fc4b20 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/Inferno.js @@ -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', + }, +}; diff --git a/client/src/components/Creatify/FontFaces/Lazer.js b/client/src/components/Creatify/FontFaces/Lazer.js new file mode 100644 index 00000000..96373b4c --- /dev/null +++ b/client/src/components/Creatify/FontFaces/Lazer.js @@ -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)', + }, +}; diff --git a/client/src/components/Creatify/FontFaces/Neon.js b/client/src/components/Creatify/FontFaces/Neon.js new file mode 100644 index 00000000..16d0eb20 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/Neon.js @@ -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', + }, +}; diff --git a/client/src/components/Creatify/FontFaces/OldBlue.js b/client/src/components/Creatify/FontFaces/OldBlue.js new file mode 100644 index 00000000..99448d87 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/OldBlue.js @@ -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 ( + + + + + {text} + + + + + {text} + + + + ); + }, +}; diff --git a/client/src/components/Creatify/FontFaces/RetroRainbow.js b/client/src/components/Creatify/FontFaces/RetroRainbow.js new file mode 100644 index 00000000..833c9671 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/RetroRainbow.js @@ -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', + }, +}; diff --git a/client/src/components/Creatify/FontFaces/TheSpecial.js b/client/src/components/Creatify/FontFaces/TheSpecial.js new file mode 100644 index 00000000..d7e5087a --- /dev/null +++ b/client/src/components/Creatify/FontFaces/TheSpecial.js @@ -0,0 +1,41 @@ +import React from 'react'; + +module.exports = { + container: {}, + editorStyle: { + fontFamily: 'Arial, sans-serif', + fontWeight: 'bold', + fontSize: '1.4em', + }, + text: { + fontFamily: 'Arial, sans-serif', + fontWeight: 'bold', + backgroundImage: 'linear-gradient(to right, #b306a9, #ef2667, #f42e2c, #ffa509, #fdfc00, #55ac2f, #0b13fd, #a804af)', + backgroundClip: 'text', + fontSize: '1.4em', + color: 'transparent', + paddingBottom: '.25em', + paddingTop: '.1em', + WebkitBackgroundClip: 'text', + }, + textRender: (text) => { + text = text + .replace(/love [^\s.!$]+/g, 'love LBRY') + .replace(/LBRY/g, 'amazing LBRY') + .replace(/julie/gi, 'super Julie') + .replace(/tom/gi, 'amazing Tom') + .replace(/(btc|bch)/gi, 'LBC') + .replace(/\w+ is \w+/gi, 'LBRY is amazing'); + + return text.split(/chris[\d\w]*/gi).reduce((result, value, index) => { + if(index !== 0) { + result.push(); + } + result.push({value}) + + return result; + }, []) + }, +}; + +const THE_FACE = 'data:image/png;base64,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'; diff --git a/client/src/components/Creatify/FontFaces/VaporWave.js b/client/src/components/Creatify/FontFaces/VaporWave.js new file mode 100644 index 00000000..7d2f0575 --- /dev/null +++ b/client/src/components/Creatify/FontFaces/VaporWave.js @@ -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 ( + + + + + {formattedText} + + + + ); + }, +}; diff --git a/client/src/components/Creatify/RichDraggable/index.js b/client/src/components/Creatify/RichDraggable/index.js new file mode 100644 index 00000000..c0e9e462 --- /dev/null +++ b/client/src/components/Creatify/RichDraggable/index.js @@ -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 ( + +
+
+ {props.children} +
+
+
+ ); + } +}; diff --git a/client/src/components/Creatify/index.js b/client/src/components/Creatify/index.js new file mode 100644 index 00000000..91c77c9e --- /dev/null +++ b/client/src/components/Creatify/index.js @@ -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 = ''; + const svgContents = ` + +${kerningAndPadding}${contents} +`; + + 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: ( +
+ +
+ ), + 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 = ( + this.setActiveElement(newElement)}> + + + ); + + 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 = `` + contents; + + const contentsWidth = contentsElement.offsetWidth; + const contentsHeight = contentsElement.offsetHeight; + + // Fix the dimensions, fixes when flex is used. + contents = `
${contents}
`; + + 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 ( +
+
+ this.addElement()} /> +
+ this.removeElement()} /> +
+
+ -
- {file || isUpdate ? ( -
- {file ? ( - - ) : ( - - )} +
+ {hasContent ? ( +
+
{ dragOver ? : null } { mouseOver ? ( @@ -133,15 +214,15 @@ class Dropzone extends React.Component { message={fileExt === 'mp4' ? 'Drag & drop new thumbnail' : null} /> ) : null } + {memeifyContent}
) : ( dragOver ? : ( - + ) )} + {memeifyContent ?
{`Don't forget to save before you publish.`}
: null}
)} diff --git a/package.json b/package.json index 4177efb1..8f066fae 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/chainquery/bundle.js b/server/chainquery/bundle.js index 60aca215..4a96169b 100644 --- a/server/chainquery/bundle.js +++ b/server/chainquery/bundle.js @@ -904,7 +904,7 @@ var claimQueries = (db, table, sequelize) => ({ }; return await table.findAll({ where: selectWhere, - order: [['height', 'DESC']], + order: [['height', 'DESC'],['claim_id', 'ASC']], }) .then(channelClaimsArray => { if (channelClaimsArray.length === 0) { @@ -917,7 +917,7 @@ var claimQueries = (db, table, sequelize) => ({ getClaimIdByLongChannelId: async (channelClaimId, claimName) => { logger$1.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`); return await table.findAll({ - where: { name: claimName, publisher_id: channelClaimId }, + where: { name: claimName, publisher_id: channelClaimId, bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } }, order: [['id', 'ASC']], }) .then(result => { diff --git a/server/chainquery/queries/claimQueries.js b/server/chainquery/queries/claimQueries.js index 8cba61c2..cf4b532b 100644 --- a/server/chainquery/queries/claimQueries.js +++ b/server/chainquery/queries/claimQueries.js @@ -103,7 +103,7 @@ export default (db, table, sequelize) => ({ }; return await table.findAll({ where: selectWhere, - order: [['height', 'DESC']], + order: [['height', 'DESC'],['claim_id', 'ASC']], }) .then(channelClaimsArray => { if (channelClaimsArray.length === 0) { @@ -116,7 +116,7 @@ export default (db, table, sequelize) => ({ getClaimIdByLongChannelId: async (channelClaimId, claimName) => { logger.debug(`finding claim id for claim ${claimName} from channel ${channelClaimId}`); return await table.findAll({ - where: { name: claimName, publisher_id: channelClaimId }, + where: { name: claimName, publisher_id: channelClaimId, bid_state: { [sequelize.Op.or]: ['Controlling', 'Active', 'Accepted'] } }, order: [['id', 'ASC']], }) .then(result => {