mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-26 07:39:59 -07:00
global: snapshot
This commit is contained in:
@@ -262,7 +262,7 @@ export function init({
|
||||
),
|
||||
);
|
||||
|
||||
const field = utils.dom.createField({
|
||||
const field = utils.dom.createHorizontalChoiceField({
|
||||
choices: chartModes,
|
||||
selected: chartMode(),
|
||||
id,
|
||||
@@ -326,8 +326,8 @@ export function init({
|
||||
|
||||
ratio =
|
||||
utils.getNumberOfDaysBetweenTwoDates(
|
||||
utils.dateFromTime(from),
|
||||
utils.dateFromTime(to),
|
||||
utils.date.fromTime(from),
|
||||
utils.date.fromTime(to),
|
||||
) / width;
|
||||
} else {
|
||||
const to = /** @type {number} */ (visibleTimeRange.to);
|
||||
@@ -963,7 +963,7 @@ export function init({
|
||||
let number;
|
||||
|
||||
if (scale === "date") {
|
||||
const date = utils.dateFromTime(data.time);
|
||||
const date = utils.date.fromTime(data.time);
|
||||
|
||||
number = date.getTime();
|
||||
|
||||
|
||||
@@ -30,13 +30,55 @@ function initPackages() {
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} initialValue
|
||||
* @param {SignalOptions<T>} [options]
|
||||
* @param {SignalOptions<T> & {save?: {id?: string; param?: string; serialize: (v: NonNullable<T>) => string; deserialize: (v: string) => NonNullable<T>}}} [options]
|
||||
* @returns {Signal<T>}
|
||||
*/
|
||||
createSignal(initialValue, options) {
|
||||
const [get, set] = this.createSolidSignal(initialValue, options);
|
||||
|
||||
// @ts-ignore
|
||||
get.set = set;
|
||||
|
||||
if (options?.save) {
|
||||
const save = options.save;
|
||||
|
||||
let serialized = null;
|
||||
if (save.param) {
|
||||
serialized = utils.url.readParam(save.param);
|
||||
}
|
||||
if (serialized === null && save.id) {
|
||||
serialized = utils.storage.read(save.id);
|
||||
}
|
||||
if (serialized) {
|
||||
set(save.deserialize(serialized));
|
||||
}
|
||||
|
||||
let firstEffect = true;
|
||||
this.createEffect(() => {
|
||||
const value = get();
|
||||
|
||||
if (!save) return;
|
||||
|
||||
if (!firstEffect && save.id) {
|
||||
if (value !== undefined && value !== null) {
|
||||
localStorage.setItem(save.id, save.serialize(value));
|
||||
} else {
|
||||
localStorage.removeItem(save.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (save.param) {
|
||||
if (value !== undefined && value !== null) {
|
||||
utils.url.writeParam(save.param, save.serialize(value));
|
||||
} else {
|
||||
utils.url.removeParam(save.param);
|
||||
}
|
||||
}
|
||||
|
||||
firstEffect = false;
|
||||
});
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return get;
|
||||
},
|
||||
@@ -434,7 +476,7 @@ function initPackages() {
|
||||
whitespaceStartDateDate + i,
|
||||
);
|
||||
|
||||
const time = utils.dateToString(date);
|
||||
const time = utils.date.toString(date);
|
||||
|
||||
if (i === whitespaceDateDataset.length - 1) {
|
||||
whitespaceDateDataset[i] = {
|
||||
@@ -859,7 +901,7 @@ const utils = {
|
||||
* @param {string} args.selected
|
||||
* @param {{createEffect: CreateEffect}} args.signals
|
||||
*/
|
||||
createField({ title, id, choices, selected, signals }) {
|
||||
createHorizontalChoiceField({ title, id, choices, selected, signals }) {
|
||||
const field = window.document.createElement("div");
|
||||
field.classList.add("field");
|
||||
|
||||
@@ -941,12 +983,12 @@ const utils = {
|
||||
},
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string | boolean | undefined} value
|
||||
* @param {string | boolean | null | undefined} value
|
||||
*/
|
||||
writeParam(key, value) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (value !== undefined) {
|
||||
if (value !== null && value !== undefined) {
|
||||
urlParams.set(key, String(value));
|
||||
} else {
|
||||
urlParams.delete(key);
|
||||
@@ -966,9 +1008,7 @@ const utils = {
|
||||
* @returns {boolean | null}
|
||||
*/
|
||||
readBoolParam(key) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const parameter = urlParams.get(key);
|
||||
const parameter = this.readParam(key);
|
||||
|
||||
if (parameter) {
|
||||
return utils.isSerializedBooleanTrue(parameter);
|
||||
@@ -976,6 +1016,29 @@ const utils = {
|
||||
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} key
|
||||
* @returns {number | null}
|
||||
*/
|
||||
readNumberParam(key) {
|
||||
const parameter = this.readParam(key);
|
||||
|
||||
if (parameter) {
|
||||
return Number(parameter);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} key
|
||||
* @returns {string | null}
|
||||
*/
|
||||
readParam(key) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(key);
|
||||
},
|
||||
pathnameToSelectedId() {
|
||||
return window.document.location.pathname.substring(1);
|
||||
},
|
||||
@@ -1044,11 +1107,21 @@ const utils = {
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
readNumber(key) {
|
||||
const saved = this.read(key);
|
||||
if (saved) {
|
||||
return Number(saved);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* @param {string} key
|
||||
*/
|
||||
readBool(key) {
|
||||
const saved = localStorage.getItem(key);
|
||||
const saved = this.read(key);
|
||||
if (saved) {
|
||||
return utils.isSerializedBooleanTrue(saved);
|
||||
}
|
||||
@@ -1056,7 +1129,13 @@ const utils = {
|
||||
},
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string | boolean | undefined} value
|
||||
*/
|
||||
read(key) {
|
||||
return localStorage.getItem(key);
|
||||
},
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {string | boolean | null | undefined} value
|
||||
*/
|
||||
write(key, value) {
|
||||
value !== undefined && value !== null
|
||||
@@ -1070,6 +1149,22 @@ const utils = {
|
||||
this.write(key, undefined);
|
||||
},
|
||||
},
|
||||
serde: {
|
||||
number: {
|
||||
/**
|
||||
* @param {number} v
|
||||
*/
|
||||
serialize(v) {
|
||||
return String(v);
|
||||
},
|
||||
/**
|
||||
* @param {string} v
|
||||
*/
|
||||
deserialize(v) {
|
||||
return Number(v);
|
||||
},
|
||||
},
|
||||
},
|
||||
formatters: {
|
||||
dollars: new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
@@ -1083,6 +1178,37 @@ const utils = {
|
||||
maximumFractionDigits: 2,
|
||||
}),
|
||||
},
|
||||
date: {
|
||||
todayUTC() {
|
||||
const today = new Date();
|
||||
return new Date(
|
||||
Date.UTC(
|
||||
today.getUTCFullYear(),
|
||||
today.getUTCMonth(),
|
||||
today.getUTCDate(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
},
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
toString(date) {
|
||||
return date.toJSON().split("T")[0];
|
||||
},
|
||||
/**
|
||||
* @param {Time} time
|
||||
*/
|
||||
fromTime(time) {
|
||||
return typeof time === "string"
|
||||
? new Date(time)
|
||||
: // @ts-ignore
|
||||
new Date(time.year, time.month, time.day);
|
||||
},
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @template {(...args: any[]) => any} F
|
||||
@@ -1118,22 +1244,6 @@ const utils = {
|
||||
setTimeout(callback, timeout);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*/
|
||||
dateToString(date) {
|
||||
return date.toJSON().split("T")[0];
|
||||
},
|
||||
/**
|
||||
* @param {Time} time
|
||||
*/
|
||||
dateFromTime(time) {
|
||||
return typeof time === "string"
|
||||
? new Date(time)
|
||||
: // @ts-ignore
|
||||
new Date(time.year, time.month, time.day);
|
||||
},
|
||||
/**
|
||||
* @param {Date} oldest
|
||||
* @param {Date} youngest
|
||||
@@ -1186,9 +1296,9 @@ const env = initEnv();
|
||||
|
||||
function createConstants() {
|
||||
const ONE_SECOND_IN_MS = 1_000;
|
||||
const FIVE_SECOND_IN_MS = 5 * ONE_SECOND_IN_MS;
|
||||
const TEN_SECOND_IN_MS = 2 * FIVE_SECOND_IN_MS;
|
||||
const ONE_MINUTE_IN_MS = 6 * TEN_SECOND_IN_MS;
|
||||
const FIVE_SECONDS_IN_MS = 5 * ONE_SECOND_IN_MS;
|
||||
const TEN_SECONDS_IN_MS = 2 * FIVE_SECONDS_IN_MS;
|
||||
const ONE_MINUTE_IN_MS = 6 * TEN_SECONDS_IN_MS;
|
||||
const FIVE_MINUTES_IN_MS = 5 * ONE_MINUTE_IN_MS;
|
||||
const TEN_MINUTES_IN_MS = 2 * FIVE_MINUTES_IN_MS;
|
||||
const ONE_HOUR_IN_MS = 6 * TEN_MINUTES_IN_MS;
|
||||
@@ -1200,8 +1310,8 @@ function createConstants() {
|
||||
|
||||
return {
|
||||
ONE_SECOND_IN_MS,
|
||||
FIVE_SECOND_IN_MS,
|
||||
TEN_SECOND_IN_MS,
|
||||
FIVE_SECONDS_IN_MS,
|
||||
TEN_SECONDS_IN_MS,
|
||||
ONE_MINUTE_IN_MS,
|
||||
FIVE_MINUTES_IN_MS,
|
||||
TEN_MINUTES_IN_MS,
|
||||
@@ -1596,22 +1706,11 @@ function createColors(dark) {
|
||||
probability0_1p: red,
|
||||
probability0_5p: orange,
|
||||
probability1p: yellow,
|
||||
year_2009: yellow,
|
||||
year_2010: yellow,
|
||||
year_2011: yellow,
|
||||
year_2012: yellow,
|
||||
year_2013: yellow,
|
||||
year_2014: yellow,
|
||||
year_2015: yellow,
|
||||
year_2016: yellow,
|
||||
year_2017: yellow,
|
||||
year_2018: yellow,
|
||||
year_2019: yellow,
|
||||
year_2020: yellow,
|
||||
year_2021: yellow,
|
||||
year_2022: yellow,
|
||||
year_2023: yellow,
|
||||
year_2024: yellow,
|
||||
epoch_1: red,
|
||||
epoch_2: orange,
|
||||
epoch_3: yellow,
|
||||
epoch_4: green,
|
||||
epoch_5: blue,
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -2059,7 +2158,7 @@ function initWebSockets(signals) {
|
||||
|
||||
const date = new Date(Number(timestamp) * 1000);
|
||||
|
||||
const dateStr = utils.dateToString(date);
|
||||
const dateStr = utils.date.toString(date);
|
||||
|
||||
/** @type {DatasetCandlestickData} */
|
||||
const candle = {
|
||||
@@ -2121,7 +2220,7 @@ packages.signals().then((signals) =>
|
||||
});
|
||||
}
|
||||
fetchLastHeight();
|
||||
setInterval(fetchLastHeight, consts.TEN_SECOND_IN_MS, {});
|
||||
setInterval(fetchLastHeight, consts.TEN_SECONDS_IN_MS, {});
|
||||
|
||||
return lastHeight;
|
||||
}
|
||||
|
||||
@@ -217,23 +217,12 @@ function createPartialOptions(colors) {
|
||||
},
|
||||
]);
|
||||
|
||||
const year = /** @type {const} */ ([
|
||||
{ id: "year-2009", key: "year_2009", name: "2009" },
|
||||
{ id: "year-2010", key: "year_2010", name: "2010" },
|
||||
{ id: "year-2011", key: "year_2011", name: "2011" },
|
||||
{ id: "year-2012", key: "year_2012", name: "2012" },
|
||||
{ id: "year-2013", key: "year_2013", name: "2013" },
|
||||
{ id: "year-2014", key: "year_2014", name: "2014" },
|
||||
{ id: "year-2015", key: "year_2015", name: "2015" },
|
||||
{ id: "year-2016", key: "year_2016", name: "2016" },
|
||||
{ id: "year-2017", key: "year_2017", name: "2017" },
|
||||
{ id: "year-2018", key: "year_2018", name: "2018" },
|
||||
{ id: "year-2019", key: "year_2019", name: "2019" },
|
||||
{ id: "year-2020", key: "year_2020", name: "2020" },
|
||||
{ id: "year-2021", key: "year_2021", name: "2021" },
|
||||
{ id: "year-2022", key: "year_2022", name: "2022" },
|
||||
{ id: "year-2023", key: "year_2023", name: "2023" },
|
||||
{ id: "year-2024", key: "year_2024", name: "2024" },
|
||||
const epochs = /** @type {const} */ ([
|
||||
{ id: "epoch-1", key: "epoch_1", name: "1" },
|
||||
{ id: "epoch-2", key: "epoch_2", name: "2" },
|
||||
{ id: "epoch-3", key: "epoch_3", name: "3" },
|
||||
{ id: "epoch-4", key: "epoch_4", name: "4" },
|
||||
{ id: "epoch-5", key: "epoch_5", name: "5" },
|
||||
]);
|
||||
|
||||
const age = /** @type {const} */ ([
|
||||
@@ -246,7 +235,7 @@ function createPartialOptions(colors) {
|
||||
...upTo,
|
||||
...fromXToY,
|
||||
...fromX,
|
||||
...year,
|
||||
...epochs,
|
||||
]);
|
||||
|
||||
const size = /** @type {const} */ ([
|
||||
@@ -475,7 +464,7 @@ function createPartialOptions(colors) {
|
||||
upTo,
|
||||
fromX,
|
||||
fromXToY,
|
||||
year,
|
||||
epochs,
|
||||
age,
|
||||
type,
|
||||
size,
|
||||
@@ -3713,8 +3702,8 @@ function createPartialOptions(colors) {
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Years",
|
||||
tree: groups.year.map(({ key, id, name }) =>
|
||||
name: "Epochs",
|
||||
tree: groups.epochs.map(({ key, id, name }) =>
|
||||
createCohortOptionsGroup({
|
||||
scale,
|
||||
color: colors[key],
|
||||
@@ -4824,10 +4813,10 @@ function createPartialOptions(colors) {
|
||||
name: "Simulations",
|
||||
tree: [
|
||||
{
|
||||
icon: "🧪",
|
||||
icon: "💰",
|
||||
kind: "simulation",
|
||||
title: "Dollar Cost Average Simulation",
|
||||
name: "Dollar Cost Average",
|
||||
title: "Simulation: Save In Bitcoin",
|
||||
name: "Save In Bitcoin",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -32,4 +32,378 @@ export function init({
|
||||
webSockets,
|
||||
}) {
|
||||
const simulationElement = elements.simulation;
|
||||
|
||||
const parametersElement = window.document.createElement("div");
|
||||
simulationElement.append(parametersElement);
|
||||
const resultsElement = window.document.createElement("div");
|
||||
simulationElement.append(resultsElement);
|
||||
|
||||
const storagePrefix = "save-in-bitcoin";
|
||||
const settings = {
|
||||
initial: signals.createSignal(/** @type {number | null} */ (1000), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
id: `${storagePrefix}-initial-amount`,
|
||||
param: "initial-amount",
|
||||
},
|
||||
}),
|
||||
later: signals.createSignal(/** @type {number | null} */ (0), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
id: `${storagePrefix}-later-amount`,
|
||||
param: "later-amount",
|
||||
},
|
||||
}),
|
||||
recurrent: {
|
||||
amount: signals.createSignal(/** @type {number | null} */ (100), {
|
||||
save: {
|
||||
...utils.serde.number,
|
||||
id: `${storagePrefix}-recurrent-amount`,
|
||||
param: "recurrent-amount",
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const initialGroup = createParameterGroup({
|
||||
title: "Initial",
|
||||
description:
|
||||
"The initial amount of dollars you're willing to eventually save in Bitcoin.",
|
||||
});
|
||||
parametersElement.append(initialGroup);
|
||||
|
||||
initialGroup.append(
|
||||
createInputField({
|
||||
name: "Directly converted",
|
||||
input: createInputDollar({
|
||||
id: "simulation-dollars-initial",
|
||||
title: "Initial amount of dollars converted",
|
||||
signal: settings.initial,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
initialGroup.append(
|
||||
createInputField({
|
||||
name: "Converted over time",
|
||||
input: createInputDollar({
|
||||
id: "simulation-dollars-later",
|
||||
title: "Dollars to spread over time",
|
||||
signal: settings.later,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
parametersElement.append(createHrElement());
|
||||
|
||||
const recurrentGroup = createParameterGroup({
|
||||
title: "Recurrent",
|
||||
description:
|
||||
"The recurrent amount of dollars you're willing to eventually save in Bitcoin.",
|
||||
});
|
||||
parametersElement.append(recurrentGroup);
|
||||
|
||||
recurrentGroup.append(
|
||||
createInputField({
|
||||
name: "Amount",
|
||||
input: createInputDollar({
|
||||
id: "simulation-dollars-recurrent",
|
||||
title: "Recurrent dollar amount",
|
||||
signal: settings.recurrent.amount,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const frequencyUL = appendUl({ parent: recurrentGroup });
|
||||
|
||||
[{ name: "Daily" }, { name: "Weekly" }, { name: "Monthly" }].forEach(
|
||||
({ name }) => {
|
||||
const li = appendLi({ name, parent: frequencyUL });
|
||||
},
|
||||
);
|
||||
|
||||
const frequencyChoiceUL = appendUl({ parent: recurrentGroup });
|
||||
|
||||
[
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
].forEach((name) => {
|
||||
const li = appendLi({ name, parent: frequencyChoiceUL });
|
||||
});
|
||||
|
||||
parametersElement.append(createHrElement());
|
||||
|
||||
const today = signals.createSignal(utils.date.todayUTC());
|
||||
setInterval(() => {
|
||||
today.set(utils.date.todayUTC());
|
||||
}, consts.FIVE_SECONDS_IN_MS);
|
||||
|
||||
const intervalGroup = createParameterGroup({
|
||||
title: "Interval",
|
||||
description: "wkfpweokf",
|
||||
});
|
||||
parametersElement.append(intervalGroup);
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {HTMLElement} args.parent
|
||||
*/
|
||||
function appendDiv({ parent }) {
|
||||
const div = window.document.createElement("div");
|
||||
parent.append(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function createInputDateField() {
|
||||
const div = appendDiv({ parent: intervalGroup });
|
||||
|
||||
appendInputDate({
|
||||
id: "",
|
||||
title: "",
|
||||
value: "2021-04-15",
|
||||
parent: div,
|
||||
signals,
|
||||
today,
|
||||
});
|
||||
|
||||
appendButton({
|
||||
onClick: () => {},
|
||||
text: "Reset",
|
||||
title: "",
|
||||
parent: div,
|
||||
});
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
createInputDateField();
|
||||
createInputDateField();
|
||||
|
||||
parametersElement.append(createHrElement());
|
||||
|
||||
const feesGroup = createParameterGroup({
|
||||
title: "Fees",
|
||||
description:
|
||||
"The amount of fees (in %) from where you'll be exchanging your currency",
|
||||
});
|
||||
parametersElement.append(feesGroup);
|
||||
|
||||
createInputNumber({
|
||||
id: "",
|
||||
title: "",
|
||||
value: 0.25,
|
||||
parent: feesGroup,
|
||||
min: 0,
|
||||
max: 10,
|
||||
});
|
||||
|
||||
parametersElement.append(createHrElement());
|
||||
|
||||
const strategyGroup = createParameterGroup({
|
||||
title: "Strategy",
|
||||
description: "The strategy used to convert your fiat into Bitcoin",
|
||||
});
|
||||
parametersElement.append(strategyGroup);
|
||||
|
||||
const ulStrategies = appendUl({ parent: strategyGroup });
|
||||
|
||||
["All in", "Weighted Local", "Weighted Cycle"].forEach((strategy) => {
|
||||
appendLi({
|
||||
name: strategy,
|
||||
parent: ulStrategies,
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// On the side
|
||||
// Value in Bitcoin
|
||||
// Value in Dollars + total converted
|
||||
//
|
||||
// Value min estimated value in 4 years
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {HTMLInputElement} args.input
|
||||
* @param {string} args.name
|
||||
*/
|
||||
function createInputField({ name, input }) {
|
||||
const div = window.document.createElement("div");
|
||||
|
||||
const label = window.document.createElement("label");
|
||||
div.append(label);
|
||||
// @ts-ignore
|
||||
label.for = input.id;
|
||||
label.innerHTML = name;
|
||||
|
||||
div.append(input);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {string} args.description
|
||||
*/
|
||||
function createParameterGroup({ title, description }) {
|
||||
const div = window.document.createElement("div");
|
||||
|
||||
const wrapper = window.document.createElement("div");
|
||||
div.append(wrapper);
|
||||
|
||||
const titleElement = window.document.createElement("h4");
|
||||
titleElement.innerHTML = title;
|
||||
wrapper.append(titleElement);
|
||||
|
||||
const descriptionElement = window.document.createElement("small");
|
||||
descriptionElement.innerHTML = description;
|
||||
wrapper.append(descriptionElement);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function createHrElement() {
|
||||
return window.document.createElement("hr");
|
||||
}
|
||||
|
||||
/**
|
||||
*@param {Object} args
|
||||
*@param {string} args.id
|
||||
*@param {string} args.title
|
||||
*@param {number} args.value
|
||||
*@param {HTMLElement} args.parent
|
||||
*@param {number} args.min
|
||||
*@param {number} args.max
|
||||
*/
|
||||
function createInputNumber({ id, title, value, parent, min, max }) {
|
||||
const input = window.document.createElement("input");
|
||||
input.id = id;
|
||||
input.title = title;
|
||||
input.type = "number";
|
||||
input.value = String(value);
|
||||
input.min = String(min);
|
||||
input.max = String(max);
|
||||
parent.append(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.id
|
||||
* @param {string} args.title
|
||||
* @param {Signal<number | null>} args.signal
|
||||
*/
|
||||
function createInputDollar({ id, title, signal }) {
|
||||
const input = window.document.createElement("input");
|
||||
input.id = id;
|
||||
input.type = "number";
|
||||
input.placeholder = "US Dollars";
|
||||
input.min = "0";
|
||||
input.title = title;
|
||||
|
||||
const value = signal();
|
||||
input.value = value !== null ? String(value) : "";
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
const value = input.value;
|
||||
signal.set(value ? Number(value) : null);
|
||||
});
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.id
|
||||
* @param {string} args.title
|
||||
* @param {Signal<number | null>} args.signal
|
||||
*/
|
||||
function createInputRangePercentage({ id, title, signal }) {
|
||||
const input = window.document.createElement("input");
|
||||
input.id = id;
|
||||
input.title = title;
|
||||
input.type = "range";
|
||||
input.min = "0";
|
||||
input.max = "100";
|
||||
|
||||
const value = signal();
|
||||
input.value = value !== null ? String(value) : "";
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
const value = input.value;
|
||||
signal.set(value ? Number(value) : null);
|
||||
});
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {HTMLElement} args.parent
|
||||
*/
|
||||
function appendUl({ parent }) {
|
||||
const ul = window.document.createElement("ul");
|
||||
parent.append(ul);
|
||||
return ul;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {string} args.name
|
||||
* @param {HTMLUListElement} args.parent
|
||||
*/
|
||||
function appendLi({ name, parent }) {
|
||||
const li = window.document.createElement("li");
|
||||
li.innerHTML = name;
|
||||
parent.append(li);
|
||||
return li;
|
||||
}
|
||||
|
||||
/**
|
||||
*@param {Object} args
|
||||
*@param {string} args.id
|
||||
*@param {string} args.title
|
||||
*@param {string} args.value
|
||||
*@param {HTMLElement} args.parent
|
||||
*@param {Accessor<Date>} args.today
|
||||
*@param {Signals} args.signals
|
||||
*/
|
||||
function appendInputDate({ id, title, value, parent, today, signals }) {
|
||||
const input = window.document.createElement("input");
|
||||
input.id = id;
|
||||
input.title = title;
|
||||
input.type = "date";
|
||||
input.value = value;
|
||||
|
||||
input.min = "2011-01-01";
|
||||
|
||||
signals.createEffect(() => {
|
||||
input.max = today().toJSON().split("T")[0];
|
||||
});
|
||||
|
||||
parent.append(input);
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
*@param {Object} args
|
||||
*@param {string} args.title
|
||||
*@param {string} args.text
|
||||
*@param {HTMLElement} args.parent
|
||||
*@param {VoidFunction} args.onClick
|
||||
*/
|
||||
function appendButton({ title, text, onClick, parent }) {
|
||||
const button = window.document.createElement("button");
|
||||
button.title = title;
|
||||
button.innerHTML = text;
|
||||
button.addEventListener("click", onClick);
|
||||
parent.append(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
4
website/scripts/types/self.d.ts
vendored
4
website/scripts/types/self.d.ts
vendored
@@ -127,8 +127,8 @@ interface PartialChartOption extends PartialOption {
|
||||
|
||||
interface PartialSimulationOption extends PartialOption {
|
||||
kind: "simulation";
|
||||
title: "Dollar Cost Average Simulation";
|
||||
name: "Dollar Cost Average";
|
||||
title: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface PartialPdfOption extends PartialOption {
|
||||
|
||||
Reference in New Issue
Block a user