diff --git a/config/development.json b/config/development.json index 2c64d379..2712bde3 100644 --- a/config/development.json +++ b/config/development.json @@ -10,7 +10,7 @@ "PublishUploadPath": "C:\\lbry\\speech\\hosted_content" }, "Logging": { - "LogLevel": "debug", + "LogLevel": "silly", "LogDirectory": "C:\\lbry\\speech\\logs\\" } } \ No newline at end of file diff --git a/controllers/publishController.js b/controllers/publishController.js index eb1d7660..fc632836 100644 --- a/controllers/publishController.js +++ b/controllers/publishController.js @@ -7,6 +7,9 @@ const errorHandlers = require('../helpers/libraries/errorHandlers.js'); function createPublishParams (claim, filePath, license, nsfw) { logger.debug(`Creating Publish Parameters for "${claim}"`); + if (typeof nsfw === 'string') { + nsfw = (nsfw.toLowerCase() === 'on'); + } const publishParams = { name : claim, file_path: filePath, @@ -17,11 +20,12 @@ function createPublishParams (claim, filePath, license, nsfw) { author : 'spee.ch', language : 'en', license, - nsfw : nsfw.toLowerCase() === 'on', + nsfw, }, claim_address : walledAddress, change_address: walledAddress, }; + logger.debug('publishParams:', publishParams); return publishParams; } @@ -49,6 +53,7 @@ module.exports = { socket.emit('publish-complete', { name: claim, result }); }) .catch(error => { + logger.error(`Error publishing ${fileName}`, error); visitor.event('Publish Route', 'Publish Failure', filePath).send(); socket.emit('publish-failure', errorHandlers.handlePublishError(error)); deleteTemporaryFile(filePath); diff --git a/helpers/logging/loggerSetup.js b/helpers/logging/loggerSetup.js index 61c5e192..7a0b2e41 100644 --- a/helpers/logging/loggerSetup.js +++ b/helpers/logging/loggerSetup.js @@ -8,10 +8,12 @@ module.exports = (winston, logLevel, logDir) => { winston.configure({ transports: [ new (winston.transports.Console)({ - level : logLevel, - timestamp : false, - colorize : true, - prettyPrint: true, + level : logLevel, + timestamp : false, + colorize : true, + prettyPrint : true, + handleExceptions : true, + humanReadableUnhandledException: true, }), new (winston.transports.File)({ filename : `${logDir}/speechLogs.log`, diff --git a/public/assets/css/style.css b/public/assets/css/style.css index 4d815c6f..2f2d4137 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -1,3 +1,9 @@ +canvas { + background-color: blue; + width: 100%; + height: auto; +} + #drop-zone { border: 1px dashed lightgrey; padding: 1em; @@ -5,14 +11,22 @@ width: 100%; } -.all-claims-img { - height: 200px; -} - #image-preview { display: none; } +#tweet-meme-button { + float: left; + margin-right: 5px; +} + .row { margin: 10px 0px 10px 0px; -} \ No newline at end of file +} + +.all-claims-img { + height: 200px; +} + + + diff --git a/public/assets/js/memeDraw.js b/public/assets/js/memeDraw.js new file mode 100644 index 00000000..db1cf2c5 --- /dev/null +++ b/public/assets/js/memeDraw.js @@ -0,0 +1,85 @@ +var canvas = document.getElementById('meme-canvas'); +var img = document.getElementById('start-image'); +var canvasWidth; +var canvasHeight; +var fontSize = 28; +var topText = document.getElementById('top-text'); +var bottomText = document.getElementById('bottom-text'); +var ctx = canvas.getContext('2d'); +var claimNameInput = document.getElementById("file-name-input"); + +// create the canvas +img.onload = function() { + // get dimensions of the start img + canvasWidth = img.width; + canvasHeight = img.height; + // hide start image + img.hidden = true; + // size the canvas + canvas.width = canvasWidth; + canvas.height = canvasHeight; + // draw the starting meme + drawMeme() +} + +// if the text changes, re-draw the meme +topText.addEventListener('keyup', drawMeme); +bottomText.addEventListener('keyup', drawMeme); + +// draw the image and draw the text over it +function drawMeme() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight); + ctx.lineWidth = 4; + ctx.font = fontSize + 'px sans-serif'; + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'white'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + + var text1 = topText.value; + text1 = text1.toUpperCase(); + x = canvasWidth / 2; + y = 0; + + wrapText(ctx, text1, x, y, canvasWidth, fontSize, false); + + ctx.textBaseline = 'bottom'; + var text2 = bottomText.value; + text2 = text2.toUpperCase(); + y = canvasHeight; + + wrapText(ctx, text2, x, y, canvasHeight, fontSize, true); + +} + +function wrapText(context, text, x, y, maxWidth, lineHeight, fromBottom) { + var pushMethod = (fromBottom)?'unshift':'push'; + + lineHeight = (fromBottom)?-lineHeight:lineHeight; + + var lines = []; + var y = y; + var line =''; + var words = text.split(' '); + + for (var i = 0; i < words.length; i++) { + var testLine = line + ' ' + words[i]; + var metrics = context.measureText(testLine); + var testWidth = metrics.width; + + if (testWidth > maxWidth) { + lines[pushMethod](line); + line = words[i] + ' '; + } else { + line = testLine; + } + } + + lines[pushMethod](line); + + for (var k in lines ) { + context.strokeText(lines[k], x, y + lineHeight * k); + context.fillText(lines[k], x, y + lineHeight * k); + } +} \ No newline at end of file diff --git a/public/assets/js/memePublish.js b/public/assets/js/memePublish.js new file mode 100644 index 00000000..1d020aab --- /dev/null +++ b/public/assets/js/memePublish.js @@ -0,0 +1,140 @@ + +// define variables +var socket = io(); +var uploader = new SocketIOFileUpload(socket); +var stagedFiles = null; +var license = 'Creative Commons'; +var nsfw = false; +var claimName; + +function dataURItoBlob(dataURI) { + // convert base64/URLEncoded data component to raw binary data held in a string + var byteString; + if (dataURI.split(',')[0].indexOf('base64') >= 0) + byteString = atob(dataURI.split(',')[1]); + else + byteString = unescape(dataURI.split(',')[1]); + + // separate out the mime component + var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + + // write the bytes of the string to a typed array + var ia = new Uint8Array(byteString.length); + for (var i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + return new Blob([ia], {type:mimeString}); +} + +function startPublish() { + //download the image + var dataUrl = canvas.toDataURL('image/jpeg'); // canvas defined in memeDraw.js + var blob = dataURItoBlob(dataUrl) + claimName = claimNameInput.value; // claimNameInput.value defined in memeDraw.js + var fileName = claimNameInput.value + ".jpg"; + var file = new File([blob], fileName, {type: 'image/jpeg', lastModified: Date.now()}); + console.log(file); + stageAndPublish(file); +}; + +/* helper functions */ +// create a progress animation +function createProgressBar(element, size){ + var x = 1; + var adder = 1; + function addOne(){ + var bars = '
|'; + for (var i = 0; i < x; i++){ bars += ' | '; } + bars += '
'; + element.innerHTML = bars; + if (x === size){ + adder = -1; + } else if ( x === 0){ + adder = 1; + } + x += adder; + }; + setInterval(addOne, 300); +} + +function stageAndPublish(file) { + // stage files + stagedFiles = [file]; // stores the selected file for + // make sure a file was selected + if (stagedFiles) { + // make sure only 1 file was selected + if (stagedFiles.length < 1) { + alert("A file is needed"); + return; + } + // make sure the content type is acceptable + switch (stagedFiles[0].type) { + case "image/png": + case "image/jpeg": + case "image/gif": + case "video/mp4": + uploader.submitFiles(stagedFiles); + break; + default: + alert("Only .png, .jpeg, .gif, and .mp4 files are currently supported"); + break; + } + } + +} + +// update the publish status +function updatePublishStatus(msg){ + document.getElementById('publish-status').innerHTML = msg; +} + +/* socketio-file-upload listeners */ +uploader.addEventListener('start', function(event){ + event.file.meta.name = claimName; + event.file.meta.license = license; + event.file.meta.nsfw = nsfw; + event.file.meta.type = stagedFiles[0].type; + // re-set the html in the publish area + document.getElementById('publish-active-area').innerHTML = ''; + // start a progress animation + createProgressBar(document.getElementById('progress-bar'), 12); +}); +uploader.addEventListener('progress', function(event){ + var percent = event.bytesLoaded / event.file.size * 100; + updatePublishStatus('File is ' + percent.toFixed(2) + '% loaded to the server'); +}); + +/* socket.io message listeners */ +socket.on('publish-status', function(msg){ + updatePublishStatus(msg); +}); +socket.on('publish-failure', function(msg){ + document.getElementById('publish-active-area').innerHTML = '' + JSON.stringify(msg) + '
--(✖╭╮✖)→
For help, post the above error text in the #speech channel on the LBRY slack'; +}); +socket.on('publish-complete', function(msg){ + var publishResults; + var directUrl = 'https://spee.ch/' + msg.name + '/' + msg.result.claim_id; + // build new publish area + publishResults = 'Your publish is complete! Go ahead, share it with the world!
'; + publishResults += 'NOTE: the blockchain will need a few minutes to process your amazing work. Please allow some time for your meme to appear at your link.
'; + publishResults += 'Your meme has been published to http://spee.ch/' + msg.name + '
'; + publishResults += 'Here is a direct link to where your meme will be stored: ' + directUrl + '
'; + publishResults += 'Your Transaction ID is: ' + msg.result.txid + '
'; + publishResults += ''; + // update publish area + document.getElementById('publish-active-area').innerHTML = publishResults; + // add a tweet button + twttr.widgets + .createShareButton( + directUrl, + document.getElementById('tweet-meme-button'), + { + text: 'Check out my meme creation on the LBRY blockchain!', + hashtags: 'LBRYMemeFodder', + via: 'lbryio' + }) + .then( function( el ) { + console.log('Tweet button added.'); + }); +}); \ No newline at end of file diff --git a/routes/home-routes.js b/routes/home-routes.js index e389b4ae..09c40644 100644 --- a/routes/home-routes.js +++ b/routes/home-routes.js @@ -7,7 +7,7 @@ module.exports = app => { }); // a catch-all route if someone visits a page that does not exist app.use('*', (req, res) => { - logger.error(`Get request on ${req.originalUrl} which was 404`); + logger.error(`Get request on ${req.originalUrl} which was a 404`); res.status(404).render('fourOhFour'); }); }; diff --git a/routes/show-routes.js b/routes/show-routes.js index 0c8b5e6e..6bd16fe4 100644 --- a/routes/show-routes.js +++ b/routes/show-routes.js @@ -3,12 +3,26 @@ const showController = require('../controllers/showController.js'); const logger = require('winston'); module.exports = (app, ua, googleAnalyticsId) => { + // route to fetch all free public claims + app.get('/meme-fodder/play', ({ originalUrl }, res) => { + // google analytics + logger.debug(`GET request on ${originalUrl}`); + // get and serve content + showController + .getAllClaims('meme-fodder') + .then(orderedFreePublicClaims => { + res.status(200).render('memeFodder', { claims: orderedFreePublicClaims }); + }) + .catch(error => { + errorHandlers.handleRequestError(error, res); + }); + }); // route to fetch all free public claims app.get('/:name/all', ({ originalUrl, params }, res) => { logger.debug(`GET request on ${originalUrl}`); // google analytics ua(googleAnalyticsId, { https: true }).event('Show Routes', '/name/all', `${params.name}/all`).send(); - // fetch all free public claims + // get and serve content showController .getAllClaims(params.name) .then(orderedFreePublicClaims => { diff --git a/views/index.handlebars b/views/index.handlebars index ea81218f..120edfff 100644 --- a/views/index.handlebars +++ b/views/index.handlebars @@ -55,6 +55,7 @@ }; if (selectedFile) { + console.log(selectedFile); previewReader.readAsDataURL(selectedFile); // reads the data and sets the img src document.getElementById('publish-name').value = selectedFile.name.substring(0, selectedFile.name.indexOf('.')); // updates metadata inputs stagedFiles = [selectedFile]; // stores the selected file for upload diff --git a/views/memeFodder.handlebars b/views/memeFodder.handlebars new file mode 100644 index 00000000..4257bf0c --- /dev/null +++ b/views/memeFodder.handlebars @@ -0,0 +1,17 @@ +Spee.ch is a platform by which you can publish images to the Lbry blockchain. Just upload an image, title it, and send it off into the lbry ecosystem.
+Spee.ch is also a platform to serve you those images. It's like have a personal chef that will serve you a meal anywhere in the world. All you have to do is ask for it, by using "spee.ch/" + the name of a claim.
+If you want a specific image, just ask for it with the claim_id by using "spee.ch/" + the name of the claim + "/" + the claim id.
+Here's how it's played...
+(1) /meme-fodder will always use the winning public, free image published to LBRY://meme-fodder. (meaning the most recent, highest bid). Want to put a different image on the chopping block? Go publish it!
+(2) Create a meme based on the current claim with the tool below. Think you got a winner? Share it with the community and see what they think!
+Below are some of the most recent images that have been fuel for our enjoyment on /meme-fodder
+(ideally I'd like to display what users have submitted here)
+(maybe a voting system? Is there a way to allow people to donate funds to a claimId so that it will show up higher in the results?)
+