From c0b0d664676ee14b7a733ec0ee513ab6053b7d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=AB=20=E3=82=A6=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=83=96?= Date: Thu, 27 Sep 2018 11:11:26 -0500 Subject: [PATCH 1/3] Fixes for the Tour --- app/components/client/tour-scripts.js | 74 ++-- app/components/head.js | 25 +- app/components/navigation.js | 6 +- app/components/playground.js | 79 +++++ app/dist/media/images/og-image.png | Bin 0 -> 48633 bytes app/dist/scripts/sockets.js | 72 ++-- app/helpers/fetch-metadata.js | 10 +- app/helpers/github.js | 105 +++++- app/index.js | 2 - app/sass/bundle.scss | 1 + app/sass/init/_extends.scss | 4 + app/sass/init/_markdown.scss | 29 -- app/sass/pages/_api.scss | 14 +- app/sass/pages/_tour.scss | 99 ++++-- app/sass/partials/_github-feed.scss | 2 +- app/sass/partials/_pre.scss | 30 ++ app/sockets.js | 394 +++++++++++++++++++++ app/views/home.js | 4 +- app/views/redirect.js | 2 +- documents/playground.md | 9 + server.js | 479 +------------------------- 21 files changed, 812 insertions(+), 628 deletions(-) create mode 100644 app/components/playground.js create mode 100644 app/dist/media/images/og-image.png create mode 100644 app/sass/partials/_pre.scss create mode 100644 app/sockets.js create mode 100644 documents/playground.md diff --git a/app/components/client/tour-scripts.js b/app/components/client/tour-scripts.js index e066cfd..1fd3f10 100644 --- a/app/components/client/tour-scripts.js +++ b/app/components/client/tour-scripts.js @@ -13,30 +13,41 @@ if (window.location.href.search && window.location.href.split("?url=")[1]) { // -$("body").on("click", "[data-action]", event => { - event.preventDefault(); +document.querySelector("body").addEventListener("click", event => { + if (event.target.dataset.action) { + event.preventDefault(); + document.querySelector(".tour").classList.add("waiting"); + handleExamples(event.target); + } - $(".tour").addClass("waiting"); + if ( + event.explicitOriginalTarget.classList && + event.explicitOriginalTarget.classList[0] === "tour__content__meme__canvas__thumbnail" + ) { + for (const thumbnail of document.querySelectorAll(".tour__content__meme__canvas__thumbnail")) { + thumbnail.classList.remove("selected"); + } - setTimeout(() => { - handleExamples(event); - $(".tour").removeClass("waiting"); - }, 2500); // "rate-limit" to allow example divs time to populate + event.explicitOriginalTarget.classList.add("selected"); + updateCanvas(event.explicitOriginalTarget); + } }); -$("body").on("click", ".tour__content__meme__canvas__thumbnail", event => { - $(".tour__content__meme__canvas__thumbnail").removeClass("selected"); - - event.currentTarget.className += " selected"; - updateCanvas(event.currentTarget); -}); - -$("#fetch-claim-uri").on("keyup", event => { +document.getElementById("fetch-claim-uri").addEventListener("keyup", event => { const key = event.keyCode ? event.keyCode : event.which; - if (key === 13 && $("#fetch-claim-uri").val()) fetchMetadata(1, $("#fetch-claim-uri").val()); + + if ( + key === 13 && + document.getElementById("fetch-claim-uri").value.length > 0 + ) fetchMetadata(1, document.getElementById("fetch-claim-uri").value); }); -$("body").on("keyup", "#meme-top-line, #meme-bottom-line", () => updateCanvas()); +document.querySelector("body").addEventListener("keyup", event => { + if ( + event.target.id === "meme-top-line" || + event.target.id === "meme-bottom-line" + ) updateCanvas(); +}); @@ -94,17 +105,18 @@ function debounce(func, wait, immediate) { } function initializeTour() { - $(".tour").addClass("waiting"); - $("#fetch-claim-uri").val("").focus(); // reset - $(".tour__sidebar__example:nth-child(1)").addClass("active"); + document.querySelector(".tour").classList.add("waiting"); + document.querySelector("#fetch-claim-uri").value = ""; + document.querySelector("#fetch-claim-uri").focus(); + document.querySelector(".tour__navigation__example:nth-child(1)").classList.add("active"); send(JSON.stringify({ "message": "landed on tour" })); setTimeout(() => { - $(".tour").removeClass("waiting"); - }, 2500); + document.querySelector(".tour__navigation__example:nth-child(1)").click(); + }, 300); } @@ -200,10 +212,10 @@ function getMemeInfo() { // TODO: Error handling const handleExamples = debounce(event => { let exampleNumber; - const data = event.currentTarget.dataset; + const data = event.dataset; - if (!parseInt($(".tour__sidebar__example.active")[0].dataset.example)) return; - exampleNumber = parseInt($(".tour__sidebar__example.active")[0].dataset.example); + if (!parseInt($(".tour__navigation__example.active")[0].dataset.example)) return; + exampleNumber = parseInt($(".tour__navigation__example.active")[0].dataset.example); switch(data.action) { case "choose claim": @@ -224,8 +236,8 @@ const handleExamples = debounce(event => { $("#tour-url button").text("Resolve"); if ($("#tour-url")[0].style.display === "none") $("#tour-url").show(); - $(".tour__sidebar__example").removeClass("active"); - $(".tour__sidebar__example:nth-child(1)").addClass("active"); + $(".tour__navigation__example").removeClass("active"); + $(".tour__navigation__example:nth-child(1)").addClass("active"); $("#tour-loader").empty().show(); $("#tour-results").empty().show(); @@ -244,8 +256,8 @@ const handleExamples = debounce(event => { $("#fetch-claim-uri").val(""); // reset URL bar $("#tour-url").hide(); - $(".tour__sidebar__example").removeClass("active"); - $(".tour__sidebar__example:nth-child(2)").addClass("active"); + $(".tour__navigation__example").removeClass("active"); + $(".tour__navigation__example:nth-child(2)").addClass("active"); $("#tour-loader").empty().show(); $("#tour-results").empty().show(); @@ -266,8 +278,8 @@ const handleExamples = debounce(event => { // $("#tour-url").after("

In the LBRY app, you can financially support your favorite creators by donating LBRY Coin (LBC). In this example, we are donating LBC in your stead.

"); if ($("#tour-url")[0].style.display === "none") $("#tour-url").show(); - $(".tour__sidebar__example").removeClass("active"); - $(".tour__sidebar__example:nth-child(3)").addClass("active"); + $(".tour__navigation__example").removeClass("active"); + $(".tour__navigation__example:nth-child(3)").addClass("active"); $("#tour-loader").empty().show(); $("#tour-results").empty().show(); diff --git a/app/components/head.js b/app/components/head.js index 97c34d1..9695bb3 100644 --- a/app/components/head.js +++ b/app/components/head.js @@ -39,30 +39,29 @@ module.exports = exports = (state, emit) => { - - - - - + + + + + + + - + - - - + + + - - + - - `; }; diff --git a/app/components/navigation.js b/app/components/navigation.js index 00b505b..9de7ee9 100644 --- a/app/components/navigation.js +++ b/app/components/navigation.js @@ -30,9 +30,9 @@ export default class Navigation extends Nanocomponent { url: "/overview" }, { - name: "Tour", - title: "Take a Tour", - url: "/tour" + name: "Playground", + title: "Experience LBRY", + url: "/playground" }, { name: "Resources", diff --git a/app/components/playground.js b/app/components/playground.js new file mode 100644 index 0000000..a7400bc --- /dev/null +++ b/app/components/playground.js @@ -0,0 +1,79 @@ +"use strict"; + + + +// P A C K A G E S + +import dedent from "dedent"; +import html from "choo/html"; +import raw from "choo/html/raw"; + + + +// E X P O R T + +export default function () { + return dedent` +
+ +

+
${raw(example1())}
+
+ `; +} + + + +// H E L P E R S + +function example1() { + return html` +
+ lbry:// + +
+ + +
+ + + `; +} + +function navigation() { // TODO: Save tutorial position to localStorage + return dedent` +
  • + + Get details of media (aka, "claim" metadata) +
  • + +
  • + + Create a meme and upload it to the LBRY blockchain +
  • + +
  • + + Support creators on LBRY with a tip, on us! +
  • + `; +} diff --git a/app/dist/media/images/og-image.png b/app/dist/media/images/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..e9d32478aa4fc4d75fd5fb4b1aeac9a3123b8dca GIT binary patch literal 48633 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 MqH-b?pY{Fz2M;l~4*&oF literal 0 HcmV?d00001 diff --git a/app/dist/scripts/sockets.js b/app/dist/scripts/sockets.js index adcebe9..6e901dc 100644 --- a/app/dist/scripts/sockets.js +++ b/app/dist/scripts/sockets.js @@ -1,34 +1,60 @@ -/* global $, log, ws */ "use strict"; +"use strict"; -// const log = console.log; // eslint-disable-line +document.addEventListener("DOMContentLoaded", () => { + initializeWebSocketConnection(); + setInterval(checkWebSocketConnection, 5000); +}); -ws.onmessage = socket => { - const data = JSON.parse(socket.data); +let ws = null; - switch (true) { - case data.message === "updated html": - $(data.selector).html(data.html); - $("#emailMessage").val(""); - break; +function checkWebSocketConnection() { + if (!ws || ws.readyState === 3) initializeWebSocketConnection(); +} - case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent - $("#flash-container").html(`
    ${data.details}
    `); +function initializeWebSocketConnection() { + ws = new WebSocket(location.origin.replace(/^http/, "ws")); - setTimeout(() => { - $("#flash-container").html(""); - }, 2100); + ws.onopen = () => { + console.log("WebSocket connection established"); // eslint-disable-line + }; - break; + ws.onmessage = socket => { + const data = JSON.parse(socket.data); - default: - log(data); - break; - } -}; + switch (true) { + case data.message === "updated html": + document.querySelector(data.selector).innerHTML = data.html; + document.getElementById("emailAddress").value = ""; + document.getElementById("emailMessage").innerHTML = ""; + if (document.getElementById("temp-loader")) document.getElementById("temp-loader").style.display = "none"; + document.querySelector(".tour").classList.remove("waiting"); + break; + + case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent + document.getElementById("flash-container").innerHTML = + `
    ${data.details}
    `; + + setTimeout(() => { + document.getElementById("flash-container").innerHTML = ""; + }, 2100); + + break; + + default: + console.log(data); // eslint-disable-line + break; + } + }; + + ws.onclose = () => { + console.log("WebSocket connection lost"); // eslint-disable-line + checkWebSocketConnection(); // reconnect now + }; +} function send(msg) { // eslint-disable-line socketReady(ws, () => ws.send(msg)); @@ -36,11 +62,11 @@ function send(msg) { // eslint-disable-line function socketReady(socket, callback) { setTimeout(() => { - if (socket.readyState === 1) { + if (socket && socket.readyState === 1) { if (callback !== undefined) callback(); return; - } else { - socketReady(socket, callback); } + + return socketReady(socket, callback); }, 5); } diff --git a/app/helpers/fetch-metadata.js b/app/helpers/fetch-metadata.js index 13c714b..2ea38ed 100644 --- a/app/helpers/fetch-metadata.js +++ b/app/helpers/fetch-metadata.js @@ -12,11 +12,11 @@ const stringifyObject = require("stringify-object"); // V A R I A B L E S -const randomString = local("/app/helpers/random-string"); +const randomString = local("app/helpers/random-string"); const loadLanguages = require("prismjs/components/"); -const logSlackError = local("/app/helpers/slack"); -const publishMeme = local("/app/helpers/publish-meme"); -const uploadImage = local("/app/helpers/upload-image"); +const logSlackError = local("app/helpers/slack"); +const publishMeme = local("app/helpers/publish-meme"); +const uploadImage = local("app/helpers/upload-image"); loadLanguages(["json"]); @@ -126,7 +126,6 @@ module.exports = exports = (data, socket) => {

    Response

    ${explorerNotice}
    ${renderedCode}
    - `), "message": "updated html", "selector": `#example${data.example}-result` @@ -195,7 +194,6 @@ module.exports = exports = (data, socket) => {

    Response

    ${explorerNotice}
    ${renderedCode}
    - `), "message": "updated html", "selector": `#example${data.example}-result` diff --git a/app/helpers/github.js b/app/helpers/github.js index ab9e2dd..da4f721 100644 --- a/app/helpers/github.js +++ b/app/helpers/github.js @@ -2,6 +2,46 @@ +// P A C K A G E S + +const async = require("async"); +const color = require("colorette"); +const local = require("app-root-path").require; +const octokit = require("@octokit/rest")(); +const redis = require("redis"); + +// V A R I A B L E S + +const logSlackError = local("app/helpers/slack"); +const relativeDate = local("app/modules/relative-date"); +let client; + +// R E D I S + +if (typeof process.env.GITHUB_OAUTH_TOKEN !== "undefined") { + octokit.authenticate({ + type: "oauth", + token: process.env.GITHUB_OAUTH_TOKEN + }); +} else process.stdout.write(`${color.red("[missing]")} GitHub token`); + +if (typeof process.env.REDISCLOUD_URL !== "undefined") { + client = redis.createClient(process.env.REDISCLOUD_URL); + + client.on("error", redisError => { + process.env.NODE_ENV === "development" ? + process.stdout.write(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) : + logSlackError( + "\n" + + "> *REDIS ERROR:* ```" + JSON.parse(JSON.stringify(redisError)) + "```" + "\n" + + "> _Cause: Someone is trying to run LBRY.tech locally without environment variables OR Heroku is busted_\n" + ) + ; + }); +} else process.stdout.write(`${color.red("[missing]")} Redis client URL`); + + + // P R O G R A M function generateEvent(event) { @@ -84,6 +124,44 @@ function generateEvent(event) { } } +function generateGitHubFeed(displayGitHubFeed) { + if (typeof process.env.REDISCLOUD_URL !== "undefined") { + client.zrevrange("events", 0, 9, (err, reply) => { + if (err) return; // TODO: Render a div with nice error message + + const events = []; + const renderedEvents = []; + + reply.forEach(item => events.push(JSON.parse(item))); + + for (const event of events) { + renderedEvents.push(` +
    + + + + +

    + ${generateEvent(event)} + ${event.repo.name} + ${relativeDate(new Date(event.created_at))} +

    +
    + `); + } + + updateGithubFeed(); // TODO: Update `.last-updated` every minute + + displayGitHubFeed(` +

    GitHub

    +
    Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "·")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST
    + + ${renderedEvents.join("")} + `); + }); + } +} + function generateUrl(type, event) { switch (type) { case "actor": @@ -115,6 +193,29 @@ function generateUrl(type, event) { } } +function updateGithubFeed() { + octokit.activity.getEventsForOrg({ + org: "lbryio", + per_page: 20, + page: 1 + }).then(({ data }) => { + async.eachSeries(data, (item, callback) => { + const eventString = JSON.stringify(item); + + client.zrank("events", eventString, (err, reply) => { + if (reply === null) client.zadd("events", item.id, eventString, callback); + else callback(); + }); + }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events + }).catch(err => { + logSlackError( + "\n" + + "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" + + "> _Cause: GitHub feed refresh_\n" + ); + }); +} + // H E L P E R @@ -129,5 +230,7 @@ function refToBranch(ref) { module.exports = exports = { generateEvent, - generateUrl + generateGitHubFeed, + generateUrl, + updateGithubFeed }; diff --git a/app/index.js b/app/index.js index 138becd..c3c0aec 100755 --- a/app/index.js +++ b/app/index.js @@ -1,4 +1,2 @@ -// "use strict"; require("make-promises-safe"); const app = require("./server.js"); // eslint-disable-line - "use strict"; require("@babel/register"); require("@babel/polyfill"); module.exports = exports = require("./client.js"); diff --git a/app/sass/bundle.scss b/app/sass/bundle.scss index cb36abf..0d14ce4 100755 --- a/app/sass/bundle.scss +++ b/app/sass/bundle.scss @@ -21,6 +21,7 @@ "partials/navigation", "partials/mission-statement", "partials/modal", + "partials/pre", "layout", diff --git a/app/sass/init/_extends.scss b/app/sass/init/_extends.scss index af6cb4d..d27d41e 100644 --- a/app/sass/init/_extends.scss +++ b/app/sass/init/_extends.scss @@ -137,6 +137,8 @@ .__loading { width: 100%; height: 10rem; + + cursor: wait; position: relative; &::before { @@ -149,12 +151,14 @@ border-top-color: $teal; border-width: 6px; content: ""; + cursor: wait; position: absolute; } &::after { top: 7rem; left: 0; + cursor: wait; font-size: 1rem; position: absolute; text-align: center; diff --git a/app/sass/init/_markdown.scss b/app/sass/init/_markdown.scss index 9d8b174..9b12971 100644 --- a/app/sass/init/_markdown.scss +++ b/app/sass/init/_markdown.scss @@ -179,36 +179,7 @@ } pre { - margin-bottom: 2rem; padding: 2rem; - - border-radius: 3px; font-size: 1rem; - line-height: 1.33; - overflow-x: auto; - overflow-y: hidden; - - &:not([class]), - &.language-text { - background-color: #27283e; - color: $white; - } - - &.language-yaml { - background-color: #27273f; - color: #ffe066; - - .atrule { - color: #f083ac; - } - - .important { - color: #ffa94d; - } - - .punctuation { - color: $white; - } - } } h2, h3, h4, h5 { diff --git a/app/sass/pages/_api.scss b/app/sass/pages/_api.scss index 1769906..e57ca6e 100644 --- a/app/sass/pages/_api.scss +++ b/app/sass/pages/_api.scss @@ -147,6 +147,10 @@ margin-bottom: 1rem; } + pre { + font-size: 0.8rem; + } + table { border: 1px solid rgba($white, 0.1); border-radius: 0.3rem; @@ -171,16 +175,6 @@ tr:nth-child(even) { background-color: rgba($white, 0.1); } - - pre { - margin-bottom: 2rem; padding: 1rem; - - border-radius: 0.3rem; - font-size: 0.8rem; - line-height: 1.33; - overflow-x: auto; - overflow-y: hidden; - } } .api__content__body { diff --git a/app/sass/pages/_tour.scss b/app/sass/pages/_tour.scss index f009ce0..36b175a 100644 --- a/app/sass/pages/_tour.scss +++ b/app/sass/pages/_tour.scss @@ -31,46 +31,47 @@ /** - * Tour | Sidebar + * Tour | Navigation * - * @class .tour__sidebar + * @class .tour__navigation * - * @class .tour__sidebar__example + * @class .tour__navigation__example * @selector {::before} * @selector {:last-of-type} * @state {.active} * @state {:hover} */ -.tour__sidebar { - width: 250px; height: 100%; +.tour__navigation { + width: 100%; - float: left; list-style-type: none; - padding-top: 1rem; - padding-right: 1rem; - vertical-align: top; + padding-bottom: 1rem; + padding-top: 1.5rem; + + &::after { + @include clearfix; + } } -.tour__sidebar__example { +.tour__navigation__example { cursor: pointer; + float: left; position: relative; + text-align: center; + width: 33.333333%; &::before { - width: 1rem; height: 1rem; - top: 0.5rem; left: 0; + width: 100%; height: 2.5rem; + top: -0.6rem; left: 0; - border: 1px solid; - border-radius: 50%; - content: attr(data-example); - font-size: 0.8rem; + content: "example " attr(data-example); + font-size: 0.6rem; + font-style: italic; line-height: 1.1; position: absolute; text-align: center; - } - - &:not(:last-of-type) { - margin-bottom: 1.5rem; + text-transform: uppercase; } &:not(.active) { @@ -93,6 +94,20 @@ } } + &.completed { + &::after { + width: 100%; height: 100%; + top: 0; left: 0; + + background-color: rgba($white, 0.7); + content: "✓"; + font-size: 3rem; + line-height: 0.85; + position: absolute; + z-index: 10; + } + } + &::before, button, span { @@ -103,7 +118,6 @@ background-color: transparent; font-size: 1.25rem; font-weight: 600; - padding-left: 1.3rem; } span { @@ -139,12 +153,10 @@ */ .tour__content { - width: calc(100% - 250px); height: 100%; min-height: 500px; - - border-left: 1px solid rgba($black, 0.05); - float: right; - padding: 1rem 0 1rem 1rem; - vertical-align: top; + border-top: 1px solid rgba($black, 0.05); + overflow-y: visible; + padding-bottom: 1rem; + padding-top: 1rem; .loader { @extend .__loading; @@ -153,6 +165,10 @@ content: "Processing request"; } } + + pre { + font-size: 1rem; + } } .tour__content__meme { @@ -167,6 +183,7 @@ .tour__content__meme__canvas { float: left; margin-right: 2%; + position: relative; width: 48%; canvas { @@ -202,7 +219,7 @@ .tour__content__meme__editor { float: right; - width: 48%; + width: 50%; h2.__metadata { margin-top: 3rem; @@ -315,10 +332,11 @@ } .tour__content__trends { + min-width: 0; min-height: 0; + display: grid; - grid-gap: 2%; - grid-template-columns: 32% 32% 32%; - overflow-y: auto; + grid-gap: 1rem; + grid-template: repeat(1, 1fr) / repeat(3, 1fr); position: relative; &:empty { @@ -332,7 +350,7 @@ .tour__content__trend { img { - width: 100%; height: 175px; + width: 100%; height: 213px; cursor: pointer; display: block; @@ -428,3 +446,20 @@ width: 3.5rem; } } + + + +/** + * Tour | Description + * + * @class .tour__description + */ + +.tour__description { + background-color: rgba($black, 0.05); + cursor: default; + font-size: 1rem; + line-height: 1.33; + padding: 1rem; + text-align: center; +} diff --git a/app/sass/partials/_github-feed.scss b/app/sass/partials/_github-feed.scss index 70fe8bc..46d1dbd 100644 --- a/app/sass/partials/_github-feed.scss +++ b/app/sass/partials/_github-feed.scss @@ -50,7 +50,6 @@ letter-spacing: 0.1rem; line-height: 1; text-transform: uppercase; - width: 100%; @media (min-width: 1301px) { top: 2.15rem; left: 0; @@ -58,6 +57,7 @@ color: rgba($black, 0.045); font-size: 4rem; position: absolute; + width: calc(100% - (1rem + 5%)); } @media (max-width: 1300px) { diff --git a/app/sass/partials/_pre.scss b/app/sass/partials/_pre.scss new file mode 100644 index 0000000..41bb560 --- /dev/null +++ b/app/sass/partials/_pre.scss @@ -0,0 +1,30 @@ +pre { + margin-bottom: 2rem; padding: 2rem; + + line-height: 1.33; + overflow-x: auto; + overflow-y: hidden; + + &:not([class]), + &.language-text { + background-color: #27283e; + color: $white; + } + + &.language-yaml { + background-color: #27273f; + color: #ffe066; + + .atrule { + color: #f083ac; + } + + .important { + color: #ffa94d; + } + + .punctuation { + color: $white; + } + } +} diff --git a/app/sockets.js b/app/sockets.js new file mode 100644 index 0000000..1dbe2e0 --- /dev/null +++ b/app/sockets.js @@ -0,0 +1,394 @@ +"use strict"; + + + +// P A C K A G E S + +const html = require("choo/html"); +const local = require("app-root-path").require; +const request = require("request-promise-native"); + +// V A R I A B L E S + +const fetchMetadata = local("app/helpers/fetch-metadata"); +const { generateGitHubFeed } = local("app/helpers/github"); +const logSlackError = local("app/helpers/slack"); + + + +// P R O G R A M + +module.exports = exports = (socket, action) => { + if (typeof socket !== "object" && typeof action !== "object") return; + + switch(true) { + case (action.message === "fetch metadata"): + fetchMetadata(action, socket); + break; + + case (action.message === "landed on homepage"): + generateGitHubFeed(result => { + socket.send(JSON.stringify({ + "html": result, + "message": "updated html", + "selector": "#github-feed" + })); + }); + break; + + case (action.message === "landed on tour"): + generateContent(1, result => { + socket.send(JSON.stringify({ + "html": result, + "message": "updated html", + "selector": "#tour-loader" + })); + }); + break; + + case (action.message === "request for tour, example 1"): + generateContent(1, result => { + socket.send(JSON.stringify({ + "html": result, + "message": "updated html", + "selector": "#tour-loader" + })); + }); + break; + + case (action.message === "request for tour, example 2"): + generateMemeCreator(socket); + break; + + case (action.message === "request for tour, example 3"): + generateContent(3, result => { + socket.send(JSON.stringify({ + "html": result, + "message": "updated html", + "selector": "#tour-loader" + })); + }); + break; + + case (action.message === "subscribe"): + newsletterSubscribe(action, socket); + break; + + default: + process.stdout.write(action); + break; + } +}; + + + +// H E L P E R S + +function generateMemeCreator(socket) { + const images = [ + { + alt: "Carl Sagan", + src: "/assets/media/images/carlsagan2.jpg" + }, + { + alt: "Doge", + src: "/assets/media/images/doge-meme.jpg" + }, + { + alt: "LBRY Logo With Green Background", + src: "/assets/media/images/lbry-green.png" + } + ]; + + const memePlaceholderData = { + bottomLine: { + placeholder: "Top line", + value: "that I made" + }, + description: { + placeholder: "Description", + value: "Check out this image I published to LBRY via lbry.tech" + }, + topLine: { + placeholder: "Top line", + value: "This is an example meme" + }, + title: { + placeholder: "Title", + value: "Dank Meme Supreme da Cheese" + } + }; + + const renderedImages = []; + + for (const image of images) { + renderedImages.push(`${image.alt}`); + } + + const memeCreator = html` +
    + + Unfortunately, it looks like canvas is not supported in your browser + + ${renderedImages} +
    + +
    +

    Image Text

    + +
    + + +
    + +
    + + +
    + + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + +
    + +
    +
    + + + `; + + return socket.send(JSON.stringify({ + "html": memeCreator, + "message": "updated html", + "selector": "#tour-loader" + })); +} + +function generateContent(exampleNumber, displayTrendingContent) { + if (exampleNumber === 1) { + return getTrendingContent().then(response => { + if (!response || !response.success || response.success !== true || !response.data) return ""; + + const rawContentCollection = []; + const renderedContentCollection = []; + const trendingContentData = response.data; + + for (const data of trendingContentData) { + rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber })); + } + + Promise.all(rawContentCollection).then(collection => { + for (const part of collection) { + try { + renderedContentCollection.push(` +
    + ${part.name} + +
    + ${part.value.stream.metadata.title} + ${part.channel_name} +
    +
    + `); + } catch (err) { + return; // TODO: Return nice error message + } + } + + renderedContentCollection.push(` + + `); + + displayTrendingContent(renderedContentCollection.join("")); + }); + }); + } + + if (exampleNumber === 3) { + const approvedUrls = [ + "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", + "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", + "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", + "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", + "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", + "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", + "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009", + "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb", + "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af", + "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284" + ]; + + const rawContentCollection = []; + const renderedContentCollection = []; + + for (const url of approvedUrls) { + rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); + } + + Promise.all(rawContentCollection).then(collection => { + for (const part of collection) { + try { + renderedContentCollection.push(` +
    + ${part.name} + +
    + ${part.value.stream.metadata.title} + ${part.channel_name} +
    +
    + `); + } catch (err) { + return; // TODO: Return nice error message + } + } + + renderedContentCollection.push(` + + `); + + displayTrendingContent(renderedContentCollection.join("")); + }); + } +} + +function getTrendingContent() { + return new Promise((resolve, reject) => { // eslint-disable-line + request({ + method: "GET", + url: "https://api.lbry.io/file/list_trending" + }, (error, response, body) => { + if (error || !JSON.parse(body)) resolve("Issue fetching content"); // error + body = JSON.parse(body); + resolve(body); + }); + }); +} + +function newsletterSubscribe(data, socket) { + const email = data.email; + + if (!validateEmail(email)) return socket.send(JSON.stringify({ + "html": "Your email is invalid", + "message": "updated html", + "selector": "#emailMessage" + })); + + return new Promise((resolve, reject) => { + request({ + method: "POST", + url: `https://api.lbry.io/list/subscribe?email=${email}&tag=developer` + }).then(body => { + if (!body || !JSON.parse(body)) { + logSlackError( + "\n" + + "> *NEWSLETTER ERROR:* ```¯\\_(ツ)_/¯ This should be an unreachable error```" + "\n" + + `> _Cause: ${email} interacted with the form_\n` + ); + + return resolve(socket.send(JSON.stringify({ + "html": "Something is terribly wrong", + "message": "updated html", + "selector": "#emailMessage" + }))); + } + + body = JSON.parse(body); + + if (!body.success) { + logSlackError( + "\n" + + "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(body.error)) + "```" + "\n" + + `> _Cause: ${email} interacted with the form_\n` + ); + + return reject(socket.send(JSON.stringify({ + "html": body.error, + "message": "updated html", + "selector": "#emailMessage" + }))); + } + + return resolve(socket.send(JSON.stringify({ + "html": "Thank you! Please confirm subscription in your inbox.", + "message": "updated html", + "selector": "#emailMessage" + }))); + }).catch(welp => { + if (welp.statusCode === 409) { + logSlackError( + "\n" + + "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(welp.error)) + "```" + "\n" + + `> _Cause: ${email} interacted with the form_\n` + ); + + return resolve(socket.send(JSON.stringify({ + "html": "You have already subscribed!", + "message": "updated html", + "selector": "#emailMessage" + }))); + } + }); + }); +} + +function validateEmail(email) { + const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\\.,;:\s@"]{2,})$/i; + return emailRegex.test(String(email)); +} diff --git a/app/views/home.js b/app/views/home.js index 8cd4b97..cd60c28 100644 --- a/app/views/home.js +++ b/app/views/home.js @@ -17,8 +17,8 @@ import linkGrid from "../components/link-grid"; const featureLinks = linkGrid([ { description: "Learn how LBRY works with 3 easy examples", - destination: "/tour", - label: "Take the Tour", + destination: "/playground", + label: "Jump into Playground", title: "New to LBRY?" }, { diff --git a/app/views/redirect.js b/app/views/redirect.js index 0fa3857..b7ddce3 100644 --- a/app/views/redirect.js +++ b/app/views/redirect.js @@ -85,7 +85,7 @@ module.exports = exports = (state, emit) => { // eslint-disable-line let pageScript = ""; if (path === "glossary") pageScript = ""; if (path === "overview") pageScript = ""; - if (path === "tour") pageScript = ""; + if (path === "playground") pageScript = ""; return html`
    diff --git a/documents/playground.md b/documents/playground.md new file mode 100644 index 0000000..40dc49d --- /dev/null +++ b/documents/playground.md @@ -0,0 +1,9 @@ +--- +title: Playground +--- + +Check out any of the interactive examples to get a feel for the LBRY protocol! + +LBRY (pronounced "library") is an application layer protocol, similar to HTTP. However, while HTTP links can direct you to decentralized content, the LBRY protocol *itself* is decentralized. + + diff --git a/server.js b/server.js index b175bba..909eb0a 100755 --- a/server.js +++ b/server.js @@ -4,10 +4,9 @@ // P A C K A G E S -const async = require("async"); const color = require("colorette"); const cors = require("cors"); -const dedent = require("dedent"); +const local = require("app-root-path").require; const fastify = require("fastify")({ logger: { @@ -16,42 +15,10 @@ const fastify = require("fastify")({ } }); -const html = require("choo/html"); -const local = require("app-root-path").require; -const octokit = require("@octokit/rest")(); -const redis = require("redis"); -const request = require("request-promise-native"); - // V A R I A B L E S -const fetchMetadata = local("app/helpers/fetch-metadata"); -const github = local("app/helpers/github"); -const log = console.log; // eslint-disable-line +const handleSocketMessages = local("app/sockets"); const logSlackError = local("app/helpers/slack"); -const relativeDate = local("app/modules/relative-date"); -let client; - -if (typeof process.env.GITHUB_OAUTH_TOKEN !== "undefined") { - octokit.authenticate({ - type: "oauth", - token: process.env.GITHUB_OAUTH_TOKEN - }); -} else log(`${color.red("[missing]")} GitHub token`); - -if (typeof process.env.REDISCLOUD_URL !== "undefined") { - client = redis.createClient(process.env.REDISCLOUD_URL); - - client.on("error", redisError => { - process.env.NODE_ENV === "development" ? - log(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) : - logSlackError( - "\n" + - "> *REDIS ERROR:* ```" + JSON.parse(JSON.stringify(redisError)) + "```" + "\n" + - "> _Cause: Someone is trying to run LBRY.tech locally without environment variables OR Heroku is busted_\n" - ) - ; - }); -} else log(`${color.red("[missing]")} Redis client URL`); @@ -81,74 +48,10 @@ fastify.ready(err => { fastify.ws.on("connection", socket => { socket.on("message", data => { data = JSON.parse(data); - - switch(data.message) { - case "fetch metadata": - fetchMetadata(data, socket); - break; - - case "landed on homepage": - generateGitHubFeed(result => { - socket.send(JSON.stringify({ - "html": result, - "message": "updated html", - "selector": "#github-feed" - })); - }); - - break; - - case "landed on tour": - generateContent(1, result => { - socket.send(JSON.stringify({ - "html": result, - "message": "updated html", - "selector": "#tour-loader" - })); - }); - - break; - - case "request for tour, example 1": - generateContent(1, result => { - socket.send(JSON.stringify({ - "html": result, - "message": "updated html", - "selector": "#tour-loader" - })); - }); - - break; - - case "request for tour, example 2": - generateMemeCreator(socket); - break; - - case "request for tour, example 3": - generateContent(3, result => { - socket.send(JSON.stringify({ - "html": result, - "message": "updated html", - "selector": "#tour-loader" - })); - }); - - break; - - case "subscribe": - newsletterSubscribe(data, socket); - break; - - default: - log(data); - break; - } + return handleSocketMessages(socket, data); }); - socket.on("close", () => { - // console.log(socket); - return socket.terminate(); - }); + socket.on("close", () => socket.terminate()); }); }); @@ -159,387 +62,15 @@ fastify.ready(err => { const start = async () => { try { await fastify.listen(process.env.PORT || 8080, process.env.IP || "0.0.0.0"); - /* - await fastify.listen( - process.env.NODE_ENV === "development" ? - 8080 : - process.env.PORT - ); - */ } catch (err) { fastify.log.error(err); process.exit(1); } process.env.NODE_ENV === "development" ? - log(`\n— ${color.green("⚡")} ${fastify.server.address().port}\n`) : + process.stdout.write(`\n— ${color.green("⚡")} ${fastify.server.address().port}\n`) : logSlackError(`Server started at port \`${fastify.server.address().port}\``) ; }; start(); - - - -// H E L P E R S - -function generateGitHubFeed(displayGitHubFeed) { - if (typeof process.env.REDISCLOUD_URL !== "undefined") { - client.zrevrange("events", 0, 9, (err, reply) => { - if (err) return; // TODO: Render a div with nice error message - - const events = []; - const renderedEvents = []; - - reply.forEach(item => events.push(JSON.parse(item))); - - for (const event of events) { - renderedEvents.push(` -
    - - - - -

    - ${github.generateEvent(event)} - ${event.repo.name} - ${relativeDate(new Date(event.created_at))} -

    -
    - `); - } - - updateGithubFeed(); // TODO: Update `.last-updated` every minute - - displayGitHubFeed(dedent` -

    GitHub

    -
    Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "·")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST
    - - ${renderedEvents.join("")} - `); - }); - } -} - -function generateMemeCreator(socket) { - const images = [ - { - alt: "Carl Sagan", - src: "/assets/media/images/carlsagan2.jpg" - }, - { - alt: "Doge", - src: "/assets/media/images/doge-meme.jpg" - }, - { - alt: "LBRY Logo With Green Background", - src: "/assets/media/images/lbry-green.png" - } - ]; - - const memePlaceholderData = { - bottomLine: { - placeholder: "Top line", - value: "that I made" - }, - description: { - placeholder: "Description", - value: "Check out this image I published to LBRY via lbry.tech" - }, - topLine: { - placeholder: "Top line", - value: "This is an example meme" - }, - title: { - placeholder: "Title", - value: "Dank Meme Supreme da Cheese" - } - }; - - const renderedImages = []; - - for (const image of images) { - renderedImages.push(`${image.alt}`); - } - - const memeCreator = html` -
    - - Unfortunately, it looks like canvas is not supported in your browser - - ${renderedImages} -
    - -
    -

    Image Text

    - -
    - - -
    - -
    - - -
    - - - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - -
    - -
    - -
    -
    - - - `; - - return socket.send(JSON.stringify({ - "html": memeCreator, - "message": "updated html", - "selector": "#tour-loader" - })); -} - -function generateContent(exampleNumber, displayTrendingContent) { - if (exampleNumber === 1) { - return getTrendingContent().then(response => { - if (!response || !response.success || response.success !== true || !response.data) return ""; - - const rawContentCollection = []; - const renderedContentCollection = []; - const trendingContentData = response.data; - - for (const data of trendingContentData) { - rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber })); - } - - Promise.all(rawContentCollection).then(collection => { - for (const part of collection) { - if ( - !part.value.stream.metadata.nsfw && - part.value.stream.metadata.thumbnail && - part.channel_name - ) { - renderedContentCollection.push(` -
    - ${part.name} - -
    - ${part.value.stream.metadata.title} - ${part.channel_name} -
    -
    - `); - } - } - - displayTrendingContent(renderedContentCollection.join("")); - }); - }); - } - - if (exampleNumber === 3) { - const approvedUrls = [ - "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", - "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", - "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", - "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", - "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", - "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", - "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009", - "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb", - "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af", - "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284" - ]; - - const rawContentCollection = []; - const renderedContentCollection = []; - - for (const url of approvedUrls) { - rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); - } - - Promise.all(rawContentCollection).then(collection => { - for (const part of collection) { - if ( - part && - part.value && - part.value.stream.metadata.thumbnail && - part.channel_name - ) { - renderedContentCollection.push(` -
    - ${part.name} - -
    - ${part.value.stream.metadata.title} - ${part.channel_name} -
    -
    - `); - } - } - - displayTrendingContent(renderedContentCollection.join("")); - }); - } -} - -function getTrendingContent() { - return new Promise((resolve, reject) => { // eslint-disable-line - request({ - method: "GET", - url: "https://api.lbry.io/file/list_trending" - }, (error, response, body) => { - if (error || !JSON.parse(body)) resolve("Issue fetching content"); // error - body = JSON.parse(body); - resolve(body); - }); - }); -} - -function newsletterSubscribe(data, socket) { - const email = data.email; - - if (!validateEmail(email)) return socket.send(JSON.stringify({ - "html": "Your email is invalid", - "message": "updated html", - "selector": "#emailMessage" - })); - - return new Promise((resolve, reject) => { - request({ - method: "POST", - url: `https://api.lbry.io/list/subscribe?email=${email}&tag=developer` - }).then(body => { - if (!body || !JSON.parse(body)) { - logSlackError( - "\n" + - "> *NEWSLETTER ERROR:* ```¯\\_(ツ)_/¯ This should be an unreachable error```" + "\n" + - `> _Cause: ${email} interacted with the form_\n` - ); - - return resolve(socket.send(JSON.stringify({ - "html": "Something is terribly wrong", - "message": "updated html", - "selector": "#emailMessage" - }))); - } - - body = JSON.parse(body); - - if (!body.success) { - logSlackError( - "\n" + - "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(body.error)) + "```" + "\n" + - `> _Cause: ${email} interacted with the form_\n` - ); - - return reject(socket.send(JSON.stringify({ - "html": body.error, - "message": "updated html", - "selector": "#emailMessage" - }))); - } - - return resolve(socket.send(JSON.stringify({ - "html": "Thank you! Please confirm subscription in your inbox.", - "message": "updated html", - "selector": "#emailMessage" - }))); - }).catch(welp => { - if (welp.statusCode === 409) { - logSlackError( - "\n" + - "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(welp.error)) + "```" + "\n" + - `> _Cause: ${email} interacted with the form_\n` - ); - - return resolve(socket.send(JSON.stringify({ - "html": "You have already subscribed!", - "message": "updated html", - "selector": "#emailMessage" - }))); - } - }); - }); -} - -function updateGithubFeed() { - octokit.activity.getEventsForOrg({ - org: "lbryio", - per_page: 20, - page: 1 - }).then(({ data }) => { - async.eachSeries(data, (item, callback) => { - const eventString = JSON.stringify(item); - - client.zrank("events", eventString, (err, reply) => { - if (reply === null) client.zadd("events", item.id, eventString, callback); - else callback(); - }); - }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events - }).catch(err => { - logSlackError( - "\n" + - "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" + - "> _Cause: GitHub feed refresh_\n" - ); - }); -} - -function validateEmail(email) { - const re = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\\.,;:\s@"]{2,})$/i; - return re.test(String(email)); -} -- 2.45.2 From f72053b29df25fc581ea8cabc3c918b4f8e577d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=AB=20=E3=82=A6=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=83=96?= Date: Thu, 27 Sep 2018 13:00:34 -0500 Subject: [PATCH 2/3] Feedback for completing examples --- app/components/playground.js | 3 + app/components/tour.js | 62 ----------- app/dist/scripts/sockets.js | 27 ++++- app/helpers/fetch-metadata.js | 14 ++- app/sass/pages/_tour.scss | 16 ++- app/sockets.js | 190 +++++++++++++++++----------------- 6 files changed, 150 insertions(+), 162 deletions(-) delete mode 100644 app/components/tour.js diff --git a/app/components/playground.js b/app/components/playground.js index a7400bc..db9b863 100644 --- a/app/components/playground.js +++ b/app/components/playground.js @@ -51,6 +51,7 @@ function navigation() { // TODO: Save tutorial position to localStorage data-action="tour, example 1" data-description="In this example, you can see what runs under the hood when selecting content to view in the LBRY app." data-example="1" + data-success="Success You resolved a claim, which is a fancy way of saying you searched for a piece of content and got back all the metadata associated with it (if it exists)." > Get details of media (aka, "claim" metadata) @@ -61,6 +62,7 @@ function navigation() { // TODO: Save tutorial position to localStorage data-action="tour, example 2" data-description="Sometimes you want to create content, not just consume it. In this example, you can create a meme and upload it to LBRY!" data-example="2" + data-success="Meme-a-riffic You've just contributed to the growing expanse that is the meme industry. Where will your meme go next? YOU DECIDE!" > Create a meme and upload it to the LBRY blockchain @@ -71,6 +73,7 @@ function navigation() { // TODO: Save tutorial position to localStorage data-action="tour, example 3" data-description="In the LBRY app, you can financially support your favorite creators by donating LBRY Coin (LBC). In this example, we are donating LBC in your stead." data-example="3" + data-success="Kudos You've just supported a creator with LBC (or, LBRY credits) with our own stash of LBC (you'd use your own IRL). You're basically saying, \"thanks for this great content, please continue!\" and that's awesome. You're awesome." > Support creators on LBRY with a tip, on us! diff --git a/app/components/tour.js b/app/components/tour.js deleted file mode 100644 index 5585ebd..0000000 --- a/app/components/tour.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; - - - -// P A C K A G E S - -import dedent from "dedent"; -import html from "choo/html"; -import raw from "choo/html/raw"; - - - -// E X P O R T - -export default function () { - return dedent` -
    -
      - ${raw(sidebar())} -
    -
    ${raw(example1())}
    -
    - `; -} - - - -// H E L P E R S - -function example1() { - return html` -
    - lbry:// - -
    - - -
    - `; -} - -function sidebar() { // TODO: Save tutorial position to localStorage - return dedent` -
  • - - Get details of media (aka, "claim" metadata)
    - In this example, you can see what runs under the hood when selecting content to view in the LBRY app. -
  • - -
  • - - Create a meme and upload it to the LBRY blockchain
    - Sometimes you want to create content, not just consume it. In this example, you can create a meme and upload it to LBRY! -
  • - -
  • - - Support creators on LBRY with a tip, on us!
    - In the LBRY app, you can financially support your favorite creators by donating LBRY Coin (LBC). In this example, we are donating LBC in your stead. -
  • - `; -} diff --git a/app/dist/scripts/sockets.js b/app/dist/scripts/sockets.js index 6e901dc..f4b0e8d 100644 --- a/app/dist/scripts/sockets.js +++ b/app/dist/scripts/sockets.js @@ -30,7 +30,32 @@ function initializeWebSocketConnection() { document.querySelector(data.selector).innerHTML = data.html; document.getElementById("emailAddress").value = ""; document.getElementById("emailMessage").innerHTML = ""; - if (document.getElementById("temp-loader")) document.getElementById("temp-loader").style.display = "none"; + + // `data.example` is added when updating HTML. + // This is when the results of an example are sent to the client. + if (data.example) { + if (!document.querySelector(`[data-example="${data.example}"`).classList.contains("completed")) { + document.getElementById("tour-example-description").classList.remove("success"); + } + + document.querySelector(`[data-example="${data.example}"`).classList.add("completed"); + document.getElementById("tour-example-description").classList.add("success"); + + document.getElementById("tour-example-description").innerHTML = + document.querySelector(`[data-example="${data.example}"`).dataset.success; + } + + // If `data.example` isn't found, reset the description area. + else { + document.getElementById("tour-example-description").classList.remove("success"); + + document.getElementById("tour-example-description").innerHTML = + document.querySelector(".tour__navigation__example.active").dataset.description; + } + + if (document.getElementById("temp-loader")) + document.getElementById("temp-loader").style.display = "none"; + document.querySelector(".tour").classList.remove("waiting"); break; diff --git a/app/helpers/fetch-metadata.js b/app/helpers/fetch-metadata.js index 2ea38ed..02d22d0 100644 --- a/app/helpers/fetch-metadata.js +++ b/app/helpers/fetch-metadata.js @@ -92,7 +92,7 @@ module.exports = exports = (data, socket) => { body.file_path = uploadResponse.filename; return publishMeme(body).then(publishResponse => { - let explorerNotice = ""; + // let explorerNotice = ""; if (publishResponse.error) { socket.send(JSON.stringify({ @@ -112,19 +112,22 @@ module.exports = exports = (data, socket) => { return; } + /* if ( publishResponse.result && publishResponse.result.txid ) explorerNotice = `

    If you want proof of the tip you just gave, check it out on our blockchain explorer!

    `; + */ const renderedCode = prism.highlight(stringifyObject(publishResponse, { indent: " ", singleQuotes: false }), prism.languages.json, "json"); return socket.send(JSON.stringify({ + "example": data.example, "html": raw(`

    Response

    - ${explorerNotice} +
    ${renderedCode}
    `), "message": "updated html", @@ -147,7 +150,7 @@ module.exports = exports = (data, socket) => { } return new Promise((resolve, reject) => { // eslint-disable-line - let explorerNotice = ""; + // let explorerNotice = ""; request({ body: body, @@ -179,20 +182,23 @@ module.exports = exports = (data, socket) => { return resolve(body.error); } + /* if ( body.result && body.result.txid ) explorerNotice = `

    If you want proof of the tip you just gave on behalf of LBRY, check it out on our blockchain explorer!

    `; + */ if (socket) { const renderedCode = prism.highlight(stringifyObject(body, { indent: " ", singleQuotes: false }), prism.languages.json, "json"); return socket.send(JSON.stringify({ + "example": data.example, "html": raw(`

    Response

    - ${explorerNotice} +
    ${renderedCode}
    `), "message": "updated html", diff --git a/app/sass/pages/_tour.scss b/app/sass/pages/_tour.scss index 36b175a..3228495 100644 --- a/app/sass/pages/_tour.scss +++ b/app/sass/pages/_tour.scss @@ -456,10 +456,22 @@ */ .tour__description { - background-color: rgba($black, 0.05); cursor: default; font-size: 1rem; line-height: 1.33; padding: 1rem; - text-align: center; + + &:not(.success) { + background-color: rgba($black, 0.05); + text-align: center; + } + + &.success { + background-color: rgba($teal, 0.05); + + strong { + display: block; + text-transform: uppercase; + } + } } diff --git a/app/sockets.js b/app/sockets.js index 1dbe2e0..3880864 100644 --- a/app/sockets.js +++ b/app/sockets.js @@ -39,6 +39,7 @@ module.exports = exports = (socket, action) => { case (action.message === "landed on tour"): generateContent(1, result => { socket.send(JSON.stringify({ + // "example": 1, "html": result, "message": "updated html", "selector": "#tour-loader" @@ -49,6 +50,7 @@ module.exports = exports = (socket, action) => { case (action.message === "request for tour, example 1"): generateContent(1, result => { socket.send(JSON.stringify({ + // "example": 1, "html": result, "message": "updated html", "selector": "#tour-loader" @@ -63,6 +65,7 @@ module.exports = exports = (socket, action) => { case (action.message === "request for tour, example 3"): generateContent(3, result => { socket.send(JSON.stringify({ + // "example": 3, "html": result, "message": "updated html", "selector": "#tour-loader" @@ -75,7 +78,7 @@ module.exports = exports = (socket, action) => { break; default: - process.stdout.write(action); + console.log(action); // eslint-disable-line break; } }; @@ -84,6 +87,98 @@ module.exports = exports = (socket, action) => { // H E L P E R S +function generateContent(exampleNumber, displayTrendingContent) { + if (exampleNumber === 1) { + return getTrendingContent().then(response => { + if (!response || !response.success || response.success !== true || !response.data) return ""; + + const rawContentCollection = []; + const renderedContentCollection = []; + const trendingContentData = response.data; + + for (const data of trendingContentData) { + rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber })); + } + + Promise.all(rawContentCollection).then(collection => { + for (const part of collection) { + try { + renderedContentCollection.push(` +
    + ${part.name} + +
    + ${part.value.stream.metadata.title} + ${part.channel_name} +
    +
    + `); + } catch (err) { + return; // TODO: Return nice error message + } + } + + renderedContentCollection.push(` + + `); + + displayTrendingContent(renderedContentCollection.join("")); + }); + }); + } + + if (exampleNumber === 3) { + const approvedUrls = [ + "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", + "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", + "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", + "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", + "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", + "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", + "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009", + "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb", + "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af", + "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284" + ]; + + const rawContentCollection = []; + const renderedContentCollection = []; + + for (const url of approvedUrls) { + rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); + } + + Promise.all(rawContentCollection).then(collection => { + for (const part of collection) { + try { + renderedContentCollection.push(` +
    + ${part.name} + +
    + ${part.value.stream.metadata.title} + ${part.channel_name} +
    +
    + `); + } catch (err) { + return; // TODO: Return nice error message + } + } + + renderedContentCollection.push(` + + `); + + displayTrendingContent(renderedContentCollection.join("")); + }); + } +} + function generateMemeCreator(socket) { const images = [ { @@ -210,104 +305,13 @@ function generateMemeCreator(socket) { `; return socket.send(JSON.stringify({ + // "example": 2, "html": memeCreator, "message": "updated html", "selector": "#tour-loader" })); } -function generateContent(exampleNumber, displayTrendingContent) { - if (exampleNumber === 1) { - return getTrendingContent().then(response => { - if (!response || !response.success || response.success !== true || !response.data) return ""; - - const rawContentCollection = []; - const renderedContentCollection = []; - const trendingContentData = response.data; - - for (const data of trendingContentData) { - rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber })); - } - - Promise.all(rawContentCollection).then(collection => { - for (const part of collection) { - try { - renderedContentCollection.push(` -
    - ${part.name} - -
    - ${part.value.stream.metadata.title} - ${part.channel_name} -
    -
    - `); - } catch (err) { - return; // TODO: Return nice error message - } - } - - renderedContentCollection.push(` - - `); - - displayTrendingContent(renderedContentCollection.join("")); - }); - }); - } - - if (exampleNumber === 3) { - const approvedUrls = [ - "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", - "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", - "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", - "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", - "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", - "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", - "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009", - "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb", - "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af", - "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284" - ]; - - const rawContentCollection = []; - const renderedContentCollection = []; - - for (const url of approvedUrls) { - rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); - } - - Promise.all(rawContentCollection).then(collection => { - for (const part of collection) { - try { - renderedContentCollection.push(` -
    - ${part.name} - -
    - ${part.value.stream.metadata.title} - ${part.channel_name} -
    -
    - `); - } catch (err) { - return; // TODO: Return nice error message - } - } - - renderedContentCollection.push(` - - `); - - displayTrendingContent(renderedContentCollection.join("")); - }); - } -} - function getTrendingContent() { return new Promise((resolve, reject) => { // eslint-disable-line request({ -- 2.45.2 From c87eb9337cde124a2b72bb9b9e3e4d4c8bb906a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=AB=20=E3=82=A6=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=83=96?= Date: Thu, 27 Sep 2018 15:46:59 -0500 Subject: [PATCH 3/3] Only whitelisted content can be tipped via the URL bar now, and other fixes --- README.md | 4 +++ app/components/client/tour-scripts.js | 19 ++++++++++--- app/components/playground.js | 2 +- app/helpers/fetch-metadata.js | 39 ++++++++++++++++----------- app/sass/pages/_tour.scss | 5 ++++ app/sockets.js | 28 ++++++++++--------- 6 files changed, 64 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index adb8b94..1d98944 100755 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ - [lbry/web-daemon](https://github.com/lbryio/web-daemon) - [Node](https://nodejs.org) (version >= 10) +## Notes +- **This repo will not run locally if you do not also have the LBRY app/daemon and the [web-daemon](https://github.com/lbryio/web-daemon) running.** +- When running locally and completing the tipping example in Playground, the LBC donated to a creator comes from _your_ LBC balance. Otherwise, the example will fail. On production, the donated LBC comes from LBRY. + ## Installation `npm i` diff --git a/app/components/client/tour-scripts.js b/app/components/client/tour-scripts.js index 1fd3f10..7eb7fd3 100644 --- a/app/components/client/tour-scripts.js +++ b/app/components/client/tour-scripts.js @@ -36,10 +36,21 @@ document.querySelector("body").addEventListener("click", event => { document.getElementById("fetch-claim-uri").addEventListener("keyup", event => { const key = event.keyCode ? event.keyCode : event.which; - if ( - key === 13 && - document.getElementById("fetch-claim-uri").value.length > 0 - ) fetchMetadata(1, document.getElementById("fetch-claim-uri").value); + switch(true) { + case (document.querySelector("[data-example='1']").classList.contains("active")): + if ( + key === 13 && + document.getElementById("fetch-claim-uri").value.length > 0 + ) fetchMetadata(1, document.getElementById("fetch-claim-uri").value); + break; + + case (document.querySelector("[data-example='3']").classList.contains("active")): + if ( + key === 13 && + document.getElementById("fetch-claim-uri").value.length > 0 + ) fetchMetadata(3, document.getElementById("fetch-claim-uri").value); + break; + } }); document.querySelector("body").addEventListener("keyup", event => { diff --git a/app/components/playground.js b/app/components/playground.js index db9b863..a0c5ad1 100644 --- a/app/components/playground.js +++ b/app/components/playground.js @@ -73,7 +73,7 @@ function navigation() { // TODO: Save tutorial position to localStorage data-action="tour, example 3" data-description="In the LBRY app, you can financially support your favorite creators by donating LBRY Coin (LBC). In this example, we are donating LBC in your stead." data-example="3" - data-success="Kudos You've just supported a creator with LBC (or, LBRY credits) with our own stash of LBC (you'd use your own IRL). You're basically saying, \"thanks for this great content, please continue!\" and that's awesome. You're awesome." + data-success="Kudos You've just supported a creator with LBC (or, LBRY credits) with our own stash of LBC (you'd use your own IRL). You're basically saying, 'thanks for this great content, please continue!' and that's awesome. You're awesome." > Support creators on LBRY with a tip, on us! diff --git a/app/helpers/fetch-metadata.js b/app/helpers/fetch-metadata.js index 02d22d0..c520bf5 100644 --- a/app/helpers/fetch-metadata.js +++ b/app/helpers/fetch-metadata.js @@ -92,8 +92,6 @@ module.exports = exports = (data, socket) => { body.file_path = uploadResponse.filename; return publishMeme(body).then(publishResponse => { - // let explorerNotice = ""; - if (publishResponse.error) { socket.send(JSON.stringify({ "details": "Meme publish failed", @@ -112,22 +110,12 @@ module.exports = exports = (data, socket) => { return; } - /* - if ( - publishResponse.result && - publishResponse.result.txid - ) explorerNotice = ` -

    If you want proof of the tip you just gave, check it out on our blockchain explorer!

    - `; - */ - const renderedCode = prism.highlight(stringifyObject(publishResponse, { indent: " ", singleQuotes: false }), prism.languages.json, "json"); return socket.send(JSON.stringify({ "example": data.example, "html": raw(`

    Response

    -
    ${renderedCode}
    `), "message": "updated html", @@ -143,6 +131,30 @@ module.exports = exports = (data, socket) => { } if (resolveMethod === "wallet_send") { + const approvedIds = [ + "3db81c073f82fd1bb670c65f526faea3b8546720", + "173412f5b1b7aa63a752e8832406aafd9f1ecb4e", + "d81bac6d49b1f92e58c37a5f633a27a45b43405e", + "b4668c0bd096317b44c40738c099b6618095e75f", + "007789cc45cbb4255cf02ba77cbf84ca8e3d7561", + "1ac47b8b3def40a25850dc726a09ce23d09e7009", + "784b3c215a6f06b663fc1aa292bcb19f29c489bb", + "758dd6497cdfc401ae1f25984738d024d47b50af", + "8a7401b88d5ed0376d98f16808194d4dcb05b284" + ]; + + if (!approvedIds.includes(claimAddress)) { + return socket.send(JSON.stringify({ + "example": data.example, + "html": raw(` +

    Response

    +
    Tipping creators not in the whitelist for this example is not allowed.
    + `), + "message": "updated html", + "selector": `#example${data.example}-result` + })); + } + apiRequestMethod = "POST"; body.amount = "0.01"; // Hardcoded tip amount @@ -150,8 +162,6 @@ module.exports = exports = (data, socket) => { } return new Promise((resolve, reject) => { // eslint-disable-line - // let explorerNotice = ""; - request({ body: body, json: true, @@ -198,7 +208,6 @@ module.exports = exports = (data, socket) => { "example": data.example, "html": raw(`

    Response

    -
    ${renderedCode}
    `), "message": "updated html", diff --git a/app/sass/pages/_tour.scss b/app/sass/pages/_tour.scss index 3228495..12246df 100644 --- a/app/sass/pages/_tour.scss +++ b/app/sass/pages/_tour.scss @@ -166,6 +166,11 @@ } } + h3 { + font-size: 1.5rem; + margin-bottom: 1rem; + } + pre { font-size: 1rem; } diff --git a/app/sockets.js b/app/sockets.js index 3880864..0de1585 100644 --- a/app/sockets.js +++ b/app/sockets.js @@ -105,9 +105,9 @@ function generateContent(exampleNumber, displayTrendingContent) { try { renderedContentCollection.push(`
    - ${part.name} + ${part.name} -
    +
    ${part.value.stream.metadata.title} ${part.channel_name}
    @@ -120,7 +120,7 @@ function generateContent(exampleNumber, displayTrendingContent) { renderedContentCollection.push(` `); @@ -133,7 +133,7 @@ function generateContent(exampleNumber, displayTrendingContent) { const approvedUrls = [ "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", - "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", + "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6", "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", @@ -150,27 +150,29 @@ function generateContent(exampleNumber, displayTrendingContent) { rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); } - Promise.all(rawContentCollection).then(collection => { + return Promise.all(rawContentCollection).then(collection => { for (const part of collection) { - try { + if ( + part && + part.value && + part.value.stream.metadata.thumbnail && + part.channel_name + ) { renderedContentCollection.push(`
    - ${part.name} - -
    + ${part.name} +
    ${part.value.stream.metadata.title} ${part.channel_name}
    `); - } catch (err) { - return; // TODO: Return nice error message } } renderedContentCollection.push(` `); @@ -296,7 +298,7 @@ function generateMemeCreator(socket) { detectLanguageAndUpdate(); initCanvas(); - document.getElementById("tour-example-description").textContent = document.querySelector("[data-action='tour, example 2']").dataset.description; + document.getElementById("tour-example-description").innerHTML = document.querySelector("[data-action='tour, example 2']").dataset.description; setTimeout(() => { document.querySelector(".tour__content__meme__canvas__thumbnail").click(); -- 2.45.2