mirror of
https://github.com/basicswap/basicswap.git
synced 2025-12-29 16:51:39 +01:00
GUI: Dynamic balances (WS) + Better Notifications (Toasts) + various fixes. (#332)
* GUI: Dynamic balances (WS) + various fixes. * BLACK + FLAKE8 * Clean-up. * Fix refresh intervals + Fix pending balance. * Fix amounts scientific notation (1e-8) * Better Notifications (Toasts) * Removed duplicated code + Balance skip if the chain is still syncing. * Fix MWEB doesnt show as pending + Various fixes. * Fix: USD values are off with part blind. * Fix: Percentage change buttons on wallet page. * Cleanup debug on wallet page. * Use ZMQ for part balances. * Fix ZMQ config. * Fix PART price in chart.
This commit is contained in:
@@ -186,6 +186,53 @@ def validOfferStateToReceiveBid(offer_state):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def checkAndNotifyBalanceChange(
|
||||||
|
swap_client, coin_type, ci, cc, new_height, trigger_source="block"
|
||||||
|
):
|
||||||
|
if not swap_client.ws_server:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
blockchain_info = ci.getBlockchainInfo()
|
||||||
|
verification_progress = blockchain_info.get("verificationprogress", 1.0)
|
||||||
|
if verification_progress < 0.99:
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_balance = ci.getSpendableBalance()
|
||||||
|
current_total_balance = swap_client.getTotalBalance(coin_type)
|
||||||
|
cached_balance = cc.get("cached_balance", None)
|
||||||
|
cached_total_balance = cc.get("cached_total_balance", None)
|
||||||
|
|
||||||
|
current_unconfirmed = current_total_balance - current_balance
|
||||||
|
cached_unconfirmed = cc.get("cached_unconfirmed", None)
|
||||||
|
|
||||||
|
if (
|
||||||
|
cached_balance is None
|
||||||
|
or current_balance != cached_balance
|
||||||
|
or cached_total_balance is None
|
||||||
|
or current_total_balance != cached_total_balance
|
||||||
|
or cached_unconfirmed is None
|
||||||
|
or current_unconfirmed != cached_unconfirmed
|
||||||
|
):
|
||||||
|
cc["cached_balance"] = current_balance
|
||||||
|
cc["cached_total_balance"] = current_total_balance
|
||||||
|
cc["cached_unconfirmed"] = current_unconfirmed
|
||||||
|
balance_event = {
|
||||||
|
"event": "coin_balance_updated",
|
||||||
|
"coin": ci.ticker(),
|
||||||
|
"height": new_height,
|
||||||
|
"trigger": trigger_source,
|
||||||
|
}
|
||||||
|
swap_client.ws_server.send_message_to_all(json.dumps(balance_event))
|
||||||
|
except Exception:
|
||||||
|
cc["cached_balance"] = None
|
||||||
|
cc["cached_total_balance"] = None
|
||||||
|
cc["cached_unconfirmed"] = None
|
||||||
|
|
||||||
|
|
||||||
def threadPollXMRChainState(swap_client, coin_type):
|
def threadPollXMRChainState(swap_client, coin_type):
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
cc = swap_client.coin_clients[coin_type]
|
cc = swap_client.coin_clients[coin_type]
|
||||||
@@ -198,6 +245,11 @@ def threadPollXMRChainState(swap_client, coin_type):
|
|||||||
)
|
)
|
||||||
with swap_client.mxDB:
|
with swap_client.mxDB:
|
||||||
cc["chain_height"] = new_height
|
cc["chain_height"] = new_height
|
||||||
|
|
||||||
|
checkAndNotifyBalanceChange(
|
||||||
|
swap_client, coin_type, ci, cc, new_height, "block"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.warning(
|
swap_client.log.warning(
|
||||||
f"threadPollXMRChainState {ci.ticker()}, error: {e}"
|
f"threadPollXMRChainState {ci.ticker()}, error: {e}"
|
||||||
@@ -219,6 +271,11 @@ def threadPollWOWChainState(swap_client, coin_type):
|
|||||||
)
|
)
|
||||||
with swap_client.mxDB:
|
with swap_client.mxDB:
|
||||||
cc["chain_height"] = new_height
|
cc["chain_height"] = new_height
|
||||||
|
|
||||||
|
checkAndNotifyBalanceChange(
|
||||||
|
swap_client, coin_type, ci, cc, new_height, "block"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.warning(
|
swap_client.log.warning(
|
||||||
f"threadPollWOWChainState {ci.ticker()}, error: {e}"
|
f"threadPollWOWChainState {ci.ticker()}, error: {e}"
|
||||||
@@ -231,6 +288,12 @@ def threadPollWOWChainState(swap_client, coin_type):
|
|||||||
def threadPollChainState(swap_client, coin_type):
|
def threadPollChainState(swap_client, coin_type):
|
||||||
ci = swap_client.ci(coin_type)
|
ci = swap_client.ci(coin_type)
|
||||||
cc = swap_client.coin_clients[coin_type]
|
cc = swap_client.coin_clients[coin_type]
|
||||||
|
|
||||||
|
if coin_type == Coins.PART and swap_client._zmq_queue_enabled:
|
||||||
|
poll_delay_range = (40, 60)
|
||||||
|
else:
|
||||||
|
poll_delay_range = (20, 30)
|
||||||
|
|
||||||
while not swap_client.chainstate_delay_event.is_set():
|
while not swap_client.chainstate_delay_event.is_set():
|
||||||
try:
|
try:
|
||||||
chain_state = ci.getBlockchainInfo()
|
chain_state = ci.getBlockchainInfo()
|
||||||
@@ -244,11 +307,14 @@ def threadPollChainState(swap_client, coin_type):
|
|||||||
cc["chain_best_block"] = chain_state["bestblockhash"]
|
cc["chain_best_block"] = chain_state["bestblockhash"]
|
||||||
if "mediantime" in chain_state:
|
if "mediantime" in chain_state:
|
||||||
cc["chain_median_time"] = chain_state["mediantime"]
|
cc["chain_median_time"] = chain_state["mediantime"]
|
||||||
|
|
||||||
|
checkAndNotifyBalanceChange(
|
||||||
|
swap_client, coin_type, ci, cc, new_height, "block"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.warning(f"threadPollChainState {ci.ticker()}, error: {e}")
|
swap_client.log.warning(f"threadPollChainState {ci.ticker()}, error: {e}")
|
||||||
swap_client.chainstate_delay_event.wait(
|
swap_client.chainstate_delay_event.wait(random.randrange(*poll_delay_range))
|
||||||
random.randrange(20, 30)
|
|
||||||
) # Random to stagger updates
|
|
||||||
|
|
||||||
|
|
||||||
class WatchedOutput: # Watch for spends
|
class WatchedOutput: # Watch for spends
|
||||||
@@ -498,6 +564,9 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
)
|
)
|
||||||
self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, "smsg")
|
self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, "smsg")
|
||||||
|
|
||||||
|
if Coins.PART in chainparams:
|
||||||
|
self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, "hashwtx")
|
||||||
|
|
||||||
self.with_coins_override = extra_opts.get("with_coins", set())
|
self.with_coins_override = extra_opts.get("with_coins", set())
|
||||||
self.without_coins_override = extra_opts.get("without_coins", set())
|
self.without_coins_override = extra_opts.get("without_coins", set())
|
||||||
self._force_db_upgrade = extra_opts.get("force_db_upgrade", False)
|
self._force_db_upgrade = extra_opts.get("force_db_upgrade", False)
|
||||||
@@ -5410,6 +5479,39 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
|
|
||||||
# bid saved in checkBidState
|
# bid saved in checkBidState
|
||||||
|
|
||||||
|
def getTotalBalance(self, coin_type) -> int:
|
||||||
|
try:
|
||||||
|
ci = self.ci(coin_type)
|
||||||
|
if hasattr(ci, "rpc_wallet"):
|
||||||
|
if coin_type in (Coins.XMR, Coins.WOW):
|
||||||
|
balance_info = ci.rpc_wallet("get_balance")
|
||||||
|
return balance_info["balance"]
|
||||||
|
elif coin_type == Coins.PART:
|
||||||
|
balances = ci.rpc_wallet("getbalances")
|
||||||
|
return ci.make_int(
|
||||||
|
balances["mine"]["trusted"]
|
||||||
|
+ balances["mine"]["untrusted_pending"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
balances = ci.rpc_wallet("getbalances")
|
||||||
|
return ci.make_int(
|
||||||
|
balances["mine"]["trusted"]
|
||||||
|
+ balances["mine"]["untrusted_pending"]
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
wallet_info = ci.rpc_wallet("getwalletinfo")
|
||||||
|
total = wallet_info.get("balance", 0)
|
||||||
|
if "unconfirmed_balance" in wallet_info:
|
||||||
|
total += wallet_info["unconfirmed_balance"]
|
||||||
|
if "immature_balance" in wallet_info:
|
||||||
|
total += wallet_info["immature_balance"]
|
||||||
|
return ci.make_int(total)
|
||||||
|
else:
|
||||||
|
return ci.getSpendableBalance()
|
||||||
|
except Exception:
|
||||||
|
return ci.getSpendableBalance()
|
||||||
|
|
||||||
def getAddressBalance(self, coin_type, address: str) -> int:
|
def getAddressBalance(self, coin_type, address: str) -> int:
|
||||||
if self.coin_clients[coin_type]["chain_lookups"] == "explorer":
|
if self.coin_clients[coin_type]["chain_lookups"] == "explorer":
|
||||||
explorers = self.coin_clients[coin_type]["explorers"]
|
explorers = self.coin_clients[coin_type]["explorers"]
|
||||||
@@ -10399,6 +10501,29 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
|
|
||||||
self.processMsg(msg)
|
self.processMsg(msg)
|
||||||
|
|
||||||
|
def processZmqHashwtx(self) -> None:
|
||||||
|
self.zmqSubscriber.recv()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if Coins.PART not in self.coin_clients:
|
||||||
|
return
|
||||||
|
|
||||||
|
ci = self.ci(Coins.PART)
|
||||||
|
cc = self.coin_clients[Coins.PART]
|
||||||
|
|
||||||
|
current_height = cc.get("chain_height", 0)
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
checkAndNotifyBalanceChange(self, Coins.PART, ci, cc, current_height, "zmq")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning(f"Error processing PART wallet transaction: {e}")
|
||||||
|
if self.debug:
|
||||||
|
self.log.error(traceback.format_exc())
|
||||||
|
|
||||||
def expireBidsAndOffers(self, now) -> None:
|
def expireBidsAndOffers(self, now) -> None:
|
||||||
bids_to_expire = set()
|
bids_to_expire = set()
|
||||||
offers_to_expire = set()
|
offers_to_expire = set()
|
||||||
@@ -10530,6 +10655,8 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
message = self.zmqSubscriber.recv(flags=zmq.NOBLOCK)
|
message = self.zmqSubscriber.recv(flags=zmq.NOBLOCK)
|
||||||
if message == b"smsg":
|
if message == b"smsg":
|
||||||
self.processZmqSmsg()
|
self.processZmqSmsg()
|
||||||
|
elif message == b"hashwtx":
|
||||||
|
self.processZmqHashwtx()
|
||||||
except zmq.Again as e: # noqa: F841
|
except zmq.Again as e: # noqa: F841
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -10831,6 +10958,76 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
settings_copy["enabled_chart_coins"] = new_value
|
settings_copy["enabled_chart_coins"] = new_value
|
||||||
settings_changed = True
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_new_offers" in data:
|
||||||
|
new_value = data["notifications_new_offers"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, bool),
|
||||||
|
"New notifications_new_offers value not boolean",
|
||||||
|
)
|
||||||
|
if settings_copy.get("notifications_new_offers", False) != new_value:
|
||||||
|
settings_copy["notifications_new_offers"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_new_bids" in data:
|
||||||
|
new_value = data["notifications_new_bids"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, bool),
|
||||||
|
"New notifications_new_bids value not boolean",
|
||||||
|
)
|
||||||
|
if settings_copy.get("notifications_new_bids", True) != new_value:
|
||||||
|
settings_copy["notifications_new_bids"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_bid_accepted" in data:
|
||||||
|
new_value = data["notifications_bid_accepted"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, bool),
|
||||||
|
"New notifications_bid_accepted value not boolean",
|
||||||
|
)
|
||||||
|
if settings_copy.get("notifications_bid_accepted", True) != new_value:
|
||||||
|
settings_copy["notifications_bid_accepted"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_balance_changes" in data:
|
||||||
|
new_value = data["notifications_balance_changes"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, bool),
|
||||||
|
"New notifications_balance_changes value not boolean",
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
settings_copy.get("notifications_balance_changes", True)
|
||||||
|
!= new_value
|
||||||
|
):
|
||||||
|
settings_copy["notifications_balance_changes"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_outgoing_transactions" in data:
|
||||||
|
new_value = data["notifications_outgoing_transactions"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, bool),
|
||||||
|
"New notifications_outgoing_transactions value not boolean",
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
settings_copy.get("notifications_outgoing_transactions", True)
|
||||||
|
!= new_value
|
||||||
|
):
|
||||||
|
settings_copy["notifications_outgoing_transactions"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if "notifications_duration" in data:
|
||||||
|
new_value = data["notifications_duration"]
|
||||||
|
ensure(
|
||||||
|
isinstance(new_value, int),
|
||||||
|
"New notifications_duration value not integer",
|
||||||
|
)
|
||||||
|
ensure(
|
||||||
|
5 <= new_value <= 60,
|
||||||
|
"notifications_duration must be between 5 and 60 seconds",
|
||||||
|
)
|
||||||
|
if settings_copy.get("notifications_duration", 20) != new_value:
|
||||||
|
settings_copy["notifications_duration"] = new_value
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
if settings_changed:
|
if settings_changed:
|
||||||
settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME)
|
settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME)
|
||||||
settings_path_new = settings_path + ".new"
|
settings_path_new = settings_path + ".new"
|
||||||
|
|||||||
@@ -1382,6 +1382,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
fp.write(
|
fp.write(
|
||||||
"zmqpubsmsg=tcp://{}:{}\n".format(COINS_RPCBIND_IP, settings["zmqport"])
|
"zmqpubsmsg=tcp://{}:{}\n".format(COINS_RPCBIND_IP, settings["zmqport"])
|
||||||
)
|
)
|
||||||
|
fp.write(
|
||||||
|
"zmqpubhashwtx=tcp://{}:{}\n".format(
|
||||||
|
COINS_RPCBIND_IP, settings["zmqport"]
|
||||||
|
)
|
||||||
|
)
|
||||||
fp.write("spentindex=1\n")
|
fp.write("spentindex=1\n")
|
||||||
fp.write("txindex=1\n")
|
fp.write("txindex=1\n")
|
||||||
fp.write("staking=0\n")
|
fp.write("staking=0\n")
|
||||||
|
|||||||
@@ -56,6 +56,42 @@ def signal_handler(sig, frame):
|
|||||||
swap_client.stopRunning()
|
swap_client.stopRunning()
|
||||||
|
|
||||||
|
|
||||||
|
def checkPARTZmqConfigBeforeStart(part_settings, swap_settings):
|
||||||
|
try:
|
||||||
|
datadir = part_settings.get("datadir")
|
||||||
|
if not datadir:
|
||||||
|
return
|
||||||
|
|
||||||
|
config_path = os.path.join(datadir, "particl.conf")
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
config_content = f.read()
|
||||||
|
|
||||||
|
zmq_host = swap_settings.get("zmqhost", "tcp://127.0.0.1")
|
||||||
|
zmq_port = swap_settings.get("zmqport", 14792)
|
||||||
|
expected_line = f"zmqpubhashwtx={zmq_host}:{zmq_port}"
|
||||||
|
|
||||||
|
if "zmqpubhashwtx=" not in config_content:
|
||||||
|
with open(config_path, "a") as f:
|
||||||
|
f.write(f"{expected_line}\n")
|
||||||
|
elif expected_line not in config_content:
|
||||||
|
lines = config_content.split("\n")
|
||||||
|
updated_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("zmqpubhashwtx="):
|
||||||
|
updated_lines.append(expected_line)
|
||||||
|
else:
|
||||||
|
updated_lines.append(line)
|
||||||
|
|
||||||
|
with open(config_path, "w") as f:
|
||||||
|
f.write("\n".join(updated_lines))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Error checking PART ZMQ config: {e}")
|
||||||
|
|
||||||
|
|
||||||
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||||
datadir_path = os.path.expanduser(node_dir)
|
datadir_path = os.path.expanduser(node_dir)
|
||||||
@@ -548,6 +584,9 @@ def runClient(
|
|||||||
continue # /decred
|
continue # /decred
|
||||||
|
|
||||||
if v["manage_daemon"] is True:
|
if v["manage_daemon"] is True:
|
||||||
|
if c == "particl" and swap_client._zmq_queue_enabled:
|
||||||
|
checkPARTZmqConfigBeforeStart(v, swap_client.settings)
|
||||||
|
|
||||||
swap_client.log.info(f"Starting {display_name} daemon")
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
|
||||||
filename: str = getCoreBinName(coin_id, v, c + "d")
|
filename: str = getCoreBinName(coin_id, v, c + "d")
|
||||||
|
|||||||
@@ -123,6 +123,145 @@ def js_coins(self, url_split, post_string, is_json) -> bytes:
|
|||||||
return bytes(json.dumps(coins), "UTF-8")
|
return bytes(json.dumps(coins), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
|
def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
|
||||||
|
swap_client = self.server.swap_client
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
swap_client.updateWalletsInfo()
|
||||||
|
wallets = swap_client.getCachedWalletsInfo()
|
||||||
|
coins_with_balances = []
|
||||||
|
|
||||||
|
for k, v in swap_client.coin_clients.items():
|
||||||
|
if k not in chainparams:
|
||||||
|
continue
|
||||||
|
if v["connection_type"] == "rpc":
|
||||||
|
|
||||||
|
balance = "0.0"
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "balance" in w and "error" not in w and "no_data" not in w:
|
||||||
|
raw_balance = w["balance"]
|
||||||
|
if isinstance(raw_balance, float):
|
||||||
|
balance = f"{raw_balance:.8f}".rstrip("0").rstrip(".")
|
||||||
|
elif isinstance(raw_balance, int):
|
||||||
|
balance = str(raw_balance)
|
||||||
|
else:
|
||||||
|
balance = raw_balance
|
||||||
|
|
||||||
|
pending = "0.0"
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "error" not in w and "no_data" not in w:
|
||||||
|
ci = swap_client.ci(k)
|
||||||
|
pending_amount = 0
|
||||||
|
if "unconfirmed" in w and float(w["unconfirmed"]) > 0.0:
|
||||||
|
pending_amount += ci.make_int(w["unconfirmed"])
|
||||||
|
if "immature" in w and float(w["immature"]) > 0.0:
|
||||||
|
pending_amount += ci.make_int(w["immature"])
|
||||||
|
if pending_amount > 0:
|
||||||
|
pending = ci.format_amount(pending_amount)
|
||||||
|
|
||||||
|
coin_entry = {
|
||||||
|
"id": int(k),
|
||||||
|
"name": getCoinName(k),
|
||||||
|
"balance": balance,
|
||||||
|
"pending": pending,
|
||||||
|
"ticker": chainparams[k]["ticker"],
|
||||||
|
}
|
||||||
|
|
||||||
|
coins_with_balances.append(coin_entry)
|
||||||
|
|
||||||
|
if k == Coins.PART:
|
||||||
|
variants = [
|
||||||
|
{
|
||||||
|
"coin": Coins.PART_ANON,
|
||||||
|
"balance_field": "anon_balance",
|
||||||
|
"pending_field": "anon_pending",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"coin": Coins.PART_BLIND,
|
||||||
|
"balance_field": "blind_balance",
|
||||||
|
"pending_field": "blind_unconfirmed",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for variant_info in variants:
|
||||||
|
variant_balance = "0.0"
|
||||||
|
variant_pending = "0.0"
|
||||||
|
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "error" not in w and "no_data" not in w:
|
||||||
|
if variant_info["balance_field"] in w:
|
||||||
|
raw_balance = w[variant_info["balance_field"]]
|
||||||
|
if isinstance(raw_balance, float):
|
||||||
|
variant_balance = f"{raw_balance:.8f}".rstrip(
|
||||||
|
"0"
|
||||||
|
).rstrip(".")
|
||||||
|
elif isinstance(raw_balance, int):
|
||||||
|
variant_balance = str(raw_balance)
|
||||||
|
else:
|
||||||
|
variant_balance = raw_balance
|
||||||
|
|
||||||
|
if (
|
||||||
|
variant_info["pending_field"] in w
|
||||||
|
and float(w[variant_info["pending_field"]]) > 0.0
|
||||||
|
):
|
||||||
|
variant_pending = str(
|
||||||
|
w[variant_info["pending_field"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
variant_entry = {
|
||||||
|
"id": int(variant_info["coin"]),
|
||||||
|
"name": getCoinName(variant_info["coin"]),
|
||||||
|
"balance": variant_balance,
|
||||||
|
"pending": variant_pending,
|
||||||
|
"ticker": chainparams[Coins.PART]["ticker"],
|
||||||
|
}
|
||||||
|
|
||||||
|
coins_with_balances.append(variant_entry)
|
||||||
|
|
||||||
|
elif k == Coins.LTC:
|
||||||
|
variant_balance = "0.0"
|
||||||
|
variant_pending = "0.0"
|
||||||
|
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "error" not in w and "no_data" not in w:
|
||||||
|
if "mweb_balance" in w:
|
||||||
|
variant_balance = w["mweb_balance"]
|
||||||
|
|
||||||
|
pending_amount = 0
|
||||||
|
if (
|
||||||
|
"mweb_unconfirmed" in w
|
||||||
|
and float(w["mweb_unconfirmed"]) > 0.0
|
||||||
|
):
|
||||||
|
pending_amount += float(w["mweb_unconfirmed"])
|
||||||
|
if "mweb_immature" in w and float(w["mweb_immature"]) > 0.0:
|
||||||
|
pending_amount += float(w["mweb_immature"])
|
||||||
|
if pending_amount > 0:
|
||||||
|
variant_pending = f"{pending_amount:.8f}".rstrip(
|
||||||
|
"0"
|
||||||
|
).rstrip(".")
|
||||||
|
|
||||||
|
variant_entry = {
|
||||||
|
"id": int(Coins.LTC_MWEB),
|
||||||
|
"name": getCoinName(Coins.LTC_MWEB),
|
||||||
|
"balance": variant_balance,
|
||||||
|
"pending": variant_pending,
|
||||||
|
"ticker": chainparams[Coins.LTC]["ticker"],
|
||||||
|
}
|
||||||
|
|
||||||
|
coins_with_balances.append(variant_entry)
|
||||||
|
|
||||||
|
return bytes(json.dumps(coins_with_balances), "UTF-8")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_data = {"error": str(e)}
|
||||||
|
return bytes(json.dumps(error_data), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
def js_wallets(self, url_split, post_string, is_json):
|
def js_wallets(self, url_split, post_string, is_json):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
swap_client.checkSystemStatus()
|
swap_client.checkSystemStatus()
|
||||||
@@ -1214,6 +1353,7 @@ def js_messageroutes(self, url_split, post_string, is_json) -> bytes:
|
|||||||
|
|
||||||
endpoints = {
|
endpoints = {
|
||||||
"coins": js_coins,
|
"coins": js_coins,
|
||||||
|
"walletbalances": js_walletbalances,
|
||||||
"wallets": js_wallets,
|
"wallets": js_wallets,
|
||||||
"offers": js_offers,
|
"offers": js_offers,
|
||||||
"sentoffers": js_sentoffers,
|
"sentoffers": js_sentoffers,
|
||||||
|
|||||||
@@ -14,6 +14,62 @@
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast Notification Animations */
|
||||||
|
.toast-slide-in {
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-slide-out {
|
||||||
|
animation: slideOutRight 0.3s ease-in forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideOutRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast Container Styles */
|
||||||
|
#ul_updates {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ul_updates li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast Hover Effects */
|
||||||
|
#ul_updates .bg-white:hover {
|
||||||
|
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #ul_updates .dark\:bg-gray-800:hover {
|
||||||
|
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
/* Table Styles */
|
/* Table Styles */
|
||||||
.padded_row td {
|
.padded_row td {
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
|
|||||||
@@ -23,13 +23,7 @@ const AmmTablesManager = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function debugLog(message, data) {
|
function debugLog(message, data) {
|
||||||
// if (isDebugEnabled()) {
|
|
||||||
// if (data) {
|
|
||||||
// console.log(`[AmmTables] ${message}`, data);
|
|
||||||
// } else {
|
|
||||||
// console.log(`[AmmTables] ${message}`);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeTabs() {
|
function initializeTabs() {
|
||||||
@@ -309,8 +303,11 @@ const AmmTablesManager = (function() {
|
|||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (offersBody.innerHTML.trim() !== tableHtml.trim()) {
|
||||||
offersBody.innerHTML = tableHtml;
|
offersBody.innerHTML = tableHtml;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderBidsTable(stateData) {
|
function renderBidsTable(stateData) {
|
||||||
if (!bidsBody) return;
|
if (!bidsBody) return;
|
||||||
@@ -441,8 +438,11 @@ const AmmTablesManager = (function() {
|
|||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (bidsBody.innerHTML.trim() !== tableHtml.trim()) {
|
||||||
bidsBody.innerHTML = tableHtml;
|
bidsBody.innerHTML = tableHtml;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
function formatDuration(seconds) {
|
||||||
if (seconds < 60) return `${seconds}s`;
|
if (seconds < 60) return `${seconds}s`;
|
||||||
@@ -724,6 +724,429 @@ const AmmTablesManager = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldDropdownOptionsShowBalance(select) {
|
||||||
|
const isMakerDropdown = select.id.includes('coin-from');
|
||||||
|
const isTakerDropdown = select.id.includes('coin-to');
|
||||||
|
|
||||||
|
|
||||||
|
const addModal = document.getElementById('add-amm-modal');
|
||||||
|
const editModal = document.getElementById('edit-amm-modal');
|
||||||
|
const addModalVisible = addModal && !addModal.classList.contains('hidden');
|
||||||
|
const editModalVisible = editModal && !editModal.classList.contains('hidden');
|
||||||
|
|
||||||
|
let isBidModal = false;
|
||||||
|
if (addModalVisible) {
|
||||||
|
const dataType = addModal.getAttribute('data-amm-type');
|
||||||
|
if (dataType) {
|
||||||
|
isBidModal = dataType === 'bid';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const modalTitle = document.getElementById('add-modal-title');
|
||||||
|
isBidModal = modalTitle && modalTitle.textContent && modalTitle.textContent.includes('Bid');
|
||||||
|
}
|
||||||
|
} else if (editModalVisible) {
|
||||||
|
const dataType = editModal.getAttribute('data-amm-type');
|
||||||
|
if (dataType) {
|
||||||
|
isBidModal = dataType === 'bid';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const modalTitle = document.getElementById('edit-modal-title');
|
||||||
|
isBidModal = modalTitle && modalTitle.textContent && modalTitle.textContent.includes('Bid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const result = isBidModal ? isTakerDropdown : isMakerDropdown;
|
||||||
|
|
||||||
|
console.log(`[DEBUG] shouldDropdownOptionsShowBalance: ${select.id}, isBidModal=${isBidModal}, isMaker=${isMakerDropdown}, isTaker=${isTakerDropdown}, result=${result}`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDropdownOptions() {
|
||||||
|
const dropdownIds = ['add-amm-coin-from', 'add-amm-coin-to', 'edit-amm-coin-from', 'edit-amm-coin-to'];
|
||||||
|
|
||||||
|
dropdownIds.forEach(dropdownId => {
|
||||||
|
const select = document.getElementById(dropdownId);
|
||||||
|
if (!select || select.style.display !== 'none') return;
|
||||||
|
|
||||||
|
const wrapper = select.parentNode.querySelector('.relative');
|
||||||
|
if (!wrapper) return;
|
||||||
|
|
||||||
|
|
||||||
|
const dropdown = wrapper.querySelector('[role="listbox"]');
|
||||||
|
if (!dropdown) return;
|
||||||
|
|
||||||
|
|
||||||
|
const options = dropdown.querySelectorAll('[data-value]');
|
||||||
|
options.forEach(optionElement => {
|
||||||
|
const coinValue = optionElement.getAttribute('data-value');
|
||||||
|
const originalOption = Array.from(select.options).find(opt => opt.value === coinValue);
|
||||||
|
if (!originalOption) return;
|
||||||
|
|
||||||
|
|
||||||
|
const textContainer = optionElement.querySelector('div.flex.flex-col, div.flex.items-center');
|
||||||
|
if (!textContainer) return;
|
||||||
|
|
||||||
|
|
||||||
|
textContainer.innerHTML = '';
|
||||||
|
|
||||||
|
const shouldShowBalance = shouldDropdownOptionsShowBalance(select);
|
||||||
|
const fullText = originalOption.textContent.trim();
|
||||||
|
const balance = originalOption.getAttribute('data-balance') || '0.00000000';
|
||||||
|
|
||||||
|
console.log(`[DEBUG] refreshDropdownOptions: ${select.id}, option=${coinValue}, shouldShowBalance=${shouldShowBalance}, balance=${balance}`);
|
||||||
|
|
||||||
|
if (shouldShowBalance) {
|
||||||
|
|
||||||
|
textContainer.className = 'flex flex-col';
|
||||||
|
|
||||||
|
const coinName = fullText.includes(' - Balance: ') ? fullText.split(' - Balance: ')[0] : fullText;
|
||||||
|
|
||||||
|
const coinNameSpan = document.createElement('span');
|
||||||
|
coinNameSpan.textContent = coinName;
|
||||||
|
coinNameSpan.className = 'text-gray-900 dark:text-white';
|
||||||
|
|
||||||
|
const balanceSpan = document.createElement('span');
|
||||||
|
balanceSpan.textContent = `Balance: ${balance}`;
|
||||||
|
balanceSpan.className = 'text-gray-500 dark:text-gray-400 text-xs';
|
||||||
|
|
||||||
|
textContainer.appendChild(coinNameSpan);
|
||||||
|
textContainer.appendChild(balanceSpan);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
textContainer.className = 'flex items-center';
|
||||||
|
|
||||||
|
const coinNameSpan = document.createElement('span');
|
||||||
|
const coinName = fullText.includes(' - Balance: ') ? fullText.split(' - Balance: ')[0] : fullText;
|
||||||
|
coinNameSpan.textContent = coinName;
|
||||||
|
coinNameSpan.className = 'text-gray-900 dark:text-white';
|
||||||
|
|
||||||
|
textContainer.appendChild(coinNameSpan);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function refreshDropdownBalances() {
|
||||||
|
const dropdownIds = ['add-amm-coin-from', 'add-amm-coin-to', 'edit-amm-coin-from', 'edit-amm-coin-to'];
|
||||||
|
|
||||||
|
dropdownIds.forEach(dropdownId => {
|
||||||
|
const select = document.getElementById(dropdownId);
|
||||||
|
if (!select || select.style.display !== 'none') return;
|
||||||
|
|
||||||
|
const wrapper = select.parentNode.querySelector('.relative');
|
||||||
|
if (!wrapper) return;
|
||||||
|
|
||||||
|
|
||||||
|
const dropdownItems = wrapper.querySelectorAll('[data-value]');
|
||||||
|
dropdownItems.forEach(item => {
|
||||||
|
const value = item.getAttribute('data-value');
|
||||||
|
const option = select.querySelector(`option[value="${value}"]`);
|
||||||
|
if (option) {
|
||||||
|
const balance = option.getAttribute('data-balance') || '0.00000000';
|
||||||
|
const pendingBalance = option.getAttribute('data-pending-balance') || '';
|
||||||
|
|
||||||
|
const balanceDiv = item.querySelector('.text-xs');
|
||||||
|
if (balanceDiv) {
|
||||||
|
balanceDiv.textContent = `Balance: ${balance}`;
|
||||||
|
|
||||||
|
|
||||||
|
let pendingDiv = item.querySelector('.text-green-500');
|
||||||
|
if (pendingBalance && parseFloat(pendingBalance) > 0) {
|
||||||
|
if (!pendingDiv) {
|
||||||
|
|
||||||
|
pendingDiv = document.createElement('div');
|
||||||
|
pendingDiv.className = 'text-green-500 text-xs';
|
||||||
|
balanceDiv.parentNode.appendChild(pendingDiv);
|
||||||
|
}
|
||||||
|
pendingDiv.textContent = `+${pendingBalance} pending`;
|
||||||
|
} else if (pendingDiv) {
|
||||||
|
|
||||||
|
pendingDiv.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
if (selectedOption) {
|
||||||
|
const textContainer = wrapper.querySelector('button .flex-grow');
|
||||||
|
const balanceDiv = textContainer ? textContainer.querySelector('.text-xs') : null;
|
||||||
|
if (balanceDiv) {
|
||||||
|
const balance = selectedOption.getAttribute('data-balance') || '0.00000000';
|
||||||
|
const pendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
|
||||||
|
|
||||||
|
balanceDiv.textContent = `Balance: ${balance}`;
|
||||||
|
|
||||||
|
|
||||||
|
let pendingDiv = textContainer.querySelector('.text-green-500');
|
||||||
|
if (pendingBalance && parseFloat(pendingBalance) > 0) {
|
||||||
|
if (!pendingDiv) {
|
||||||
|
|
||||||
|
pendingDiv = document.createElement('div');
|
||||||
|
pendingDiv.className = 'text-green-500 text-xs';
|
||||||
|
textContainer.appendChild(pendingDiv);
|
||||||
|
}
|
||||||
|
pendingDiv.textContent = `+${pendingBalance} pending`;
|
||||||
|
} else if (pendingDiv) {
|
||||||
|
|
||||||
|
pendingDiv.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshOfferDropdownBalanceDisplay() {
|
||||||
|
refreshDropdownBalances();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshBidDropdownBalanceDisplay() {
|
||||||
|
refreshDropdownBalances();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDropdownBalanceDisplay(modalType = null) {
|
||||||
|
if (modalType === 'offer') {
|
||||||
|
refreshOfferDropdownBalanceDisplay();
|
||||||
|
} else if (modalType === 'bid') {
|
||||||
|
refreshBidDropdownBalanceDisplay();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const addModal = document.getElementById('add-amm-modal');
|
||||||
|
const editModal = document.getElementById('edit-amm-modal');
|
||||||
|
const addModalVisible = addModal && !addModal.classList.contains('hidden');
|
||||||
|
const editModalVisible = editModal && !editModal.classList.contains('hidden');
|
||||||
|
|
||||||
|
let detectedType = null;
|
||||||
|
if (addModalVisible) {
|
||||||
|
detectedType = addModal.getAttribute('data-amm-type');
|
||||||
|
} else if (editModalVisible) {
|
||||||
|
detectedType = editModal.getAttribute('data-amm-type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectedType === 'offer') {
|
||||||
|
refreshOfferDropdownBalanceDisplay();
|
||||||
|
} else if (detectedType === 'bid') {
|
||||||
|
refreshBidDropdownBalanceDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDropdownsForModalType(modalPrefix) {
|
||||||
|
const coinFromSelect = document.getElementById(`${modalPrefix}-amm-coin-from`);
|
||||||
|
const coinToSelect = document.getElementById(`${modalPrefix}-amm-coin-to`);
|
||||||
|
|
||||||
|
if (!coinFromSelect || !coinToSelect) return;
|
||||||
|
|
||||||
|
|
||||||
|
const balanceData = {};
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(coinFromSelect.options).forEach(option => {
|
||||||
|
const balance = option.getAttribute('data-balance');
|
||||||
|
if (balance) {
|
||||||
|
balanceData[option.value] = balance;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(coinToSelect.options).forEach(option => {
|
||||||
|
const balance = option.getAttribute('data-balance');
|
||||||
|
if (balance) {
|
||||||
|
balanceData[option.value] = balance;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
updateDropdownOptions(coinFromSelect, balanceData);
|
||||||
|
updateDropdownOptions(coinToSelect, balanceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDropdownOptions(select, balanceData, pendingData = {}) {
|
||||||
|
Array.from(select.options).forEach(option => {
|
||||||
|
const coinName = option.value;
|
||||||
|
const balance = balanceData[coinName] || '0.00000000';
|
||||||
|
const pending = pendingData[coinName] || '0.0';
|
||||||
|
|
||||||
|
|
||||||
|
option.setAttribute('data-balance', balance);
|
||||||
|
option.setAttribute('data-pending-balance', pending);
|
||||||
|
|
||||||
|
|
||||||
|
option.textContent = coinName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSimpleDropdown(select, showBalance = false) {
|
||||||
|
if (!select) return;
|
||||||
|
|
||||||
|
|
||||||
|
const existingWrapper = select.parentNode.querySelector('.relative');
|
||||||
|
if (existingWrapper) {
|
||||||
|
existingWrapper.remove();
|
||||||
|
select.style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
select.style.display = 'none';
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'relative';
|
||||||
|
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.className = 'flex items-center justify-between w-full p-2.5 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:text-white';
|
||||||
|
button.style.minHeight = '60px';
|
||||||
|
|
||||||
|
|
||||||
|
const displayContent = document.createElement('div');
|
||||||
|
displayContent.className = 'flex items-center';
|
||||||
|
|
||||||
|
const icon = document.createElement('img');
|
||||||
|
icon.className = 'w-5 h-5 mr-2';
|
||||||
|
icon.alt = '';
|
||||||
|
|
||||||
|
const textContainer = document.createElement('div');
|
||||||
|
textContainer.className = 'flex-grow text-left';
|
||||||
|
|
||||||
|
const arrow = document.createElement('div');
|
||||||
|
arrow.innerHTML = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>`;
|
||||||
|
|
||||||
|
displayContent.appendChild(icon);
|
||||||
|
displayContent.appendChild(textContainer);
|
||||||
|
button.appendChild(displayContent);
|
||||||
|
button.appendChild(arrow);
|
||||||
|
|
||||||
|
|
||||||
|
const dropdown = document.createElement('div');
|
||||||
|
dropdown.className = 'absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg hidden dark:bg-gray-700 dark:border-gray-600 max-h-60 overflow-y-auto';
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(select.options).forEach(option => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'flex items-center p-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer';
|
||||||
|
item.setAttribute('data-value', option.value);
|
||||||
|
|
||||||
|
const itemIcon = document.createElement('img');
|
||||||
|
itemIcon.className = 'w-5 h-5 mr-2';
|
||||||
|
itemIcon.src = `/static/images/coins/${getImageFilename(option.value)}`;
|
||||||
|
itemIcon.alt = '';
|
||||||
|
|
||||||
|
const itemText = document.createElement('div');
|
||||||
|
const coinName = option.textContent.trim();
|
||||||
|
const balance = option.getAttribute('data-balance') || '0.00000000';
|
||||||
|
const pendingBalance = option.getAttribute('data-pending-balance') || '';
|
||||||
|
|
||||||
|
if (showBalance) {
|
||||||
|
itemText.className = 'flex flex-col';
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="text-gray-900 dark:text-white">${coinName}</div>
|
||||||
|
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${balance}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
if (pendingBalance && parseFloat(pendingBalance) > 0) {
|
||||||
|
html += `<div class="text-green-500 text-xs">+${pendingBalance} pending</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemText.innerHTML = html;
|
||||||
|
} else {
|
||||||
|
itemText.className = 'text-gray-900 dark:text-white';
|
||||||
|
itemText.textContent = coinName;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.appendChild(itemIcon);
|
||||||
|
item.appendChild(itemText);
|
||||||
|
|
||||||
|
|
||||||
|
item.addEventListener('click', function() {
|
||||||
|
select.value = this.getAttribute('data-value');
|
||||||
|
|
||||||
|
|
||||||
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
const selectedCoinName = selectedOption.textContent.trim();
|
||||||
|
const selectedBalance = selectedOption.getAttribute('data-balance') || '0.00000000';
|
||||||
|
const selectedPendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
|
||||||
|
|
||||||
|
icon.src = itemIcon.src;
|
||||||
|
|
||||||
|
if (showBalance) {
|
||||||
|
let html = `
|
||||||
|
<div class="text-gray-900 dark:text-white">${selectedCoinName}</div>
|
||||||
|
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${selectedBalance}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
if (selectedPendingBalance && parseFloat(selectedPendingBalance) > 0) {
|
||||||
|
html += `<div class="text-green-500 text-xs">+${selectedPendingBalance} pending</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
textContainer.innerHTML = html;
|
||||||
|
textContainer.className = 'flex-grow text-left flex flex-col justify-center';
|
||||||
|
} else {
|
||||||
|
textContainer.textContent = selectedCoinName;
|
||||||
|
textContainer.className = 'flex-grow text-left';
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.classList.add('hidden');
|
||||||
|
|
||||||
|
|
||||||
|
const event = new Event('change', { bubbles: true });
|
||||||
|
select.dispatchEvent(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdown.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
if (selectedOption) {
|
||||||
|
const selectedCoinName = selectedOption.textContent.trim();
|
||||||
|
const selectedBalance = selectedOption.getAttribute('data-balance') || '0.00000000';
|
||||||
|
const selectedPendingBalance = selectedOption.getAttribute('data-pending-balance') || '';
|
||||||
|
|
||||||
|
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
|
||||||
|
|
||||||
|
if (showBalance) {
|
||||||
|
let html = `
|
||||||
|
<div class="text-gray-900 dark:text-white">${selectedCoinName}</div>
|
||||||
|
<div class="text-gray-500 dark:text-gray-400 text-xs">Balance: ${selectedBalance}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
if (selectedPendingBalance && parseFloat(selectedPendingBalance) > 0) {
|
||||||
|
html += `<div class="text-green-500 text-xs">+${selectedPendingBalance} pending</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
textContainer.innerHTML = html;
|
||||||
|
textContainer.className = 'flex-grow text-left flex flex-col justify-center';
|
||||||
|
} else {
|
||||||
|
textContainer.textContent = selectedCoinName;
|
||||||
|
textContainer.className = 'flex-grow text-left';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
dropdown.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!wrapper.contains(e.target)) {
|
||||||
|
dropdown.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.appendChild(button);
|
||||||
|
wrapper.appendChild(dropdown);
|
||||||
|
select.parentNode.insertBefore(wrapper, select);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function setupButtonHandlers() {
|
function setupButtonHandlers() {
|
||||||
const addOfferButton = document.getElementById('add-new-offer-btn');
|
const addOfferButton = document.getElementById('add-new-offer-btn');
|
||||||
if (addOfferButton) {
|
if (addOfferButton) {
|
||||||
@@ -844,6 +1267,40 @@ const AmmTablesManager = (function() {
|
|||||||
modalTitle.textContent = `Add New ${type.charAt(0).toUpperCase() + type.slice(1)}`;
|
modalTitle.textContent = `Add New ${type.charAt(0).toUpperCase() + type.slice(1)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const modal = document.getElementById('add-amm-modal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
modal.setAttribute('data-amm-type', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
updateDropdownsForModalType('add');
|
||||||
|
|
||||||
|
initializeCustomSelects(type);
|
||||||
|
|
||||||
|
|
||||||
|
refreshDropdownBalanceDisplay(type);
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof fetchBalanceData === 'function') {
|
||||||
|
fetchBalanceData()
|
||||||
|
.then(balanceData => {
|
||||||
|
if (type === 'offer' && typeof updateOfferDropdownBalances === 'function') {
|
||||||
|
updateOfferDropdownBalances(balanceData);
|
||||||
|
} else if (type === 'bid' && typeof updateBidDropdownBalances === 'function') {
|
||||||
|
updateBidDropdownBalances(balanceData);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating dropdown balances:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
document.getElementById('add-amm-type').value = type;
|
document.getElementById('add-amm-type').value = type;
|
||||||
|
|
||||||
document.getElementById('add-amm-name').value = 'Unnamed Offer';
|
document.getElementById('add-amm-name').value = 'Unnamed Offer';
|
||||||
@@ -940,11 +1397,6 @@ const AmmTablesManager = (function() {
|
|||||||
if (type === 'offer') {
|
if (type === 'offer') {
|
||||||
setupBiddingControls('add');
|
setupBiddingControls('add');
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('add-amm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAddModal() {
|
function closeAddModal() {
|
||||||
@@ -1269,6 +1721,40 @@ const AmmTablesManager = (function() {
|
|||||||
modalTitle.textContent = `Edit ${type.charAt(0).toUpperCase() + type.slice(1)}`;
|
modalTitle.textContent = `Edit ${type.charAt(0).toUpperCase() + type.slice(1)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const modal = document.getElementById('edit-amm-modal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
modal.setAttribute('data-amm-type', type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
updateDropdownsForModalType('edit');
|
||||||
|
|
||||||
|
initializeCustomSelects(type);
|
||||||
|
|
||||||
|
|
||||||
|
refreshDropdownBalanceDisplay(type);
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof fetchBalanceData === 'function') {
|
||||||
|
fetchBalanceData()
|
||||||
|
.then(balanceData => {
|
||||||
|
if (type === 'offer' && typeof updateOfferDropdownBalances === 'function') {
|
||||||
|
updateOfferDropdownBalances(balanceData);
|
||||||
|
} else if (type === 'bid' && typeof updateBidDropdownBalances === 'function') {
|
||||||
|
updateBidDropdownBalances(balanceData);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating dropdown balances:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
document.getElementById('edit-amm-type').value = type;
|
document.getElementById('edit-amm-type').value = type;
|
||||||
document.getElementById('edit-amm-id').value = id || '';
|
document.getElementById('edit-amm-id').value = id || '';
|
||||||
document.getElementById('edit-amm-original-name').value = name;
|
document.getElementById('edit-amm-original-name').value = name;
|
||||||
@@ -1282,8 +1768,12 @@ const AmmTablesManager = (function() {
|
|||||||
coinFromSelect.value = item.coin_from || '';
|
coinFromSelect.value = item.coin_from || '';
|
||||||
coinToSelect.value = item.coin_to || '';
|
coinToSelect.value = item.coin_to || '';
|
||||||
|
|
||||||
|
if (coinFromSelect) {
|
||||||
coinFromSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
coinFromSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
if (coinToSelect) {
|
||||||
coinToSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
coinToSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('edit-amm-amount').value = item.amount || '';
|
document.getElementById('edit-amm-amount').value = item.amount || '';
|
||||||
|
|
||||||
@@ -1370,11 +1860,6 @@ const AmmTablesManager = (function() {
|
|||||||
setupBiddingControls('edit');
|
setupBiddingControls('edit');
|
||||||
populateBiddingControls('edit', item);
|
populateBiddingControls('edit', item);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('edit-amm-modal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(`Error processing the configuration: ${error.message}`);
|
alert(`Error processing the configuration: ${error.message}`);
|
||||||
debugLog('Error opening edit modal:', error);
|
debugLog('Error opening edit modal:', error);
|
||||||
@@ -1808,7 +2293,7 @@ const AmmTablesManager = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeCustomSelects() {
|
function initializeCustomSelects(modalType = null) {
|
||||||
const coinSelects = [
|
const coinSelects = [
|
||||||
document.getElementById('add-amm-coin-from'),
|
document.getElementById('add-amm-coin-from'),
|
||||||
document.getElementById('add-amm-coin-to'),
|
document.getElementById('add-amm-coin-to'),
|
||||||
@@ -1821,116 +2306,16 @@ const AmmTablesManager = (function() {
|
|||||||
document.getElementById('edit-offer-swap-type')
|
document.getElementById('edit-offer-swap-type')
|
||||||
];
|
];
|
||||||
|
|
||||||
function createCoinDropdown(select) {
|
|
||||||
if (!select) return;
|
|
||||||
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
wrapper.className = 'relative';
|
|
||||||
|
|
||||||
const display = document.createElement('div');
|
|
||||||
display.className = 'flex items-center w-full p-2.5 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white cursor-pointer';
|
|
||||||
|
|
||||||
const icon = document.createElement('img');
|
|
||||||
icon.className = 'w-5 h-5 mr-2';
|
|
||||||
icon.alt = '';
|
|
||||||
|
|
||||||
const text = document.createElement('span');
|
|
||||||
text.className = 'flex-grow';
|
|
||||||
|
|
||||||
const arrow = document.createElement('span');
|
|
||||||
arrow.className = 'ml-2';
|
|
||||||
arrow.innerHTML = `
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
|
|
||||||
display.appendChild(icon);
|
|
||||||
display.appendChild(text);
|
|
||||||
display.appendChild(arrow);
|
|
||||||
|
|
||||||
const dropdown = document.createElement('div');
|
|
||||||
dropdown.className = 'absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg hidden dark:bg-gray-700 dark:border-gray-600 max-h-60 overflow-y-auto';
|
|
||||||
|
|
||||||
Array.from(select.options).forEach(option => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
item.className = 'flex items-center p-2 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer text-gray-900 dark:text-white';
|
|
||||||
item.setAttribute('data-value', option.value);
|
|
||||||
item.setAttribute('data-symbol', option.getAttribute('data-symbol') || '');
|
|
||||||
|
|
||||||
const optionIcon = document.createElement('img');
|
|
||||||
optionIcon.className = 'w-5 h-5 mr-2';
|
|
||||||
optionIcon.src = `/static/images/coins/${getImageFilename(option.value)}`;
|
|
||||||
optionIcon.alt = '';
|
|
||||||
|
|
||||||
const optionText = document.createElement('span');
|
|
||||||
optionText.textContent = option.textContent.trim();
|
|
||||||
|
|
||||||
item.appendChild(optionIcon);
|
|
||||||
item.appendChild(optionText);
|
|
||||||
|
|
||||||
item.addEventListener('click', function() {
|
|
||||||
select.value = this.getAttribute('data-value');
|
|
||||||
|
|
||||||
text.textContent = optionText.textContent;
|
|
||||||
icon.src = optionIcon.src;
|
|
||||||
|
|
||||||
dropdown.classList.add('hidden');
|
|
||||||
|
|
||||||
const event = new Event('change', { bubbles: true });
|
|
||||||
select.dispatchEvent(event);
|
|
||||||
|
|
||||||
if (select.id === 'add-amm-coin-from' || select.id === 'add-amm-coin-to') {
|
|
||||||
const coinFrom = document.getElementById('add-amm-coin-from');
|
|
||||||
const coinTo = document.getElementById('add-amm-coin-to');
|
|
||||||
const swapType = document.getElementById('add-offer-swap-type');
|
|
||||||
|
|
||||||
if (coinFrom && coinTo && swapType) {
|
|
||||||
updateSwapTypeOptions(coinFrom.value, coinTo.value, swapType);
|
|
||||||
}
|
|
||||||
} else if (select.id === 'edit-amm-coin-from' || select.id === 'edit-amm-coin-to') {
|
|
||||||
const coinFrom = document.getElementById('edit-amm-coin-from');
|
|
||||||
const coinTo = document.getElementById('edit-amm-coin-to');
|
|
||||||
const swapType = document.getElementById('edit-offer-swap-type');
|
|
||||||
|
|
||||||
if (coinFrom && coinTo && swapType) {
|
|
||||||
updateSwapTypeOptions(coinFrom.value, coinTo.value, swapType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dropdown.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedOption = select.options[select.selectedIndex];
|
|
||||||
text.textContent = selectedOption.textContent.trim();
|
|
||||||
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
|
|
||||||
|
|
||||||
display.addEventListener('click', function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
dropdown.classList.toggle('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function() {
|
|
||||||
dropdown.classList.add('hidden');
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.appendChild(display);
|
|
||||||
wrapper.appendChild(dropdown);
|
|
||||||
select.parentNode.insertBefore(wrapper, select);
|
|
||||||
|
|
||||||
select.style.display = 'none';
|
|
||||||
|
|
||||||
select.addEventListener('change', function() {
|
|
||||||
const selectedOption = this.options[this.selectedIndex];
|
|
||||||
text.textContent = selectedOption.textContent.trim();
|
|
||||||
icon.src = `/static/images/coins/${getImageFilename(selectedOption.value)}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSwapTypeDropdown(select) {
|
function createSwapTypeDropdown(select) {
|
||||||
if (!select) return;
|
if (!select) return;
|
||||||
|
|
||||||
|
|
||||||
|
if (select.style.display === 'none' && select.parentNode.querySelector('.relative')) {
|
||||||
|
return; // Custom dropdown already exists
|
||||||
|
}
|
||||||
|
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.className = 'relative';
|
wrapper.className = 'relative';
|
||||||
|
|
||||||
@@ -1980,7 +2365,9 @@ const AmmTablesManager = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedOption = select.options[select.selectedIndex];
|
const selectedOption = select.options[select.selectedIndex];
|
||||||
|
if (selectedOption) {
|
||||||
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
|
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
|
||||||
|
}
|
||||||
|
|
||||||
display.addEventListener('click', function(e) {
|
display.addEventListener('click', function(e) {
|
||||||
if (select.disabled) return;
|
if (select.disabled) return;
|
||||||
@@ -2000,7 +2387,9 @@ const AmmTablesManager = (function() {
|
|||||||
|
|
||||||
select.addEventListener('change', function() {
|
select.addEventListener('change', function() {
|
||||||
const selectedOption = this.options[this.selectedIndex];
|
const selectedOption = this.options[this.selectedIndex];
|
||||||
|
if (selectedOption) {
|
||||||
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
|
text.textContent = selectedOption.getAttribute('data-desc') || selectedOption.textContent.trim();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const observer = new MutationObserver(function(mutations) {
|
const observer = new MutationObserver(function(mutations) {
|
||||||
@@ -2022,7 +2411,18 @@ const AmmTablesManager = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coinSelects.forEach(select => createCoinDropdown(select));
|
coinSelects.forEach(select => {
|
||||||
|
if (!select) return;
|
||||||
|
|
||||||
|
let showBalance = false;
|
||||||
|
if (modalType === 'offer' && select.id.includes('coin-from')) {
|
||||||
|
showBalance = true; // OFFER: maker shows balance
|
||||||
|
} else if (modalType === 'bid' && select.id.includes('coin-to')) {
|
||||||
|
showBalance = true; // BID: taker shows balance
|
||||||
|
}
|
||||||
|
|
||||||
|
createSimpleDropdown(select, showBalance);
|
||||||
|
});
|
||||||
|
|
||||||
swapTypeSelects.forEach(select => createSwapTypeDropdown(select));
|
swapTypeSelects.forEach(select => createSwapTypeDropdown(select));
|
||||||
}
|
}
|
||||||
@@ -2301,19 +2701,27 @@ const AmmTablesManager = (function() {
|
|||||||
|
|
||||||
if (refreshButton) {
|
if (refreshButton) {
|
||||||
refreshButton.addEventListener('click', async function() {
|
refreshButton.addEventListener('click', async function() {
|
||||||
|
|
||||||
|
if (refreshButton.disabled) return;
|
||||||
|
|
||||||
const icon = refreshButton.querySelector('svg');
|
const icon = refreshButton.querySelector('svg');
|
||||||
|
refreshButton.disabled = true;
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
icon.classList.add('animate-spin');
|
icon.classList.add('animate-spin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
await initializePrices();
|
await initializePrices();
|
||||||
updateTables();
|
updateTables();
|
||||||
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (icon) {
|
if (icon) {
|
||||||
icon.classList.remove('animate-spin');
|
icon.classList.remove('animate-spin');
|
||||||
}
|
}
|
||||||
}, 1000);
|
refreshButton.disabled = false;
|
||||||
|
}, 500); // Reduced from 1000ms to 500ms
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2326,7 +2734,11 @@ const AmmTablesManager = (function() {
|
|||||||
return {
|
return {
|
||||||
updateTables,
|
updateTables,
|
||||||
startRefreshTimer,
|
startRefreshTimer,
|
||||||
stopRefreshTimer
|
stopRefreshTimer,
|
||||||
|
refreshDropdownBalanceDisplay,
|
||||||
|
refreshOfferDropdownBalanceDisplay,
|
||||||
|
refreshBidDropdownBalanceDisplay,
|
||||||
|
refreshDropdownOptions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -367,16 +367,45 @@ const ApiManager = (function() {
|
|||||||
|
|
||||||
const results = {};
|
const results = {};
|
||||||
const fetchPromises = coinSymbols.map(async coin => {
|
const fetchPromises = coinSymbols.map(async coin => {
|
||||||
if (coin === 'WOW') {
|
let useCoinGecko = false;
|
||||||
|
let coingeckoId = null;
|
||||||
|
|
||||||
|
if (window.CoinManager) {
|
||||||
|
const coinConfig = window.CoinManager.getCoinByAnyIdentifier(coin);
|
||||||
|
if (coinConfig) {
|
||||||
|
useCoinGecko = !coinConfig.usesCryptoCompare || coin === 'PART';
|
||||||
|
coingeckoId = coinConfig.coingeckoId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const coinGeckoCoins = {
|
||||||
|
'WOW': 'wownero',
|
||||||
|
'PART': 'particl',
|
||||||
|
'BTC': 'bitcoin'
|
||||||
|
};
|
||||||
|
if (coinGeckoCoins[coin]) {
|
||||||
|
useCoinGecko = true;
|
||||||
|
coingeckoId = coinGeckoCoins[coin];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useCoinGecko && coingeckoId) {
|
||||||
return this.rateLimiter.queueRequest('coingecko', async () => {
|
return this.rateLimiter.queueRequest('coingecko', async () => {
|
||||||
const url = `https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=1`;
|
let days;
|
||||||
|
if (resolution === 'day') {
|
||||||
|
days = 1;
|
||||||
|
} else if (resolution === 'year') {
|
||||||
|
days = 365;
|
||||||
|
} else {
|
||||||
|
days = 180;
|
||||||
|
}
|
||||||
|
const url = `https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`;
|
||||||
try {
|
try {
|
||||||
const response = await this.makePostRequest(url);
|
const response = await this.makePostRequest(url);
|
||||||
if (response && response.prices) {
|
if (response && response.prices) {
|
||||||
results[coin] = response.prices;
|
results[coin] = response.prices;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching CoinGecko data for WOW:`, error);
|
console.error(`Error fetching CoinGecko data for ${coin}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
216
basicswap/static/js/modules/balance-updates.js
Normal file
216
basicswap/static/js/modules/balance-updates.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
const BalanceUpdatesManager = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
balanceUpdateDelay: 2000,
|
||||||
|
swapEventDelay: 5000,
|
||||||
|
periodicRefreshInterval: 120000,
|
||||||
|
walletPeriodicRefreshInterval: 60000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
handlers: new Map(),
|
||||||
|
timeouts: new Map(),
|
||||||
|
intervals: new Map(),
|
||||||
|
initialized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function fetchBalanceData() {
|
||||||
|
return fetch('/json/walletbalances', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(balanceData => {
|
||||||
|
if (balanceData.error) {
|
||||||
|
throw new Error(balanceData.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(balanceData)) {
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return balanceData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimeoutByKey(key) {
|
||||||
|
if (state.timeouts.has(key)) {
|
||||||
|
clearTimeout(state.timeouts.get(key));
|
||||||
|
state.timeouts.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTimeoutByKey(key, callback, delay) {
|
||||||
|
clearTimeoutByKey(key);
|
||||||
|
const timeoutId = setTimeout(callback, delay);
|
||||||
|
state.timeouts.set(key, timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearIntervalByKey(key) {
|
||||||
|
if (state.intervals.has(key)) {
|
||||||
|
clearInterval(state.intervals.get(key));
|
||||||
|
state.intervals.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIntervalByKey(key, callback, interval) {
|
||||||
|
clearIntervalByKey(key);
|
||||||
|
const intervalId = setInterval(callback, interval);
|
||||||
|
state.intervals.set(key, intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBalanceUpdate(contextKey, updateCallback, errorContext) {
|
||||||
|
clearTimeoutByKey(`${contextKey}_balance_update`);
|
||||||
|
setTimeoutByKey(`${contextKey}_balance_update`, () => {
|
||||||
|
fetchBalanceData()
|
||||||
|
.then(balanceData => {
|
||||||
|
updateCallback(balanceData);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error updating ${errorContext} balances via WebSocket:`, error);
|
||||||
|
});
|
||||||
|
}, config.balanceUpdateDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSwapEvent(contextKey, updateCallback, errorContext) {
|
||||||
|
clearTimeoutByKey(`${contextKey}_swap_event`);
|
||||||
|
setTimeoutByKey(`${contextKey}_swap_event`, () => {
|
||||||
|
fetchBalanceData()
|
||||||
|
.then(balanceData => {
|
||||||
|
updateCallback(balanceData);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error updating ${errorContext} balances via swap event:`, error);
|
||||||
|
});
|
||||||
|
}, config.swapEventDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWebSocketHandler(contextKey, balanceUpdateCallback, swapEventCallback, errorContext) {
|
||||||
|
const handlerId = window.WebSocketManager.addMessageHandler('message', (data) => {
|
||||||
|
if (data && data.event) {
|
||||||
|
if (data.event === 'coin_balance_updated') {
|
||||||
|
handleBalanceUpdate(contextKey, balanceUpdateCallback, errorContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swapEventCallback) {
|
||||||
|
const swapEvents = ['new_bid', 'bid_accepted', 'swap_completed'];
|
||||||
|
if (swapEvents.includes(data.event)) {
|
||||||
|
handleSwapEvent(contextKey, swapEventCallback, errorContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state.handlers.set(contextKey, handlerId);
|
||||||
|
return handlerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPeriodicRefresh(contextKey, updateCallback, errorContext, interval) {
|
||||||
|
const refreshInterval = interval || config.periodicRefreshInterval;
|
||||||
|
|
||||||
|
setIntervalByKey(`${contextKey}_periodic`, () => {
|
||||||
|
fetchBalanceData()
|
||||||
|
.then(balanceData => {
|
||||||
|
updateCallback(balanceData);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error in periodic ${errorContext} balance refresh:`, error);
|
||||||
|
});
|
||||||
|
}, refreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(contextKey) {
|
||||||
|
if (state.handlers.has(contextKey)) {
|
||||||
|
const handlerId = state.handlers.get(contextKey);
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.removeMessageHandler === 'function') {
|
||||||
|
window.WebSocketManager.removeMessageHandler('message', handlerId);
|
||||||
|
}
|
||||||
|
state.handlers.delete(contextKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeoutByKey(`${contextKey}_balance_update`);
|
||||||
|
clearTimeoutByKey(`${contextKey}_swap_event`);
|
||||||
|
|
||||||
|
clearIntervalByKey(`${contextKey}_periodic`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupAll() {
|
||||||
|
state.handlers.forEach((handlerId) => {
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.removeMessageHandler === 'function') {
|
||||||
|
window.WebSocketManager.removeMessageHandler('message', handlerId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.handlers.clear();
|
||||||
|
|
||||||
|
state.timeouts.forEach(timeoutId => clearTimeout(timeoutId));
|
||||||
|
state.timeouts.clear();
|
||||||
|
|
||||||
|
state.intervals.forEach(intervalId => clearInterval(intervalId));
|
||||||
|
state.intervals.clear();
|
||||||
|
|
||||||
|
state.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialize: function() {
|
||||||
|
if (state.initialized) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.CleanupManager) {
|
||||||
|
window.CleanupManager.registerResource('balanceUpdatesManager', this, (mgr) => mgr.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', cleanupAll);
|
||||||
|
|
||||||
|
state.initialized = true;
|
||||||
|
console.log('BalanceUpdatesManager initialized');
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
setup: function(options) {
|
||||||
|
const {
|
||||||
|
contextKey,
|
||||||
|
balanceUpdateCallback,
|
||||||
|
swapEventCallback,
|
||||||
|
errorContext,
|
||||||
|
enablePeriodicRefresh = false,
|
||||||
|
periodicInterval
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
if (!contextKey || !balanceUpdateCallback || !errorContext) {
|
||||||
|
throw new Error('Missing required options: contextKey, balanceUpdateCallback, errorContext');
|
||||||
|
}
|
||||||
|
|
||||||
|
setupWebSocketHandler(contextKey, balanceUpdateCallback, swapEventCallback, errorContext);
|
||||||
|
|
||||||
|
if (enablePeriodicRefresh) {
|
||||||
|
setupPeriodicRefresh(contextKey, balanceUpdateCallback, errorContext, periodicInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchBalanceData: fetchBalanceData,
|
||||||
|
|
||||||
|
cleanup: cleanup,
|
||||||
|
|
||||||
|
dispose: cleanupAll,
|
||||||
|
|
||||||
|
isInitialized: function() {
|
||||||
|
return state.initialized;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.BalanceUpdatesManager = BalanceUpdatesManager;
|
||||||
|
}
|
||||||
@@ -1,11 +1,40 @@
|
|||||||
const NotificationManager = (function() {
|
const NotificationManager = (function() {
|
||||||
|
|
||||||
const config = {
|
|
||||||
|
const defaultConfig = {
|
||||||
showNewOffers: false,
|
showNewOffers: false,
|
||||||
showNewBids: true,
|
showNewBids: true,
|
||||||
showBidAccepted: true
|
showBidAccepted: true,
|
||||||
|
showBalanceChanges: true,
|
||||||
|
showOutgoingTransactions: true,
|
||||||
|
notificationDuration: 20000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
const saved = localStorage.getItem('notification_settings');
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
return { ...defaultConfig, ...JSON.parse(saved) };
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading notification settings:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ...defaultConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function saveConfig(newConfig) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('notification_settings', JSON.stringify(newConfig));
|
||||||
|
Object.assign(config, newConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving notification settings:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = loadConfig();
|
||||||
|
|
||||||
function ensureToastContainer() {
|
function ensureToastContainer() {
|
||||||
let container = document.getElementById('ul_updates');
|
let container = document.getElementById('ul_updates');
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@@ -19,13 +48,67 @@ const NotificationManager = (function() {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCoinIcon(coinSymbol) {
|
||||||
|
if (window.CoinManager && typeof window.CoinManager.getCoinIcon === 'function') {
|
||||||
|
return window.CoinManager.getCoinIcon(coinSymbol);
|
||||||
|
}
|
||||||
|
return 'default.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToastIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
'new_offer': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||||
|
</svg>`,
|
||||||
|
'new_bid': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||||
|
</svg>`,
|
||||||
|
'bid_accepted': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
||||||
|
</svg>`,
|
||||||
|
'balance_change': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
|
||||||
|
</svg>`,
|
||||||
|
'success': `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||||
|
</svg>`
|
||||||
|
};
|
||||||
|
return icons[type] || icons['success'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToastColor(type, options = {}) {
|
||||||
|
const colors = {
|
||||||
|
'new_offer': 'bg-blue-500',
|
||||||
|
'new_bid': 'bg-green-500',
|
||||||
|
'bid_accepted': 'bg-purple-500',
|
||||||
|
'balance_change': 'bg-yellow-500',
|
||||||
|
'success': 'bg-blue-500'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === 'balance_change' && options.subtitle) {
|
||||||
|
if (options.subtitle.includes('sent') || options.subtitle.includes('sending')) {
|
||||||
|
return 'bg-red-500';
|
||||||
|
} else {
|
||||||
|
return 'bg-green-500';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors[type] || colors['success'];
|
||||||
|
}
|
||||||
|
|
||||||
const publicAPI = {
|
const publicAPI = {
|
||||||
initialize: function(options = {}) {
|
initialize: function(options = {}) {
|
||||||
Object.assign(config, options);
|
Object.assign(config, options);
|
||||||
|
|
||||||
|
|
||||||
|
this.initializeBalanceTracking();
|
||||||
|
|
||||||
if (window.CleanupManager) {
|
if (window.CleanupManager) {
|
||||||
window.CleanupManager.registerResource('notificationManager', this, (mgr) => {
|
window.CleanupManager.registerResource('notificationManager', this, (mgr) => {
|
||||||
|
|
||||||
|
if (this.balanceTimeouts) {
|
||||||
|
Object.values(this.balanceTimeouts).forEach(timeout => clearTimeout(timeout));
|
||||||
|
}
|
||||||
console.log('NotificationManager disposed');
|
console.log('NotificationManager disposed');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -33,30 +116,157 @@ const NotificationManager = (function() {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
createToast: function(title, type = 'success') {
|
updateSettings: function(newSettings) {
|
||||||
|
saveConfig(newSettings);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSettings: function() {
|
||||||
|
return { ...config };
|
||||||
|
},
|
||||||
|
|
||||||
|
testToasts: function() {
|
||||||
|
if (!this.createToast) return;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'+0.05000000 PART',
|
||||||
|
'balance_change',
|
||||||
|
{ coinSymbol: 'PART', subtitle: 'Incoming funds pending' }
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'+0.00123456 XMR',
|
||||||
|
'balance_change',
|
||||||
|
{ coinSymbol: 'XMR', subtitle: 'Incoming funds confirmed' }
|
||||||
|
);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'-29.86277595 PART',
|
||||||
|
'balance_change',
|
||||||
|
{ coinSymbol: 'PART', subtitle: 'Funds sent' }
|
||||||
|
);
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'-0.05000000 PART (Anon)',
|
||||||
|
'balance_change',
|
||||||
|
{ coinSymbol: 'PART', subtitle: 'Funds sending' }
|
||||||
|
);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'+1.23456789 PART (Anon)',
|
||||||
|
'balance_change',
|
||||||
|
{ coinSymbol: 'PART', subtitle: 'Incoming funds confirmed' }
|
||||||
|
);
|
||||||
|
}, 2500);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'New network offer',
|
||||||
|
'new_offer',
|
||||||
|
{ offerId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66', subtitle: 'Click to view offer' }
|
||||||
|
);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.createToast(
|
||||||
|
'New bid received',
|
||||||
|
'new_bid',
|
||||||
|
{ bidId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66', subtitle: 'Click to view bid' }
|
||||||
|
);
|
||||||
|
}, 3500);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
initializeBalanceTracking: function() {
|
||||||
|
|
||||||
|
fetch('/json/walletbalances')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(balanceData => {
|
||||||
|
if (Array.isArray(balanceData)) {
|
||||||
|
balanceData.forEach(coin => {
|
||||||
|
const balance = parseFloat(coin.balance) || 0;
|
||||||
|
const pending = parseFloat(coin.pending) || 0;
|
||||||
|
|
||||||
|
const coinKey = coin.name.replace(/\s+/g, '_');
|
||||||
|
const storageKey = `prev_balance_${coinKey}`;
|
||||||
|
const pendingStorageKey = `prev_pending_${coinKey}`;
|
||||||
|
|
||||||
|
|
||||||
|
if (!localStorage.getItem(storageKey)) {
|
||||||
|
localStorage.setItem(storageKey, balance.toString());
|
||||||
|
}
|
||||||
|
if (!localStorage.getItem(pendingStorageKey)) {
|
||||||
|
localStorage.setItem(pendingStorageKey, pending.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error initializing balance tracking:', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createToast: function(title, type = 'success', options = {}) {
|
||||||
const messages = ensureToastContainer();
|
const messages = ensureToastContainer();
|
||||||
const message = document.createElement('li');
|
const message = document.createElement('li');
|
||||||
|
const toastId = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
const iconColor = getToastColor(type, options);
|
||||||
|
const icon = getToastIcon(type);
|
||||||
|
|
||||||
|
|
||||||
|
let coinIconHtml = '';
|
||||||
|
if (options.coinSymbol) {
|
||||||
|
const coinIcon = getCoinIcon(options.coinSymbol);
|
||||||
|
coinIconHtml = `<img src="/static/images/coins/${coinIcon}" class="w-5 h-5 mr-2" alt="${options.coinSymbol}" onerror="this.style.display='none'">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let clickAction = '';
|
||||||
|
let cursorStyle = 'cursor-default';
|
||||||
|
|
||||||
|
if (options.offerId) {
|
||||||
|
clickAction = `onclick="window.location.href='/offer/${options.offerId}'"`;
|
||||||
|
cursorStyle = 'cursor-pointer';
|
||||||
|
} else if (options.bidId) {
|
||||||
|
clickAction = `onclick="window.location.href='/bid/${options.bidId}'"`;
|
||||||
|
cursorStyle = 'cursor-pointer';
|
||||||
|
} else if (options.coinSymbol) {
|
||||||
|
clickAction = `onclick="window.location.href='/wallet/${options.coinSymbol}'"`;
|
||||||
|
cursorStyle = 'cursor-pointer';
|
||||||
|
}
|
||||||
|
|
||||||
message.innerHTML = `
|
message.innerHTML = `
|
||||||
<div id="hide">
|
<div class="toast-slide-in">
|
||||||
<div id="toast-${type}" class="flex items-center p-4 mb-4 w-full max-w-xs text-gray-500
|
<div id="${toastId}" class="flex items-center p-4 mb-3 w-full max-w-sm text-gray-500
|
||||||
bg-white rounded-lg shadow" role="alert">
|
bg-white dark:bg-gray-800 dark:text-gray-400 rounded-lg shadow-lg border border-gray-200
|
||||||
|
dark:border-gray-700 ${cursorStyle} hover:shadow-xl transition-shadow" role="alert" ${clickAction}>
|
||||||
<div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10
|
<div class="inline-flex flex-shrink-0 justify-center items-center w-10 h-10
|
||||||
bg-blue-500 rounded-lg">
|
${iconColor} rounded-lg text-white">
|
||||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" height="18" width="18"
|
${icon}
|
||||||
viewBox="0 0 24 24">
|
|
||||||
<g fill="#ffffff">
|
|
||||||
<path d="M8.5,20a1.5,1.5,0,0,1-1.061-.439L.379,12.5,2.5,10.379l6,6,13-13L23.621,
|
|
||||||
5.5,9.561,19.561A1.5,1.5,0,0,1,8.5,20Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="uppercase w-40 ml-3 text-sm font-semibold text-gray-900">${title}</div>
|
<div class="flex items-center ml-3 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
<button type="button" onclick="closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5
|
${coinIconHtml}
|
||||||
bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-0 focus:outline-none
|
<div class="flex flex-col">
|
||||||
focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8">
|
<span class="font-semibold">${title}</span>
|
||||||
|
${options.subtitle ? `<span class="text-xs text-gray-500 dark:text-gray-400">${options.subtitle}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="event.stopPropagation(); closeAlert(event)" class="ml-auto -mx-1.5 -my-1.5
|
||||||
|
bg-white dark:bg-gray-800 text-gray-400 hover:text-gray-900 dark:hover:text-white
|
||||||
|
rounded-lg p-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 inline-flex h-8 w-8 transition-colors
|
||||||
|
focus:outline-none">
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">Close</span>
|
||||||
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1
|
||||||
1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293
|
1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293
|
||||||
4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||||
@@ -67,34 +277,206 @@ const NotificationManager = (function() {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
messages.appendChild(message);
|
messages.appendChild(message);
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (message.parentNode) {
|
||||||
|
message.classList.add('toast-slide-out');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (message.parentNode) {
|
||||||
|
message.parentNode.removeChild(message);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, config.notificationDuration);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleWebSocketEvent: function(data) {
|
handleWebSocketEvent: function(data) {
|
||||||
if (!data || !data.event) return;
|
if (!data || !data.event) return;
|
||||||
let toastTitle;
|
let toastTitle, toastType, toastOptions = {};
|
||||||
let shouldShowToast = false;
|
let shouldShowToast = false;
|
||||||
|
|
||||||
switch (data.event) {
|
switch (data.event) {
|
||||||
case 'new_offer':
|
case 'new_offer':
|
||||||
toastTitle = `New network <a class="underline" href=/offer/${data.offer_id}>offer</a>`;
|
toastTitle = `New network offer`;
|
||||||
|
toastType = 'new_offer';
|
||||||
|
toastOptions.offerId = data.offer_id;
|
||||||
|
toastOptions.subtitle = 'Click to view offer';
|
||||||
shouldShowToast = config.showNewOffers;
|
shouldShowToast = config.showNewOffers;
|
||||||
break;
|
break;
|
||||||
case 'new_bid':
|
case 'new_bid':
|
||||||
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>New bid</a> on
|
toastTitle = `New bid received`;
|
||||||
<a class="underline" href=/offer/${data.offer_id}>offer</a>`;
|
toastOptions.bidId = data.bid_id;
|
||||||
|
toastOptions.subtitle = 'Click to view bid';
|
||||||
|
toastType = 'new_bid';
|
||||||
shouldShowToast = config.showNewBids;
|
shouldShowToast = config.showNewBids;
|
||||||
break;
|
break;
|
||||||
case 'bid_accepted':
|
case 'bid_accepted':
|
||||||
toastTitle = `<a class="underline" href=/bid/${data.bid_id}>Bid</a> accepted`;
|
toastTitle = `Bid accepted`;
|
||||||
|
toastOptions.bidId = data.bid_id;
|
||||||
|
toastOptions.subtitle = 'Click to view swap';
|
||||||
|
toastType = 'bid_accepted';
|
||||||
shouldShowToast = config.showBidAccepted;
|
shouldShowToast = config.showBidAccepted;
|
||||||
break;
|
break;
|
||||||
|
case 'coin_balance_updated':
|
||||||
|
if (data.coin && config.showBalanceChanges) {
|
||||||
|
this.handleBalanceUpdate(data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toastTitle && shouldShowToast) {
|
if (toastTitle && shouldShowToast) {
|
||||||
this.createToast(toastTitle);
|
this.createToast(toastTitle, toastType, toastOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleBalanceUpdate: function(data) {
|
||||||
|
if (!data.coin) return;
|
||||||
|
|
||||||
|
this.fetchAndShowBalanceChange(data.coin);
|
||||||
|
const balanceKey = `balance_${data.coin}`;
|
||||||
|
|
||||||
|
if (this.balanceTimeouts && this.balanceTimeouts[balanceKey]) {
|
||||||
|
clearTimeout(this.balanceTimeouts[balanceKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.balanceTimeouts) {
|
||||||
|
this.balanceTimeouts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.balanceTimeouts[balanceKey] = setTimeout(() => {
|
||||||
|
this.fetchAndShowBalanceChange(data.coin);
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchAndShowBalanceChange: function(coinSymbol) {
|
||||||
|
|
||||||
|
fetch('/json/walletbalances')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(balanceData => {
|
||||||
|
if (Array.isArray(balanceData)) {
|
||||||
|
|
||||||
|
let coinsToCheck;
|
||||||
|
if (coinSymbol === 'PART') {
|
||||||
|
coinsToCheck = balanceData.filter(coin => coin.ticker === 'PART');
|
||||||
|
} else if (coinSymbol === 'LTC') {
|
||||||
|
coinsToCheck = balanceData.filter(coin => coin.ticker === 'LTC');
|
||||||
|
} else {
|
||||||
|
coinsToCheck = balanceData.filter(coin =>
|
||||||
|
coin.ticker === coinSymbol ||
|
||||||
|
coin.name.toLowerCase() === coinSymbol.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
coinsToCheck.forEach(coinData => {
|
||||||
|
this.checkSingleCoinBalance(coinData, coinSymbol);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching balance for notification:', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
checkSingleCoinBalance: function(coinData, originalCoinSymbol) {
|
||||||
|
const currentBalance = parseFloat(coinData.balance) || 0;
|
||||||
|
const currentPending = parseFloat(coinData.pending) || 0;
|
||||||
|
|
||||||
|
const coinKey = coinData.name.replace(/\s+/g, '_');
|
||||||
|
const storageKey = `prev_balance_${coinKey}`;
|
||||||
|
const pendingStorageKey = `prev_pending_${coinKey}`;
|
||||||
|
const prevBalance = parseFloat(localStorage.getItem(storageKey)) || 0;
|
||||||
|
const prevPending = parseFloat(localStorage.getItem(pendingStorageKey)) || 0;
|
||||||
|
|
||||||
|
|
||||||
|
const balanceIncrease = currentBalance - prevBalance;
|
||||||
|
const pendingIncrease = currentPending - prevPending;
|
||||||
|
const pendingDecrease = prevPending - currentPending;
|
||||||
|
|
||||||
|
|
||||||
|
const isPendingToConfirmed = pendingDecrease > 0.00000001 && balanceIncrease > 0.00000001;
|
||||||
|
|
||||||
|
|
||||||
|
const displaySymbol = originalCoinSymbol;
|
||||||
|
let variantInfo = '';
|
||||||
|
|
||||||
|
if (coinData.name !== 'Particl' && coinData.name.includes('Particl')) {
|
||||||
|
|
||||||
|
variantInfo = ` (${coinData.name.replace('Particl ', '')})`;
|
||||||
|
} else if (coinData.name !== 'Litecoin' && coinData.name.includes('Litecoin')) {
|
||||||
|
|
||||||
|
variantInfo = ` (${coinData.name.replace('Litecoin ', '')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (balanceIncrease > 0.00000001) {
|
||||||
|
const displayAmount = balanceIncrease.toFixed(8).replace(/\.?0+$/, '');
|
||||||
|
const subtitle = isPendingToConfirmed ? 'Funds confirmed' : 'Incoming funds confirmed';
|
||||||
|
this.createToast(
|
||||||
|
`+${displayAmount} ${displaySymbol}${variantInfo}`,
|
||||||
|
'balance_change',
|
||||||
|
{
|
||||||
|
coinSymbol: originalCoinSymbol,
|
||||||
|
subtitle: subtitle
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (balanceIncrease < -0.00000001 && config.showOutgoingTransactions) {
|
||||||
|
const displayAmount = Math.abs(balanceIncrease).toFixed(8).replace(/\.?0+$/, '');
|
||||||
|
this.createToast(
|
||||||
|
`-${displayAmount} ${displaySymbol}${variantInfo}`,
|
||||||
|
'balance_change',
|
||||||
|
{
|
||||||
|
coinSymbol: originalCoinSymbol,
|
||||||
|
subtitle: 'Funds sent'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingIncrease > 0.00000001) {
|
||||||
|
const displayAmount = pendingIncrease.toFixed(8).replace(/\.?0+$/, '');
|
||||||
|
this.createToast(
|
||||||
|
`+${displayAmount} ${displaySymbol}${variantInfo}`,
|
||||||
|
'balance_change',
|
||||||
|
{
|
||||||
|
coinSymbol: originalCoinSymbol,
|
||||||
|
subtitle: 'Incoming funds pending'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingIncrease < -0.00000001 && config.showOutgoingTransactions && !isPendingToConfirmed) {
|
||||||
|
const displayAmount = Math.abs(pendingIncrease).toFixed(8).replace(/\.?0+$/, '');
|
||||||
|
this.createToast(
|
||||||
|
`-${displayAmount} ${displaySymbol}${variantInfo}`,
|
||||||
|
'balance_change',
|
||||||
|
{
|
||||||
|
coinSymbol: originalCoinSymbol,
|
||||||
|
subtitle: 'Funds sending'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (pendingDecrease > 0.00000001 && !isPendingToConfirmed) {
|
||||||
|
const displayAmount = pendingDecrease.toFixed(8).replace(/\.?0+$/, '');
|
||||||
|
this.createToast(
|
||||||
|
`${displayAmount} ${displaySymbol}${variantInfo}`,
|
||||||
|
'balance_change',
|
||||||
|
{
|
||||||
|
coinSymbol: originalCoinSymbol,
|
||||||
|
subtitle: 'Pending funds confirmed'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
localStorage.setItem(storageKey, currentBalance.toString());
|
||||||
|
localStorage.setItem(pendingStorageKey, currentPending.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
updateConfig: function(newConfig) {
|
updateConfig: function(newConfig) {
|
||||||
Object.assign(config, newConfig);
|
Object.assign(config, newConfig);
|
||||||
return this;
|
return this;
|
||||||
@@ -122,5 +504,5 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//console.log('NotificationManager initialized with methods:', Object.keys(NotificationManager));
|
|
||||||
console.log('NotificationManager initialized');
|
console.log('NotificationManager initialized');
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const PriceManager = (function() {
|
|||||||
|
|
||||||
setTimeout(() => this.getPrices(), 1500);
|
setTimeout(() => this.getPrices(), 1500);
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
|
console.log('PriceManager initialized');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ const PriceManager = (function() {
|
|||||||
return fetchPromise;
|
return fetchPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('PriceManager: Fetching latest prices.');
|
|
||||||
lastFetchTime = Date.now();
|
lastFetchTime = Date.now();
|
||||||
fetchPromise = this.fetchPrices()
|
fetchPromise = this.fetchPrices()
|
||||||
.then(prices => {
|
.then(prices => {
|
||||||
@@ -166,14 +167,14 @@ const PriceManager = (function() {
|
|||||||
|
|
||||||
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
console.log('Using cached price data');
|
|
||||||
return cachedData.value;
|
return cachedData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const existingCache = localStorage.getItem(PRICES_CACHE_KEY);
|
const existingCache = localStorage.getItem(PRICES_CACHE_KEY);
|
||||||
if (existingCache) {
|
if (existingCache) {
|
||||||
console.log('Using localStorage cached price data');
|
|
||||||
return JSON.parse(existingCache).value;
|
return JSON.parse(existingCache).value;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -230,4 +231,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('PriceManager initialized');
|
|
||||||
|
|||||||
@@ -261,9 +261,12 @@ const SummaryManager = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.event) {
|
if (data.event) {
|
||||||
|
const summaryEvents = ['new_offer', 'offer_revoked', 'new_bid', 'bid_accepted', 'swap_completed'];
|
||||||
|
if (summaryEvents.includes(data.event)) {
|
||||||
publicAPI.fetchSummaryData()
|
publicAPI.fetchSummaryData()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
|
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
|
||||||
window.NotificationManager.handleWebSocketEvent(data);
|
window.NotificationManager.handleWebSocketEvent(data);
|
||||||
@@ -334,9 +337,12 @@ const SummaryManager = (function() {
|
|||||||
|
|
||||||
wsManager.addMessageHandler('message', (data) => {
|
wsManager.addMessageHandler('message', (data) => {
|
||||||
if (data.event) {
|
if (data.event) {
|
||||||
|
const summaryEvents = ['new_offer', 'offer_revoked', 'new_bid', 'bid_accepted', 'swap_completed'];
|
||||||
|
if (summaryEvents.includes(data.event)) {
|
||||||
this.fetchSummaryData()
|
this.fetchSummaryData()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
|
if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') {
|
||||||
window.NotificationManager.handleWebSocketEvent(data);
|
window.NotificationManager.handleWebSocketEvent(data);
|
||||||
|
|||||||
@@ -180,8 +180,29 @@ const WalletManager = (function() {
|
|||||||
|
|
||||||
if (coinSymbol) {
|
if (coinSymbol) {
|
||||||
if (coinName === 'Particl') {
|
if (coinName === 'Particl') {
|
||||||
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
|
let isBlind = false;
|
||||||
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
|
let isAnon = false;
|
||||||
|
|
||||||
|
const flexContainer = el.closest('.flex');
|
||||||
|
if (flexContainer) {
|
||||||
|
const h4Element = flexContainer.querySelector('h4');
|
||||||
|
if (h4Element) {
|
||||||
|
isBlind = h4Element.textContent?.includes('Blind');
|
||||||
|
isAnon = h4Element.textContent?.includes('Anon');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isBlind && !isAnon) {
|
||||||
|
const parentRow = el.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell) {
|
||||||
|
isBlind = labelCell.textContent?.includes('Blind');
|
||||||
|
isAnon = labelCell.textContent?.includes('Anon');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
|
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
|
||||||
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
|
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
|
||||||
} else if (coinName === 'Litecoin') {
|
} else if (coinName === 'Litecoin') {
|
||||||
@@ -248,8 +269,29 @@ const WalletManager = (function() {
|
|||||||
const usdValue = (amount * price).toFixed(2);
|
const usdValue = (amount * price).toFixed(2);
|
||||||
|
|
||||||
if (coinName === 'Particl') {
|
if (coinName === 'Particl') {
|
||||||
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
|
let isBlind = false;
|
||||||
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
|
let isAnon = false;
|
||||||
|
|
||||||
|
const flexContainer = el.closest('.flex');
|
||||||
|
if (flexContainer) {
|
||||||
|
const h4Element = flexContainer.querySelector('h4');
|
||||||
|
if (h4Element) {
|
||||||
|
isBlind = h4Element.textContent?.includes('Blind');
|
||||||
|
isAnon = h4Element.textContent?.includes('Anon');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isBlind && !isAnon) {
|
||||||
|
const parentRow = el.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell) {
|
||||||
|
isBlind = labelCell.textContent?.includes('Blind');
|
||||||
|
isAnon = labelCell.textContent?.includes('Anon');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
|
const balanceType = isBlind ? 'blind' : isAnon ? 'anon' : 'public';
|
||||||
localStorage.setItem(`particl-${balanceType}-last-value`, usdValue);
|
localStorage.setItem(`particl-${balanceType}-last-value`, usdValue);
|
||||||
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
|
localStorage.setItem(`particl-${balanceType}-amount`, amount.toString());
|
||||||
|
|||||||
@@ -443,7 +443,45 @@ const UIEnhancer = {
|
|||||||
|
|
||||||
const selectNameElement = select.nextElementSibling?.querySelector('.select-name');
|
const selectNameElement = select.nextElementSibling?.querySelector('.select-name');
|
||||||
if (selectNameElement) {
|
if (selectNameElement) {
|
||||||
|
|
||||||
|
if (select.id === 'coin_from' && name.includes(' - Balance: ')) {
|
||||||
|
|
||||||
|
const parts = name.split(' - Balance: ');
|
||||||
|
const coinName = parts[0];
|
||||||
|
const balanceInfo = parts[1] || '';
|
||||||
|
|
||||||
|
|
||||||
|
selectNameElement.innerHTML = '';
|
||||||
|
selectNameElement.style.display = 'flex';
|
||||||
|
selectNameElement.style.flexDirection = 'column';
|
||||||
|
selectNameElement.style.alignItems = 'flex-start';
|
||||||
|
selectNameElement.style.lineHeight = '1.2';
|
||||||
|
|
||||||
|
|
||||||
|
const coinNameDiv = document.createElement('div');
|
||||||
|
coinNameDiv.textContent = coinName;
|
||||||
|
coinNameDiv.style.fontWeight = 'normal';
|
||||||
|
coinNameDiv.style.color = 'inherit';
|
||||||
|
|
||||||
|
|
||||||
|
const balanceDiv = document.createElement('div');
|
||||||
|
balanceDiv.textContent = `Balance: ${balanceInfo}`;
|
||||||
|
balanceDiv.style.fontSize = '0.75rem';
|
||||||
|
balanceDiv.style.color = '#6b7280';
|
||||||
|
balanceDiv.style.marginTop = '1px';
|
||||||
|
|
||||||
|
selectNameElement.appendChild(coinNameDiv);
|
||||||
|
selectNameElement.appendChild(balanceDiv);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
selectNameElement.textContent = name;
|
selectNameElement.textContent = name;
|
||||||
|
selectNameElement.style.display = 'block';
|
||||||
|
selectNameElement.style.flexDirection = '';
|
||||||
|
selectNameElement.style.alignItems = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectCache(select);
|
updateSelectCache(select);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.bidsTabNavigationInitialized = true;
|
window.bidsTabNavigationInitialized = true;
|
||||||
console.log('Bids tab navigation initialized');
|
//console.log('Bids tab navigation initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInitialNavigation() {
|
function handleInitialNavigation() {
|
||||||
@@ -54,14 +54,14 @@
|
|||||||
const tabToActivate = localStorage.getItem('bidsTabToActivate');
|
const tabToActivate = localStorage.getItem('bidsTabToActivate');
|
||||||
|
|
||||||
if (tabToActivate) {
|
if (tabToActivate) {
|
||||||
//console.log('Activating tab from localStorage:', tabToActivate);
|
|
||||||
localStorage.removeItem('bidsTabToActivate');
|
localStorage.removeItem('bidsTabToActivate');
|
||||||
activateTabWithRetry('#' + tabToActivate);
|
activateTabWithRetry('#' + tabToActivate);
|
||||||
} else if (window.location.hash) {
|
} else if (window.location.hash) {
|
||||||
//console.log('Activating tab from hash:', window.location.hash);
|
|
||||||
activateTabWithRetry(window.location.hash);
|
activateTabWithRetry(window.location.hash);
|
||||||
} else {
|
} else {
|
||||||
//console.log('Activating default tab: #all');
|
|
||||||
activateTabWithRetry('#all');
|
activateTabWithRetry('#all');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
|
|
||||||
const hash = window.location.hash;
|
const hash = window.location.hash;
|
||||||
if (hash) {
|
if (hash) {
|
||||||
//console.log('Hash changed, activating tab:', hash);
|
|
||||||
activateTabWithRetry(hash);
|
activateTabWithRetry(hash);
|
||||||
} else {
|
} else {
|
||||||
//console.log('Hash cleared, activating default tab: #all');
|
|
||||||
activateTabWithRetry('#all');
|
activateTabWithRetry('#all');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
const normalizedTabId = tabId.startsWith('#') ? tabId : '#' + tabId;
|
const normalizedTabId = tabId.startsWith('#') ? tabId : '#' + tabId;
|
||||||
|
|
||||||
if (normalizedTabId !== '#all' && normalizedTabId !== '#sent' && normalizedTabId !== '#received') {
|
if (normalizedTabId !== '#all' && normalizedTabId !== '#sent' && normalizedTabId !== '#received') {
|
||||||
//console.log('Invalid tab ID, defaulting to #all');
|
|
||||||
activateTabWithRetry('#all');
|
activateTabWithRetry('#all');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -96,17 +96,17 @@
|
|||||||
|
|
||||||
if (!tabButton) {
|
if (!tabButton) {
|
||||||
if (retryCount < 5) {
|
if (retryCount < 5) {
|
||||||
//console.log('Tab button not found, retrying...', retryCount + 1);
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
activateTabWithRetry(normalizedTabId, retryCount + 1);
|
activateTabWithRetry(normalizedTabId, retryCount + 1);
|
||||||
}, 100);
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
//console.error('Failed to find tab button after retries');
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('Activating tab:', normalizedTabId);
|
|
||||||
|
|
||||||
tabButton.click();
|
tabButton.click();
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
(tabId === '#sent' ? 'sent' : 'received');
|
(tabId === '#sent' ? 'sent' : 'received');
|
||||||
|
|
||||||
if (typeof window.updateBidsTable === 'function') {
|
if (typeof window.updateBidsTable === 'function') {
|
||||||
//console.log('Triggering data load for', tabId);
|
|
||||||
window.updateBidsTable();
|
window.updateBidsTable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,16 +51,11 @@
|
|||||||
Add Bid
|
Add Bid
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="refreshAmmTables" class="flex items-center px-4 py-2.5 bg-blue-600 hover:bg-blue-600 border-blue-500 font-medium text-sm text-white border rounded-md shadow-button focus:ring-0 focus:outline-none">
|
|
||||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
||||||
</svg>
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-b pb-5 border-gray-200 dark:border-gray-500">
|
<div class="pb-5">
|
||||||
<ul class="flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400" id="ammTabs" role="tablist">
|
<ul class="flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400" id="ammTabs" role="tablist">
|
||||||
<li class="mr-2" role="presentation">
|
<li class="mr-2" role="presentation">
|
||||||
<button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="offers-tab" data-tabs-target="#offers-content" type="button" role="tab" aria-controls="offers" aria-selected="true">
|
<button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="offers-tab" data-tabs-target="#offers-content" type="button" role="tab" aria-controls="offers" aria-selected="true">
|
||||||
@@ -290,6 +285,7 @@
|
|||||||
<button type="submit" name="kill_orphans" value="1" class="inline-flex items-center px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white text-xs font-medium rounded-md shadow-sm focus:ring-0 focus:outline-none" title="Kill orphaned AMM processes">
|
<button type="submit" name="kill_orphans" value="1" class="inline-flex items-center px-3 py-1.5 bg-red-500 hover:bg-red-600 text-white text-xs font-medium rounded-md shadow-sm focus:ring-0 focus:outline-none" title="Kill orphaned AMM processes">
|
||||||
Kill Orphans
|
Kill Orphans
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||||
Use "Check Processes" to see running AMM processes. Use "Kill Orphans" to clean up duplicate processes. Use "Force Start" to automatically clean up and start fresh.
|
Use "Check Processes" to see running AMM processes. Use "Kill Orphans" to clean up duplicate processes. Use "Force Start" to automatically clean up and start fresh.
|
||||||
@@ -974,7 +970,7 @@
|
|||||||
|
|
||||||
if (localStorage.getItem('amm_create_default_refresh') === 'true') {
|
if (localStorage.getItem('amm_create_default_refresh') === 'true') {
|
||||||
localStorage.removeItem('amm_create_default_refresh');
|
localStorage.removeItem('amm_create_default_refresh');
|
||||||
console.log('[AMM] Page loaded after create default config - redirecting to fresh page');
|
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
@@ -1066,7 +1062,7 @@
|
|||||||
<div class="absolute inset-0 bg-gray-500 opacity-75 dark:bg-gray-900 dark:opacity-90"></div>
|
<div class="absolute inset-0 bg-gray-500 opacity-75 dark:bg-gray-900 dark:opacity-90"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle md:max-w-2xl md:w-full">
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-visible shadow-xl transform transition-all sm:my-8 sm:align-middle md:max-w-2xl md:w-full">
|
||||||
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
||||||
@@ -1086,7 +1082,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div class="min-h-[80px]">
|
||||||
<label for="add-amm-coin-from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Maker</label>
|
<label for="add-amm-coin-from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Maker</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -1094,15 +1090,14 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<select id="add-amm-coin-from" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
<select id="add-amm-coin-from" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
||||||
{% for c in coins %}
|
{% for c in coins %}
|
||||||
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}">
|
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}" data-balance="{{ c[2] }}">{{ c[1] }}</option>
|
||||||
{{ c[1] }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="min-h-[80px]">
|
||||||
<label for="add-amm-coin-to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Taker</label>
|
<label for="add-amm-coin-to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Taker</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -1110,12 +1105,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<select id="add-amm-coin-to" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
<select id="add-amm-coin-to" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
||||||
{% for c in coins %}
|
{% for c in coins %}
|
||||||
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}">
|
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}" data-balance="{{ c[2] }}">{{ c[1] }}</option>
|
||||||
{{ c[1] }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1123,13 +1117,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="add-amm-amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Offer Amount <span class="text-red-500">*</span></label>
|
<label for="add-amm-amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Offer Amount <span class="text-red-500">*</span></label>
|
||||||
<input type="text" id="add-amm-amount" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
<input type="text" id="add-amm-amount" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
||||||
|
<div class="mt-2 flex space-x-2">
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(0.25, 'add-amm-amount')">25%</button>
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(0.5, 'add-amm-amount')">50%</button>
|
||||||
|
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(1, 'add-amm-amount')">100%</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label id="add-amm-rate-label" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Minimum Rate <span class="text-red-500">*</span></label>
|
<label id="add-amm-rate-label" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Minimum Rate <span class="text-red-500">*</span></label>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<input type="text" id="add-amm-rate" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
<input type="text" id="add-amm-rate" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
||||||
<button type="button" id="add-get-rate-button" class="px-2 py-1.5 bg-blue-500 hover:bg-blue-600 font-medium text-xs text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Get Rate</button>
|
<div class="mt-2">
|
||||||
|
<button type="button" id="add-get-rate-button" class="py-1 px-2 bg-blue-500 hover:bg-blue-600 text-white text-xs rounded-md focus:outline-none">Get Rate</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1348,7 +1347,7 @@
|
|||||||
<div class="absolute inset-0 bg-gray-500 opacity-75 dark:bg-gray-900 dark:opacity-90"></div>
|
<div class="absolute inset-0 bg-gray-500 opacity-75 dark:bg-gray-900 dark:opacity-90"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle lg:max-w-xl md:max-w-2xl md:w-full">
|
<div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-visible shadow-xl transform transition-all sm:my-8 sm:align-middle lg:max-w-xl md:max-w-2xl md:w-full">
|
||||||
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
<div class="mt-3 text-center sm:mt-0 sm:text-left w-full">
|
||||||
@@ -1370,7 +1369,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div class="min-h-[80px]">
|
||||||
<label for="edit-amm-coin-from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Maker</label>
|
<label for="edit-amm-coin-from" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Maker</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -1378,15 +1377,14 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<select id="edit-amm-coin-from" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
<select id="edit-amm-coin-from" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
||||||
{% for c in coins %}
|
{% for c in coins %}
|
||||||
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}">
|
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}" data-balance="{{ c[2] }}">{{ c[1] }}</option>
|
||||||
{{ c[1] }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="min-h-[80px]">
|
||||||
<label for="edit-amm-coin-to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Taker</label>
|
<label for="edit-amm-coin-to" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Taker</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="absolute top-1/2 right-3 transform -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -1394,12 +1392,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<select id="edit-amm-coin-to" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
<select id="edit-amm-coin-to" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 appearance-none">
|
||||||
{% for c in coins %}
|
{% for c in coins %}
|
||||||
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}">
|
<option value="{{ c[1] }}" data-symbol="{{ c[0] }}" data-balance="{{ c[2] }}">{{ c[1] }}</option>
|
||||||
{{ c[1] }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1407,13 +1404,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="edit-amm-amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Offer Amount <span class="text-red-500">*</span></label>
|
<label for="edit-amm-amount" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Offer Amount <span class="text-red-500">*</span></label>
|
||||||
<input type="text" id="edit-amm-amount" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
<input type="text" id="edit-amm-amount" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
||||||
|
<div class="mt-2 flex space-x-2">
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(0.25, 'edit-amm-amount')">25%</button>
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(0.5, 'edit-amm-amount')">50%</button>
|
||||||
|
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-xs rounded-md focus:outline-none" onclick="setAmmAmount(1, 'edit-amm-amount')">100%</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label id="edit-amm-rate-label" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Minimum Rate <span class="text-red-500">*</span></label>
|
<label id="edit-amm-rate-label" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Minimum Rate <span class="text-red-500">*</span></label>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<input type="text" id="edit-amm-rate" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
<input type="text" id="edit-amm-rate" pattern="[0-9]*\.?[0-9]*" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="0.0">
|
||||||
<button type="button" id="edit-get-rate-button" class="px-2 py-1.5 bg-blue-500 hover:bg-blue-600 font-medium text-xs text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Get Rate</button>
|
<div class="mt-2">
|
||||||
|
<button type="button" id="edit-get-rate-button" class="py-1 px-2 bg-blue-500 hover:bg-blue-600 text-white text-xs rounded-md focus:outline-none">Get Rate</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2033,6 +2035,187 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setAmmAmount(percent, fieldId) {
|
||||||
|
const amountInput = document.getElementById(fieldId);
|
||||||
|
let coinSelect;
|
||||||
|
|
||||||
|
|
||||||
|
let modalType = null;
|
||||||
|
if (fieldId.includes('add-amm')) {
|
||||||
|
const addModal = document.getElementById('add-amm-modal');
|
||||||
|
modalType = addModal ? addModal.getAttribute('data-amm-type') : null;
|
||||||
|
} else if (fieldId.includes('edit-amm')) {
|
||||||
|
const editModal = document.getElementById('edit-amm-modal');
|
||||||
|
modalType = editModal ? editModal.getAttribute('data-amm-type') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (fieldId.includes('add-amm')) {
|
||||||
|
const isBidModal = modalType === 'bid';
|
||||||
|
coinSelect = document.getElementById(isBidModal ? 'add-amm-coin-to' : 'add-amm-coin-from');
|
||||||
|
} else if (fieldId.includes('edit-amm')) {
|
||||||
|
const isBidModal = modalType === 'bid';
|
||||||
|
coinSelect = document.getElementById(isBidModal ? 'edit-amm-coin-to' : 'edit-amm-coin-from');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!amountInput || !coinSelect) {
|
||||||
|
console.error('Required elements not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOption = coinSelect.options[coinSelect.selectedIndex];
|
||||||
|
if (!selectedOption) {
|
||||||
|
alert('Please select a coin first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = selectedOption.getAttribute('data-balance');
|
||||||
|
if (!balance) {
|
||||||
|
console.error('Balance not found for selected coin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const floatBalance = parseFloat(balance);
|
||||||
|
if (isNaN(floatBalance) || floatBalance <= 0) {
|
||||||
|
alert('Invalid balance for selected coin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculatedAmount = floatBalance * percent;
|
||||||
|
amountInput.value = calculatedAmount.toFixed(8);
|
||||||
|
|
||||||
|
|
||||||
|
const event = new Event('input', { bubbles: true });
|
||||||
|
amountInput.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAmmModalBalances(balanceData) {
|
||||||
|
const addModal = document.getElementById('add-amm-modal');
|
||||||
|
const editModal = document.getElementById('edit-amm-modal');
|
||||||
|
const addModalVisible = addModal && !addModal.classList.contains('hidden');
|
||||||
|
const editModalVisible = editModal && !editModal.classList.contains('hidden');
|
||||||
|
|
||||||
|
let modalType = null;
|
||||||
|
if (addModalVisible) {
|
||||||
|
modalType = addModal.getAttribute('data-amm-type');
|
||||||
|
} else if (editModalVisible) {
|
||||||
|
modalType = editModal.getAttribute('data-amm-type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modalType === 'offer') {
|
||||||
|
updateOfferDropdownBalances(balanceData);
|
||||||
|
} else if (modalType === 'bid') {
|
||||||
|
updateBidDropdownBalances(balanceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWebSocketBalanceUpdates() {
|
||||||
|
window.BalanceUpdatesManager.setup({
|
||||||
|
contextKey: 'amm',
|
||||||
|
balanceUpdateCallback: updateAmmModalBalances,
|
||||||
|
swapEventCallback: updateAmmModalBalances,
|
||||||
|
errorContext: 'AMM',
|
||||||
|
enablePeriodicRefresh: true,
|
||||||
|
periodicInterval: 120000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAmmDropdownBalances(balanceData) {
|
||||||
|
|
||||||
|
const balanceMap = {};
|
||||||
|
const pendingMap = {};
|
||||||
|
balanceData.forEach(coin => {
|
||||||
|
balanceMap[coin.name] = coin.balance;
|
||||||
|
pendingMap[coin.name] = coin.pending || '0.0';
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropdownIds = ['add-amm-coin-from', 'edit-amm-coin-from', 'add-amm-coin-to', 'edit-amm-coin-to'];
|
||||||
|
|
||||||
|
dropdownIds.forEach(dropdownId => {
|
||||||
|
const select = document.getElementById(dropdownId);
|
||||||
|
if (!select) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(select.options).forEach(option => {
|
||||||
|
const coinName = option.value;
|
||||||
|
const balance = balanceMap[coinName] || '0.0';
|
||||||
|
const pending = pendingMap[coinName] || '0.0';
|
||||||
|
|
||||||
|
option.setAttribute('data-balance', balance);
|
||||||
|
option.setAttribute('data-pending-balance', pending);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const addModal = document.getElementById('add-amm-modal');
|
||||||
|
const editModal = document.getElementById('edit-amm-modal');
|
||||||
|
const addModalVisible = addModal && !addModal.classList.contains('hidden');
|
||||||
|
const editModalVisible = editModal && !editModal.classList.contains('hidden');
|
||||||
|
|
||||||
|
let currentModalType = null;
|
||||||
|
if (addModalVisible) {
|
||||||
|
currentModalType = addModal.getAttribute('data-amm-type');
|
||||||
|
} else if (editModalVisible) {
|
||||||
|
currentModalType = editModal.getAttribute('data-amm-type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentModalType && window.ammTablesManager) {
|
||||||
|
if (currentModalType === 'offer' && typeof window.ammTablesManager.refreshOfferDropdownBalanceDisplay === 'function') {
|
||||||
|
window.ammTablesManager.refreshOfferDropdownBalanceDisplay();
|
||||||
|
} else if (currentModalType === 'bid' && typeof window.ammTablesManager.refreshBidDropdownBalanceDisplay === 'function') {
|
||||||
|
window.ammTablesManager.refreshBidDropdownBalanceDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOfferDropdownBalances(balanceData) {
|
||||||
|
updateAmmDropdownBalances(balanceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBidDropdownBalances(balanceData) {
|
||||||
|
updateAmmDropdownBalances(balanceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateOfferCustomDropdownDisplay(select, balanceMap, pendingMap) {
|
||||||
|
console.log(`[DEBUG] updateOfferCustomDropdownDisplay called for ${select.id} - doing nothing`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBidCustomDropdownDisplay(select, balanceMap, pendingMap) {
|
||||||
|
console.log(`[DEBUG] updateBidCustomDropdownDisplay called for ${select.id} - doing nothing`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCustomDropdownDisplay(select, balanceMap, pendingMap) {
|
||||||
|
console.log(`[DEBUG] updateCustomDropdownDisplay called for ${select.id} - doing nothing`);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
window.BalanceUpdatesManager.initialize();
|
||||||
|
setupWebSocketBalanceUpdates();
|
||||||
|
|
||||||
|
function cleanupAmmBalanceUpdates() {
|
||||||
|
window.BalanceUpdatesManager.cleanup('amm');
|
||||||
|
|
||||||
|
if (window.ammDropdowns) {
|
||||||
|
window.ammDropdowns.forEach(dropdown => {
|
||||||
|
if (dropdown.parentNode) {
|
||||||
|
dropdown.parentNode.removeChild(dropdown);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.ammDropdowns = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.CleanupManager) {
|
||||||
|
window.CleanupManager.registerResource('ammBalanceUpdates', null, cleanupAmmBalanceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', cleanupAmmBalanceUpdates);
|
||||||
|
window.setAmmAmount = setAmmAmount;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
<script src="/static/js/modules/price-manager.js"></script>
|
<script src="/static/js/modules/price-manager.js"></script>
|
||||||
<script src="/static/js/modules/tooltips-manager.js"></script>
|
<script src="/static/js/modules/tooltips-manager.js"></script>
|
||||||
<script src="/static/js/modules/notification-manager.js"></script>
|
<script src="/static/js/modules/notification-manager.js"></script>
|
||||||
|
<script src="/static/js/modules/balance-updates.js"></script>
|
||||||
<script src="/static/js/modules/identity-manager.js"></script>
|
<script src="/static/js/modules/identity-manager.js"></script>
|
||||||
<script src="/static/js/modules/summary-manager.js"></script>
|
<script src="/static/js/modules/summary-manager.js"></script>
|
||||||
<script src="/static/js/amm_counter.js"></script>
|
<script src="/static/js/amm_counter.js"></script>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
<select class="select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 bold focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');">
|
<select class="select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 bold focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');">
|
||||||
<option value="-1">Select coin you send</option>
|
<option value="-1">Select coin you send</option>
|
||||||
{% for c in coins_from %}
|
{% for c in coins_from %}
|
||||||
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
|
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png" data-balance="{{ c[2] }}">{{ c[1] }} - Balance: {{ c[2] }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<div class="select-dropdown"> <img class="select-icon" src="" alt=""> <img class="select-image" src="" alt=""> </div>
|
<div class="select-dropdown"> <img class="select-icon" src="" alt=""> <img class="select-image" src="" alt=""> </div>
|
||||||
@@ -167,6 +167,11 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0 bold" placeholder="0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');">
|
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0 bold" placeholder="0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-2 flex space-x-2">
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setOfferAmount(0.25, 'amt_from')">25%</button>
|
||||||
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setOfferAmount(0.5, 'amt_from')">50%</button>
|
||||||
|
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setOfferAmount(1, 'amt_from')">100%</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -332,6 +337,142 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function setOfferAmount(percent, fieldId) {
|
||||||
|
const amountInput = document.getElementById(fieldId);
|
||||||
|
const coinFromSelect = document.getElementById('coin_from');
|
||||||
|
|
||||||
|
if (!amountInput || !coinFromSelect) {
|
||||||
|
console.error('Required elements not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOption = coinFromSelect.options[coinFromSelect.selectedIndex];
|
||||||
|
if (!selectedOption || selectedOption.value === '-1') {
|
||||||
|
alert('Please select a coin first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = selectedOption.getAttribute('data-balance');
|
||||||
|
if (!balance) {
|
||||||
|
console.error('Balance not found for selected coin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const floatBalance = parseFloat(balance);
|
||||||
|
if (isNaN(floatBalance) || floatBalance <= 0) {
|
||||||
|
alert('Invalid balance for selected coin');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculatedAmount = floatBalance * percent;
|
||||||
|
amountInput.value = calculatedAmount.toFixed(8);
|
||||||
|
|
||||||
|
|
||||||
|
if (amountInput.onchange) {
|
||||||
|
amountInput.onchange();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof set_rate === 'function') {
|
||||||
|
set_rate(fieldId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCustomDropdownDisplay(select, coinName, balance, pending) {
|
||||||
|
const selectNameElement = select.nextElementSibling?.querySelector('.select-name');
|
||||||
|
if (!selectNameElement) return;
|
||||||
|
|
||||||
|
|
||||||
|
selectNameElement.innerHTML = '';
|
||||||
|
selectNameElement.style.display = 'flex';
|
||||||
|
selectNameElement.style.flexDirection = 'column';
|
||||||
|
selectNameElement.style.alignItems = 'flex-start';
|
||||||
|
selectNameElement.style.lineHeight = '1.2';
|
||||||
|
|
||||||
|
|
||||||
|
const coinNameDiv = document.createElement('div');
|
||||||
|
coinNameDiv.textContent = coinName;
|
||||||
|
coinNameDiv.style.fontWeight = 'normal';
|
||||||
|
coinNameDiv.style.color = 'inherit';
|
||||||
|
|
||||||
|
|
||||||
|
const balanceDiv = document.createElement('div');
|
||||||
|
balanceDiv.textContent = `Balance: ${balance}`;
|
||||||
|
balanceDiv.style.fontSize = '0.75rem';
|
||||||
|
balanceDiv.style.color = '#6b7280';
|
||||||
|
balanceDiv.style.marginTop = '1px';
|
||||||
|
|
||||||
|
selectNameElement.appendChild(coinNameDiv);
|
||||||
|
selectNameElement.appendChild(balanceDiv);
|
||||||
|
|
||||||
|
|
||||||
|
if (pending !== '0.0' && parseFloat(pending) > 0) {
|
||||||
|
const pendingDiv = document.createElement('div');
|
||||||
|
pendingDiv.textContent = `+${pending} pending`;
|
||||||
|
pendingDiv.style.fontSize = '0.75rem';
|
||||||
|
pendingDiv.style.color = '#10b981';
|
||||||
|
pendingDiv.style.marginTop = '1px';
|
||||||
|
selectNameElement.appendChild(pendingDiv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCoinDropdownBalances(balanceData) {
|
||||||
|
|
||||||
|
const coinFromSelect = document.getElementById('coin_from');
|
||||||
|
if (!coinFromSelect) return;
|
||||||
|
|
||||||
|
|
||||||
|
const balanceMap = {};
|
||||||
|
const pendingMap = {};
|
||||||
|
balanceData.forEach(coin => {
|
||||||
|
balanceMap[coin.id] = coin.balance;
|
||||||
|
pendingMap[coin.id] = coin.pending || '0.0';
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Array.from(coinFromSelect.options).forEach(option => {
|
||||||
|
if (option.value === '-1') return;
|
||||||
|
|
||||||
|
const coinId = parseInt(option.value);
|
||||||
|
const balance = balanceMap[coinId] || '0.0';
|
||||||
|
const pending = pendingMap[coinId] || '0.0';
|
||||||
|
|
||||||
|
|
||||||
|
option.setAttribute('data-balance', balance);
|
||||||
|
|
||||||
|
|
||||||
|
const coinName = option.textContent.split(' - Balance:')[0];
|
||||||
|
if (pending !== '0.0' && parseFloat(pending) > 0) {
|
||||||
|
option.textContent = `${coinName} - Balance: ${balance} (+${pending} pending)`;
|
||||||
|
} else {
|
||||||
|
option.textContent = `${coinName} - Balance: ${balance}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const select = option.closest('select');
|
||||||
|
if (select && select.value === option.value) {
|
||||||
|
updateCustomDropdownDisplay(select, coinName, balance, pending);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
window.BalanceUpdatesManager.initialize();
|
||||||
|
|
||||||
|
window.BalanceUpdatesManager.setup({
|
||||||
|
contextKey: 'offer',
|
||||||
|
balanceUpdateCallback: updateCoinDropdownBalances,
|
||||||
|
swapEventCallback: updateCoinDropdownBalances,
|
||||||
|
errorContext: 'New Offer',
|
||||||
|
enablePeriodicRefresh: true,
|
||||||
|
periodicInterval: 120000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="static/js/new_offer.js"></script>
|
<script src="static/js/new_offer.js"></script>
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
<button class="tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0" data-tab="general" id="general-tab">
|
<button class="tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0" data-tab="general" id="general-tab">
|
||||||
General
|
General
|
||||||
</button>
|
</button>
|
||||||
|
<button class="tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0" data-tab="notifications" id="notifications-tab">
|
||||||
|
Notifications
|
||||||
|
</button>
|
||||||
<button class="tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0" data-tab="tor" id="tor-tab">
|
<button class="tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0" data-tab="tor" id="tor-tab">
|
||||||
Tor
|
Tor
|
||||||
</button>
|
</button>
|
||||||
@@ -432,6 +435,153 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content hidden" id="notifications" role="tabpanel" aria-labelledby="notifications-tab">
|
||||||
|
<form method="post">
|
||||||
|
<div class="space-y-6">
|
||||||
|
|
||||||
|
<div class="bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
|
<div class="px-6 py-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
Notification Settings
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
||||||
|
Configure which notifications you want to see.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 pb-6 space-y-6">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Notification Types</h4>
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="notifications_new_offers" name="notifications_new_offers" value="true" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"{% if notification_settings.notifications_new_offers %} checked{% endif %}>
|
||||||
|
<label for="notifications_new_offers" class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">New Offers</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 ml-7 mt-1">Show notifications for new network offers</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="notifications_new_bids" name="notifications_new_bids" value="true" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"{% if notification_settings.notifications_new_bids %} checked{% endif %}>
|
||||||
|
<label for="notifications_new_bids" class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">New Bids</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 ml-7 mt-1">Show notifications for new bids on your offers</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="notifications_bid_accepted" name="notifications_bid_accepted" value="true" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"{% if notification_settings.notifications_bid_accepted %} checked{% endif %}>
|
||||||
|
<label for="notifications_bid_accepted" class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">Bid Accepted</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 ml-7 mt-1">Show notifications when your bids are accepted</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="notifications_balance_changes" name="notifications_balance_changes" value="true" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"{% if notification_settings.notifications_balance_changes %} checked{% endif %}>
|
||||||
|
<label for="notifications_balance_changes" class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">Balance Changes</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 ml-7 mt-1">Show notifications for incoming and outgoing funds</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input type="checkbox" id="notifications_outgoing_transactions" name="notifications_outgoing_transactions" value="true" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"{% if notification_settings.notifications_outgoing_transactions %} checked{% endif %}>
|
||||||
|
<label for="notifications_outgoing_transactions" class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-300">Outgoing Transactions</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 ml-7 mt-1">Show notifications for sent funds</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Display Duration</h4>
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||||
|
<div>
|
||||||
|
<label for="notifications_duration" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Duration</label>
|
||||||
|
<div class="relative w-48">
|
||||||
|
<select id="notifications_duration" name="notifications_duration" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-1 focus:outline-none">
|
||||||
|
<option value="5"{% if notification_settings.notifications_duration == 5 %} selected{% endif %}>5 seconds</option>
|
||||||
|
<option value="10"{% if notification_settings.notifications_duration == 10 %} selected{% endif %}>10 seconds</option>
|
||||||
|
<option value="15"{% if notification_settings.notifications_duration == 15 %} selected{% endif %}>15 seconds</option>
|
||||||
|
<option value="20"{% if notification_settings.notifications_duration == 20 %} selected{% endif %}>20 seconds</option>
|
||||||
|
<option value="30"{% if notification_settings.notifications_duration == 30 %} selected{% endif %}>30 seconds</option>
|
||||||
|
<option value="60"{% if notification_settings.notifications_duration == 60 %} selected{% endif %}>1 minute</option>
|
||||||
|
</select>
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||||
|
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if general_settings.debug_ui %}
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">Test Notifications</h4>
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
||||||
|
<div>
|
||||||
|
<button type="button" onclick="window.NotificationManager && window.NotificationManager.testToasts()" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors focus:outline-none">
|
||||||
|
Test All Notification Types
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="flex justify-end mt-6">
|
||||||
|
<button name="apply_notifications" value="Apply" type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors focus:outline-none">
|
||||||
|
Apply Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function syncNotificationSettings() {
|
||||||
|
if (window.NotificationManager && typeof window.NotificationManager.updateSettings === 'function') {
|
||||||
|
|
||||||
|
const backendSettings = {
|
||||||
|
showNewOffers: document.getElementById('notifications_new_offers').checked,
|
||||||
|
showNewBids: document.getElementById('notifications_new_bids').checked,
|
||||||
|
showBidAccepted: document.getElementById('notifications_bid_accepted').checked,
|
||||||
|
showBalanceChanges: document.getElementById('notifications_balance_changes').checked,
|
||||||
|
showOutgoingTransactions: document.getElementById('notifications_outgoing_transactions').checked,
|
||||||
|
notificationDuration: parseInt(document.getElementById('notifications_duration').value) * 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
window.NotificationManager.updateSettings(backendSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('notifications-tab').addEventListener('click', function() {
|
||||||
|
setTimeout(syncNotificationSettings, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
syncNotificationSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('change', function(e) {
|
||||||
|
if (e.target.closest('#notifications')) {
|
||||||
|
syncNotificationSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="tab-content hidden" id="tor" role="tabpanel" aria-labelledby="tor-tab">
|
<div class="tab-content hidden" id="tor" role="tabpanel" aria-labelledby="tor-tab">
|
||||||
<form method="post" id="tor-form">
|
<form method="post" id="tor-form">
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<span class="inline-block align-middle">
|
<span class="inline-block align-middle">
|
||||||
<img class="mr-2 h-16" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"></span>({{ w.ticker }}) {{ w.name }} Wallet </h2>
|
<img class="mr-2 h-16" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"></span>({{ w.ticker }}) {{ w.name }} Wallet </h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto"> <a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallet/{{ w.ticker }}"> {{ circular_arrows_svg | safe }}<span>Refresh</span> </a> </div>
|
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -667,16 +667,7 @@ function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|||||||
var floatBalance;
|
var floatBalance;
|
||||||
var calculatedAmount;
|
var calculatedAmount;
|
||||||
|
|
||||||
console.log('SetAmount Called with:', {
|
|
||||||
percent: percent,
|
|
||||||
balance: balance,
|
|
||||||
cid: cid,
|
|
||||||
blindBalance: blindBalance,
|
|
||||||
anonBalance: anonBalance,
|
|
||||||
selectedType: selectedType,
|
|
||||||
blindBalanceType: typeof blindBalance,
|
|
||||||
blindBalanceNumeric: Number(blindBalance)
|
|
||||||
});
|
|
||||||
|
|
||||||
const safeParseFloat = (value) => {
|
const safeParseFloat = (value) => {
|
||||||
const numValue = Number(value);
|
const numValue = Number(value);
|
||||||
@@ -706,11 +697,7 @@ function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|||||||
|
|
||||||
calculatedAmount = Math.max(0, Math.floor(floatBalance * percent * 100000000) / 100000000);
|
calculatedAmount = Math.max(0, Math.floor(floatBalance * percent * 100000000) / 100000000);
|
||||||
|
|
||||||
console.log('Calculated Amount:', {
|
|
||||||
floatBalance: floatBalance,
|
|
||||||
calculatedAmount: calculatedAmount,
|
|
||||||
percent: percent
|
|
||||||
});
|
|
||||||
|
|
||||||
if (percent === 1) {
|
if (percent === 1) {
|
||||||
calculatedAmount = floatBalance;
|
calculatedAmount = floatBalance;
|
||||||
@@ -727,43 +714,6 @@ function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|||||||
if (subfeeCheckbox) {
|
if (subfeeCheckbox) {
|
||||||
subfeeCheckbox.checked = (percent === 1);
|
subfeeCheckbox.checked = (percent === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Final Amount Set:', amountInput.value);
|
|
||||||
}function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|
||||||
var amountInput = document.getElementById('amount');
|
|
||||||
var typeSelect = document.getElementById('withdraw_type');
|
|
||||||
var selectedType = typeSelect.value;
|
|
||||||
var floatBalance;
|
|
||||||
var calculatedAmount;
|
|
||||||
|
|
||||||
switch(selectedType) {
|
|
||||||
case 'plain':
|
|
||||||
floatBalance = parseFloat(balance);
|
|
||||||
break;
|
|
||||||
case 'blind':
|
|
||||||
floatBalance = parseFloat(blindBalance);
|
|
||||||
break;
|
|
||||||
case 'anon':
|
|
||||||
floatBalance = parseFloat(anonBalance);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
floatBalance = parseFloat(balance);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedAmount = Math.floor(floatBalance * percent * 100000000) / 100000000;
|
|
||||||
|
|
||||||
if (percent === 1) {
|
|
||||||
calculatedAmount = floatBalance;
|
|
||||||
}
|
|
||||||
|
|
||||||
amountInput.value = calculatedAmount.toFixed(8);
|
|
||||||
|
|
||||||
var subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
|
||||||
if (subfeeCheckbox) {
|
|
||||||
subfeeCheckbox.checked = (percent === 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -818,17 +768,14 @@ function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|||||||
|
|
||||||
const specialCids = [6, 9];
|
const specialCids = [6, 9];
|
||||||
|
|
||||||
console.log("CID:", cid);
|
|
||||||
console.log("Percent:", percent);
|
|
||||||
console.log("Balance:", balance);
|
|
||||||
console.log("Calculated Amount:", calculatedAmount);
|
|
||||||
|
|
||||||
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
||||||
amountInput.setAttribute('data-hidden', 'true');
|
amountInput.setAttribute('data-hidden', 'true');
|
||||||
amountInput.placeholder = 'Sweep All';
|
amountInput.placeholder = 'Sweep All';
|
||||||
amountInput.value = '';
|
amountInput.value = '';
|
||||||
amountInput.disabled = true;
|
amountInput.disabled = true;
|
||||||
console.log("Sweep All activated for special CID:", cid);
|
|
||||||
} else {
|
} else {
|
||||||
amountInput.value = calculatedAmount.toFixed(8);
|
amountInput.value = calculatedAmount.toFixed(8);
|
||||||
amountInput.setAttribute('data-hidden', 'false');
|
amountInput.setAttribute('data-hidden', 'false');
|
||||||
@@ -840,17 +787,15 @@ function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
|||||||
if (sweepAllCheckbox) {
|
if (sweepAllCheckbox) {
|
||||||
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
||||||
sweepAllCheckbox.checked = true;
|
sweepAllCheckbox.checked = true;
|
||||||
console.log("Sweep All checkbox checked");
|
|
||||||
} else {
|
} else {
|
||||||
sweepAllCheckbox.checked = false;
|
sweepAllCheckbox.checked = false;
|
||||||
console.log("Sweep All checkbox unchecked");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
let subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
||||||
if (subfeeCheckbox) {
|
if (subfeeCheckbox) {
|
||||||
subfeeCheckbox.checked = (percent === 1);
|
subfeeCheckbox.checked = (percent === 1);
|
||||||
console.log("Subfee checkbox status for CID", cid, ":", subfeeCheckbox.checked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1148,6 +1093,375 @@ function confirmUTXOResize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.initialize === 'function') {
|
||||||
|
window.WebSocketManager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentBalances = window.currentBalances || {};
|
||||||
|
|
||||||
|
{% if w.cid == '1' %}
|
||||||
|
window.currentBalances = {
|
||||||
|
mainBalance: '{{ w.balance }}',
|
||||||
|
blindBalance: '{{ w.blind_balance }}',
|
||||||
|
anonBalance: '{{ w.anon_balance }}'
|
||||||
|
};
|
||||||
|
{% elif w.cid == '3' %}
|
||||||
|
window.currentBalances = {
|
||||||
|
mainBalance: '{{ w.balance }}',
|
||||||
|
mwebBalance: '{{ w.mweb_balance }}'
|
||||||
|
};
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
function updateWalletBalances() {
|
||||||
|
fetch('/json/walletbalances', {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Server error: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(balanceData => {
|
||||||
|
if (balanceData.error) {
|
||||||
|
throw new Error(balanceData.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(balanceData)) {
|
||||||
|
throw new Error('Invalid response format');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
balanceData.forEach(coin => {
|
||||||
|
updateWalletDisplay(coin);
|
||||||
|
updatePercentageButtons(coin);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.WalletManager && typeof window.WalletManager.updatePrices === 'function') {
|
||||||
|
window.WalletManager.updatePrices(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 100);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('[Wallet] Error updating wallet balances:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePercentageButtons(coinData) {
|
||||||
|
const currentCoinId = {{ w.cid }};
|
||||||
|
|
||||||
|
const isPartVariant = (currentCoinId === 1 && (coinData.name === 'Particl' || coinData.name === 'Particl Anon' || coinData.name === 'Particl Blind'));
|
||||||
|
const isLtcVariant = (currentCoinId === 3 && (coinData.name === 'Litecoin' || coinData.name === 'Litecoin MWEB'));
|
||||||
|
const isCurrentCoin = (coinData.id === currentCoinId);
|
||||||
|
|
||||||
|
if (isPartVariant || isLtcVariant || isCurrentCoin) {
|
||||||
|
const buttons = document.querySelectorAll('button[onclick*="setAmount"]');
|
||||||
|
buttons.forEach(button => {
|
||||||
|
const onclick = button.getAttribute('onclick');
|
||||||
|
if (onclick) {
|
||||||
|
if (currentCoinId === 1) {
|
||||||
|
window.currentBalances = window.currentBalances || {};
|
||||||
|
|
||||||
|
const anonBalance = coinData.name === 'Particl Anon' ? coinData.balance :
|
||||||
|
(window.currentBalances.anonBalance || '0');
|
||||||
|
const blindBalance = coinData.name === 'Particl Blind' ? coinData.balance :
|
||||||
|
(window.currentBalances.blindBalance || '0');
|
||||||
|
const mainBalance = coinData.name === 'Particl' ? coinData.balance :
|
||||||
|
(window.currentBalances.mainBalance || '0');
|
||||||
|
|
||||||
|
if (coinData.name === 'Particl') window.currentBalances.mainBalance = coinData.balance;
|
||||||
|
if (coinData.name === 'Particl Anon') window.currentBalances.anonBalance = coinData.balance;
|
||||||
|
if (coinData.name === 'Particl Blind') window.currentBalances.blindBalance = coinData.balance;
|
||||||
|
|
||||||
|
const newOnclick = onclick.replace(
|
||||||
|
/setAmount\([^,]+,\s*'[^']*',\s*\d+,\s*'[^']*',\s*'[^']*'\)/,
|
||||||
|
`setAmount(${onclick.match(/setAmount\(([^,]+)/)[1]}, '${mainBalance}', ${currentCoinId}, '${blindBalance}', '${anonBalance}')`
|
||||||
|
);
|
||||||
|
button.setAttribute('onclick', newOnclick);
|
||||||
|
} else if (currentCoinId === 3) {
|
||||||
|
window.currentBalances = window.currentBalances || {};
|
||||||
|
|
||||||
|
const mwebBalance = coinData.name === 'Litecoin MWEB' ? coinData.balance :
|
||||||
|
(window.currentBalances.mwebBalance || '0');
|
||||||
|
const mainBalance = coinData.name === 'Litecoin' ? coinData.balance :
|
||||||
|
(window.currentBalances.mainBalance || '0');
|
||||||
|
|
||||||
|
if (coinData.name === 'Litecoin') window.currentBalances.mainBalance = coinData.balance;
|
||||||
|
if (coinData.name === 'Litecoin MWEB') window.currentBalances.mwebBalance = coinData.balance;
|
||||||
|
|
||||||
|
const newOnclick = onclick.replace(
|
||||||
|
/setAmount\([^,]+,\s*'[^']*',\s*\d+,\s*'[^']*'\)/,
|
||||||
|
`setAmount(${onclick.match(/setAmount\(([^,]+)/)[1]}, '${mainBalance}', ${currentCoinId}, '${mwebBalance}')`
|
||||||
|
);
|
||||||
|
button.setAttribute('onclick', newOnclick);
|
||||||
|
} else {
|
||||||
|
const newOnclick = onclick.replace(
|
||||||
|
/setAmount\([^,]+,\s*'[^']*',\s*\d+\)/,
|
||||||
|
`setAmount(${onclick.match(/setAmount\(([^,]+)/)[1]}, '${coinData.balance}', ${currentCoinId})`
|
||||||
|
);
|
||||||
|
button.setAttribute('onclick', newOnclick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWalletDisplay(coinData) {
|
||||||
|
|
||||||
|
if (coinData.name === 'Particl') {
|
||||||
|
|
||||||
|
updateSpecificBalance('Particl', 'Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
} else if (coinData.name === 'Particl Anon') {
|
||||||
|
|
||||||
|
updateSpecificBalance('Particl', 'Anon Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
|
||||||
|
removePendingBalance('Particl', 'Anon Balance:');
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingBalance('Particl', 'Anon Balance:', coinData.pending, coinData.ticker || 'PART', 'Pending:', coinData);
|
||||||
|
}
|
||||||
|
} else if (coinData.name === 'Particl Blind') {
|
||||||
|
|
||||||
|
updateSpecificBalance('Particl', 'Blind Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
|
||||||
|
removePendingBalance('Particl', 'Blind Balance:');
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Unconfirmed:', coinData);
|
||||||
|
}
|
||||||
|
} else if (coinData.name === 'Litecoin MWEB') {
|
||||||
|
|
||||||
|
updateSpecificBalance('Litecoin', 'MWEB Balance:', coinData.balance, coinData.ticker || 'LTC');
|
||||||
|
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingBalance('Litecoin', 'MWEB Balance:', coinData.pending, coinData.ticker || 'LTC', 'Pending:', coinData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name);
|
||||||
|
|
||||||
|
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingDisplay(coinData);
|
||||||
|
} else {
|
||||||
|
removePendingDisplay(coinData.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpecificBalance(coinName, labelText, balance, ticker, isPending = false) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentRow = element.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell) {
|
||||||
|
const currentLabel = labelCell.textContent.trim();
|
||||||
|
|
||||||
|
if (currentLabel.includes(labelText)) {
|
||||||
|
if (isPending) {
|
||||||
|
element.textContent = `+${balance} ${ticker}`;
|
||||||
|
} else {
|
||||||
|
element.textContent = `${balance} ${ticker}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.WalletManager && typeof window.WalletManager.updatePrices === 'function') {
|
||||||
|
window.WalletManager.updatePrices(false);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePendingDisplay(coinData) {
|
||||||
|
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinData.name) {
|
||||||
|
const parentRow = element.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell && labelCell.textContent.includes('Balance:')) {
|
||||||
|
|
||||||
|
const balanceCell = parentRow.querySelector('td:nth-child(2)');
|
||||||
|
if (balanceCell) {
|
||||||
|
let pendingSpan = balanceCell.querySelector('.inline-block.py-1.px-2.rounded-full.bg-green-100');
|
||||||
|
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
if (!pendingSpan) {
|
||||||
|
|
||||||
|
pendingSpan = document.createElement('span');
|
||||||
|
pendingSpan.className = 'inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500';
|
||||||
|
balanceCell.appendChild(document.createTextNode(' '));
|
||||||
|
balanceCell.appendChild(pendingSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticker = coinData.ticker || coinData.name;
|
||||||
|
|
||||||
|
pendingSpan.textContent = `Pending: +${coinData.pending} ${ticker}`;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (pendingSpan) {
|
||||||
|
pendingSpan.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePendingDisplay(coinName) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentRow = element.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const balanceCell = parentRow.querySelector('td:nth-child(2)');
|
||||||
|
if (balanceCell) {
|
||||||
|
const pendingSpan = balanceCell.querySelector('.inline-block.py-1.px-2.rounded-full.bg-green-100');
|
||||||
|
if (pendingSpan) {
|
||||||
|
pendingSpan.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePendingBalance(coinName, balanceType, pendingAmount, ticker, pendingLabel, coinData) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentRow = element.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell && labelCell.textContent.includes(balanceType)) {
|
||||||
|
|
||||||
|
const balanceCell = parentRow.querySelector('td:nth-child(2)');
|
||||||
|
if (balanceCell) {
|
||||||
|
let pendingSpan = balanceCell.querySelector('.inline-block.py-1.px-2.rounded-full.bg-green-100');
|
||||||
|
|
||||||
|
if (pendingAmount && parseFloat(pendingAmount) > 0) {
|
||||||
|
if (!pendingSpan) {
|
||||||
|
|
||||||
|
pendingSpan = document.createElement('span');
|
||||||
|
pendingSpan.className = 'inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500';
|
||||||
|
balanceCell.appendChild(document.createTextNode(' '));
|
||||||
|
balanceCell.appendChild(pendingSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pendingSpan.textContent = `${pendingLabel} +${pendingAmount} ${ticker}`;
|
||||||
|
} else if (pendingSpan) {
|
||||||
|
|
||||||
|
pendingSpan.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePendingBalance(coinName, balanceType) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentRow = element.closest('tr');
|
||||||
|
if (parentRow) {
|
||||||
|
const labelCell = parentRow.querySelector('td:first-child');
|
||||||
|
if (labelCell && labelCell.textContent.includes(balanceType)) {
|
||||||
|
|
||||||
|
const balanceCell = parentRow.querySelector('td:nth-child(2)');
|
||||||
|
if (balanceCell) {
|
||||||
|
const pendingSpan = balanceCell.querySelector('.inline-block.py-1.px-2.rounded-full.bg-green-100');
|
||||||
|
if (pendingSpan) {
|
||||||
|
pendingSpan.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWalletWebSocketUpdates() {
|
||||||
|
window.BalanceUpdatesManager.setup({
|
||||||
|
contextKey: 'wallet',
|
||||||
|
balanceUpdateCallback: updateWalletBalances,
|
||||||
|
swapEventCallback: updateWalletBalances,
|
||||||
|
errorContext: 'Wallet',
|
||||||
|
enablePeriodicRefresh: true,
|
||||||
|
periodicInterval: 120000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.addMessageHandler === 'function') {
|
||||||
|
const priceHandlerId = window.WebSocketManager.addMessageHandler('message', (data) => {
|
||||||
|
if (data && data.event) {
|
||||||
|
if (data.event === 'price_updated' || data.event === 'prices_updated') {
|
||||||
|
clearTimeout(window.walletPriceUpdateTimeout);
|
||||||
|
window.walletPriceUpdateTimeout = setTimeout(() => {
|
||||||
|
if (window.WalletManager && typeof window.WalletManager.updatePrices === 'function') {
|
||||||
|
window.WalletManager.updatePrices(true);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.walletPriceHandlerId = priceHandlerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupWalletBalanceUpdates() {
|
||||||
|
window.BalanceUpdatesManager.cleanup('wallet');
|
||||||
|
|
||||||
|
if (window.walletPriceHandlerId && window.WebSocketManager) {
|
||||||
|
window.WebSocketManager.removeMessageHandler('message', window.walletPriceHandlerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(window.walletPriceUpdateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.BalanceUpdatesManager.initialize();
|
||||||
|
setupWalletWebSocketUpdates();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
updateWalletBalances();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', cleanupWalletBalanceUpdates);
|
||||||
|
|
||||||
document.getElementById('confirmYes').addEventListener('click', function() {
|
document.getElementById('confirmYes').addEventListener('click', function() {
|
||||||
if (typeof confirmCallback === 'function') {
|
if (typeof confirmCallback === 'function') {
|
||||||
confirmCallback();
|
confirmCallback();
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
<div id="total-btc-value" class="text-sm text-white mt-2"></div>
|
<div id="total-btc-value" class="text-sm text-white mt-2"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
|
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
|
||||||
<a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/changepassword">{{ lock_svg | safe }}<span>Change/Set Password</span></a>
|
<a class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" href="/changepassword">{{ lock_svg | safe }}<span>Change/Set Password</span></a>
|
||||||
<a class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallets">{{ circular_arrows_svg | safe }}<span>Refresh</span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,5 +189,426 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.initialize === 'function') {
|
||||||
|
window.WebSocketManager.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWalletsWebSocketUpdates() {
|
||||||
|
window.BalanceUpdatesManager.setup({
|
||||||
|
contextKey: 'wallets',
|
||||||
|
balanceUpdateCallback: updateWalletBalances,
|
||||||
|
swapEventCallback: updateWalletBalances,
|
||||||
|
errorContext: 'Wallets',
|
||||||
|
enablePeriodicRefresh: true,
|
||||||
|
periodicInterval: 60000
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.WebSocketManager && typeof window.WebSocketManager.addMessageHandler === 'function') {
|
||||||
|
const priceHandlerId = window.WebSocketManager.addMessageHandler('message', (data) => {
|
||||||
|
if (data && data.event) {
|
||||||
|
if (data.event === 'price_updated' || data.event === 'prices_updated') {
|
||||||
|
clearTimeout(window.walletsPriceUpdateTimeout);
|
||||||
|
window.walletsPriceUpdateTimeout = setTimeout(() => {
|
||||||
|
if (window.WalletManager && typeof window.WalletManager.updatePrices === 'function') {
|
||||||
|
window.WalletManager.updatePrices(true);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.walletsPriceHandlerId = priceHandlerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWalletBalances(balanceData) {
|
||||||
|
if (balanceData) {
|
||||||
|
balanceData.forEach(coin => {
|
||||||
|
updateWalletDisplay(coin);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (window.WalletManager && typeof window.WalletManager.updatePrices === 'function') {
|
||||||
|
window.WalletManager.updatePrices(true);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
|
} else {
|
||||||
|
window.BalanceUpdatesManager.fetchBalanceData()
|
||||||
|
.then(data => updateWalletBalances(data))
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error updating wallet balances:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWalletDisplay(coinData) {
|
||||||
|
if (coinData.name === 'Particl') {
|
||||||
|
updateSpecificBalance('Particl', 'Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
} else if (coinData.name === 'Particl Anon') {
|
||||||
|
updateSpecificBalance('Particl', 'Anon Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
removePendingBalance('Particl', 'Anon Balance:');
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingBalance('Particl', 'Anon Balance:', coinData.pending, coinData.ticker || 'PART', 'Anon Pending:', coinData);
|
||||||
|
}
|
||||||
|
} else if (coinData.name === 'Particl Blind') {
|
||||||
|
updateSpecificBalance('Particl', 'Blind Balance:', coinData.balance, coinData.ticker || 'PART');
|
||||||
|
removePendingBalance('Particl', 'Blind Balance:');
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Blind Unconfirmed:', coinData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name);
|
||||||
|
|
||||||
|
if (coinData.name !== 'Particl Anon' && coinData.name !== 'Particl Blind' && coinData.name !== 'Litecoin MWEB') {
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
updatePendingDisplay(coinData);
|
||||||
|
} else {
|
||||||
|
removePendingDisplay(coinData.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSpecificBalance(coinName, labelText, balance, ticker, isPending = false) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentDiv = element.closest('.flex.mb-2.justify-between.items-center');
|
||||||
|
const labelElement = parentDiv ? parentDiv.querySelector('h4') : null;
|
||||||
|
|
||||||
|
if (labelElement) {
|
||||||
|
const currentLabel = labelElement.textContent.trim();
|
||||||
|
|
||||||
|
if (currentLabel === labelText) {
|
||||||
|
if (isPending) {
|
||||||
|
const cleanBalance = balance.toString().replace(/^\+/, '');
|
||||||
|
element.textContent = `+${cleanBalance} ${ticker}`;
|
||||||
|
} else {
|
||||||
|
element.textContent = `${balance} ${ticker}`;
|
||||||
|
}
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePendingDisplay(coinData) {
|
||||||
|
const walletContainer = findWalletContainer(coinData.name);
|
||||||
|
if (!walletContainer) return;
|
||||||
|
|
||||||
|
const existingPendingElements = walletContainer.querySelectorAll('.flex.mb-2.justify-between.items-center');
|
||||||
|
let staticPendingElement = null;
|
||||||
|
let staticUsdElement = null;
|
||||||
|
|
||||||
|
existingPendingElements.forEach(element => {
|
||||||
|
const labelElement = element.querySelector('h4');
|
||||||
|
if (labelElement) {
|
||||||
|
const labelText = labelElement.textContent;
|
||||||
|
if (labelText.includes('Pending:') && !labelText.includes('USD')) {
|
||||||
|
staticPendingElement = element;
|
||||||
|
} else if (labelText.includes('Pending USD value:')) {
|
||||||
|
staticUsdElement = element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (staticPendingElement && staticUsdElement) {
|
||||||
|
const pendingSpan = staticPendingElement.querySelector('.coinname-value');
|
||||||
|
if (pendingSpan) {
|
||||||
|
const cleanPending = coinData.pending.toString().replace(/^\+/, '');
|
||||||
|
pendingSpan.textContent = `+${cleanPending} ${coinData.ticker || coinData.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialUSD = '$0.00';
|
||||||
|
if (window.WalletManager && window.WalletManager.coinPrices) {
|
||||||
|
const coinId = coinData.name.toLowerCase().replace(' ', '-');
|
||||||
|
const price = window.WalletManager.coinPrices[coinId];
|
||||||
|
if (price && price.usd) {
|
||||||
|
const cleanPending = coinData.pending.toString().replace(/^\+/, '');
|
||||||
|
const usdValue = (parseFloat(cleanPending) * price.usd).toFixed(2);
|
||||||
|
initialUSD = `$${usdValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usdDiv = staticUsdElement.querySelector('.usd-value');
|
||||||
|
if (usdDiv) {
|
||||||
|
usdDiv.textContent = initialUSD;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pendingContainer = walletContainer.querySelector('.pending-container');
|
||||||
|
|
||||||
|
if (!pendingContainer) {
|
||||||
|
pendingContainer = document.createElement('div');
|
||||||
|
pendingContainer.className = 'pending-container';
|
||||||
|
|
||||||
|
const pendingRow = document.createElement('div');
|
||||||
|
pendingRow.className = 'flex mb-2 justify-between items-center';
|
||||||
|
const cleanPending = coinData.pending.toString().replace(/^\+/, '');
|
||||||
|
pendingRow.innerHTML = `
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Pending:</h4>
|
||||||
|
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="${coinData.name}">+${cleanPending} ${coinData.ticker || coinData.name}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
pendingContainer.appendChild(pendingRow);
|
||||||
|
|
||||||
|
let initialUSD = '$0.00';
|
||||||
|
if (window.WalletManager && window.WalletManager.coinPrices) {
|
||||||
|
const coinId = coinData.name.toLowerCase().replace(' ', '-');
|
||||||
|
const price = window.WalletManager.coinPrices[coinId];
|
||||||
|
if (price && price.usd) {
|
||||||
|
const usdValue = (parseFloat(cleanPending) * price.usd).toFixed(2);
|
||||||
|
initialUSD = `$${usdValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usdRow = document.createElement('div');
|
||||||
|
usdRow.className = 'flex mb-2 justify-between items-center';
|
||||||
|
usdRow.innerHTML = `
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Pending USD value:</h4>
|
||||||
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value">${initialUSD}</div>
|
||||||
|
`;
|
||||||
|
pendingContainer.appendChild(usdRow);
|
||||||
|
|
||||||
|
const balanceRow = walletContainer.querySelector('.flex.mb-2.justify-between.items-center');
|
||||||
|
let insertAfterElement = balanceRow;
|
||||||
|
|
||||||
|
if (balanceRow) {
|
||||||
|
let nextElement = balanceRow.nextElementSibling;
|
||||||
|
while (nextElement) {
|
||||||
|
const labelElement = nextElement.querySelector('h4');
|
||||||
|
if (labelElement && labelElement.textContent.includes('USD value:') &&
|
||||||
|
!labelElement.textContent.includes('Pending') && !labelElement.textContent.includes('Unconfirmed')) {
|
||||||
|
insertAfterElement = nextElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextElement = nextElement.nextElementSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertAfterElement && insertAfterElement.nextSibling) {
|
||||||
|
walletContainer.insertBefore(pendingContainer, insertAfterElement.nextSibling);
|
||||||
|
} else {
|
||||||
|
walletContainer.appendChild(pendingContainer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const pendingElement = pendingContainer.querySelector('.coinname-value');
|
||||||
|
if (pendingElement) {
|
||||||
|
const cleanPending = coinData.pending.toString().replace(/^\+/, ''); // Remove existing + if any
|
||||||
|
pendingElement.textContent = `+${cleanPending} ${coinData.ticker || coinData.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePendingDisplay(coinName) {
|
||||||
|
const walletContainer = findWalletContainer(coinName);
|
||||||
|
if (!walletContainer) return;
|
||||||
|
|
||||||
|
const pendingContainer = walletContainer.querySelector('.pending-container');
|
||||||
|
if (pendingContainer) {
|
||||||
|
pendingContainer.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingPendingElements = walletContainer.querySelectorAll('.flex.mb-2.justify-between.items-center');
|
||||||
|
existingPendingElements.forEach(element => {
|
||||||
|
const labelElement = element.querySelector('h4');
|
||||||
|
if (labelElement) {
|
||||||
|
const labelText = labelElement.textContent;
|
||||||
|
if (labelText.includes('Pending:') || labelText.includes('Pending USD value:')) {
|
||||||
|
element.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSpecificPending(coinName, labelText) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentDiv = element.closest('.flex.mb-2.justify-between.items-center');
|
||||||
|
const labelElement = parentDiv ? parentDiv.querySelector('h4') : null;
|
||||||
|
|
||||||
|
if (labelElement) {
|
||||||
|
const currentLabel = labelElement.textContent.trim();
|
||||||
|
|
||||||
|
if (currentLabel === labelText) {
|
||||||
|
parentDiv.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePendingBalance(coinName, balanceType, pendingAmount, ticker, pendingLabel, coinData) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
let targetElement = null;
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentElement = element.closest('.flex.mb-2.justify-between.items-center');
|
||||||
|
if (parentElement) {
|
||||||
|
const labelElement = parentElement.querySelector('h4');
|
||||||
|
if (labelElement && labelElement.textContent.includes(balanceType)) {
|
||||||
|
targetElement = parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetElement) return;
|
||||||
|
|
||||||
|
let insertAfterElement = targetElement;
|
||||||
|
let nextElement = targetElement.nextElementSibling;
|
||||||
|
while (nextElement) {
|
||||||
|
const labelElement = nextElement.querySelector('h4');
|
||||||
|
if (labelElement) {
|
||||||
|
const labelText = labelElement.textContent;
|
||||||
|
if (labelText.includes('USD value:') && !labelText.includes('Pending') && !labelText.includes('Unconfirmed')) {
|
||||||
|
insertAfterElement = nextElement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (labelText.includes('Balance:') || labelText.includes('Pending:') || labelText.includes('Unconfirmed:')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextElement = nextElement.nextElementSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pendingElement = insertAfterElement.nextElementSibling;
|
||||||
|
while (pendingElement && !pendingElement.querySelector('h4')?.textContent.includes(pendingLabel)) {
|
||||||
|
pendingElement = pendingElement.nextElementSibling;
|
||||||
|
if (pendingElement && pendingElement.querySelector('h4')?.textContent.includes('Balance:')) {
|
||||||
|
pendingElement = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pendingElement) {
|
||||||
|
const newPendingElement = document.createElement('div');
|
||||||
|
newPendingElement.className = 'flex mb-2 justify-between items-center';
|
||||||
|
newPendingElement.innerHTML = `
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">${pendingLabel}</h4>
|
||||||
|
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="${coinName}">+${pendingAmount} ${ticker}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
insertAfterElement.parentNode.insertBefore(newPendingElement, insertAfterElement.nextSibling);
|
||||||
|
|
||||||
|
let initialUSD = '$0.00';
|
||||||
|
if (window.WalletManager && window.WalletManager.coinPrices) {
|
||||||
|
const coinId = coinName.toLowerCase().replace(' ', '-');
|
||||||
|
const price = window.WalletManager.coinPrices[coinId];
|
||||||
|
if (price && price.usd) {
|
||||||
|
const usdValue = (parseFloat(pendingAmount) * price.usd).toFixed(2);
|
||||||
|
initialUSD = `$${usdValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usdElement = document.createElement('div');
|
||||||
|
usdElement.className = 'flex mb-2 justify-between items-center';
|
||||||
|
usdElement.innerHTML = `
|
||||||
|
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">${pendingLabel.replace(':', '')} USD value:</h4>
|
||||||
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value">${initialUSD}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
newPendingElement.parentNode.insertBefore(usdElement, newPendingElement.nextSibling);
|
||||||
|
} else {
|
||||||
|
const pendingSpan = pendingElement.querySelector('.coinname-value');
|
||||||
|
if (pendingSpan) {
|
||||||
|
pendingSpan.textContent = `+${pendingAmount} ${ticker}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePendingBalance(coinName, balanceType) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
let targetElement = null;
|
||||||
|
|
||||||
|
balanceElements.forEach(element => {
|
||||||
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
|
if (elementCoinName === coinName) {
|
||||||
|
const parentElement = element.closest('.flex.mb-2.justify-between.items-center');
|
||||||
|
if (parentElement) {
|
||||||
|
const labelElement = parentElement.querySelector('h4');
|
||||||
|
if (labelElement && labelElement.textContent.includes(balanceType)) {
|
||||||
|
targetElement = parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetElement) return;
|
||||||
|
|
||||||
|
let nextElement = targetElement.nextElementSibling;
|
||||||
|
while (nextElement) {
|
||||||
|
const labelElement = nextElement.querySelector('h4');
|
||||||
|
if (labelElement) {
|
||||||
|
const labelText = labelElement.textContent;
|
||||||
|
if (labelText.includes('Pending:') || labelText.includes('Unconfirmed:') ||
|
||||||
|
labelText.includes('Anon Pending:') || labelText.includes('Blind Unconfirmed:') ||
|
||||||
|
labelText.includes('Pending USD value:') || labelText.includes('Unconfirmed USD value:') ||
|
||||||
|
labelText.includes('Anon Pending USD value:') || labelText.includes('Blind Unconfirmed USD value:')) {
|
||||||
|
const elementToRemove = nextElement;
|
||||||
|
nextElement = nextElement.nextElementSibling;
|
||||||
|
elementToRemove.remove();
|
||||||
|
} else if (labelText.includes('Balance:')) {
|
||||||
|
break; // Stop if we hit another balance
|
||||||
|
} else {
|
||||||
|
nextElement = nextElement.nextElementSibling;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextElement = nextElement.nextElementSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWalletContainer(coinName) {
|
||||||
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
|
for (const element of balanceElements) {
|
||||||
|
if (element.getAttribute('data-coinname') === coinName) {
|
||||||
|
return element.closest('.bg-coolGray-100, .dark\\:bg-gray-600');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupWalletsBalanceUpdates() {
|
||||||
|
window.BalanceUpdatesManager.cleanup('wallets');
|
||||||
|
|
||||||
|
if (window.walletsPriceHandlerId && window.WebSocketManager) {
|
||||||
|
window.WebSocketManager.removeMessageHandler('message', window.walletsPriceHandlerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(window.walletsPriceUpdateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.BalanceUpdatesManager.initialize();
|
||||||
|
setupWalletsWebSocketUpdates();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
updateWalletBalances();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
if (window.CleanupManager) {
|
||||||
|
window.CleanupManager.registerResource('walletsBalanceUpdates', null, cleanupWalletsBalanceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', cleanupWalletsBalanceUpdates);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import traceback
|
|||||||
import sys
|
import sys
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
from .util import listAvailableCoins
|
from .util import listAvailableCoinsWithBalances
|
||||||
|
|
||||||
DEFAULT_AMM_CONFIG_FILE = "createoffers.json"
|
DEFAULT_AMM_CONFIG_FILE = "createoffers.json"
|
||||||
DEFAULT_AMM_STATE_FILE = "createoffers_state.json"
|
DEFAULT_AMM_STATE_FILE = "createoffers_state.json"
|
||||||
@@ -1286,7 +1286,7 @@ def page_amm(self, _, post_string):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_messages.append(f"Failed to read state file: {str(e)}")
|
err_messages.append(f"Failed to read state file: {str(e)}")
|
||||||
|
|
||||||
coins = listAvailableCoins(swap_client)
|
coins = listAvailableCoinsWithBalances(swap_client)
|
||||||
|
|
||||||
template = server.env.get_template("amm.html")
|
template = server.env.get_template("amm.html")
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from .util import (
|
|||||||
have_data_entry,
|
have_data_entry,
|
||||||
inputAmount,
|
inputAmount,
|
||||||
listAvailableCoins,
|
listAvailableCoins,
|
||||||
|
listAvailableCoinsWithBalances,
|
||||||
PAGE_LIMIT,
|
PAGE_LIMIT,
|
||||||
setCoinFilter,
|
setCoinFilter,
|
||||||
set_pagination_filters,
|
set_pagination_filters,
|
||||||
@@ -526,7 +527,7 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
if swap_client.debug_ui:
|
if swap_client.debug_ui:
|
||||||
messages.append("Debug mode active.")
|
messages.append("Debug mode active.")
|
||||||
|
|
||||||
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
|
coins_from, coins_to = listAvailableCoinsWithBalances(swap_client, split_from=True)
|
||||||
|
|
||||||
addrs_from_raw = swap_client.listSMSGAddresses("offer_send_from")
|
addrs_from_raw = swap_client.listSMSGAddresses("offer_send_from")
|
||||||
addrs_to_raw = swap_client.listSMSGAddresses("offer_send_to")
|
addrs_to_raw = swap_client.listSMSGAddresses("offer_send_to")
|
||||||
|
|||||||
@@ -60,6 +60,38 @@ def page_settings(self, url_split, post_string):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
swap_client.editGeneralSettings(data)
|
swap_client.editGeneralSettings(data)
|
||||||
|
elif have_data_entry(form_data, "apply_notifications"):
|
||||||
|
active_tab = "notifications"
|
||||||
|
data = {
|
||||||
|
"notifications_new_offers": toBool(
|
||||||
|
get_data_entry_or(
|
||||||
|
form_data, "notifications_new_offers", "false"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"notifications_new_bids": toBool(
|
||||||
|
get_data_entry_or(form_data, "notifications_new_bids", "false")
|
||||||
|
),
|
||||||
|
"notifications_bid_accepted": toBool(
|
||||||
|
get_data_entry_or(
|
||||||
|
form_data, "notifications_bid_accepted", "false"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"notifications_balance_changes": toBool(
|
||||||
|
get_data_entry_or(
|
||||||
|
form_data, "notifications_balance_changes", "false"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"notifications_outgoing_transactions": toBool(
|
||||||
|
get_data_entry_or(
|
||||||
|
form_data, "notifications_outgoing_transactions", "false"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"notifications_duration": int(
|
||||||
|
get_data_entry_or(form_data, "notifications_duration", "20")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
swap_client.editGeneralSettings(data)
|
||||||
|
messages.append("Notification settings applied.")
|
||||||
elif have_data_entry(form_data, "apply_tor"):
|
elif have_data_entry(form_data, "apply_tor"):
|
||||||
active_tab = "tor"
|
active_tab = "tor"
|
||||||
# TODO: Detect if running in docker
|
# TODO: Detect if running in docker
|
||||||
@@ -186,6 +218,27 @@ def page_settings(self, url_split, post_string):
|
|||||||
"enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""),
|
"enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notification_settings = {
|
||||||
|
"notifications_new_offers": swap_client.settings.get(
|
||||||
|
"notifications_new_offers", False
|
||||||
|
),
|
||||||
|
"notifications_new_bids": swap_client.settings.get(
|
||||||
|
"notifications_new_bids", True
|
||||||
|
),
|
||||||
|
"notifications_bid_accepted": swap_client.settings.get(
|
||||||
|
"notifications_bid_accepted", True
|
||||||
|
),
|
||||||
|
"notifications_balance_changes": swap_client.settings.get(
|
||||||
|
"notifications_balance_changes", True
|
||||||
|
),
|
||||||
|
"notifications_outgoing_transactions": swap_client.settings.get(
|
||||||
|
"notifications_outgoing_transactions", True
|
||||||
|
),
|
||||||
|
"notifications_duration": swap_client.settings.get(
|
||||||
|
"notifications_duration", 20
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
tor_control_password = (
|
tor_control_password = (
|
||||||
""
|
""
|
||||||
if swap_client.tor_control_password is None
|
if swap_client.tor_control_password is None
|
||||||
@@ -209,6 +262,7 @@ def page_settings(self, url_split, post_string):
|
|||||||
"chains": chains_formatted,
|
"chains": chains_formatted,
|
||||||
"general_settings": general_settings,
|
"general_settings": general_settings,
|
||||||
"chart_settings": chart_settings,
|
"chart_settings": chart_settings,
|
||||||
|
"notification_settings": notification_settings,
|
||||||
"tor_settings": tor_settings,
|
"tor_settings": tor_settings,
|
||||||
"active_tab": active_tab,
|
"active_tab": active_tab,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -659,6 +659,76 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
|
|||||||
return coins
|
return coins
|
||||||
|
|
||||||
|
|
||||||
|
def listAvailableCoinsWithBalances(swap_client, with_variants=True, split_from=False):
|
||||||
|
swap_client.updateWalletsInfo()
|
||||||
|
wallets = swap_client.getCachedWalletsInfo()
|
||||||
|
|
||||||
|
coins_from = []
|
||||||
|
coins = []
|
||||||
|
|
||||||
|
for k, v in swap_client.coin_clients.items():
|
||||||
|
if k not in chainparams:
|
||||||
|
continue
|
||||||
|
if v["connection_type"] == "rpc":
|
||||||
|
|
||||||
|
balance = "0.0"
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "balance" in w and "error" not in w and "no_data" not in w:
|
||||||
|
balance = w["balance"]
|
||||||
|
|
||||||
|
coin_entry = (int(k), getCoinName(k), balance)
|
||||||
|
coins.append(coin_entry)
|
||||||
|
if split_from:
|
||||||
|
coins_from.append(coin_entry)
|
||||||
|
|
||||||
|
if with_variants and k == Coins.PART:
|
||||||
|
|
||||||
|
for variant in (Coins.PART_ANON, Coins.PART_BLIND):
|
||||||
|
variant_balance = "0.0"
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "error" not in w and "no_data" not in w:
|
||||||
|
if variant == Coins.PART_ANON and "anon_balance" in w:
|
||||||
|
variant_balance = w["anon_balance"]
|
||||||
|
elif variant == Coins.PART_BLIND and "blind_balance" in w:
|
||||||
|
variant_balance = w["blind_balance"]
|
||||||
|
|
||||||
|
variant_entry = (
|
||||||
|
int(variant),
|
||||||
|
getCoinName(variant),
|
||||||
|
variant_balance,
|
||||||
|
)
|
||||||
|
coins.append(variant_entry)
|
||||||
|
if split_from:
|
||||||
|
coins_from.append(variant_entry)
|
||||||
|
|
||||||
|
if with_variants and k == Coins.LTC:
|
||||||
|
|
||||||
|
for variant in (Coins.LTC_MWEB,):
|
||||||
|
variant_balance = "0.0"
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if (
|
||||||
|
"error" not in w
|
||||||
|
and "no_data" not in w
|
||||||
|
and "mweb_balance" in w
|
||||||
|
):
|
||||||
|
variant_balance = w["mweb_balance"]
|
||||||
|
|
||||||
|
variant_entry = (
|
||||||
|
int(variant),
|
||||||
|
getCoinName(variant),
|
||||||
|
variant_balance,
|
||||||
|
)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
if split_from:
|
||||||
|
return coins_from, coins
|
||||||
|
return coins
|
||||||
|
|
||||||
|
|
||||||
def checkAddressesOwned(swap_client, ci, wallet_info):
|
def checkAddressesOwned(swap_client, ci, wallet_info):
|
||||||
if "stealth_address" in wallet_info:
|
if "stealth_address" in wallet_info:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user