Fixes for the Tour

This commit is contained in:
ポール ウェッブ 2018-09-27 11:11:26 -05:00
parent ddf534fea6
commit c0b0d66467
21 changed files with 812 additions and 628 deletions

View file

@ -13,30 +13,41 @@ if (window.location.href.search && window.location.href.split("?url=")[1]) { //
$("body").on("click", "[data-action]", event => { document.querySelector("body").addEventListener("click", event => {
if (event.target.dataset.action) {
event.preventDefault(); 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(() => { event.explicitOriginalTarget.classList.add("selected");
handleExamples(event); updateCanvas(event.explicitOriginalTarget);
$(".tour").removeClass("waiting"); }
}, 2500); // "rate-limit" to allow example divs time to populate
}); });
$("body").on("click", ".tour__content__meme__canvas__thumbnail", event => { document.getElementById("fetch-claim-uri").addEventListener("keyup", event => {
$(".tour__content__meme__canvas__thumbnail").removeClass("selected");
event.currentTarget.className += " selected";
updateCanvas(event.currentTarget);
});
$("#fetch-claim-uri").on("keyup", event => {
const key = event.keyCode ? event.keyCode : event.which; 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() { function initializeTour() {
$(".tour").addClass("waiting"); document.querySelector(".tour").classList.add("waiting");
$("#fetch-claim-uri").val("").focus(); // reset document.querySelector("#fetch-claim-uri").value = "";
$(".tour__sidebar__example:nth-child(1)").addClass("active"); document.querySelector("#fetch-claim-uri").focus();
document.querySelector(".tour__navigation__example:nth-child(1)").classList.add("active");
send(JSON.stringify({ send(JSON.stringify({
"message": "landed on tour" "message": "landed on tour"
})); }));
setTimeout(() => { setTimeout(() => {
$(".tour").removeClass("waiting"); document.querySelector(".tour__navigation__example:nth-child(1)").click();
}, 2500); }, 300);
} }
@ -200,10 +212,10 @@ function getMemeInfo() { // TODO: Error handling
const handleExamples = debounce(event => { const handleExamples = debounce(event => {
let exampleNumber; let exampleNumber;
const data = event.currentTarget.dataset; const data = event.dataset;
if (!parseInt($(".tour__sidebar__example.active")[0].dataset.example)) return; if (!parseInt($(".tour__navigation__example.active")[0].dataset.example)) return;
exampleNumber = parseInt($(".tour__sidebar__example.active")[0].dataset.example); exampleNumber = parseInt($(".tour__navigation__example.active")[0].dataset.example);
switch(data.action) { switch(data.action) {
case "choose claim": case "choose claim":
@ -224,8 +236,8 @@ const handleExamples = debounce(event => {
$("#tour-url button").text("Resolve"); $("#tour-url button").text("Resolve");
if ($("#tour-url")[0].style.display === "none") $("#tour-url").show(); if ($("#tour-url")[0].style.display === "none") $("#tour-url").show();
$(".tour__sidebar__example").removeClass("active"); $(".tour__navigation__example").removeClass("active");
$(".tour__sidebar__example:nth-child(1)").addClass("active"); $(".tour__navigation__example:nth-child(1)").addClass("active");
$("#tour-loader").empty().show(); $("#tour-loader").empty().show();
$("#tour-results").empty().show(); $("#tour-results").empty().show();
@ -244,8 +256,8 @@ const handleExamples = debounce(event => {
$("#fetch-claim-uri").val(""); // reset URL bar $("#fetch-claim-uri").val(""); // reset URL bar
$("#tour-url").hide(); $("#tour-url").hide();
$(".tour__sidebar__example").removeClass("active"); $(".tour__navigation__example").removeClass("active");
$(".tour__sidebar__example:nth-child(2)").addClass("active"); $(".tour__navigation__example:nth-child(2)").addClass("active");
$("#tour-loader").empty().show(); $("#tour-loader").empty().show();
$("#tour-results").empty().show(); $("#tour-results").empty().show();
@ -266,8 +278,8 @@ const handleExamples = debounce(event => {
// $("#tour-url").after("<p>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.</p>"); // $("#tour-url").after("<p>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.</p>");
if ($("#tour-url")[0].style.display === "none") $("#tour-url").show(); if ($("#tour-url")[0].style.display === "none") $("#tour-url").show();
$(".tour__sidebar__example").removeClass("active"); $(".tour__navigation__example").removeClass("active");
$(".tour__sidebar__example:nth-child(3)").addClass("active"); $(".tour__navigation__example:nth-child(3)").addClass("active");
$("#tour-loader").empty().show(); $("#tour-loader").empty().show();
$("#tour-results").empty().show(); $("#tour-results").empty().show();

View file

@ -39,30 +39,29 @@ module.exports = exports = (state, emit) => {
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
<!--/ Open Graph /--> <!--/ Open Graph /-->
<meta property="og:type" content="website"/> <meta property="og:image" content="/assets/media/images/og-image.png"/>
<meta property="og:title" content="${config.meta.title}"/> <meta property="og:image:height" content="720"/>
<meta property="og:url" content="https://lbry.tech${state.href}"/> <meta property="og:image:width" content="1280"/>
<meta property="og:site_name" content="${config.meta.title}"/>
<meta property="og:image" content="/assets/images/apple-touch-icon.png"/>
<meta property="og:locale" content="en_US"/> <meta property="og:locale" content="en_US"/>
<meta property="og:site_name" content="${config.meta.title}"/>
<meta property="og:title" content="${title}"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://lbry.tech${state.href}"/>
<!--/ Social/App Stuff /--> <!--/ Social/App Stuff /-->
<meta name="apple-mobile-web-app-title" content="${config.meta.title}"/> <meta name="apple-mobile-web-app-title" content="${config.meta.title}"/>
<meta name="application-name" content="${config.meta.title}"/> <meta name="application-name" content="${config.meta.title}"/>
<meta name="msapplication-TileColor" content="${config.meta.color}"/> <meta name="msapplication-TileColor" content="${config.meta.color}"/>
<meta name="msapplication-TileImage" content="/assets/images/apple-touch-icon.png"/> <meta name="msapplication-TileImage" content="/assets/apple-touch-icon.png"/>
<meta name="theme-color" content="${config.meta.color}"/> <meta name="theme-color" content="${config.meta.color}"/>
<link rel="apple-touch-icon" href="/assets/images/apple-touch-icon.png"/> <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png"/>
<link rel="icon" href="/assets/images/favicon.svg" type="image/svg+xml"/> <link rel="icon" href="/assets/favicon.svg" type="image/svg+xml"/>
<link rel="mask-icon" href="/assets/images/favicon.svg" color="${config.meta.color}"/> <link rel="mask-icon" href="/assets/favicon.svg" color="${config.meta.color}"/>
<link rel="shortcut icon" href="/assets/favicon.ico"/> <link rel="shortcut icon" href="/assets/favicon.ico"/>
<link rel="stylesheet" href="/assets/bundle.css"/>
<link href="/assets/bundle.css" rel="stylesheet"/>
<script src="/assets/scripts/vendor/zepto.js"></script> <script src="/assets/scripts/vendor/zepto.js"></script>
<script>const ws = new WebSocket(location.origin.replace(/^http/, "ws"));</script>
<script src="/assets/scripts/sockets.js"></script> <script src="/assets/scripts/sockets.js"></script>
`; `;
}; };

View file

@ -30,9 +30,9 @@ export default class Navigation extends Nanocomponent {
url: "/overview" url: "/overview"
}, },
{ {
name: "Tour", name: "Playground",
title: "Take a Tour", title: "Experience LBRY",
url: "/tour" url: "/playground"
}, },
{ {
name: "Resources", name: "Resources",

View file

@ -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`
<section class="tour">
<ul class="tour__navigation">
${raw(navigation())}
</ul>
<p class="tour__description" id="tour-example-description"></p>
<section class="tour__content">${raw(example1())}</section>
</section>
`;
}
// H E L P E R S
function example1() {
return html`
<div class="tour__content__urlbar" id="tour-url">
<span>lbry://</span><input id="fetch-claim-uri" placeholder="&thinsp;Enter a LBRY address or select a video below" type="text"/>
<button class="button" data-action="execute claim" type="button">Resolve</button>
</div>
<div class="tour__content__trends" id="tour-loader"></div>
<div id="tour-results"></div>
<script>
document.getElementById("tour-example-description").textContent = document.querySelector("[data-action='tour, example 1']").dataset.description
</script>
`;
}
function navigation() { // TODO: Save tutorial position to localStorage
return dedent`
<li
class="tour__navigation__example"
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"
>
<button type="button">Resolve</button>
<span>Get details of media (aka, "claim" metadata)</span>
</li>
<li
class="tour__navigation__example"
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"
>
<button type="button">Publish</button>
<span>Create a meme and upload it to the LBRY blockchain</span>
</li>
<li
class="tour__navigation__example"
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"
>
<button type="button">Support</button>
<span>Support creators on LBRY with a tip, on us!</span>
</li>
`;
}

BIN
app/dist/media/images/og-image.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -1,46 +1,72 @@
/* global $, log, ws */ "use strict"; "use strict";
// const log = console.log; // eslint-disable-line document.addEventListener("DOMContentLoaded", () => {
initializeWebSocketConnection();
setInterval(checkWebSocketConnection, 5000);
});
let ws = null;
function checkWebSocketConnection() {
if (!ws || ws.readyState === 3) initializeWebSocketConnection();
}
function initializeWebSocketConnection() {
ws = new WebSocket(location.origin.replace(/^http/, "ws"));
ws.onopen = () => {
console.log("WebSocket connection established"); // eslint-disable-line
};
ws.onmessage = socket => { ws.onmessage = socket => {
const data = JSON.parse(socket.data); const data = JSON.parse(socket.data);
switch (true) { switch (true) {
case data.message === "updated html": case data.message === "updated html":
$(data.selector).html(data.html); document.querySelector(data.selector).innerHTML = data.html;
$("#emailMessage").val(""); 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; break;
case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent
$("#flash-container").html(`<div class="flash active${data.type ? " " + data.type : ""}">${data.details}</div>`); document.getElementById("flash-container").innerHTML =
`<div class="flash active${data.type ? " " + data.type : ""}">${data.details}</div>`;
setTimeout(() => { setTimeout(() => {
$("#flash-container").html(""); document.getElementById("flash-container").innerHTML = "";
}, 2100); }, 2100);
break; break;
default: default:
log(data); console.log(data); // eslint-disable-line
break; break;
} }
}; };
ws.onclose = () => {
console.log("WebSocket connection lost"); // eslint-disable-line
checkWebSocketConnection(); // reconnect now
};
}
function send(msg) { // eslint-disable-line function send(msg) { // eslint-disable-line
socketReady(ws, () => ws.send(msg)); socketReady(ws, () => ws.send(msg));
} }
function socketReady(socket, callback) { function socketReady(socket, callback) {
setTimeout(() => { setTimeout(() => {
if (socket.readyState === 1) { if (socket && socket.readyState === 1) {
if (callback !== undefined) callback(); if (callback !== undefined) callback();
return; return;
} else {
socketReady(socket, callback);
} }
return socketReady(socket, callback);
}, 5); }, 5);
} }

View file

@ -12,11 +12,11 @@ const stringifyObject = require("stringify-object");
// V A R I A B L E S // 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 loadLanguages = require("prismjs/components/");
const logSlackError = local("/app/helpers/slack"); const logSlackError = local("app/helpers/slack");
const publishMeme = local("/app/helpers/publish-meme"); const publishMeme = local("app/helpers/publish-meme");
const uploadImage = local("/app/helpers/upload-image"); const uploadImage = local("app/helpers/upload-image");
loadLanguages(["json"]); loadLanguages(["json"]);
@ -126,7 +126,6 @@ module.exports = exports = (data, socket) => {
<h3>Response</h3> <h3>Response</h3>
${explorerNotice} ${explorerNotice}
<pre><code class="language-json">${renderedCode}</code></pre> <pre><code class="language-json">${renderedCode}</code></pre>
<script>$("#temp-loader").hide();</script>
`), `),
"message": "updated html", "message": "updated html",
"selector": `#example${data.example}-result` "selector": `#example${data.example}-result`
@ -195,7 +194,6 @@ module.exports = exports = (data, socket) => {
<h3>Response</h3> <h3>Response</h3>
${explorerNotice} ${explorerNotice}
<pre><code class="language-json">${renderedCode}</code></pre> <pre><code class="language-json">${renderedCode}</code></pre>
<script>$("#temp-loader").hide();</script>
`), `),
"message": "updated html", "message": "updated html",
"selector": `#example${data.example}-result` "selector": `#example${data.example}-result`

View file

@ -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 // P R O G R A M
function generateEvent(event) { 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(`
<div class='github-feed__event'>
<a href="${generateUrl("actor", event)}" target="_blank" rel="noopener noreferrer">
<img src="${event.actor.avatar_url}" class="github-feed__event__avatar" alt=""/>
</a>
<p>
${generateEvent(event)}
<a href="${generateUrl("repo", event)}" title="View this repo on GitHub" target="_blank" rel="noopener noreferrer"><strong>${event.repo.name}</strong></a>
<em class="github-feed__event__time">${relativeDate(new Date(event.created_at))}</em>
</p>
</div>
`);
}
updateGithubFeed(); // TODO: Update `.last-updated` every minute
displayGitHubFeed(`
<h3>GitHub</h3>
<h5 class="last-updated">Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "&middot;")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST</h5>
${renderedEvents.join("")}
`);
});
}
}
function generateUrl(type, event) { function generateUrl(type, event) {
switch (type) { switch (type) {
case "actor": 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 // H E L P E R
@ -129,5 +230,7 @@ function refToBranch(ref) {
module.exports = exports = { module.exports = exports = {
generateEvent, generateEvent,
generateUrl generateGitHubFeed,
generateUrl,
updateGithubFeed
}; };

View file

@ -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"); "use strict"; require("@babel/register"); require("@babel/polyfill");
module.exports = exports = require("./client.js"); module.exports = exports = require("./client.js");

View file

@ -21,6 +21,7 @@
"partials/navigation", "partials/navigation",
"partials/mission-statement", "partials/mission-statement",
"partials/modal", "partials/modal",
"partials/pre",
"layout", "layout",

View file

@ -137,6 +137,8 @@
.__loading { .__loading {
width: 100%; height: 10rem; width: 100%; height: 10rem;
cursor: wait;
position: relative; position: relative;
&::before { &::before {
@ -149,12 +151,14 @@
border-top-color: $teal; border-top-color: $teal;
border-width: 6px; border-width: 6px;
content: ""; content: "";
cursor: wait;
position: absolute; position: absolute;
} }
&::after { &::after {
top: 7rem; left: 0; top: 7rem; left: 0;
cursor: wait;
font-size: 1rem; font-size: 1rem;
position: absolute; position: absolute;
text-align: center; text-align: center;

View file

@ -179,36 +179,7 @@
} }
pre { pre {
margin-bottom: 2rem; padding: 2rem;
border-radius: 3px;
font-size: 1rem; 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 { h2, h3, h4, h5 {

View file

@ -147,6 +147,10 @@
margin-bottom: 1rem; margin-bottom: 1rem;
} }
pre {
font-size: 0.8rem;
}
table { table {
border: 1px solid rgba($white, 0.1); border: 1px solid rgba($white, 0.1);
border-radius: 0.3rem; border-radius: 0.3rem;
@ -171,16 +175,6 @@
tr:nth-child(even) { tr:nth-child(even) {
background-color: rgba($white, 0.1); 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 { .api__content__body {

View file

@ -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 {::before}
* @selector {:last-of-type} * @selector {:last-of-type}
* @state {.active} * @state {.active}
* @state {:hover} * @state {:hover}
*/ */
.tour__sidebar { .tour__navigation {
width: 250px; height: 100%; width: 100%;
float: left;
list-style-type: none; list-style-type: none;
padding-top: 1rem; padding-bottom: 1rem;
padding-right: 1rem; padding-top: 1.5rem;
vertical-align: top;
&::after {
@include clearfix;
}
} }
.tour__sidebar__example { .tour__navigation__example {
cursor: pointer; cursor: pointer;
float: left;
position: relative; position: relative;
text-align: center;
width: 33.333333%;
&::before { &::before {
width: 1rem; height: 1rem; width: 100%; height: 2.5rem;
top: 0.5rem; left: 0; top: -0.6rem; left: 0;
border: 1px solid; content: "example " attr(data-example);
border-radius: 50%; font-size: 0.6rem;
content: attr(data-example); font-style: italic;
font-size: 0.8rem;
line-height: 1.1; line-height: 1.1;
position: absolute; position: absolute;
text-align: center; text-align: center;
} text-transform: uppercase;
&:not(:last-of-type) {
margin-bottom: 1.5rem;
} }
&:not(.active) { &: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, &::before,
button, button,
span { span {
@ -103,7 +118,6 @@
background-color: transparent; background-color: transparent;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
padding-left: 1.3rem;
} }
span { span {
@ -139,12 +153,10 @@
*/ */
.tour__content { .tour__content {
width: calc(100% - 250px); height: 100%; min-height: 500px; border-top: 1px solid rgba($black, 0.05);
overflow-y: visible;
border-left: 1px solid rgba($black, 0.05); padding-bottom: 1rem;
float: right; padding-top: 1rem;
padding: 1rem 0 1rem 1rem;
vertical-align: top;
.loader { .loader {
@extend .__loading; @extend .__loading;
@ -153,6 +165,10 @@
content: "Processing request"; content: "Processing request";
} }
} }
pre {
font-size: 1rem;
}
} }
.tour__content__meme { .tour__content__meme {
@ -167,6 +183,7 @@
.tour__content__meme__canvas { .tour__content__meme__canvas {
float: left; float: left;
margin-right: 2%; margin-right: 2%;
position: relative;
width: 48%; width: 48%;
canvas { canvas {
@ -202,7 +219,7 @@
.tour__content__meme__editor { .tour__content__meme__editor {
float: right; float: right;
width: 48%; width: 50%;
h2.__metadata { h2.__metadata {
margin-top: 3rem; margin-top: 3rem;
@ -315,10 +332,11 @@
} }
.tour__content__trends { .tour__content__trends {
min-width: 0; min-height: 0;
display: grid; display: grid;
grid-gap: 2%; grid-gap: 1rem;
grid-template-columns: 32% 32% 32%; grid-template: repeat(1, 1fr) / repeat(3, 1fr);
overflow-y: auto;
position: relative; position: relative;
&:empty { &:empty {
@ -332,7 +350,7 @@
.tour__content__trend { .tour__content__trend {
img { img {
width: 100%; height: 175px; width: 100%; height: 213px;
cursor: pointer; cursor: pointer;
display: block; display: block;
@ -428,3 +446,20 @@
width: 3.5rem; 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;
}

View file

@ -50,7 +50,6 @@
letter-spacing: 0.1rem; letter-spacing: 0.1rem;
line-height: 1; line-height: 1;
text-transform: uppercase; text-transform: uppercase;
width: 100%;
@media (min-width: 1301px) { @media (min-width: 1301px) {
top: 2.15rem; left: 0; top: 2.15rem; left: 0;
@ -58,6 +57,7 @@
color: rgba($black, 0.045); color: rgba($black, 0.045);
font-size: 4rem; font-size: 4rem;
position: absolute; position: absolute;
width: calc(100% - (1rem + 5%));
} }
@media (max-width: 1300px) { @media (max-width: 1300px) {

View file

@ -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;
}
}
}

394
app/sockets.js Normal file
View file

@ -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(`<img alt="${image.alt}" class="tour__content__meme__canvas__thumbnail" src="${image.src}"/>`);
}
const memeCreator = html`
<div class="tour__content__meme__canvas">
<img alt="Base image for LBRY meme creator" id="base-image" style="height: 0; position: absolute; visibility: hidden;"/>
<canvas id="meme-canvas" height="300" width="400">Unfortunately, it looks like canvas is <strong>not supported</strong> in your browser</canvas>
${renderedImages}
</div>
<form class="tour__content__meme__editor">
<h2>Image Text</h2>
<fieldset>
<label for="meme-top-line">Top line</label>
<input id="meme-top-line" name="meme-top-line" placeholder="${memePlaceholderData.topLine.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.topLine.value}" required/>
</fieldset>
<fieldset>
<label for="meme-bottom-line">Bottom line</label>
<input id="meme-bottom-line" name="meme-bottom-line" placeholder="${memePlaceholderData.bottomLine.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.bottomLine.value}" required/>
</fieldset>
<h2 class="__metadata">Metadata</h2>
<fieldset>
<label for="meme-title">Title</label>
<input id="meme-title" name="meme-title" placeholder="${memePlaceholderData.title.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.title.value}" required/>
</fieldset>
<fieldset>
<label for="meme-description">Description</label>
<textarea id="meme-description" name="meme-description" placeholder="${memePlaceholderData.description.placeholder}" spellcheck="false" type="text" required>${memePlaceholderData.description.value}</textarea>
</fieldset>
<fieldset>
<label for="meme-language">Language</label>
<select id="meme-language" name="meme-language">
<option value="ar">Arabic</option>
<option value="zh">Chinese (Mandarin)</option>
<option value="en">English</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="jp">Japanese</option>
<option value="ru">Russian</option>
<option value="es">Spanish</option>
<option value="">Not specified</option>
</select>
</fieldset>
<fieldset>
<label for="meme-license">License</label>
<select id="meme-license" name="meme-license" required>
<option value="Public Domain">Public Domain</option>
<option value="Creative Commons Attribution 4.0 International">Creative Commons Attribution 4.0 International</option>
<option value="Creative Commons Attribution-ShareAlike 4.0 International">Creative Commons Attribution-ShareAlike 4.0 International</option>
<option value="Creative Commons Attribution-NoDerivatives 4.0 International">Creative Commons Attribution-NoDerivatives 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial 4.0 International">Creative Commons Attribution-NonCommercial 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International</option>
<option value="None">None</option>
</select>
</fieldset>
<fieldset>
<label><input id="meme-nsfw-flag" name="nsfw" type="checkbox"/>NSFW</label>
</fieldset>
<fieldset>
<button data-action="upload image" class="__button-black" type="button">Submit</button>
</fieldset>
</form>
<script>
detectLanguageAndUpdate();
initCanvas();
document.getElementById("tour-example-description").textContent = document.querySelector("[data-action='tour, example 2']").dataset.description;
setTimeout(() => {
document.querySelector(".tour__content__meme__canvas__thumbnail").click();
}, 100);
</script>
`;
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(`
<figure class="tour__content__trend">
<img alt="${part.name}" data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}" src="${part.value.stream.metadata.thumbnail}"/>
<figcaption data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}">
${part.value.stream.metadata.title}
<span>${part.channel_name}</span>
</figcaption>
</figure>
`);
} catch (err) {
return; // TODO: Return nice error message
}
}
renderedContentCollection.push(`
<script>
document.getElementById("tour-example-description").textContent = document.querySelector("[data-action='tour, example 1']").dataset.description
</script>
`);
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(`
<figure class="tour__content__trend">
<img alt="${part.name}" data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}" src="${part.value.stream.metadata.thumbnail}"/>
<figcaption data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}">
${part.value.stream.metadata.title}
<span>${part.channel_name}</span>
</figcaption>
</figure>
`);
} catch (err) {
return; // TODO: Return nice error message
}
}
renderedContentCollection.push(`
<script>
document.getElementById("tour-example-description").textContent = document.querySelector("[data-action='tour, example 3']").dataset.description
</script>
`);
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));
}

View file

@ -17,8 +17,8 @@ import linkGrid from "../components/link-grid";
const featureLinks = linkGrid([ const featureLinks = linkGrid([
{ {
description: "Learn how LBRY works with 3 easy examples", description: "Learn how LBRY works with 3 easy examples",
destination: "/tour", destination: "/playground",
label: "Take the Tour", label: "Jump into Playground",
title: "New to LBRY?" title: "New to LBRY?"
}, },
{ {

View file

@ -85,7 +85,7 @@ module.exports = exports = (state, emit) => { // eslint-disable-line
let pageScript = ""; let pageScript = "";
if (path === "glossary") pageScript = "<script>" + fs.readFileSync("./app/components/client/glossary-scripts.js", "utf-8") + "</script>"; if (path === "glossary") pageScript = "<script>" + fs.readFileSync("./app/components/client/glossary-scripts.js", "utf-8") + "</script>";
if (path === "overview") pageScript = "<script>" + fs.readFileSync("./app/components/client/ecosystem-scripts.js", "utf-8") + "</script>"; if (path === "overview") pageScript = "<script>" + fs.readFileSync("./app/components/client/ecosystem-scripts.js", "utf-8") + "</script>";
if (path === "tour") pageScript = "<script>" + fs.readFileSync("./app/components/client/tour-scripts.js", "utf-8") + "</script>"; if (path === "playground") pageScript = "<script>" + fs.readFileSync("./app/components/client/tour-scripts.js", "utf-8") + "</script>";
return html` return html`
<article class="page" itemtype="http://schema.org/BlogPosting"> <article class="page" itemtype="http://schema.org/BlogPosting">

9
documents/playground.md Normal file
View file

@ -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.
<Playground/>

479
server.js
View file

@ -4,10 +4,9 @@
// P A C K A G E S // P A C K A G E S
const async = require("async");
const color = require("colorette"); const color = require("colorette");
const cors = require("cors"); const cors = require("cors");
const dedent = require("dedent"); const local = require("app-root-path").require;
const fastify = require("fastify")({ const fastify = require("fastify")({
logger: { 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 // V A R I A B L E S
const fetchMetadata = local("app/helpers/fetch-metadata"); const handleSocketMessages = local("app/sockets");
const github = local("app/helpers/github");
const log = console.log; // eslint-disable-line
const logSlackError = local("app/helpers/slack"); 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 => { fastify.ws.on("connection", socket => {
socket.on("message", data => { socket.on("message", data => {
data = JSON.parse(data); data = JSON.parse(data);
return handleSocketMessages(socket, 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; socket.on("close", () => socket.terminate());
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;
}
});
socket.on("close", () => {
// console.log(socket);
return socket.terminate();
});
}); });
}); });
@ -159,387 +62,15 @@ fastify.ready(err => {
const start = async () => { const start = async () => {
try { try {
await fastify.listen(process.env.PORT || 8080, process.env.IP || "0.0.0.0"); 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) { } catch (err) {
fastify.log.error(err); fastify.log.error(err);
process.exit(1); process.exit(1);
} }
process.env.NODE_ENV === "development" ? 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}\``) logSlackError(`Server started at port \`${fastify.server.address().port}\``)
; ;
}; };
start(); 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(`
<div class='github-feed__event'>
<a href="${github.generateUrl("actor", event)}" target="_blank" rel="noopener noreferrer">
<img src="${event.actor.avatar_url}" class="github-feed__event__avatar" alt=""/>
</a>
<p>
${github.generateEvent(event)}
<a href="${github.generateUrl("repo", event)}" title="View this repo on GitHub" target="_blank" rel="noopener noreferrer"><strong>${event.repo.name}</strong></a>
<em class="github-feed__event__time">${relativeDate(new Date(event.created_at))}</em>
</p>
</div>
`);
}
updateGithubFeed(); // TODO: Update `.last-updated` every minute
displayGitHubFeed(dedent`
<h3>GitHub</h3>
<h5 class="last-updated">Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "&middot;")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST</h5>
${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(`<img alt="${image.alt}" class="tour__content__meme__canvas__thumbnail" src="${image.src}"/>`);
}
const memeCreator = html`
<div class="tour__content__meme__canvas">
<img alt="Base image for LBRY meme creator" id="base-image" style="height: 0; visibility: hidden;"/>
<canvas id="meme-canvas" height="300" width="400">Unfortunately, it looks like canvas is <strong>not supported</strong> in your browser</canvas>
${renderedImages}
</div>
<form class="tour__content__meme__editor">
<h2>Image Text</h2>
<fieldset>
<label for="meme-top-line">Top line</label>
<input id="meme-top-line" name="meme-top-line" placeholder="${memePlaceholderData.topLine.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.topLine.value}" required/>
</fieldset>
<fieldset>
<label for="meme-bottom-line">Bottom line</label>
<input id="meme-bottom-line" name="meme-bottom-line" placeholder="${memePlaceholderData.bottomLine.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.bottomLine.value}" required/>
</fieldset>
<h2 class="__metadata">Metadata</h2>
<fieldset>
<label for="meme-title">Title</label>
<input id="meme-title" name="meme-title" placeholder="${memePlaceholderData.title.placeholder}" spellcheck="false" type="text" value="${memePlaceholderData.title.value}" required/>
</fieldset>
<fieldset>
<label for="meme-description">Description</label>
<textarea id="meme-description" name="meme-description" placeholder="${memePlaceholderData.description.placeholder}" spellcheck="false" type="text" required>${memePlaceholderData.description.value}</textarea>
</fieldset>
<fieldset>
<label for="meme-language">Language</label>
<select id="meme-language" name="meme-language">
<option value="ar">Arabic</option>
<option value="zh">Chinese (Mandarin)</option>
<option value="en">English</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="it">Italian</option>
<option value="jp">Japanese</option>
<option value="ru">Russian</option>
<option value="es">Spanish</option>
<option value="">Not specified</option>
</select>
</fieldset>
<fieldset>
<label for="meme-license">License</label>
<select id="meme-license" name="meme-license" required>
<option value="Public Domain">Public Domain</option>
<option value="Creative Commons Attribution 4.0 International">Creative Commons Attribution 4.0 International</option>
<option value="Creative Commons Attribution-ShareAlike 4.0 International">Creative Commons Attribution-ShareAlike 4.0 International</option>
<option value="Creative Commons Attribution-NoDerivatives 4.0 International">Creative Commons Attribution-NoDerivatives 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial 4.0 International">Creative Commons Attribution-NonCommercial 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</option>
<option value="Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International</option>
<option value="None">None</option>
</select>
</fieldset>
<fieldset>
<label><input id="meme-nsfw-flag" name="nsfw" type="checkbox"/>NSFW</label>
</fieldset>
<fieldset>
<button data-action="upload image" class="__button-black" type="button">Submit</button>
</fieldset>
</form>
<script>
detectLanguageAndUpdate();
initCanvas();
setTimeout(() => {
$(".tour__content__meme__canvas__thumbnail").click();
}, 100);
</script>
`;
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(`
<figure class="tour__content__trend">
<img alt="${part.name}" data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}" src="${part.value.stream.metadata.thumbnail}"/>
<figcaption data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}">
${part.value.stream.metadata.title}
<span>${part.channel_name}</span>
</figcaption>
</figure>
`);
}
}
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(`
<figure class="tour__content__trend">
<img alt="${part.name}" data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}" src="${part.value.stream.metadata.thumbnail}"/>
<figcaption data-action="choose claim" data-claim-id="${exampleNumber === 1 ? part.name : part.claim_id}">
${part.value.stream.metadata.title}
<span>${part.channel_name}</span>
</figcaption>
</figure>
`);
}
}
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));
}