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] 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)); -}