mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-23 13:15:49 +00:00
add data according to interactive tutorials
This commit is contained in:
@@ -2,34 +2,39 @@
|
|||||||
|
|
||||||
// Locale strings. TODO: maybe move these out to their own file.
|
// Locale strings. TODO: maybe move these out to their own file.
|
||||||
LOCALES = {
|
LOCALES = {
|
||||||
"en": {
|
en: {
|
||||||
// Leave empty, use the keys provided (in English) by default
|
// Leave empty, use the keys provided (in English) by default
|
||||||
},
|
},
|
||||||
"ja": {
|
ja: {
|
||||||
"Address:": "アドレス:",
|
"Address:": "アドレス:",
|
||||||
"Secret:": "シード:",
|
"Secret:": "シード:",
|
||||||
"Balance:": "残高:",
|
"Balance:": "残高:",
|
||||||
"Complete all previous steps first": "前の手順をすべて完了して下さい",
|
"Complete all previous steps first": "前の手順をすべて完了して下さい",
|
||||||
"Conection to the XRP Ledger required": "XRP Ledgerの接続が必要です",
|
"Conection to the XRP Ledger required": "XRP Ledgerの接続が必要です",
|
||||||
"Error:": "エラー:",
|
"Error:": "エラー:",
|
||||||
"Populated this page's examples with these credentials.": "このページの例にこのアドレスとシードを入力しました。",
|
"Populated this page's examples with these credentials.":
|
||||||
"There was an error connecting to the Faucet. Please try again.": "テストネットワークFaucetにエラーが発生しました。もう一度試してください。",
|
"このページの例にこのアドレスとシードを入力しました。",
|
||||||
|
"There was an error connecting to the Faucet. Please try again.":
|
||||||
|
"テストネットワークFaucetにエラーが発生しました。もう一度試してください。",
|
||||||
"Connecting...": "接続しています...",
|
"Connecting...": "接続しています...",
|
||||||
"Connection required": "接続が必要です",
|
"Connection required": "接続が必要です",
|
||||||
"Connected": "接続されました",
|
Connected: "接続されました",
|
||||||
"Faucet returned an error:": "テストネットワークFaucetがこのエラーを返しました:",
|
"Faucet returned an error:":
|
||||||
"Validated": "検証済み",
|
"テストネットワークFaucetがこのエラーを返しました:",
|
||||||
|
Validated: "検証済み",
|
||||||
"Final Result:": "確定結果:",
|
"Final Result:": "確定結果:",
|
||||||
"(Still pending...)": "(まだ未決…)",
|
"(Still pending...)": "(まだ未決…)",
|
||||||
"(None)": "(無)",
|
"(None)": "(無)",
|
||||||
"Prepared transaction:": "準備済みトランザクション:",
|
"Prepared transaction:": "準備済みトランザクション:",
|
||||||
"Failed to achieve consensus (final)": "検証済みレジャーには含まれません(決定結果)",
|
"Failed to achieve consensus (final)":
|
||||||
|
"検証済みレジャーには含まれません(決定結果)",
|
||||||
"Preliminary result:": "予備結果:",
|
"Preliminary result:": "予備結果:",
|
||||||
"Unknown": "不明",
|
Unknown: "不明",
|
||||||
"Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.": "有効なアドレスかシードの取得出来ませんでした。前の手順が完了しましたことを確認して下さい。",
|
"Couldn't get a valid address/secret value. Check that the previous steps were completed successfully.":
|
||||||
"Transaction hash:": "トランザクションハッシュ:"
|
"有効なアドレスかシードの取得出来ませんでした。前の手順が完了しましたことを確認して下さい。",
|
||||||
}
|
"Transaction hash:": "トランザクションハッシュ:",
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick-n-dirty localization function. TODO: migrate to a real localization
|
* 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
|
* @return {String} The translated string, if one is available, or the provided
|
||||||
* key value if no translation is available.
|
* key value if no translation is available.
|
||||||
*/
|
*/
|
||||||
const current_locale = $("html").prop("lang")
|
const current_locale = $("html").prop("lang");
|
||||||
function tl(key) {
|
function tl(key) {
|
||||||
let mesg = LOCALES[current_locale][key]
|
let mesg = LOCALES[current_locale][key];
|
||||||
if (typeof mesg === "undefined") {
|
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.
|
* and with most non-alphanumeric characters removed.
|
||||||
*/
|
*/
|
||||||
function slugify(s) {
|
function slugify(s) {
|
||||||
const unacceptable_chars = /[^A-Za-z0-9._ ]+/g
|
const unacceptable_chars = /[^A-Za-z0-9._ ]+/g;
|
||||||
const whitespace_regex = /\s+/g
|
const whitespace_regex = /\s+/g;
|
||||||
s = s.replace(unacceptable_chars, "")
|
s = s.replace(unacceptable_chars, "");
|
||||||
s = s.replace(whitespace_regex, "_")
|
s = s.replace(whitespace_regex, "_");
|
||||||
s = s.toLowerCase()
|
s = s.toLowerCase();
|
||||||
if (!s) {
|
if (!s) {
|
||||||
s = "_"
|
s = "_";
|
||||||
}
|
}
|
||||||
return s
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a given step has been marked completed already.
|
* Check whether a given step has been marked completed already.
|
||||||
* @param {String} step_name The exact name of the step, as defined in the
|
* @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.
|
* @return {Boolean} Whether or not this step has been marked complete.
|
||||||
*/
|
*/
|
||||||
function is_complete(step_name) {
|
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.
|
* @return {Boolean} Whether or not this step has been marked complete.
|
||||||
*/
|
*/
|
||||||
function is_complete_by_id(step_id) {
|
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.
|
* start_step(step_name) function in the MD file.
|
||||||
*/
|
*/
|
||||||
function complete_step(step_name) {
|
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.
|
* @param {String} step_id The slugified name of the step.
|
||||||
*/
|
*/
|
||||||
function complete_step_by_id(step_id) {
|
function complete_step_by_id(step_id) {
|
||||||
$(".bc-"+step_id).removeClass("active").addClass("done")
|
$(".bc-" + step_id)
|
||||||
$(".bc-"+step_id).next().removeClass("disabled").addClass("active")
|
.removeClass("active")
|
||||||
|
.addClass("done");
|
||||||
|
$(".bc-" + step_id)
|
||||||
|
.next()
|
||||||
|
.removeClass("disabled")
|
||||||
|
.addClass("active");
|
||||||
|
|
||||||
// Enable follow-up steps that require this step to be done first
|
// Enable follow-up steps that require this step to be done first
|
||||||
const next_ui = $(`#interactive-${step_id}`).nextAll(
|
const next_ui = $(`#interactive-${step_id}`)
|
||||||
".interactive-block").eq(0).find(".previous-steps-required")
|
.nextAll(".interactive-block")
|
||||||
next_ui.prop("title", "")
|
.eq(0)
|
||||||
next_ui.prop("disabled", false)
|
.find(".previous-steps-required");
|
||||||
next_ui.removeClass("disabled")
|
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) {
|
function get_block_id(jEl) {
|
||||||
// Traverse up, then slice "interactive-" off the block's HTML ID
|
// 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) {
|
function pretty_print(j) {
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(JSON.parse(j),null,2)
|
return JSON.stringify(JSON.parse(j), null, 2);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// probably already decoded JSON
|
// 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.
|
* them an appropriate tooltip message.
|
||||||
*/
|
*/
|
||||||
function disable_followup_steps() {
|
function disable_followup_steps() {
|
||||||
$(".previous-steps-required").prop("title", tl("Complete all previous steps first"))
|
$(".previous-steps-required").prop(
|
||||||
$(".previous-steps-required").prop("disabled", true).addClass("disabled")
|
"title",
|
||||||
$(".connection-required").prop("title", tl("Conection to the XRP Ledger required"))
|
tl("Complete all previous steps first")
|
||||||
$(".connection-required").prop("disabled", true).addClass("disabled")
|
);
|
||||||
|
$(".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) {
|
function show_error(block, message) {
|
||||||
block.find(".output-area").html(
|
block.find(".output-area").html(
|
||||||
`<p class="devportal-callout warning"><strong>${tl("Error:")}</strong>
|
`<p class="devportal-callout warning"><strong>${tl("Error:")}</strong>
|
||||||
${message}</p>`)
|
${message}</p>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -174,52 +192,57 @@ function show_error(block, message) {
|
|||||||
* use the generated credentials instead of the placeholder EXAMPLE_ADDR and
|
* use the generated credentials instead of the placeholder EXAMPLE_ADDR and
|
||||||
* EXAMPLE_SECRET.
|
* EXAMPLE_SECRET.
|
||||||
*/
|
*/
|
||||||
const EXAMPLE_ADDR = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
|
const EXAMPLE_ADDR = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe";
|
||||||
const EXAMPLE_SECRET = "s████████████████████████████"
|
const EXAMPLE_SECRET = "s████████████████████████████";
|
||||||
function setup_generate_step() {
|
function setup_generate_step() {
|
||||||
|
|
||||||
$("#generate-creds-button").click(async (event) => {
|
$("#generate-creds-button").click(async (event) => {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
block.find(".output-area").html("")
|
block.find(".output-area").html("");
|
||||||
block.find(".loader").show()
|
block.find(".loader").show();
|
||||||
// Get faucet URL (Testnet/Devnet/etc.)
|
// Get faucet URL (Testnet/Devnet/etc.)
|
||||||
const faucet_url = $("#generate-creds-button").data("fauceturl")
|
const faucet_url = $("#generate-creds-button").data("fauceturl");
|
||||||
|
|
||||||
try {
|
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(`<div><strong>${tl("Address:")}</strong>
|
block.find(".output-area").html(`<div><strong>${tl("Address:")}</strong>
|
||||||
<span id="use-address">${data.account.address}</span></div>
|
<span id="use-address">${data.account.address}</span></div>
|
||||||
<div><strong>${tl("Secret:")}</strong>
|
<div><strong>${tl("Secret:")}</strong>
|
||||||
<span id="use-secret">${data.account.secret}</span></div>
|
<span id="use-secret">${data.account.secret}</span></div>
|
||||||
<strong>${tl("Balance:")}</strong>
|
<strong>${tl("Balance:")}</strong>
|
||||||
${Number(data.balance).toLocaleString(current_locale)} XRP`)
|
${Number(data.balance).toLocaleString(current_locale)} XRP`);
|
||||||
|
|
||||||
// Automatically populate all examples in the page with the
|
// Automatically populate all examples in the page with the
|
||||||
// generated credentials...
|
// generated credentials...
|
||||||
$("code span:contains('" + EXAMPLE_ADDR + "')").each(function () {
|
$("code span:contains('" + EXAMPLE_ADDR + "')").each(function () {
|
||||||
let eltext = $(this).text()
|
let eltext = $(this).text();
|
||||||
$(this).text( eltext.replace(EXAMPLE_ADDR, data.account.address) )
|
$(this).text(eltext.replace(EXAMPLE_ADDR, data.account.address));
|
||||||
})
|
});
|
||||||
$("code span:contains('" + EXAMPLE_SECRET + "')").each(function () {
|
$("code span:contains('" + EXAMPLE_SECRET + "')").each(function () {
|
||||||
let eltext = $(this).text()
|
let eltext = $(this).text();
|
||||||
$(this).text( eltext.replace(EXAMPLE_SECRET, data.account.secret) )
|
$(this).text(eltext.replace(EXAMPLE_SECRET, data.account.secret));
|
||||||
})
|
});
|
||||||
|
|
||||||
block.find(".output-area").append(`<p>${tl("Populated this page's examples with these credentials.")}</p>`)
|
block
|
||||||
|
.find(".output-area")
|
||||||
complete_step("Generate")
|
.append(
|
||||||
|
`<p>${tl(
|
||||||
|
"Populated this page's examples with these credentials."
|
||||||
|
)}</p>`
|
||||||
|
);
|
||||||
|
|
||||||
|
complete_step("Generate");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
block.find(".loader").hide()
|
block.find(".loader").hide();
|
||||||
block.find(".output-area").html(
|
block.find(".output-area").html(
|
||||||
`<p class="devportal-callout warning"><strong>${tl("Error:")}</strong>
|
`<p class="devportal-callout warning"><strong>${tl("Error:")}</strong>
|
||||||
${tl("There was an error connecting to the Faucet. Please try again.")}
|
${tl("There was an error connecting to the Faucet. Please try again.")}
|
||||||
</p>`)
|
</p>`
|
||||||
return
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,13 +252,20 @@ function setup_generate_step() {
|
|||||||
* @return {String, undefined} The address, if available, or undefined if not
|
* @return {String, undefined} The address, if available, or undefined if not
|
||||||
*/
|
*/
|
||||||
function get_address(event) {
|
function get_address(event) {
|
||||||
const address = $("#use-address").text()
|
const address = $("#use-address").text();
|
||||||
if (!address) {
|
if (!address) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
if (!block.length) {return}
|
if (!block.length) {
|
||||||
show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully."))
|
return;
|
||||||
}
|
}
|
||||||
return address
|
show_error(
|
||||||
|
block,
|
||||||
|
tl(
|
||||||
|
"Couldn't get a valid address/secret value. Check that the previous steps were completed successfully."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,19 +275,33 @@ function get_address(event) {
|
|||||||
* @return {Wallet, undefined} The Wallet instance, if available, or undefined if not
|
* @return {Wallet, undefined} The Wallet instance, if available, or undefined if not
|
||||||
*/
|
*/
|
||||||
function get_wallet(event) {
|
function get_wallet(event) {
|
||||||
const secret = $("#use-secret").text()
|
const secret = $("#use-secret").text();
|
||||||
if (!secret) {
|
if (!secret) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
if (!block.length) {return}
|
if (!block.length) {
|
||||||
show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully."))
|
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) {
|
if (secret == EXAMPLE_SECRET) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
if (!block.length) {return}
|
if (!block.length) {
|
||||||
show_error(block, tl("Can't use the example secret here. Check that the previous steps were completed successfully."))
|
return;
|
||||||
return
|
|
||||||
}
|
}
|
||||||
return xrpl.Wallet.fromSeed(secret)
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,30 +309,42 @@ function get_wallet(event) {
|
|||||||
* @param {String} faucet_url The URL of the faucet to call, for example:
|
* @param {String} faucet_url The URL of the faucet to call, for example:
|
||||||
* https://faucet.altnet.rippletest.net/accounts
|
* 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
|
// 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") {
|
if (typeof destination != "undefined") {
|
||||||
body["destination"] = destination
|
body["destination"] = destination;
|
||||||
}
|
}
|
||||||
body["memos"] = [
|
body["memos"] = [memo];
|
||||||
{
|
|
||||||
data: "xrpl.org-tutorials",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const response = await fetch(faucet_url, {
|
const response = await fetch(faucet_url, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json; charset=utf-8"
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body),
|
||||||
})
|
});
|
||||||
const data = await response.json()
|
const data = await response.json();
|
||||||
|
|
||||||
if (!response.ok) {
|
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 || [];
|
window.after_connect = window.after_connect || [];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be used with _snippets/interactive-tutorials/connect-step.md
|
* To be used with _snippets/interactive-tutorials/connect-step.md
|
||||||
* Adds an event to the "Connect" button to connect to the appropriate
|
* 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() {
|
function setup_connect_step() {
|
||||||
if (!$("#connect-button").length) {
|
if (!$("#connect-button").length) {
|
||||||
console.debug("Connect step not included. Skipping related setup.")
|
console.debug("Connect step not included. Skipping related setup.");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const ws_url = $("#connect-button").data("wsurl")
|
const ws_url = $("#connect-button").data("wsurl");
|
||||||
if (!ws_url) {
|
if (!ws_url) {
|
||||||
console.error("Interactive Tutorial: WS URL not found. Did you set use_network?")
|
console.error(
|
||||||
return
|
"Interactive Tutorial: WS URL not found. Did you set use_network?"
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
api = new xrpl.Client(ws_url)
|
api = new xrpl.Client(ws_url);
|
||||||
api.on('connected', async function() {
|
api.on("connected", async function () {
|
||||||
$("#connection-status").text(tl("Connected"))
|
$("#connection-status").text(tl("Connected"));
|
||||||
$("#connect-button").prop("disabled", true)
|
$("#connect-button").prop("disabled", true);
|
||||||
$("#loader-connect").hide()
|
$("#loader-connect").hide();
|
||||||
$(".connection-required").prop("disabled", false)
|
$(".connection-required").prop("disabled", false);
|
||||||
$(".connection-required").prop("title", "")
|
$(".connection-required").prop("title", "");
|
||||||
|
|
||||||
// Subscribe to ledger close events
|
// Subscribe to ledger close events
|
||||||
api.request({command: "subscribe", streams: ["ledger"]})
|
api.request({ command: "subscribe", streams: ["ledger"] });
|
||||||
|
|
||||||
complete_step("Connect")
|
complete_step("Connect");
|
||||||
})
|
});
|
||||||
api.on('disconnected', (code) => {
|
api.on("disconnected", (code) => {
|
||||||
$("#connection-status").text( tl("Disconnected") +" ("+code+")" )
|
$("#connection-status").text(tl("Disconnected") + " (" + code + ")");
|
||||||
$("#connect-button").prop("disabled", false)
|
$("#connect-button").prop("disabled", false);
|
||||||
$(".connection-required").prop("disabled", true)
|
$(".connection-required").prop("disabled", true);
|
||||||
$(".connection-required").prop("title", tl("Connection required"))
|
$(".connection-required").prop("title", tl("Connection required"));
|
||||||
|
|
||||||
disable_followup_steps()
|
disable_followup_steps();
|
||||||
})
|
});
|
||||||
$("#connect-button").click(async (event) => {
|
$("#connect-button").click(async (event) => {
|
||||||
$("#connection-status").text( tl("Connecting...") )
|
$("#connection-status").text(tl("Connecting..."));
|
||||||
$("#loader-connect").show()
|
$("#loader-connect").show();
|
||||||
await api.connect()
|
await api.connect();
|
||||||
|
|
||||||
for (const fn of after_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.
|
* Requires xrpl.js to be loaded and instantiated as "api" first.
|
||||||
*/
|
*/
|
||||||
function setup_wait_steps() {
|
function setup_wait_steps() {
|
||||||
const wait_steps = $(".wait-step")
|
const wait_steps = $(".wait-step");
|
||||||
|
|
||||||
wait_steps.each(async (i, el) => {
|
wait_steps.each(async (i, el) => {
|
||||||
const wait_step = $(el)
|
const wait_step = $(el);
|
||||||
const explorer_url = wait_step.data("explorerurl")
|
const explorer_url = wait_step.data("explorerurl");
|
||||||
const status_box = wait_step.find(".tx-validation-status")
|
const status_box = wait_step.find(".tx-validation-status");
|
||||||
api.on('ledgerClosed', async (ledger) => {
|
api.on("ledgerClosed", async (ledger) => {
|
||||||
// Update the latest validated ledger index in this step's table
|
// 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")) {
|
if (!status_box.data("status_pending")) {
|
||||||
// Before submission or after a final result.
|
// Before submission or after a final result.
|
||||||
// Either way, nothing more to do here.
|
// Either way, nothing more to do here.
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transaction = wait_step.find(".waiting-for-tx").text().trim()
|
const transaction = wait_step.find(".waiting-for-tx").text().trim();
|
||||||
const min_ledger = parseInt(wait_step.find(".earliest-ledger-version").text())
|
const min_ledger = parseInt(
|
||||||
const max_ledger = parseInt(wait_step.find(".lastledgersequence").text())
|
wait_step.find(".earliest-ledger-version").text()
|
||||||
let tx_response
|
);
|
||||||
|
const max_ledger = parseInt(wait_step.find(".lastledgersequence").text());
|
||||||
|
let tx_response;
|
||||||
try {
|
try {
|
||||||
tx_response = await api.request({
|
tx_response = await api.request({
|
||||||
command: "tx",
|
command: "tx",
|
||||||
transaction,
|
transaction,
|
||||||
min_ledger,
|
min_ledger,
|
||||||
max_ledger
|
max_ledger,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (tx_response.result.validated) {
|
if (tx_response.result.validated) {
|
||||||
status_box.html(
|
status_box.html(
|
||||||
`<th>${tl("Final Result:")}</th><td>${tx_response.result.meta.TransactionResult}
|
`<th>${tl("Final Result:")}</th><td>${
|
||||||
|
tx_response.result.meta.TransactionResult
|
||||||
|
}
|
||||||
(<a href="${explorer_url}/transactions/${transaction}"
|
(<a href="${explorer_url}/transactions/${transaction}"
|
||||||
target="_blank">${tl("Validated")}</a>)</td>`)
|
target="_blank">${tl("Validated")}</a>)</td>`
|
||||||
|
);
|
||||||
|
|
||||||
const step_id = get_block_id(wait_step)
|
const step_id = get_block_id(wait_step);
|
||||||
if (!is_complete_by_id(step_id)) {
|
if (!is_complete_by_id(step_id)) {
|
||||||
status_box.data("status_pending", false)
|
status_box.data("status_pending", false);
|
||||||
complete_step_by_id(step_id)
|
complete_step_by_id(step_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
status_box.html(
|
status_box.html(
|
||||||
`<th>${tl("Final Result:")}</th>
|
`<th>${tl("Final Result:")}</th>
|
||||||
<td><img class="throbber" src="assets/img/xrp-loader-96.png">
|
<td><img class="throbber" src="assets/img/xrp-loader-96.png">
|
||||||
${tl("(Still pending...)")}</td>`)
|
${tl("(Still pending...)")}</td>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.data.error == "txnNotFound" && e.data.searched_all) {
|
if (e.data.error == "txnNotFound" && e.data.searched_all) {
|
||||||
status_box.html(
|
status_box.html(
|
||||||
`<th>${tl("Final Result:")}</th><td>${tl("Failed to achieve consensus (final)")}</td>`)
|
`<th>${tl("Final Result:")}</th><td>${tl(
|
||||||
|
"Failed to achieve consensus (final)"
|
||||||
|
)}</td>`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
status_box.html(
|
status_box.html(
|
||||||
`<th>${tl("Final Result:")}</th><td>${tl("Unknown")}</td>`)
|
`<th>${tl("Final Result:")}</th><td>${tl("Unknown")}</td>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) // end 'ledger' event handler
|
}); // end 'ledger' event handler
|
||||||
}) // end "each" wait_step
|
}); // end "each" wait_step
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -430,26 +496,25 @@ function setup_wait_steps() {
|
|||||||
* transaction blob via api.request({command: "submit", opts})
|
* transaction blob via api.request({command: "submit", opts})
|
||||||
*/
|
*/
|
||||||
async function activate_wait_step(step_name, prelim) {
|
async function activate_wait_step(step_name, prelim) {
|
||||||
const step_id = slugify(step_name)
|
const step_id = slugify(step_name);
|
||||||
const wait_step = $(`#interactive-${step_id} .wait-step`)
|
const wait_step = $(`#interactive-${step_id} .wait-step`);
|
||||||
const status_box = wait_step.find(".tx-validation-status")
|
const status_box = wait_step.find(".tx-validation-status");
|
||||||
const tx_id = prelim.result.tx_json.hash
|
const tx_id = prelim.result.tx_json.hash;
|
||||||
const lls = prelim.result.tx_json.LastLedgerSequence || tl("(None)")
|
const lls = prelim.result.tx_json.LastLedgerSequence || tl("(None)");
|
||||||
|
|
||||||
if (wait_step.find(".waiting-for-tx").text() == tx_id) {
|
if (wait_step.find(".waiting-for-tx").text() == tx_id) {
|
||||||
// Re-submitting the same transaction? Don't update min_ledger.
|
// Re-submitting the same transaction? Don't update min_ledger.
|
||||||
} else {
|
} else {
|
||||||
wait_step.find(".waiting-for-tx").text(tx_id)
|
wait_step.find(".waiting-for-tx").text(tx_id);
|
||||||
wait_step.find(".earliest-ledger-version").text(
|
wait_step
|
||||||
prelim.result.validated_ledger_index
|
.find(".earliest-ledger-version")
|
||||||
)
|
.text(prelim.result.validated_ledger_index);
|
||||||
}
|
}
|
||||||
wait_step.find(".lastledgersequence").text(lls)
|
wait_step.find(".lastledgersequence").text(lls);
|
||||||
status_box.html("")
|
status_box.html("");
|
||||||
status_box.data("status_pending", true)
|
status_box.data("status_pending", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the hexadecimal ASCII representation of a string (must contain only
|
* Get the hexadecimal ASCII representation of a string (must contain only
|
||||||
* 7-bit ASCII characters).
|
* 7-bit ASCII characters).
|
||||||
@@ -457,11 +522,11 @@ async function activate_wait_step(step_name, prelim) {
|
|||||||
* @return {String} The uppercase hexadecimal representation of the string.
|
* @return {String} The uppercase hexadecimal representation of the string.
|
||||||
*/
|
*/
|
||||||
function text_to_hex(s) {
|
function text_to_hex(s) {
|
||||||
result = ""
|
result = "";
|
||||||
for (let i = 0; i < s.length; i++) {
|
for (let i = 0; i < s.length; i++) {
|
||||||
result += s.charCodeAt(i).toString(16)
|
result += s.charCodeAt(i).toString(16);
|
||||||
}
|
}
|
||||||
return result.toUpperCase()
|
return result.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -475,29 +540,30 @@ function text_to_hex(s) {
|
|||||||
* added to them (in-place).
|
* added to them (in-place).
|
||||||
*/
|
*/
|
||||||
function add_memo(event, tx_json) {
|
function add_memo(event, tx_json) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
|
|
||||||
const tutorial_info = {
|
const tutorial_info = {
|
||||||
"path": window.location.pathname,
|
path: window.location.pathname,
|
||||||
"button": event.target.id,
|
button: event.target.id,
|
||||||
"step": block.data("stepnumber"),
|
step: block.data("stepnumber"),
|
||||||
"totalsteps": block.data("totalsteps")
|
totalsteps: block.data("totalsteps"),
|
||||||
}
|
};
|
||||||
|
|
||||||
const memo = {
|
const memo = {
|
||||||
"Memo": {
|
Memo: {
|
||||||
"MemoData": text_to_hex(JSON.stringify(tutorial_info, null, 0)),
|
MemoData: text_to_hex(JSON.stringify(tutorial_info, null, 0)),
|
||||||
"MemoFormat": "6170706C69636174696F6E2F6A736F6E", // application/json
|
MemoFormat: "6170706C69636174696F6E2F6A736F6E", // application/json
|
||||||
// The MemoType decodes to a URL that explains the format of this memo type:
|
// 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
|
// https://github.com/XRPLF/xrpl-dev-portal/blob/master/tool/INTERACTIVE_TUTORIALS_README.md
|
||||||
"MemoType": "68747470733A2F2F6769746875622E636F6D2F5852504C462F7872706C2D6465762D706F7274616C2F626C6F622F6D61737465722F746F6F6C2F494E5445524143544956455F5455544F5249414C535F524541444D452E6D64"
|
MemoType:
|
||||||
}
|
"68747470733A2F2F6769746875622E636F6D2F5852504C462F7872706C2D6465762D706F7274616C2F626C6F622F6D61737465722F746F6F6C2F494E5445524143544956455F5455544F5249414C535F524541444D452E6D64",
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (tx_json.Memos === undefined) {
|
if (tx_json.Memos === undefined) {
|
||||||
tx_json["Memos"] = [memo]
|
tx_json["Memos"] = [memo];
|
||||||
} else {
|
} else {
|
||||||
tx_json["Memos"].push(memo)
|
tx_json["Memos"].push(memo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,29 +588,35 @@ function add_memo(event, tx_json) {
|
|||||||
* which was probably added by a "Get Credentials" step.
|
* which was probably added by a "Get Credentials" step.
|
||||||
*/
|
*/
|
||||||
async function generic_full_send(event, tx_json, wallet) {
|
async function generic_full_send(event, tx_json, wallet) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
const blob_selector = $(event.target).data("txBlobFrom")
|
const blob_selector = $(event.target).data("txBlobFrom");
|
||||||
const wait_step_name = $(event.target).data("waitStepName")
|
const wait_step_name = $(event.target).data("waitStepName");
|
||||||
block.find(".output-area").html("")
|
block.find(".output-area").html("");
|
||||||
|
|
||||||
if (wallet === undefined) {
|
if (wallet === undefined) {
|
||||||
wallet = get_wallet(event)
|
wallet = get_wallet(event);
|
||||||
|
}
|
||||||
|
if (!wallet) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!wallet) {return}
|
|
||||||
|
|
||||||
add_memo(event, tx_json)
|
add_memo(event, tx_json);
|
||||||
|
|
||||||
block.find(".loader").show()
|
block.find(".loader").show();
|
||||||
const prepared = await api.autofill(tx_json)
|
const prepared = await api.autofill(tx_json);
|
||||||
block.find(".output-area").append(
|
block.find(".output-area").append(
|
||||||
`<p>${tl("Prepared transaction:")}</p>
|
`<p>${tl("Prepared transaction:")}</p>
|
||||||
<pre><code>${pretty_print(prepared)}</code></pre>`)
|
<pre><code>${pretty_print(prepared)}</code></pre>`
|
||||||
|
);
|
||||||
|
|
||||||
const {tx_blob, hash} = wallet.sign(prepared)
|
const { tx_blob, hash } = wallet.sign(prepared);
|
||||||
block.find(".output-area").append(
|
block
|
||||||
`<p>${tl("Transaction hash:")} <code id="tx_id">${hash}</code></p>`)
|
.find(".output-area")
|
||||||
|
.append(
|
||||||
|
`<p>${tl("Transaction hash:")} <code id="tx_id">${hash}</code></p>`
|
||||||
|
);
|
||||||
|
|
||||||
await do_submit(block, {"tx_blob": tx_blob}, wait_step_name)
|
await do_submit(block, { tx_blob: tx_blob }, wait_step_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -561,11 +633,11 @@ async function generic_full_send(event, tx_json, wallet) {
|
|||||||
* click event handler.
|
* click event handler.
|
||||||
*/
|
*/
|
||||||
async function submit_handler(event) {
|
async function submit_handler(event) {
|
||||||
const block = $(event.target).closest(".interactive-block")
|
const block = $(event.target).closest(".interactive-block");
|
||||||
const blob_selector = $(event.target).data("txBlobFrom")
|
const blob_selector = $(event.target).data("txBlobFrom");
|
||||||
const wait_step_name = $(event.target).data("waitStepName")
|
const wait_step_name = $(event.target).data("waitStepName");
|
||||||
const tx_blob = $(blob_selector).text()
|
const tx_blob = $(blob_selector).text();
|
||||||
do_submit(block, {tx_blob}, wait_step_name)
|
do_submit(block, { tx_blob }, wait_step_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -579,30 +651,31 @@ async function submit_handler(event) {
|
|||||||
* _snippets/interactive-tutorials/wait-step.md
|
* _snippets/interactive-tutorials/wait-step.md
|
||||||
*/
|
*/
|
||||||
async function do_submit(block, submit_opts, wait_step_name) {
|
async function do_submit(block, submit_opts, wait_step_name) {
|
||||||
block.find(".loader").show()
|
block.find(".loader").show();
|
||||||
try {
|
try {
|
||||||
submit_opts["command"] = "submit"
|
submit_opts["command"] = "submit";
|
||||||
const prelim_result = await api.request(submit_opts)
|
const prelim_result = await api.request(submit_opts);
|
||||||
block.find(".output-area").append(
|
block.find(".output-area").append(
|
||||||
`<p>${tl("Preliminary result:")}</p>
|
`<p>${tl("Preliminary result:")}</p>
|
||||||
<pre><code>${pretty_print(prelim_result)}</code></pre>`)
|
<pre><code>${pretty_print(prelim_result)}</code></pre>`
|
||||||
|
);
|
||||||
|
|
||||||
block.find(".loader").hide()
|
block.find(".loader").hide();
|
||||||
submit_step_id = get_block_id(block)
|
submit_step_id = get_block_id(block);
|
||||||
complete_step_by_id(submit_step_id)
|
complete_step_by_id(submit_step_id);
|
||||||
if (wait_step_name) {
|
if (wait_step_name) {
|
||||||
activate_wait_step(wait_step_name, prelim_result)
|
activate_wait_step(wait_step_name, prelim_result);
|
||||||
}
|
}
|
||||||
return prelim_result
|
return prelim_result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
block.find(".loader").hide()
|
block.find(".loader").hide();
|
||||||
show_error(block, error)
|
show_error(block, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
disable_followup_steps()
|
disable_followup_steps();
|
||||||
setup_generate_step()
|
setup_generate_step();
|
||||||
setup_connect_step()
|
setup_connect_step();
|
||||||
setup_wait_steps()
|
setup_wait_steps();
|
||||||
})
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user