Complete legacy operator UI and playout migration
This commit is contained in:
496
Web/comparison-workflow.js
Normal file
496
Web/comparison-workflow.js
Normal file
@@ -0,0 +1,496 @@
|
||||
(function (root, factory) {
|
||||
"use strict";
|
||||
|
||||
const api = Object.freeze(factory());
|
||||
if (typeof module === "object" && module.exports) module.exports = api;
|
||||
if (root) root.MbnComparisonWorkflow = api;
|
||||
})(typeof globalThis === "object" ? globalThis : this, function () {
|
||||
"use strict";
|
||||
|
||||
const DEFAULT_FADE_DURATION = 6;
|
||||
const SCHEMA_VERSION = 1;
|
||||
const PAIR_LIST_SCHEMA_VERSION = 1;
|
||||
const MAX_SAVED_PAIRS = 500;
|
||||
const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/;
|
||||
const stockCodePattern = /^[A-Za-z0-9._-]{1,32}$/;
|
||||
const worldSymbolPattern = /^[A-Za-z0-9@._:-]{1,64}$/;
|
||||
const controlCharacterPattern = /[\u0000-\u001f\u007f]/;
|
||||
|
||||
const runtimeAsset = Object.freeze({
|
||||
relativePath: "bin/Debug/Res/종목비교.ini",
|
||||
sha256: "980F74C000EF6A34B85FDAAF6D2756269483637186C91A93548F35C5CFC1B7E4"
|
||||
});
|
||||
|
||||
const marketTargetRows = [
|
||||
["kospi", "코스피 지수", "Kospi"],
|
||||
["kosdaq", "코스닥 지수", "Kosdaq"],
|
||||
["kospi200", "코스피200 지수", "Kospi200"],
|
||||
["futures", "선물 지수", "Futures"],
|
||||
["krx100", "KRX100지수", "Krx100"],
|
||||
["won-dollar", "환율-원달러", "WonDollar"],
|
||||
["won-yen", "환율-원엔", "WonYen"],
|
||||
["won-yuan", "환율-원위엔", "WonYuan"],
|
||||
["won-euro", "환율-원유로", "WonEuro"],
|
||||
["dow", "해외지수-다우", "Dow"],
|
||||
["nasdaq", "해외지수-나스닥", "Nasdaq"],
|
||||
["sp500", "해외지수-S&P", "Sp500"],
|
||||
["germany-dax", "해외지수-독일", "GermanyDax"],
|
||||
["united-kingdom-ftse", "해외지수-영국", "UnitedKingdomFtse"],
|
||||
["france-cac", "해외지수-프랑스", "FranceCac"],
|
||||
["nikkei", "해외지수-일본", "Nikkei"],
|
||||
["hang-seng", "해외지수-홍콩", "HangSeng"],
|
||||
["taiwan-weighted", "해외지수-대만", "TaiwanWeighted"],
|
||||
["singapore-straits-times", "해외지수-싱가포르", "SingaporeStraitsTimes"],
|
||||
["thailand-set", "해외지수-태국", "ThailandSet"],
|
||||
["philippines-composite", "해외지수-필리핀", "PhilippinesComposite"],
|
||||
["malaysia-klse", "해외지수-말레이시아", "MalaysiaKlse"],
|
||||
["indonesia-composite", "해외지수-인도네시아", "IndonesiaComposite"],
|
||||
["shanghai-composite", "해외지수-중국", "ShanghaiComposite"]
|
||||
];
|
||||
|
||||
const marketTargets = Object.freeze(marketTargetRows.map(([id, displayName, target]) =>
|
||||
Object.freeze({ kind: "market-target", id, displayName, target })));
|
||||
if (marketTargets.length !== 24 || new Set(marketTargets.map(value => value.target)).size !== 24) {
|
||||
throw new Error("The legacy comparison selector must expose exactly 24 market targets.");
|
||||
}
|
||||
const marketTargetsByTarget = new Map(marketTargets.map(value => [value.target, value]));
|
||||
const marketTargetsByLookup = new Map();
|
||||
for (const target of marketTargets) {
|
||||
marketTargetsByLookup.set(target.id.toLocaleLowerCase("en-US"), target);
|
||||
marketTargetsByLookup.set(target.target.toLocaleLowerCase("en-US"), target);
|
||||
marketTargetsByLookup.set(target.displayName.toLocaleLowerCase("ko-KR"), target);
|
||||
}
|
||||
|
||||
function action(id, label, kind, details = {}) {
|
||||
return Object.freeze({ id, label, kind, ...details });
|
||||
}
|
||||
|
||||
const actions = Object.freeze([
|
||||
action("two-column-expected", "2열판 예상체결", "two-column", { mode: "EXPECTED" }),
|
||||
action("two-column-current", "2열판", "two-column", { mode: "CURRENT" }),
|
||||
action("comparison-candle", "종목별 비교분석_캔들 그래프", "comparison", {
|
||||
builderKey: "s5026", code: "5026", graphicType: "COMPARISON_CANDLE", period: "FiveDays"
|
||||
}),
|
||||
action("comparison-line", "종목별 비교분석_라인 그래프", "comparison", {
|
||||
builderKey: "s5087", code: "5087", graphicType: "COMPARISON_LINE", period: "FiveDays"
|
||||
}),
|
||||
action("return-5d", "5일", "return", { period: "FiveDays" }),
|
||||
action("return-1m", "1개월", "return", { period: "OneMonth" }),
|
||||
action("return-3m", "3개월", "return", { period: "ThreeMonths" }),
|
||||
action("return-6m", "6개월", "return", { period: "SixMonths" }),
|
||||
action("return-12m", "12개월", "return", { period: "TwelveMonths" })
|
||||
]);
|
||||
if (actions.length !== 9 || new Set(actions.map(value => value.id)).size !== 9) {
|
||||
throw new Error("The legacy comparison tree must expose exactly nine actions.");
|
||||
}
|
||||
const actionsById = new Map(actions.map(value => [value.id, value]));
|
||||
const yieldActionIds = Object.freeze(actions
|
||||
.filter(value => value.kind === "return")
|
||||
.map(value => value.id));
|
||||
const expectedMarketTargets = new Set(["Kospi", "Kosdaq", "Kospi200", "Krx100"]);
|
||||
|
||||
function cleanText(value, maximumLength = 128) {
|
||||
if (typeof value !== "string") return null;
|
||||
const text = value.trim();
|
||||
return text && text.length <= maximumLength && !controlCharacterPattern.test(text) ? text : null;
|
||||
}
|
||||
|
||||
function getMarketTarget(value) {
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) value = value.target ?? value.id ?? value.displayName;
|
||||
const text = cleanText(value);
|
||||
return text ? marketTargetsByLookup.get(text.toLocaleLowerCase("ko-KR")) ?? null : null;
|
||||
}
|
||||
|
||||
function normalizeMarket(value) {
|
||||
const text = String(value || "").trim().toLocaleLowerCase("en-US");
|
||||
return text === "kospi" || text === "kosdaq" ? text : null;
|
||||
}
|
||||
|
||||
function normalizeTarget(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
switch (value.kind) {
|
||||
case "market-target": {
|
||||
const target = marketTargetsByTarget.get(value.target);
|
||||
if (!target) return null;
|
||||
if ((value.id !== undefined && value.id !== target.id) ||
|
||||
(value.displayName !== undefined && value.displayName !== target.displayName)) return null;
|
||||
return target;
|
||||
}
|
||||
case "krx-stock":
|
||||
case "nxt-stock": {
|
||||
const market = normalizeMarket(value.market);
|
||||
const stockName = cleanText(value.stockName ?? value.name);
|
||||
const stockCode = cleanText(value.stockCode ?? value.code, 32);
|
||||
if (!market || !stockName || stockName.includes(",") || stockName.includes("(NXT)") ||
|
||||
!stockCode || !stockCodePattern.test(stockCode)) return null;
|
||||
const displayName = value.kind === "nxt-stock" ? `${stockName}(NXT)` : stockName;
|
||||
if (value.displayName !== undefined && value.displayName !== displayName) return null;
|
||||
return Object.freeze({ kind: value.kind, market, stockName, stockCode, displayName });
|
||||
}
|
||||
case "world-stock": {
|
||||
const inputName = cleanText(value.inputName);
|
||||
const displayName = cleanText(value.displayName ?? value.name);
|
||||
const symbol = cleanText(value.symbol, 64);
|
||||
const nation = cleanText(value.nation, 2)?.toLocaleUpperCase("en-US");
|
||||
if (!inputName || inputName.includes(",") || !displayName || !symbol ||
|
||||
!worldSymbolPattern.test(symbol) || (nation !== "US" && nation !== "TW")) return null;
|
||||
return Object.freeze({ kind: "world-stock", inputName, displayName, symbol, nation });
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function targetKey(value) {
|
||||
const target = normalizeTarget(value);
|
||||
if (!target) return null;
|
||||
switch (target.kind) {
|
||||
case "market-target": return `market:${target.target}`;
|
||||
case "krx-stock": return `krx:${target.market}:${target.stockCode}`;
|
||||
case "nxt-stock": return `nxt:${target.market}:${target.stockCode}`;
|
||||
case "world-stock": return `world:${target.nation}:${target.symbol}`;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
function subjectTokenForTarget(value) {
|
||||
const target = normalizeTarget(value);
|
||||
if (!target) return null;
|
||||
switch (target.kind) {
|
||||
case "market-target": return target.target;
|
||||
case "krx-stock": return target.stockName;
|
||||
case "nxt-stock": return `${target.stockName}(NXT)`;
|
||||
case "world-stock": return target.inputName;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
function pairParts(value) {
|
||||
if (Array.isArray(value)) return [value[0], value[1]];
|
||||
if (value && typeof value === "object") return [value.first ?? value.pair?.[0], value.second ?? value.pair?.[1]];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
function normalizePair(value) {
|
||||
const [firstValue, secondValue] = pairParts(value);
|
||||
const first = normalizeTarget(firstValue);
|
||||
const second = normalizeTarget(secondValue);
|
||||
return first && second ? Object.freeze([first, second]) : null;
|
||||
}
|
||||
|
||||
function normalizePairState(value) {
|
||||
const [firstValue, secondValue] = pairParts(value);
|
||||
const first = firstValue == null ? null : normalizeTarget(firstValue);
|
||||
const second = secondValue == null ? null : normalizeTarget(secondValue);
|
||||
return (firstValue == null || first) && (secondValue == null || second)
|
||||
? Object.freeze([first, second])
|
||||
: null;
|
||||
}
|
||||
|
||||
function rotatePair(value, selectedValue) {
|
||||
const pair = normalizePairState(value);
|
||||
const selected = normalizeTarget(selectedValue);
|
||||
return pair && selected ? Object.freeze([selected, pair[0]]) : null;
|
||||
}
|
||||
|
||||
function swapPair(value) {
|
||||
const pair = normalizePairState(value);
|
||||
return pair ? Object.freeze([pair[1], pair[0]]) : null;
|
||||
}
|
||||
|
||||
function clearPair() {
|
||||
return Object.freeze([null, null]);
|
||||
}
|
||||
|
||||
function pairLabel(value) {
|
||||
const pair = normalizePair(value);
|
||||
return pair ? `${pair[0].displayName} ↔ ${pair[1].displayName}` : "";
|
||||
}
|
||||
|
||||
function unsupported(actionId, reason) {
|
||||
return Object.freeze({ actionId, supported: false, reason, effectiveMode: null, warning: null });
|
||||
}
|
||||
|
||||
function supported(actionId, effectiveMode = null, warning = null) {
|
||||
return Object.freeze({ actionId, supported: true, reason: null, effectiveMode, warning });
|
||||
}
|
||||
|
||||
function getCompatibility(actionId, pairValue) {
|
||||
const selectedAction = actionsById.get(actionId);
|
||||
if (!selectedAction) return unsupported(actionId, "unknown-action");
|
||||
const pair = normalizePair(pairValue);
|
||||
if (!pair) return unsupported(actionId, "pair-incomplete-or-invalid");
|
||||
const futuresCount = pair.filter(value => value.kind === "market-target" && value.target === "Futures").length;
|
||||
const hasWorld = pair.some(value => value.kind === "world-stock");
|
||||
|
||||
if (selectedAction.kind === "comparison" || selectedAction.kind === "return") {
|
||||
return pair.every(value => value.kind === "krx-stock")
|
||||
? supported(actionId, "GRAPH")
|
||||
: unsupported(actionId, "comparison-graphs-require-two-krx-stocks");
|
||||
}
|
||||
|
||||
if (futuresCount > 1) return unsupported(actionId, "two-futures-targets-are-unsupported");
|
||||
if (futuresCount === 1) {
|
||||
const counterpart = pair.find(value => !(value.kind === "market-target" && value.target === "Futures"));
|
||||
if (!counterpart || counterpart.kind !== "market-target") {
|
||||
return unsupported(actionId, "futures-requires-a-market-target-counterpart");
|
||||
}
|
||||
return selectedAction.mode === "EXPECTED"
|
||||
? supported(actionId, "CURRENT", "The current Core routes a futures expected action through the current s5032 branch.")
|
||||
: supported(actionId, "CURRENT");
|
||||
}
|
||||
|
||||
if (selectedAction.mode === "EXPECTED") {
|
||||
const allowed = pair.every(value => value.kind === "krx-stock" ||
|
||||
(value.kind === "market-target" && expectedMarketTargets.has(value.target)));
|
||||
return allowed
|
||||
? supported(actionId, "EXPECTED")
|
||||
: unsupported(actionId, "expected-mode-supports-only-krx-stocks-and-four-domestic-indices");
|
||||
}
|
||||
|
||||
const hasMarketTarget = pair.some(value => value.kind === "market-target");
|
||||
if (hasMarketTarget && hasWorld) {
|
||||
return unsupported(actionId, "mixed-market-and-world-is-unsupported");
|
||||
}
|
||||
return supported(actionId, "CURRENT");
|
||||
}
|
||||
|
||||
function isActionAllowed(actionId, pairValue) {
|
||||
return getCompatibility(actionId, pairValue).supported;
|
||||
}
|
||||
|
||||
function buildComparisonSelection(actionId, pairValue) {
|
||||
const selectedAction = actionsById.get(actionId);
|
||||
const pair = normalizePair(pairValue);
|
||||
const compatibility = getCompatibility(actionId, pair);
|
||||
if (!selectedAction || !pair || !compatibility.supported) {
|
||||
throw new RangeError(`The comparison action is unavailable: ${compatibility.reason || "invalid-selection"}.`);
|
||||
}
|
||||
const subject = pair.map(subjectTokenForTarget).join(",");
|
||||
if (selectedAction.kind === "two-column") {
|
||||
const futures = pair.some(value => value.kind === "market-target" && value.target === "Futures");
|
||||
const code = futures ? "5032" : "8032";
|
||||
return Object.freeze({
|
||||
builderKey: futures ? "s5032" : "s8018",
|
||||
code,
|
||||
aliases: Object.freeze([code]),
|
||||
selection: Object.freeze({
|
||||
groupCode: pair.some(value => value.kind === "market-target") ? "INDEX" : "STOCK",
|
||||
subject,
|
||||
graphicType: "2열판",
|
||||
subtype: selectedAction.mode,
|
||||
dataCode: ""
|
||||
}),
|
||||
effectiveMode: compatibility.effectiveMode,
|
||||
warning: compatibility.warning
|
||||
});
|
||||
}
|
||||
const builderKey = selectedAction.kind === "return" ? "s5029" : selectedAction.builderKey;
|
||||
const code = selectedAction.kind === "return" ? "5029" : selectedAction.code;
|
||||
const graphicType = selectedAction.kind === "return" ? "RETURN_COMPARISON" : selectedAction.graphicType;
|
||||
return Object.freeze({
|
||||
builderKey,
|
||||
code,
|
||||
aliases: Object.freeze([code]),
|
||||
selection: Object.freeze({
|
||||
groupCode: "STOCK",
|
||||
subject,
|
||||
graphicType,
|
||||
subtype: selectedAction.period,
|
||||
dataCode: ""
|
||||
}),
|
||||
effectiveMode: compatibility.effectiveMode,
|
||||
warning: compatibility.warning
|
||||
});
|
||||
}
|
||||
|
||||
function requireOptions(options) {
|
||||
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
||||
throw new TypeError("Comparison playlist options are required.");
|
||||
}
|
||||
const id = cleanText(options.id);
|
||||
if (!id || !identifierPattern.test(id)) throw new TypeError("A safe comparison playlist id is required.");
|
||||
const fadeDuration = options.fadeDuration === undefined ? DEFAULT_FADE_DURATION : Number(options.fadeDuration);
|
||||
if (!Number.isInteger(fadeDuration) || fadeDuration < 0 || fadeDuration > 60) {
|
||||
throw new RangeError("Fade duration must be an integer from 0 through 60.");
|
||||
}
|
||||
return { id, fadeDuration };
|
||||
}
|
||||
|
||||
function createComparisonPlaylistEntry(actionId, pairValue, options) {
|
||||
const selectedAction = actionsById.get(actionId);
|
||||
if (!selectedAction) throw new RangeError("The comparison action is unknown.");
|
||||
const pair = normalizePair(pairValue);
|
||||
if (!pair) throw new RangeError("Select two comparison targets first.");
|
||||
const normalized = requireOptions(options);
|
||||
const mapping = buildComparisonSelection(actionId, pair);
|
||||
const entry = {
|
||||
id: normalized.id,
|
||||
builderKey: mapping.builderKey,
|
||||
code: mapping.code,
|
||||
aliases: mapping.aliases,
|
||||
title: pairLabel(pair),
|
||||
detail: selectedAction.label,
|
||||
category: selectedAction.kind === "two-column" ? "plate" : "chart",
|
||||
market: "compare",
|
||||
selection: mapping.selection,
|
||||
enabled: true,
|
||||
fadeDuration: normalized.fadeDuration,
|
||||
graphicType: mapping.selection.graphicType,
|
||||
subtype: mapping.selection.subtype,
|
||||
effectiveMode: mapping.effectiveMode,
|
||||
warning: mapping.warning,
|
||||
operator: Object.freeze({
|
||||
source: "legacy-comparison-workflow",
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
actionId,
|
||||
pair,
|
||||
effectiveMode: mapping.effectiveMode,
|
||||
assetPath: runtimeAsset.relativePath,
|
||||
assetSha256: runtimeAsset.sha256
|
||||
})
|
||||
};
|
||||
return entry;
|
||||
}
|
||||
|
||||
function createYieldBatchEntries(pairValue, options = {}) {
|
||||
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
||||
throw new TypeError("Yield batch options must be an object.");
|
||||
}
|
||||
const idPrefix = cleanText(options.idPrefix ?? "comparison-return", 96);
|
||||
if (!idPrefix || !identifierPattern.test(idPrefix)) throw new TypeError("A safe yield batch id prefix is required.");
|
||||
return Object.freeze(yieldActionIds.map(actionId => createComparisonPlaylistEntry(actionId, pairValue, {
|
||||
id: `${idPrefix}-${actionId.replace("return-", "")}`,
|
||||
fadeDuration: options.fadeDuration
|
||||
})));
|
||||
}
|
||||
|
||||
function sameSelection(left, right) {
|
||||
if (!left || typeof left !== "object" || !right || typeof right !== "object") return false;
|
||||
return ["groupCode", "subject", "graphicType", "subtype", "dataCode"]
|
||||
.every(key => typeof left[key] === "string" && left[key] === right[key]);
|
||||
}
|
||||
|
||||
function restoreComparisonPlaylistEntry(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value) || typeof value.enabled !== "boolean") return null;
|
||||
const operator = value.operator;
|
||||
if (!operator || typeof operator !== "object" || operator.source !== "legacy-comparison-workflow" ||
|
||||
operator.schemaVersion !== SCHEMA_VERSION || operator.assetPath !== runtimeAsset.relativePath ||
|
||||
operator.assetSha256 !== runtimeAsset.sha256 || !actionsById.has(operator.actionId)) return null;
|
||||
let recreated;
|
||||
try {
|
||||
recreated = createComparisonPlaylistEntry(operator.actionId, operator.pair, {
|
||||
id: value.id,
|
||||
fadeDuration: value.fadeDuration
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const scalars = [
|
||||
"id", "builderKey", "code", "title", "detail", "category", "market",
|
||||
"fadeDuration", "graphicType", "subtype", "effectiveMode", "warning"
|
||||
];
|
||||
if (!scalars.every(key => value[key] === recreated[key]) ||
|
||||
!Array.isArray(value.aliases) || value.aliases.length !== recreated.aliases.length ||
|
||||
!value.aliases.every((alias, index) => alias === recreated.aliases[index]) ||
|
||||
!sameSelection(value.selection, recreated.selection) ||
|
||||
operator.effectiveMode !== recreated.operator.effectiveMode) return null;
|
||||
return { ...recreated, enabled: value.enabled };
|
||||
}
|
||||
|
||||
function createPairRecord(idValue, pairValue) {
|
||||
const id = cleanText(idValue);
|
||||
const pair = normalizePair(pairValue);
|
||||
if (!id || !identifierPattern.test(id) || !pair) return null;
|
||||
return Object.freeze({ id, pair });
|
||||
}
|
||||
|
||||
function emptyPairList() {
|
||||
return Object.freeze({ schemaVersion: PAIR_LIST_SCHEMA_VERSION, pairs: Object.freeze([]) });
|
||||
}
|
||||
|
||||
function freezePairList(records) {
|
||||
return Object.freeze({ schemaVersion: PAIR_LIST_SCHEMA_VERSION, pairs: Object.freeze(records) });
|
||||
}
|
||||
|
||||
function normalizePairList(value) {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
||||
value.schemaVersion !== PAIR_LIST_SCHEMA_VERSION || !Array.isArray(value.pairs)) return emptyPairList();
|
||||
const records = [];
|
||||
const ids = new Set();
|
||||
for (const raw of value.pairs) {
|
||||
if (records.length >= MAX_SAVED_PAIRS) break;
|
||||
const record = createPairRecord(raw?.id, raw?.pair ?? raw);
|
||||
if (!record || ids.has(record.id)) continue;
|
||||
ids.add(record.id);
|
||||
records.push(record);
|
||||
}
|
||||
return freezePairList(records);
|
||||
}
|
||||
|
||||
function appendPair(value, recordOrId, pairValue) {
|
||||
const list = normalizePairList(value);
|
||||
const record = typeof recordOrId === "string"
|
||||
? createPairRecord(recordOrId, pairValue)
|
||||
: createPairRecord(recordOrId?.id, recordOrId?.pair ?? recordOrId);
|
||||
if (!record) throw new TypeError("A valid saved comparison pair is required.");
|
||||
if (list.pairs.length >= MAX_SAVED_PAIRS) throw new RangeError("The saved comparison pair limit was reached.");
|
||||
if (list.pairs.some(value => value.id === record.id)) throw new RangeError("The saved comparison pair id already exists.");
|
||||
return freezePairList([...list.pairs, record]);
|
||||
}
|
||||
|
||||
function reorderPairList(value, idValue, directionValue) {
|
||||
const list = normalizePairList(value);
|
||||
const id = cleanText(idValue);
|
||||
const direction = directionValue === "up" ? -1 : directionValue === "down" ? 1 : Number(directionValue);
|
||||
if (!id || (direction !== -1 && direction !== 1)) return list;
|
||||
const from = list.pairs.findIndex(value => value.id === id);
|
||||
const to = from + direction;
|
||||
if (from < 0 || to < 0 || to >= list.pairs.length) return list;
|
||||
const records = [...list.pairs];
|
||||
[records[from], records[to]] = [records[to], records[from]];
|
||||
return freezePairList(records);
|
||||
}
|
||||
|
||||
function deletePair(value, idValue) {
|
||||
const list = normalizePairList(value);
|
||||
const id = cleanText(idValue);
|
||||
return id ? freezePairList(list.pairs.filter(value => value.id !== id)) : list;
|
||||
}
|
||||
|
||||
function deleteAllPairs() {
|
||||
return emptyPairList();
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeAsset,
|
||||
marketTargets,
|
||||
marketQuoteTargets: marketTargets,
|
||||
actions,
|
||||
comparisonActions: actions,
|
||||
yieldActionIds,
|
||||
pairListSchemaVersion: PAIR_LIST_SCHEMA_VERSION,
|
||||
normalizeTarget,
|
||||
getMarketTarget,
|
||||
targetKey,
|
||||
subjectTokenForTarget,
|
||||
normalizePair,
|
||||
rotatePair,
|
||||
swapPair,
|
||||
clearPair,
|
||||
pairLabel,
|
||||
getCompatibility,
|
||||
isActionAllowed,
|
||||
buildComparisonSelection,
|
||||
createComparisonPlaylistEntry,
|
||||
createYieldBatchEntries,
|
||||
restoreComparisonPlaylistEntry,
|
||||
refreshComparisonPlaylistEntry: restoreComparisonPlaylistEntry,
|
||||
createPairRecord,
|
||||
normalizePairList,
|
||||
appendPair,
|
||||
reorderPairList,
|
||||
deletePair,
|
||||
deleteAllPairs
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user