diff --git a/assets/js/interactive-tutorial.js b/assets/js/interactive-tutorial.js index e25886e632..b57b076cf2 100644 --- a/assets/js/interactive-tutorial.js +++ b/assets/js/interactive-tutorial.js @@ -2,34 +2,39 @@ // Locale strings. TODO: maybe move these out to their own file. LOCALES = { - "en": { + en: { // Leave empty, use the keys provided (in English) by default }, - "ja": { + ja: { "Address:": "アドレス:", "Secret:": "シード:", "Balance:": "残高:", "Complete all previous steps first": "前の手順をすべて完了して下さい", "Conection to the XRP Ledger required": "XRP Ledgerの接続が必要です", "Error:": "エラー:", - "Populated this page's examples with these credentials.": "このページの例にこのアドレスとシードを入力しました。", - "There was an error connecting to the Faucet. Please try again.": "テストネットワークFaucetにエラーが発生しました。もう一度試してください。", + "Populated this page's examples with these credentials.": + "このページの例にこのアドレスとシードを入力しました。", + "There was an error connecting to the Faucet. Please try again.": + "テストネットワークFaucetにエラーが発生しました。もう一度試してください。", "Connecting...": "接続しています...", "Connection required": "接続が必要です", - "Connected": "接続されました", - "Faucet returned an error:": "テストネットワークFaucetがこのエラーを返しました:", - "Validated": "検証済み", + Connected: "接続されました", + "Faucet returned an error:": + "テストネットワークFaucetがこのエラーを返しました:", + Validated: "検証済み", "Final Result:": "確定結果:", "(Still pending...)": "(まだ未決…)", "(None)": "(無)", "Prepared transaction:": "準備済みトランザクション:", - "Failed to achieve consensus (final)": "検証済みレジャーには含まれません(決定結果)", + "Failed to achieve consensus (final)": + "検証済みレジャーには含まれません(決定結果)", "Preliminary result:": "予備結果:", - "Unknown": "不明", - "Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.": "有効なアドレスかシードの取得出来ませんでした。前の手順が完了しましたことを確認して下さい。", - "Transaction hash:": "トランザクションハッシュ:" - } -} + Unknown: "不明", + "Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.": + "有効なアドレスかシードの取得出来ませんでした。前の手順が完了しましたことを確認して下さい。", + "Transaction hash:": "トランザクションハッシュ:", + }, +}; /** * Quick-n-dirty localization function. TODO: migrate to a real localization @@ -38,13 +43,13 @@ LOCALES = { * @return {String} The translated string, if one is available, or the provided * key value if no translation is available. */ -const current_locale = $("html").prop("lang") +const current_locale = $("html").prop("lang"); function tl(key) { - let mesg = LOCALES[current_locale][key] + let mesg = LOCALES[current_locale][key]; if (typeof mesg === "undefined") { - mesg = key + mesg = key; } - return mesg + return mesg; } /** @@ -57,18 +62,17 @@ function tl(key) { * and with most non-alphanumeric characters removed. */ function slugify(s) { - const unacceptable_chars = /[^A-Za-z0-9._ ]+/g - const whitespace_regex = /\s+/g - s = s.replace(unacceptable_chars, "") - s = s.replace(whitespace_regex, "_") - s = s.toLowerCase() + const unacceptable_chars = /[^A-Za-z0-9._ ]+/g; + const whitespace_regex = /\s+/g; + s = s.replace(unacceptable_chars, ""); + s = s.replace(whitespace_regex, "_"); + s = s.toLowerCase(); if (!s) { - s = "_" + s = "_"; } - return s + return s; } - /** * Check whether a given step has been marked completed already. * @param {String} step_name The exact name of the step, as defined in the @@ -76,7 +80,7 @@ function slugify(s) { * @return {Boolean} Whether or not this step has been marked complete. */ function is_complete(step_name) { - return is_complete_by_id(slugify(step_name)) + return is_complete_by_id(slugify(step_name)); } /** @@ -86,7 +90,7 @@ function is_complete(step_name) { * @return {Boolean} Whether or not this step has been marked complete. */ function is_complete_by_id(step_id) { - return $(".bc-"+step_id).hasClass("done") + return $(".bc-" + step_id).hasClass("done"); } /** @@ -97,7 +101,7 @@ function is_complete_by_id(step_id) { * start_step(step_name) function in the MD file. */ function complete_step(step_name) { - complete_step_by_id(slugify(step_name)) + complete_step_by_id(slugify(step_name)); } /** @@ -106,15 +110,22 @@ function complete_step(step_name) { * @param {String} step_id The slugified name of the step. */ function complete_step_by_id(step_id) { - $(".bc-"+step_id).removeClass("active").addClass("done") - $(".bc-"+step_id).next().removeClass("disabled").addClass("active") + $(".bc-" + step_id) + .removeClass("active") + .addClass("done"); + $(".bc-" + step_id) + .next() + .removeClass("disabled") + .addClass("active"); // Enable follow-up steps that require this step to be done first - const next_ui = $(`#interactive-${step_id}`).nextAll( - ".interactive-block").eq(0).find(".previous-steps-required") - next_ui.prop("title", "") - next_ui.prop("disabled", false) - next_ui.removeClass("disabled") + const next_ui = $(`#interactive-${step_id}`) + .nextAll(".interactive-block") + .eq(0) + .find(".previous-steps-required"); + next_ui.prop("title", ""); + next_ui.prop("disabled", false); + next_ui.removeClass("disabled"); } /** @@ -125,7 +136,7 @@ function complete_step_by_id(step_id) { */ function get_block_id(jEl) { // Traverse up, then slice "interactive-" off the block's HTML ID - return jEl.closest(".interactive-block").prop("id").slice(12) + return jEl.closest(".interactive-block").prop("id").slice(12); } /** @@ -135,10 +146,10 @@ function get_block_id(jEl) { */ function pretty_print(j) { try { - return JSON.stringify(JSON.parse(j),null,2) + return JSON.stringify(JSON.parse(j), null, 2); } catch (e) { // probably already decoded JSON - return JSON.stringify(j,null,2) + return JSON.stringify(j, null, 2); } } @@ -147,10 +158,16 @@ function pretty_print(j) { * them an appropriate tooltip message. */ function disable_followup_steps() { - $(".previous-steps-required").prop("title", tl("Complete all previous steps first")) - $(".previous-steps-required").prop("disabled", true).addClass("disabled") - $(".connection-required").prop("title", tl("Conection to the XRP Ledger required")) - $(".connection-required").prop("disabled", true).addClass("disabled") + $(".previous-steps-required").prop( + "title", + tl("Complete all previous steps first") + ); + $(".previous-steps-required").prop("disabled", true).addClass("disabled"); + $(".connection-required").prop( + "title", + tl("Conection to the XRP Ledger required") + ); + $(".connection-required").prop("disabled", true).addClass("disabled"); } /** @@ -163,7 +180,8 @@ function disable_followup_steps() { function show_error(block, message) { block.find(".output-area").html( `
${tl("Error:")} - ${message}
`) + ${message}` + ); } /** @@ -174,52 +192,57 @@ function show_error(block, message) { * use the generated credentials instead of the placeholder EXAMPLE_ADDR and * EXAMPLE_SECRET. */ -const EXAMPLE_ADDR = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe" -const EXAMPLE_SECRET = "s████████████████████████████" +const EXAMPLE_ADDR = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"; +const EXAMPLE_SECRET = "s████████████████████████████"; function setup_generate_step() { - - $("#generate-creds-button").click( async (event) => { - const block = $(event.target).closest(".interactive-block") - block.find(".output-area").html("") - block.find(".loader").show() + $("#generate-creds-button").click(async (event) => { + const block = $(event.target).closest(".interactive-block"); + block.find(".output-area").html(""); + block.find(".loader").show(); // Get faucet URL (Testnet/Devnet/etc.) - const faucet_url = $("#generate-creds-button").data("fauceturl") + const faucet_url = $("#generate-creds-button").data("fauceturl"); try { - const data = await call_faucet(faucet_url) + const data = await call_faucet(faucet_url, undefined, event); - block.find(".loader").hide() + block.find(".loader").hide(); block.find(".output-area").html(`${tl("Populated this page's examples with these credentials.")}
`) + block + .find(".output-area") + .append( + `${tl( + "Populated this page's examples with these credentials." + )}
` + ); - complete_step("Generate") - - } catch(err) { - block.find(".loader").hide() + complete_step("Generate"); + } catch (err) { + block.find(".loader").hide(); block.find(".output-area").html( `${tl("Error:")} ${tl("There was an error connecting to the Faucet. Please try again.")} -
`) - return + ` + ); + return; } - }) + }); } /** @@ -229,13 +252,20 @@ function setup_generate_step() { * @return {String, undefined} The address, if available, or undefined if not */ function get_address(event) { - const address = $("#use-address").text() + const address = $("#use-address").text(); if (!address) { - const block = $(event.target).closest(".interactive-block") - if (!block.length) {return} - show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.")) + const block = $(event.target).closest(".interactive-block"); + if (!block.length) { + return; + } + show_error( + block, + tl( + "Couldn't get a valid address/secret value. Check that the previous steps were completed successfully." + ) + ); } - return address + return address; } /** @@ -245,19 +275,33 @@ function get_address(event) { * @return {Wallet, undefined} The Wallet instance, if available, or undefined if not */ function get_wallet(event) { - const secret = $("#use-secret").text() + const secret = $("#use-secret").text(); if (!secret) { - const block = $(event.target).closest(".interactive-block") - if (!block.length) {return} - show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.")) + const block = $(event.target).closest(".interactive-block"); + if (!block.length) { + return; + } + show_error( + block, + tl( + "Couldn't get a valid address/secret value. Check that the previous steps were completed successfully." + ) + ); } if (secret == EXAMPLE_SECRET) { - const block = $(event.target).closest(".interactive-block") - if (!block.length) {return} - show_error(block, tl("Can't use the example secret here. Check that the previous steps were completed successfully.")) - return + const block = $(event.target).closest(".interactive-block"); + if (!block.length) { + return; + } + show_error( + block, + tl( + "Can't use the example secret here. Check that the previous steps were completed successfully." + ) + ); + return; } - return xrpl.Wallet.fromSeed(secret) + return xrpl.Wallet.fromSeed(secret); } /** @@ -265,30 +309,42 @@ function get_wallet(event) { * @param {String} faucet_url The URL of the faucet to call, for example: * https://faucet.altnet.rippletest.net/accounts */ -async function call_faucet(faucet_url, destination) { +async function call_faucet(faucet_url, destination, event) { // Future feature: support the Faucet's optional xrpAmount param - const body = {} + const block = $(event.target).closest(".interactive-block"); + + const tutorial_info = { + path: window.location.pathname, + button: event.target.id, + step: block.data("stepnumber"), + totalsteps: block.data("totalsteps"), + }; + const memo = { + data: JSON.stringify(tutorial_info, null, 0), + format: "application/json", // application/json + // The MemoType decodes to a URL that explains the format of this memo type: + // https://github.com/XRPLF/xrpl-dev-portal/blob/master/tool/INTERACTIVE_TUTORIALS_README.md + type: "https://github.com/XRPLF/xrpl-dev-portal/blob/master/tool/INTERACTIVE_TUTORIALS_README.md", + }; + + const body = {}; if (typeof destination != "undefined") { - body["destination"] = destination + body["destination"] = destination; } - body["memos"] = [ - { - data: "xrpl.org-tutorials", - }, - ] + body["memos"] = [memo]; const response = await fetch(faucet_url, { - method: 'POST', + method: "POST", headers: { - "Content-Type": "application/json; charset=utf-8" + "Content-Type": "application/json; charset=utf-8", }, - body: JSON.stringify(body) - }) - const data = await response.json() + body: JSON.stringify(body), + }); + const data = await response.json(); if (!response.ok) { - throw `${tl("Faucet returned an error:")} ${data.error}` + throw `${tl("Faucet returned an error:")} ${data.error}`; } - return data + return data; } /** @@ -300,7 +356,6 @@ async function call_faucet(faucet_url, destination) { */ window.after_connect = window.after_connect || []; - /** * To be used with _snippets/interactive-tutorials/connect-step.md * Adds an event to the "Connect" button to connect to the appropriate @@ -309,44 +364,46 @@ window.after_connect = window.after_connect || []; */ function setup_connect_step() { if (!$("#connect-button").length) { - console.debug("Connect step not included. Skipping related setup.") - return + console.debug("Connect step not included. Skipping related setup."); + return; } - const ws_url = $("#connect-button").data("wsurl") + const ws_url = $("#connect-button").data("wsurl"); if (!ws_url) { - console.error("Interactive Tutorial: WS URL not found. Did you set use_network?") - return + console.error( + "Interactive Tutorial: WS URL not found. Did you set use_network?" + ); + return; } - api = new xrpl.Client(ws_url) - api.on('connected', async function() { - $("#connection-status").text(tl("Connected")) - $("#connect-button").prop("disabled", true) - $("#loader-connect").hide() - $(".connection-required").prop("disabled", false) - $(".connection-required").prop("title", "") + api = new xrpl.Client(ws_url); + api.on("connected", async function () { + $("#connection-status").text(tl("Connected")); + $("#connect-button").prop("disabled", true); + $("#loader-connect").hide(); + $(".connection-required").prop("disabled", false); + $(".connection-required").prop("title", ""); // Subscribe to ledger close events - api.request({command: "subscribe", streams: ["ledger"]}) + api.request({ command: "subscribe", streams: ["ledger"] }); - complete_step("Connect") - }) - api.on('disconnected', (code) => { - $("#connection-status").text( tl("Disconnected") +" ("+code+")" ) - $("#connect-button").prop("disabled", false) - $(".connection-required").prop("disabled", true) - $(".connection-required").prop("title", tl("Connection required")) + complete_step("Connect"); + }); + api.on("disconnected", (code) => { + $("#connection-status").text(tl("Disconnected") + " (" + code + ")"); + $("#connect-button").prop("disabled", false); + $(".connection-required").prop("disabled", true); + $(".connection-required").prop("title", tl("Connection required")); - disable_followup_steps() - }) + disable_followup_steps(); + }); $("#connect-button").click(async (event) => { - $("#connection-status").text( tl("Connecting...") ) - $("#loader-connect").show() - await api.connect() + $("#connection-status").text(tl("Connecting...")); + $("#loader-connect").show(); + await api.connect(); for (const fn of after_connect) { - fn() + fn(); } - }) + }); } /** @@ -359,62 +416,71 @@ function setup_connect_step() { * Requires xrpl.js to be loaded and instantiated as "api" first. */ function setup_wait_steps() { - const wait_steps = $(".wait-step") + const wait_steps = $(".wait-step"); wait_steps.each(async (i, el) => { - const wait_step = $(el) - const explorer_url = wait_step.data("explorerurl") - const status_box = wait_step.find(".tx-validation-status") - api.on('ledgerClosed', async (ledger) => { + const wait_step = $(el); + const explorer_url = wait_step.data("explorerurl"); + const status_box = wait_step.find(".tx-validation-status"); + api.on("ledgerClosed", async (ledger) => { // Update the latest validated ledger index in this step's table - wait_step.find(".validated-ledger-version").text(ledger.ledger_index) + wait_step.find(".validated-ledger-version").text(ledger.ledger_index); if (!status_box.data("status_pending")) { // Before submission or after a final result. // Either way, nothing more to do here. - return + return; } - const transaction = wait_step.find(".waiting-for-tx").text().trim() - const min_ledger = parseInt(wait_step.find(".earliest-ledger-version").text()) - const max_ledger = parseInt(wait_step.find(".lastledgersequence").text()) - let tx_response + const transaction = wait_step.find(".waiting-for-tx").text().trim(); + const min_ledger = parseInt( + wait_step.find(".earliest-ledger-version").text() + ); + const max_ledger = parseInt(wait_step.find(".lastledgersequence").text()); + let tx_response; try { tx_response = await api.request({ command: "tx", transaction, min_ledger, - max_ledger - }) + max_ledger, + }); if (tx_response.result.validated) { status_box.html( - `
- ${tl("(Still pending...)")}${pretty_print(prepared)}`)
+ ${pretty_print(prepared)}`
+ );
- const {tx_blob, hash} = wallet.sign(prepared)
- block.find(".output-area").append(
- `${tl("Transaction hash:")} ${hash}
${tl("Transaction hash:")} ${hash}
${tl("Preliminary result:")}
-${pretty_print(prelim_result)}`)
+ ${pretty_print(prelim_result)}`
+ );
- block.find(".loader").hide()
- submit_step_id = get_block_id(block)
- complete_step_by_id(submit_step_id)
- if (wait_step_name){
- activate_wait_step(wait_step_name, prelim_result)
+ block.find(".loader").hide();
+ submit_step_id = get_block_id(block);
+ complete_step_by_id(submit_step_id);
+ if (wait_step_name) {
+ activate_wait_step(wait_step_name, prelim_result);
}
- return prelim_result
- } catch(error) {
- block.find(".loader").hide()
- show_error(block, error)
+ return prelim_result;
+ } catch (error) {
+ block.find(".loader").hide();
+ show_error(block, error);
}
}
$(document).ready(() => {
- disable_followup_steps()
- setup_generate_step()
- setup_connect_step()
- setup_wait_steps()
-})
+ disable_followup_steps();
+ setup_generate_step();
+ setup_connect_step();
+ setup_wait_steps();
+});