Love is All. Love U Planet.
This commit is contained in:
parent
2625f571d2
commit
cd6e36e57d
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>G1 to IPNS Public Key Conversion</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>G1 to IPNS Public Key Conversion</h1>
|
||||
<input type="text" id="g1PublicKey" size=40 placeholder="Enter G1 Public Key">
|
||||
<button onclick="convertPublicKey()">Convert</button>
|
||||
<p>IPNS Public Key:</p>
|
||||
<p id="ipnsPublicKey"></p>
|
||||
|
||||
<!-- Include the tweetnacl library for Ed25519 operations -->
|
||||
<script src="./nacl.min.js"></script>
|
||||
|
||||
<script>
|
||||
// Custom Base58 encoding and decoding functions
|
||||
function base58Encode(bytes) {
|
||||
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const base = BigInt(ALPHABET.length);
|
||||
let encoded = '';
|
||||
|
||||
let value = BigInt('0');
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
value = value * BigInt(256) + BigInt(bytes[i]);
|
||||
}
|
||||
|
||||
while (value > BigInt(0)) {
|
||||
const remainder = value % base;
|
||||
value = value / base;
|
||||
encoded = ALPHABET[Number(remainder)] + encoded;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function base58Decode(encoded) {
|
||||
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const base = BigInt(ALPHABET.length);
|
||||
|
||||
let value = BigInt('0');
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
const char = encoded[i];
|
||||
const charValue = BigInt(ALPHABET.indexOf(char));
|
||||
value = value * base + charValue;
|
||||
}
|
||||
|
||||
const valueBytes = [];
|
||||
while (value > BigInt(0)) {
|
||||
const byteValue = Number(value % BigInt(256));
|
||||
value = value / BigInt(256);
|
||||
valueBytes.unshift(byteValue);
|
||||
}
|
||||
|
||||
return new Uint8Array(valueBytes);
|
||||
}
|
||||
|
||||
function convertPublicKey() {
|
||||
const g1PublicKey = document.getElementById("g1PublicKey").value;
|
||||
|
||||
// Decode the Base58 encoded G1 public key using custom function
|
||||
const decodedShared = base58Decode(g1PublicKey);
|
||||
|
||||
// Create a new Uint8Array to hold the IPNS public key bytes
|
||||
const ipnsPublicKeyBytes = new Uint8Array(decodedShared.length + 6);
|
||||
|
||||
// Prefix bytes for IPNS public key
|
||||
const prefixBytes = new Uint8Array([0, 36, 8, 1, 18, 32]);
|
||||
|
||||
// Copy the prefix bytes and G1 public key bytes into the new array
|
||||
ipnsPublicKeyBytes.set(prefixBytes, 0);
|
||||
ipnsPublicKeyBytes.set(decodedShared, prefixBytes.length);
|
||||
|
||||
// Encode the IPNS public key using custom Base58 encoding
|
||||
const ipnsPublicKey = '1' + base58Encode(ipnsPublicKeyBytes); // Prepend '1'
|
||||
|
||||
document.getElementById("ipnsPublicKey").innerText = ipnsPublicKey;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -17,5 +17,8 @@ You receive an access to this crypto landing recorded into IPFS
|
|||
Get Friends.
|
||||
Explore Trust.
|
||||
|
||||
https://ipfs.copylaradio.com/ipfs/QmbVVpUUBEkp35oMKUmdYyw4SWEQsFeJnxW5EPa5tAyfQn/
|
||||
For LOCALHOST : http://ipfs.localhost:8080/ipfs/Qme4r4fHf6VfaqoXobTnzjWVwBRhRPzMazJyrZ9ubbdJMP/ (PROD)
|
||||
For WAN : https://ipfs.copylaradio.com/ipfs/QmZjtNz7iNgPiULbyT2Zyi6QHGXt8WqKo1DzKkM5EKPLfL/ (DEMO ONLY)
|
||||
|
||||
|
||||
Enjoy the visit.
|
||||
|
|
|
@ -77,7 +77,9 @@
|
|||
|
||||
// Provide the latitude for adjustment
|
||||
const latitude = centerLat;
|
||||
const zoomLevel = calculateZoomLevel(desiredImageWidthInKm, latitude);
|
||||
const zoomLevel = calculateZoomLevel(desiredImageWidthInKm, latitude);
|
||||
// const zoomLevel = calculateZoomLevel(desiredImageWidthInKm, latitude) + 2;
|
||||
|
||||
console.log('Recommended zoom level:', zoomLevel);
|
||||
|
||||
const map = L.map('map', {
|
||||
|
@ -98,6 +100,7 @@
|
|||
//~ maxZoom: 18
|
||||
//~ }).addTo(map);
|
||||
|
||||
|
||||
const tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: `U Planet | Astroport | ${southWestLat} : ${southWestLon}`
|
||||
}).addTo(map);
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>G1 to IPNS Public Key Conversion</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>G1 to IPNS Public Key Conversion</h1>
|
||||
<input type="text" id="g1PublicKey" size=40 placeholder="Enter G1 Public Key">
|
||||
<button onclick="convertPublicKey()">Convert</button>
|
||||
<p>IPNS Public Key:</p>
|
||||
<p id="ipnsPublicKey"></p>
|
||||
<br>
|
||||
<h1>Generate Seed from Salt and Password</h1>
|
||||
<p>Generate Seed:</p>
|
||||
<form id="generateSeedForm">
|
||||
<label for="salt">Salt:</label>
|
||||
<input type="text" id="salt" name="salt" required>
|
||||
<br>
|
||||
<label for="password">Password:</label>
|
||||
<input type="text" id="password" name="password" required>
|
||||
<br>
|
||||
<button type="submit">Generate Seed</button>
|
||||
</form>
|
||||
<p>Generated Seed:</p>
|
||||
<p id="generatedSeed"></p>
|
||||
|
||||
<!-- Include the tweetnacl library for Ed25519 operations -->
|
||||
<script src="./nacl.min.js"></script>
|
||||
|
||||
<!-- Include the scrypt-async library for scrypt key derivation -->
|
||||
<script src="./scrypt-async.min.js"></script>
|
||||
|
||||
<script>
|
||||
function generateSeed(salt, password, scryptParams) {
|
||||
return new Promise((resolve, reject) => {
|
||||
scrypt(
|
||||
password,
|
||||
salt,
|
||||
scryptParams.N,
|
||||
scryptParams.r,
|
||||
scryptParams.p,
|
||||
scryptParams.seedLength,
|
||||
function (seedArrayBuffer) {
|
||||
// Convert the ArrayBuffer to a Uint8Array
|
||||
const seedBytes = new Uint8Array(seedArrayBuffer);
|
||||
resolve(seedBytes);
|
||||
},
|
||||
function (error) {
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("generateSeedForm").addEventListener("submit", async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
const salt = document.getElementById("salt").value;
|
||||
const password = document.getElementById("password").value;
|
||||
|
||||
// Define the scrypt parameters
|
||||
const scryptParams = {
|
||||
N: 16384, // N parameter
|
||||
r: 8, // r parameter
|
||||
p: 8, // p parameter
|
||||
seedLength: 64 // Seed length in bytes
|
||||
};
|
||||
|
||||
try {
|
||||
// Call the generateSeed function with the defined parameters
|
||||
const seed = await generateSeed(salt, password, scryptParams);
|
||||
|
||||
// Now you can use the generated seed as needed
|
||||
document.getElementById("generatedSeed").innerText = Array.from(seed).map(byte => ('00' + byte.toString(16)).slice(-2)).join('');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("Error generating seed: " + error.message); // Display the error message
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
// Custom Base58 encoding and decoding functions
|
||||
function base58Encode(bytes) {
|
||||
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const base = BigInt(ALPHABET.length);
|
||||
let encoded = '';
|
||||
|
||||
let value = BigInt('0');
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
value = value * BigInt(256) + BigInt(bytes[i]);
|
||||
}
|
||||
|
||||
while (value > BigInt(0)) {
|
||||
const remainder = value % base;
|
||||
value = value / base;
|
||||
encoded = ALPHABET[Number(remainder)] + encoded;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
function base58Decode(encoded) {
|
||||
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||
const base = BigInt(ALPHABET.length);
|
||||
|
||||
let value = BigInt('0');
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
const char = encoded[i];
|
||||
const charValue = BigInt(ALPHABET.indexOf(char));
|
||||
value = value * base + charValue;
|
||||
}
|
||||
|
||||
const valueBytes = [];
|
||||
while (value > BigInt(0)) {
|
||||
const byteValue = Number(value % BigInt(256));
|
||||
value = value / BigInt(256);
|
||||
valueBytes.unshift(byteValue);
|
||||
}
|
||||
|
||||
return new Uint8Array(valueBytes);
|
||||
}
|
||||
|
||||
function convertPublicKey() {
|
||||
const g1PublicKey = document.getElementById("g1PublicKey").value;
|
||||
|
||||
// Decode the Base58 encoded G1 public key using custom function
|
||||
const decodedShared = base58Decode(g1PublicKey);
|
||||
|
||||
// Create a new Uint8Array to hold the IPNS public key bytes
|
||||
const ipnsPublicKeyBytes = new Uint8Array(decodedShared.length + 6);
|
||||
|
||||
// Prefix bytes for IPNS public key
|
||||
const prefixBytes = new Uint8Array([0, 36, 8, 1, 18, 32]);
|
||||
|
||||
// Copy the prefix bytes and G1 public key bytes into the new array
|
||||
ipnsPublicKeyBytes.set(prefixBytes, 0);
|
||||
ipnsPublicKeyBytes.set(decodedShared, prefixBytes.length);
|
||||
|
||||
// Encode the IPNS public key using custom Base58 encoding
|
||||
const ipnsPublicKey = '1' + base58Encode(ipnsPublicKeyBytes); // Prepend '1'
|
||||
|
||||
document.getElementById("ipnsPublicKey").innerText = ipnsPublicKey;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang=""><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>Ğ1Gate 🜶 Là où va la Ğ1</title>
|
||||
<meta name="description" content="Explorez la blockchain Ğ1 de façon visuelle pour découvrir où partent toutes ces Ğ1.">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="g1gate_fichiers/styles.css" title="G1Gate">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>Où va la Ğ1…</h1>
|
||||
|
||||
<p id="description">
|
||||
Explorez la blockchain Ğ1 pour savoir où va la Ğ1.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form method="get" action="#expenses" id="explore">
|
||||
<p>
|
||||
<label>
|
||||
Clef à explorer :<br>
|
||||
<input type="text" name="pubkey" size="50" value="EA7Dsw39ShZg4SpURsrgMaMqrweJPUFPYHwZA8e92e3D">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
Limite du nombre de transactions :
|
||||
<input type="number" name="txLimit" value="30" size="5">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" label="Explorer">
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<section id="expenses"><h2>Dépenses de <q>sasa vava</q></h2><article class="svg-container"><svg viewBox="0,0,1280,720" width="1280" height="720" style="max-width: 100%; height: auto; height: intrinsic;" font-family="sans-serif" font-size="10"><a xlink:href="#Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm" target="_self" transform="translate(0,0)"><rect fill="#FFC431" fill-opacity="1" width="1242" height="720"></rect><title>4 DU</title><clipPath id="O-378df07a9583b-clip-0"><rect width="1242" height="720"></rect></clipPath><text clip-path="url(https://g1.quest/g1gate/#O-378df07a9583b-clip-0)"><tspan x="3" y="1.4000000000000001em" fill-opacity="0.7">Tortue</tspan></text></a><a xlink:href="#5nHhk6wwHELKPn5zvUYjzK9gN1iT214udKyXg5PFazq3" target="_self" transform="translate(1242,0)"><rect fill="#548AFF" fill-opacity="1" width="38" height="720"></rect><title>1 DU</title><clipPath id="O-378df07a9583b-clip-1"><rect width="38" height="720"></rect></clipPath><text clip-path="url(https://g1.quest/g1gate/#O-378df07a9583b-clip-1)"><tspan x="3" y="1.4000000000000001em" fill-opacity="0.7">No tune</tspan></text></a></svg></article></section>
|
||||
|
||||
<footer>
|
||||
<blockquote>
|
||||
Follow the money
|
||||
</blockquote>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="g1gate_fichiers/app.js">
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body></html>
|
|
@ -0,0 +1,488 @@
|
|||
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
|
||||
export const DU = 10.68;
|
||||
export const MAX_NB_TX = 30;
|
||||
|
||||
export const CESIUM_G1_NODES = [
|
||||
"https://g1.data.brussels.ovh",
|
||||
"https://g1.data.e-is.pro",
|
||||
"https://g1.data.cuates.net",
|
||||
"https://g1.data.madeirawonders.com",
|
||||
|
||||
// "https://g1.data.pini.fr" // Could not resolve hostname
|
||||
// "https://g1.data.le-sou.org" // vide
|
||||
// "https://abyayala.g1labs.net" // /g1/movement returns "404 Not Found"
|
||||
// "https://g1.data.presles.fr", // CORS
|
||||
// "https://cesiumplus971.dns1.us", // CORS
|
||||
// "https://g1.data.mithril.re", // /g1/movement returns "403 Forbidden"
|
||||
];
|
||||
|
||||
let chartColors = [
|
||||
'#FFC431',
|
||||
'#548AFF',
|
||||
'#FF826E',
|
||||
'#67B0C3',
|
||||
'#CFD8DC',
|
||||
'#5CD6B3'
|
||||
];
|
||||
|
||||
export const Treemap = (
|
||||
// Copyright 2021-2023 Observable, Inc.
|
||||
// Released under the ISC license.
|
||||
// https://observablehq.com/@d3/treemap
|
||||
function Treemap(data, { // data is either tabular (array of objects) or hierarchy (nested objects)
|
||||
path, // as an alternative to id and parentId, returns an array identifier, imputing internal nodes
|
||||
id = Array.isArray(data) ? d => d.id : null, // if tabular data, given a d in data, returns a unique identifier (string)
|
||||
parentId = Array.isArray(data) ? d => d.parentId : null, // if tabular data, given a node d, returns its parent’s identifier
|
||||
children, // if hierarchical data, given a d in data, returns its children
|
||||
value, // given a node d, returns a quantitative value (for area encoding; null for count)
|
||||
sort = (a, b) => d3.descending(a.value, b.value), // how to sort nodes prior to layout
|
||||
label, // given a leaf node d, returns the name to display on the rectangle
|
||||
group, // given a leaf node d, returns a categorical value (for color encoding)
|
||||
title, // given a leaf node d, returns its hover text
|
||||
link, // given a leaf node d, its link (if any)
|
||||
linkTarget = "_blank", // the target attribute for links (if any)
|
||||
tile = d3.treemapBinary, // treemap strategy
|
||||
width = 640, // outer width, in pixels
|
||||
height = 400, // outer height, in pixels
|
||||
margin = 0, // shorthand for margins
|
||||
marginTop = margin, // top margin, in pixels
|
||||
marginRight = margin, // right margin, in pixels
|
||||
marginBottom = margin, // bottom margin, in pixels
|
||||
marginLeft = margin, // left margin, in pixels
|
||||
padding = 1, // shorthand for inner and outer padding
|
||||
paddingInner = padding, // to separate a node from its adjacent siblings
|
||||
paddingOuter = padding, // shorthand for top, right, bottom, and left padding
|
||||
paddingTop = paddingOuter, // to separate a node’s top edge from its children
|
||||
paddingRight = paddingOuter, // to separate a node’s right edge from its children
|
||||
paddingBottom = paddingOuter, // to separate a node’s bottom edge from its children
|
||||
paddingLeft = paddingOuter, // to separate a node’s left edge from its children
|
||||
round = true, // whether to round to exact pixels
|
||||
colors = d3.schemeTableau10, // array of colors
|
||||
zDomain, // array of values for the color scale
|
||||
fill = "#ccc", // fill for node rects (if no group color encoding)
|
||||
fillOpacity = group == null ? null : 0.6, // fill opacity for node rects
|
||||
stroke, // stroke for node rects
|
||||
strokeWidth, // stroke width for node rects
|
||||
strokeOpacity, // stroke opacity for node rects
|
||||
strokeLinejoin, // stroke line join for node rects
|
||||
} = {}) {
|
||||
|
||||
// If id and parentId options are specified, or the path option, use d3.stratify
|
||||
// to convert tabular data to a hierarchy; otherwise we assume that the data is
|
||||
// specified as an object {children} with nested objects (a.k.a. the “flare.json”
|
||||
// format), and use d3.hierarchy.
|
||||
|
||||
// We take special care of any node that has both a value and children, see
|
||||
// https://observablehq.com/@d3/treemap-parent-with-value.
|
||||
const stratify = data => (d3.stratify().path(path)(data)).each(node => {
|
||||
if (node.children?.length && node.data != null) {
|
||||
const child = new d3.Node(node.data);
|
||||
node.data = null;
|
||||
child.depth = node.depth + 1;
|
||||
child.height = 0;
|
||||
child.parent = node;
|
||||
child.id = node.id + "/";
|
||||
node.children.unshift(child);
|
||||
}
|
||||
});
|
||||
const root = path != null ? stratify(data)
|
||||
: id != null || parentId != null ? d3.stratify().id(id).parentId(parentId)(data)
|
||||
: d3.hierarchy(data, children);
|
||||
|
||||
// Compute the values of internal nodes by aggregating from the leaves.
|
||||
value == null ? root.count() : root.sum(d => Math.max(0, d ? value(d) : null));
|
||||
|
||||
// Prior to sorting, if a group channel is specified, construct an ordinal color scale.
|
||||
const leaves = root.leaves();
|
||||
const G = group == null ? null : leaves.map(d => group(d.data, d));
|
||||
if (zDomain === undefined) zDomain = G;
|
||||
zDomain = new d3.InternSet(zDomain);
|
||||
const color = group == null ? null : d3.scaleOrdinal(zDomain, colors);
|
||||
|
||||
// Compute labels and titles.
|
||||
const L = label == null ? null : leaves.map(d => label(d.data, d));
|
||||
const T = title === undefined ? L : title == null ? null : leaves.map(d => title(d.data, d));
|
||||
|
||||
// Sort the leaves (typically by descending value for a pleasing layout).
|
||||
if (sort != null) root.sort(sort);
|
||||
|
||||
// Compute the treemap layout.
|
||||
d3.treemap()
|
||||
.tile(tile)
|
||||
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
|
||||
.paddingInner(paddingInner)
|
||||
.paddingTop(paddingTop)
|
||||
.paddingRight(paddingRight)
|
||||
.paddingBottom(paddingBottom)
|
||||
.paddingLeft(paddingLeft)
|
||||
.round(round)
|
||||
(root);
|
||||
|
||||
const svg = d3.create("svg")
|
||||
.attr("viewBox", [-marginLeft, -marginTop, width, height])
|
||||
.attr("width", width)
|
||||
.attr("height", height)
|
||||
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
|
||||
.attr("font-family", "sans-serif")
|
||||
.attr("font-size", 10);
|
||||
|
||||
const node = svg.selectAll("a")
|
||||
.data(leaves)
|
||||
.join("a")
|
||||
.attr("xlink:href", link == null ? null : (d, i) => link(d.data, d))
|
||||
.attr("target", link == null ? null : linkTarget)
|
||||
.attr("transform", d => `translate(${d.x0},${d.y0})`);
|
||||
|
||||
node.append("rect")
|
||||
.attr("fill", color ? (d, i) => color(G[i]) : fill)
|
||||
.attr("fill-opacity", fillOpacity)
|
||||
.attr("stroke", stroke)
|
||||
.attr("stroke-width", strokeWidth)
|
||||
.attr("stroke-opacity", strokeOpacity)
|
||||
.attr("stroke-linejoin", strokeLinejoin)
|
||||
.attr("width", d => d.x1 - d.x0)
|
||||
.attr("height", d => d.y1 - d.y0);
|
||||
|
||||
if (T) {
|
||||
node.append("title").text((d, i) => T[i]);
|
||||
}
|
||||
|
||||
if (L) {
|
||||
// A unique identifier for clip paths (to avoid conflicts).
|
||||
const uid = `O-${Math.random().toString(16).slice(2)}`;
|
||||
|
||||
node.append("clipPath")
|
||||
.attr("id", (d, i) => `${uid}-clip-${i}`)
|
||||
.append("rect")
|
||||
.attr("width", d => d.x1 - d.x0)
|
||||
.attr("height", d => d.y1 - d.y0);
|
||||
|
||||
node.append("text")
|
||||
.attr("clip-path", (d, i) => `url(${new URL(`#${uid}-clip-${i}`, location)})`)
|
||||
.selectAll("tspan")
|
||||
.data((d, i) => `${L[i]}`.split(/\n/g))
|
||||
.join("tspan")
|
||||
.attr("x", 3)
|
||||
.attr("y", (d, i, D) => `${(i === D.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
|
||||
.attr("fill-opacity", (d, i, D) => i === D.length - 1 ? 0.7 : null)
|
||||
.text(d => d);
|
||||
}
|
||||
|
||||
return Object.assign(svg.node(), {scales: {color}});
|
||||
}
|
||||
);
|
||||
|
||||
export const shuffle = (array) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
};
|
||||
|
||||
export const G12DU = (amount) => {
|
||||
|
||||
return (Math.round(amount / DU * 100) / 100);
|
||||
};
|
||||
|
||||
|
||||
export const query__expenses = (walletPk, size = 100) => {
|
||||
|
||||
return {
|
||||
_source: ["amount", "recipient"]
|
||||
,size: size
|
||||
,query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{term: {"issuer": walletPk}}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchExpenses = async (pubkey, limit = 100) => {
|
||||
|
||||
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||
|
||||
for (let node of CESIUM_G1_NODES) {
|
||||
try {
|
||||
const url = `${node}/g1/movement/_search`;
|
||||
let queryBody = query__expenses(pubkey, limit);
|
||||
|
||||
console.log('expenses queryBody : \n', JSON.stringify(queryBody));
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(queryBody)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log('node for the expenses of :\n', pubkey, '\n', node);
|
||||
console.log('expenses data :\n', data);
|
||||
|
||||
let expensesByRecipient = {};
|
||||
let totalAmount = 0;
|
||||
|
||||
for (const hit of data.hits.hits) {
|
||||
|
||||
const tx = hit._source;
|
||||
|
||||
if (!(tx.recipient in expensesByRecipient)) {
|
||||
|
||||
expensesByRecipient[tx.recipient] = 0;
|
||||
}
|
||||
|
||||
expensesByRecipient[tx.recipient] += tx.amount/100;
|
||||
|
||||
totalAmount += tx.amount;
|
||||
}
|
||||
|
||||
totalAmount = totalAmount/100;
|
||||
|
||||
return {
|
||||
expensesTotalAmount: totalAmount
|
||||
,expensesByRecipient: expensesByRecipient
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||
// Si une erreur se produit, passez simplement au noeud suivant
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Failed to fetch data from all nodes");
|
||||
};
|
||||
|
||||
|
||||
export const query__cesium_profile = (pubkey) => {
|
||||
|
||||
return {
|
||||
_source: [
|
||||
"title",
|
||||
"issuer"
|
||||
]
|
||||
,query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{term: {"_type": "profile"}}
|
||||
]
|
||||
,should: [
|
||||
{ term: { "issuer": pubkey } },
|
||||
]
|
||||
,minimum_should_match: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const query__cesium_profiles = (pubkeys) => {
|
||||
|
||||
return {
|
||||
_source: ["title"]
|
||||
,query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{term: {"_type": "profile"}}
|
||||
]
|
||||
,should: [
|
||||
...pubkeys.map(pk => ({ term: { "issuer": pk } })),
|
||||
]
|
||||
,minimum_should_match: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchCesiumProfile = async (pubkey) => {
|
||||
|
||||
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||
|
||||
for (let node of CESIUM_G1_NODES) {
|
||||
|
||||
try {
|
||||
const url = `${node}/user/profile/_search`;
|
||||
let queryBody = query__cesium_profile(pubkey);
|
||||
|
||||
console.log('cesium_profile queryBody : \n', JSON.stringify(queryBody));
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(queryBody)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.hits.hits[0] == undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data.hits.hits[0]._source;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||
// Si une erreur se produit, passez simplement au noeud suivant
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Failed to fetch data from all nodes");
|
||||
};
|
||||
|
||||
export const fetchCesiumProfiles = async (pubkeys) => {
|
||||
|
||||
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||
|
||||
for (let node of CESIUM_G1_NODES) {
|
||||
|
||||
try {
|
||||
const url = `${node}/user/profile/_search`;
|
||||
let queryBody = query__cesium_profiles(pubkeys);
|
||||
|
||||
console.log('cesium_profiles queryBody : \n', JSON.stringify(queryBody));
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(queryBody)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
let profiles = [];
|
||||
|
||||
for (const hit of data.hits.hits) {
|
||||
|
||||
profiles[hit._id] = {
|
||||
title: hit._source.title
|
||||
};
|
||||
}
|
||||
|
||||
return profiles;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||
// Si une erreur se produit, passez simplement au noeud suivant
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Failed to fetch data from all nodes");
|
||||
};
|
||||
|
||||
export const displayExpenses = (expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, currentPubkey, currentProfile) => {
|
||||
|
||||
let screenElt = document.querySelector('#expenses');
|
||||
|
||||
screenElt.innerHTML = '';
|
||||
|
||||
let currentProfileTitleElt = document.createElement('h2');
|
||||
screenElt.append(currentProfileTitleElt);
|
||||
let title = null;
|
||||
if (currentProfile == undefined) {
|
||||
title = 'Dépenses du portefeuille <code>' + currentPubkey.substr(0, 8) + '</code>';
|
||||
} else {
|
||||
title = 'Dépenses de <q>' + currentProfile.title + '</q>';
|
||||
}
|
||||
currentProfileTitleElt.innerHTML = title;
|
||||
|
||||
let svgContainer = document.createElement('article');
|
||||
screenElt.append(svgContainer);
|
||||
svgContainer.classList.add("svg-container");
|
||||
|
||||
let chartData = [];
|
||||
|
||||
// Formatting data
|
||||
for (const recipient in expensesByRecipient) {
|
||||
|
||||
let recipientObj = {};
|
||||
|
||||
recipientObj.pk = recipient;
|
||||
recipientObj.amount = expensesByRecipient[recipient];
|
||||
let numberOptions = { roundingMode: 'ceil', minimumFractionDigits: 0, maximumFractionDigits: 0 };
|
||||
recipientObj.displayedAmount = G12DU(recipientObj.amount).toLocaleString('fr-FR', numberOptions) + ' DU';
|
||||
|
||||
if (recipientsCesiumProfiles[recipient] != undefined
|
||||
&& recipientsCesiumProfiles[recipient].title != undefined
|
||||
) {
|
||||
recipientObj.title = recipientsCesiumProfiles[recipient].title;
|
||||
} else {
|
||||
recipientObj.title = recipient.substr(0, 8);
|
||||
}
|
||||
|
||||
chartData.push(recipientObj);
|
||||
}
|
||||
|
||||
let chart = Treemap(chartData, {
|
||||
path: d => d.title,
|
||||
value: d => d.amount,
|
||||
group: d => d.title,
|
||||
label: (d, n) => d.title,
|
||||
title: (d, n) => d.displayedAmount,
|
||||
link: (d, n) => '#' + d.pk + '',
|
||||
linkTarget: '_self',
|
||||
tile: d3.treemapSquarify,
|
||||
width: 1280,
|
||||
height: 720,
|
||||
padding: 0,
|
||||
colors: chartColors,
|
||||
fillOpacity: 1
|
||||
});
|
||||
|
||||
svgContainer.append(chart);
|
||||
|
||||
screenElt.scrollIntoView({behavior: 'smooth'}, true);
|
||||
};
|
||||
|
||||
|
||||
let formElt = document.querySelector('form#explore');
|
||||
|
||||
const treemapIt = async (pubkey, maxNbTx = MAX_NB_TX) => {
|
||||
|
||||
let dotsPos = pubkey.indexOf(':');
|
||||
if (dotsPos != -1) {
|
||||
pubkey = pubkey.substr(0, dotsPos);
|
||||
}
|
||||
|
||||
let { expensesTotalAmount, expensesByRecipient } = await fetchExpenses(pubkey, maxNbTx);
|
||||
|
||||
let recipientsList = Object.keys(expensesByRecipient);
|
||||
let recipientsCesiumProfiles = await fetchCesiumProfiles(recipientsList);
|
||||
|
||||
let currentProfile = await fetchCesiumProfile(pubkey);
|
||||
console.log('currentProfile :\n', currentProfile);
|
||||
|
||||
displayExpenses(expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, pubkey, currentProfile);
|
||||
|
||||
let svg = document.querySelector('#expenses svg');
|
||||
let links = svg.querySelectorAll("a");
|
||||
|
||||
for (const link of links) {
|
||||
|
||||
link.addEventListener('click', (linkEvent) => {
|
||||
|
||||
// linkEvent.currentTarget.preventDefault();
|
||||
console.log('linkEvent.currentTarget :\n', linkEvent.currentTarget);
|
||||
let pubkey = linkEvent.currentTarget.getAttribute('href').substr(1);
|
||||
|
||||
treemapIt(pubkey);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
formElt.addEventListener('submit', (formEvent) => {
|
||||
|
||||
formEvent.preventDefault();
|
||||
|
||||
let pubkey = formEvent.target.querySelector('input[name="pubkey"]').value;
|
||||
|
||||
let txLimit = formEvent.target.querySelector('input[name="txLimit"]').value;
|
||||
|
||||
treemapIt(pubkey, txLimit);
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
header {
|
||||
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
header #description {
|
||||
|
||||
margin-top: 0.5rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#expenses svg {
|
||||
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
border-top: 2px solid hsl(0, 0%, 95%);
|
||||
}
|
||||
|
||||
footer blockquote {
|
||||
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
|
@ -174,7 +174,8 @@ var earth3d;
|
|||
$elem.appendTo(widget.options.locationsElement);
|
||||
$elem.click(function() {
|
||||
// alert('Clicked on ' + location.name + ' : ' + location.link );
|
||||
window.open( location.link, "AstroTab");
|
||||
// window.open( location.link, "AstroTab");
|
||||
window.parent.location.href = location.link;
|
||||
});
|
||||
location.$element = $elem;
|
||||
},
|
||||
|
|
|
@ -13,14 +13,22 @@
|
|||
}
|
||||
#coordinates-display {
|
||||
position: absolute;
|
||||
bottom: 10px; /* Adjust the bottom position as needed */
|
||||
bottom: 20px; /* Adjust the bottom position as needed */
|
||||
left: 10px; /* Adjust the left position as needed */
|
||||
background-color: white;
|
||||
padding: 5px;
|
||||
border: 1px solid black;
|
||||
z-index: 1000;
|
||||
}
|
||||
#map-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="map-container" style="position: relative;">
|
||||
|
@ -131,7 +139,7 @@ if (deg <= 0.01) {
|
|||
container.style.height = '100vh';
|
||||
container.style.backgroundColor = 'rgba(255, 255, 255, 0.4)';
|
||||
container.style.border = 'none';
|
||||
container.style.zIndex = '1000';
|
||||
container.style.zIndex = '999';
|
||||
container.style.display = 'flex';
|
||||
container.style.flexDirection = 'column';
|
||||
container.style.alignItems = 'center';
|
||||
|
@ -150,6 +158,7 @@ if (deg <= 0.01) {
|
|||
|
||||
button.addEventListener('click', () => {
|
||||
const url = `http://astroport.localhost:1234/?uplanet=@&lat=${southWestLat}&lon=${southWestLon}`;
|
||||
// const url = `https://astroport.copylaradio.com/?uplanet=@&lat=${southWestLat}&lon=${southWestLon}`;
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue