updates
This commit is contained in:
114
modules/boersenchecker/assets/boersenchecker-native.js
Normal file
114
modules/boersenchecker/assets/boersenchecker-native.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function toPartialUrl(input, fallbackView) {
|
||||||
|
const url = new URL(input, window.location.origin);
|
||||||
|
if (!url.searchParams.has('view') && fallbackView) {
|
||||||
|
url.searchParams.set('view', fallbackView);
|
||||||
|
}
|
||||||
|
url.searchParams.set('partial', '1');
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHtml(url, options) {
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
Accept: 'text/html',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
function enhanceHost(host, state) {
|
||||||
|
host.querySelectorAll('a[href]').forEach((link) => {
|
||||||
|
const href = link.getAttribute('href') || '';
|
||||||
|
if (!href.startsWith('/apps/boersenchecker')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
state.load(href);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
host.querySelectorAll('form').forEach((form) => {
|
||||||
|
form.addEventListener('submit', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const method = String(form.getAttribute('method') || 'get').toUpperCase();
|
||||||
|
const action = form.getAttribute('action') || state.currentUrl.toString();
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const url = toPartialUrl(action, state.defaultView);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (method === 'GET') {
|
||||||
|
const next = new URL(url.toString());
|
||||||
|
next.search = '';
|
||||||
|
next.searchParams.set('partial', '1');
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
next.searchParams.append(key, String(value));
|
||||||
|
}
|
||||||
|
await state.load(next.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await fetchHtml(url, {
|
||||||
|
method,
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
state.render(html, url);
|
||||||
|
} catch (error) {
|
||||||
|
state.renderError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.initBoersencheckerApp = function initBoersencheckerApp(host, options) {
|
||||||
|
if (!(host instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryRoute = String(options?.entryRoute || '/apps/boersenchecker');
|
||||||
|
const defaultView = String(options?.defaultView || 'overview');
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
defaultView,
|
||||||
|
currentUrl: toPartialUrl(entryRoute, defaultView),
|
||||||
|
async load(targetUrl) {
|
||||||
|
const url = toPartialUrl(targetUrl, defaultView);
|
||||||
|
this.currentUrl = url;
|
||||||
|
host.innerHTML = '<div class="window-app-loading"><p>Börsenchecker wird geladen...</p></div>';
|
||||||
|
try {
|
||||||
|
const html = await fetchHtml(url);
|
||||||
|
this.render(html, url);
|
||||||
|
} catch (error) {
|
||||||
|
this.renderError(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render(html, url) {
|
||||||
|
this.currentUrl = url;
|
||||||
|
host.innerHTML = html;
|
||||||
|
enhanceHost(host, this);
|
||||||
|
if (typeof window.initBoersencheckerCharts === 'function') {
|
||||||
|
window.initBoersencheckerCharts(host);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderError(error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Börsenchecker konnte nicht geladen werden.';
|
||||||
|
host.innerHTML = `<div class="window-app-error-state"><p>${message}</p></div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.load(entryRoute);
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const app = document.querySelector('[data-bc-home]');
|
'use strict';
|
||||||
if (!app) return;
|
|
||||||
|
function initBoersencheckerCharts(root) {
|
||||||
|
const scope = root instanceof Element ? root : document;
|
||||||
|
const app = scope.matches?.('[data-bc-home]') ? scope : scope.querySelector('[data-bc-home]');
|
||||||
|
if (!app || app.dataset.bcChartBound === '1') return;
|
||||||
|
app.dataset.bcChartBound = '1';
|
||||||
|
|
||||||
const chartShell = app.querySelector('[data-bc-chart]');
|
const chartShell = app.querySelector('[data-bc-chart]');
|
||||||
const instrumentSelect = app.querySelector('[data-bc-instrument]');
|
const instrumentSelect = app.querySelector('[data-bc-instrument]');
|
||||||
@@ -9,9 +14,10 @@
|
|||||||
const rangeButtons = Array.from(app.querySelectorAll('[data-range]'));
|
const rangeButtons = Array.from(app.querySelectorAll('[data-range]'));
|
||||||
const statusNode = app.querySelector('[data-bc-chart-status]');
|
const statusNode = app.querySelector('[data-bc-chart-status]');
|
||||||
const summaryNode = app.querySelector('[data-bc-chart-summary]');
|
const summaryNode = app.querySelector('[data-bc-chart-summary]');
|
||||||
const endpoint = app.getAttribute('data-chart-endpoint') || '';
|
const endpoint = app.getAttribute('data-bc-chart-endpoint') || app.getAttribute('data-chart-endpoint') || '';
|
||||||
const instrumentsScript = app.querySelector('[data-bc-instruments-json]');
|
const instrumentsScript = app.querySelector('[data-bc-instruments-json]');
|
||||||
const instrumentMap = new Map();
|
const instrumentMap = new Map();
|
||||||
|
|
||||||
if (instrumentsScript?.textContent) {
|
if (instrumentsScript?.textContent) {
|
||||||
try {
|
try {
|
||||||
const items = JSON.parse(instrumentsScript.textContent);
|
const items = JSON.parse(instrumentsScript.textContent);
|
||||||
@@ -20,6 +26,7 @@
|
|||||||
}
|
}
|
||||||
} catch (_error) {}
|
} catch (_error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeRange = '1m';
|
let activeRange = '1m';
|
||||||
let currentPayload = null;
|
let currentPayload = null;
|
||||||
|
|
||||||
@@ -108,7 +115,10 @@
|
|||||||
}
|
}
|
||||||
if (statusNode) statusNode.textContent = 'Chartdaten werden geladen...';
|
if (statusNode) statusNode.textContent = 'Chartdaten werden geladen...';
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${endpoint}${endpoint.includes('?') ? '&' : '?'}instrument_id=${encodeURIComponent(instrumentId)}`, { headers: { Accept: 'application/json' } });
|
const response = await fetch(`${endpoint}${endpoint.includes('?') ? '&' : '?'}instrument_id=${encodeURIComponent(instrumentId)}`, {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { Accept: 'application/json' }
|
||||||
|
});
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
if (!payload.ok) {
|
if (!payload.ok) {
|
||||||
throw new Error(payload.message || 'Chartdaten konnten nicht geladen werden.');
|
throw new Error(payload.message || 'Chartdaten konnten nicht geladen werden.');
|
||||||
@@ -124,7 +134,9 @@
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
currentPayload = null;
|
currentPayload = null;
|
||||||
chartShell.innerHTML = `<div class="muted">${error.message}</div>`;
|
if (chartShell) {
|
||||||
|
chartShell.innerHTML = `<div class="muted">${error instanceof Error ? error.message : 'Chartdaten konnten nicht geladen werden.'}</div>`;
|
||||||
|
}
|
||||||
if (statusNode) statusNode.textContent = 'Fehler beim Laden der Chartdaten.';
|
if (statusNode) statusNode.textContent = 'Fehler beim Laden der Chartdaten.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,4 +161,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadChart();
|
loadChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.initBoersencheckerCharts = initBoersencheckerCharts;
|
||||||
|
initBoersencheckerCharts(document);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -15,11 +15,27 @@ return [
|
|||||||
'icon' => 'BC',
|
'icon' => 'BC',
|
||||||
'entry_route' => '/apps/boersenchecker',
|
'entry_route' => '/apps/boersenchecker',
|
||||||
'window_mode' => 'window',
|
'window_mode' => 'window',
|
||||||
'content_mode' => 'iframe',
|
'content_mode' => 'native-module',
|
||||||
'default_width' => 1220,
|
'default_width' => 1220,
|
||||||
'default_height' => 860,
|
'default_height' => 860,
|
||||||
'supports_widget' => false,
|
'supports_widget' => false,
|
||||||
'supports_tray' => false,
|
'supports_tray' => false,
|
||||||
'module_name' => 'finance',
|
'module_name' => 'finance',
|
||||||
'summary' => (string) ($manifest['description'] ?? 'Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung.'),
|
'summary' => (string) ($manifest['description'] ?? 'Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung.'),
|
||||||
|
'native_module' => [
|
||||||
|
'initializer' => 'initBoersencheckerApp',
|
||||||
|
'options' => [
|
||||||
|
'entryRoute' => '/apps/boersenchecker',
|
||||||
|
'defaultView' => 'overview',
|
||||||
|
],
|
||||||
|
'assets' => [
|
||||||
|
'styles' => [
|
||||||
|
['href' => '/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.css&v=' . rawurlencode((string) (is_file(__DIR__ . '/assets/boersenchecker.css') ? filemtime(__DIR__ . '/assets/boersenchecker.css') : '0'))],
|
||||||
|
],
|
||||||
|
'scripts' => [
|
||||||
|
['src' => '/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.js&v=' . rawurlencode((string) (is_file(__DIR__ . '/assets/boersenchecker.js') ? filemtime(__DIR__ . '/assets/boersenchecker.js') : '0'))],
|
||||||
|
['src' => '/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker-native.js&v=' . rawurlencode((string) (is_file(__DIR__ . '/assets/boersenchecker-native.js') ? filemtime(__DIR__ . '/assets/boersenchecker-native.js') : '0'))],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ session_start();
|
|||||||
|
|
||||||
$projectRoot = dirname(__DIR__, 3);
|
$projectRoot = dirname(__DIR__, 3);
|
||||||
ModuleHttp::requireDesktopAccess($projectRoot);
|
ModuleHttp::requireDesktopAccess($projectRoot);
|
||||||
|
$isPartial = !empty($_GET['partial']);
|
||||||
|
|
||||||
$view = trim((string) ($_GET['view'] ?? 'overview'));
|
$view = trim((string) ($_GET['view'] ?? 'overview'));
|
||||||
$allowedViews = ['overview', 'depotverwaltung', 'aktienverwaltung', 'setup'];
|
$allowedViews = ['overview', 'depotverwaltung', 'aktienverwaltung', 'setup'];
|
||||||
@@ -149,6 +150,36 @@ $renderSetup = static function (): void {
|
|||||||
<?= module_shell_footer() ?>
|
<?= module_shell_footer() ?>
|
||||||
<?php
|
<?php
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$renderContent = static function () use ($view, $renderSetup): void {
|
||||||
|
?>
|
||||||
|
<div class="boersenchecker-module-shell window-app-shell">
|
||||||
|
<?php
|
||||||
|
if ($view === 'setup') {
|
||||||
|
$renderSetup();
|
||||||
|
} elseif ($view === 'depotverwaltung') {
|
||||||
|
$page = new \Modules\Boersenchecker\Support\DashboardPage();
|
||||||
|
module_tpl('boersenchecker', 'dashboard', $page->handle());
|
||||||
|
} elseif ($view === 'aktienverwaltung') {
|
||||||
|
$page = new \Modules\Boersenchecker\Support\InstrumentPage();
|
||||||
|
module_tpl('boersenchecker', 'instruments', $page->handle());
|
||||||
|
} else {
|
||||||
|
$page = new \Modules\Boersenchecker\Support\HomePage();
|
||||||
|
module_tpl('boersenchecker', 'home', $page->handle());
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
};
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
$renderContent();
|
||||||
|
$pageContent = (string) ob_get_clean();
|
||||||
|
|
||||||
|
if ($isPartial) {
|
||||||
|
echo $pageContent;
|
||||||
|
return;
|
||||||
|
}
|
||||||
?><!DOCTYPE html>
|
?><!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
@@ -250,22 +281,7 @@ $renderSetup = static function (): void {
|
|||||||
<link rel="stylesheet" href="/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.css&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.css'), ENT_QUOTES) ?>">
|
<link rel="stylesheet" href="/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.css&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.css'), ENT_QUOTES) ?>">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="boersenchecker-module-shell">
|
<?= $pageContent ?>
|
||||||
<?php
|
|
||||||
if ($view === 'setup') {
|
|
||||||
$renderSetup();
|
|
||||||
} elseif ($view === 'depotverwaltung') {
|
|
||||||
$page = new \Modules\Boersenchecker\Support\DashboardPage();
|
|
||||||
module_tpl('boersenchecker', 'dashboard', $page->handle());
|
|
||||||
} elseif ($view === 'aktienverwaltung') {
|
|
||||||
$page = new \Modules\Boersenchecker\Support\InstrumentPage();
|
|
||||||
module_tpl('boersenchecker', 'instruments', $page->handle());
|
|
||||||
} else {
|
|
||||||
$page = new \Modules\Boersenchecker\Support\HomePage();
|
|
||||||
module_tpl('boersenchecker', 'home', $page->handle());
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.js&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.js'), ENT_QUOTES) ?>"></script>
|
<script src="/module-assets/index.php?module=boersenchecker&path=assets/boersenchecker.js&v=<?= htmlspecialchars($assetVersion('assets/boersenchecker.js'), ENT_QUOTES) ?>"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ $renderStartIcon = static function (array $profile): string {
|
|||||||
<button class="tray-menu-entry" type="button" data-account-action="login">Login</button>
|
<button class="tray-menu-entry" type="button" data-account-action="login">Login</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tray-menu" id="tray-fx-menu" hidden>
|
||||||
|
<p class="tray-menu-copy" id="tray-fx-status">Kursstatus wird geladen ...</p>
|
||||||
|
<button class="tray-menu-entry" type="button" data-fx-action="refresh">Refresh</button>
|
||||||
|
<button class="tray-menu-entry" type="button" data-fx-action="force-refresh">Force-Refresh</button>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1405,6 +1405,14 @@ h1 {
|
|||||||
background: rgba(255, 255, 255, 0.12);
|
background: rgba(255, 255, 255, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tray-menu-copy {
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 4px 2px;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
.window-content p:last-child {
|
.window-content p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ if (payloadNode) {
|
|||||||
const startMenuUserAvatar = document.getElementById('start-menu-user-avatar');
|
const startMenuUserAvatar = document.getElementById('start-menu-user-avatar');
|
||||||
const startMenuSessionAction = document.getElementById('start-menu-session-action');
|
const startMenuSessionAction = document.getElementById('start-menu-session-action');
|
||||||
const accountMenu = document.getElementById('tray-account-menu');
|
const accountMenu = document.getElementById('tray-account-menu');
|
||||||
|
const fxTrayMenu = document.getElementById('tray-fx-menu');
|
||||||
|
const fxTrayStatusNode = document.getElementById('tray-fx-status');
|
||||||
const clockNode = document.getElementById('clock');
|
const clockNode = document.getElementById('clock');
|
||||||
const clockTopNode = document.getElementById('clock-top');
|
const clockTopNode = document.getElementById('clock-top');
|
||||||
const debugToggleNode = document.getElementById('debug-toggle');
|
const debugToggleNode = document.getElementById('debug-toggle');
|
||||||
@@ -389,6 +391,88 @@ if (payloadNode) {
|
|||||||
accountButton.setAttribute('aria-expanded', String(isOpen));
|
accountButton.setAttribute('aria-expanded', String(isOpen));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setFxTrayMenuOpen = (isOpen) => {
|
||||||
|
const fxButton = document.querySelector('.tray-pill[data-tray-app-id="fx-rates"]');
|
||||||
|
|
||||||
|
if (!(fxTrayMenu instanceof HTMLElement) || !(fxButton instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fxTrayMenu.hidden = !isOpen;
|
||||||
|
fxButton.setAttribute('aria-expanded', String(isOpen));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadFxTrayStatus = async () => {
|
||||||
|
const fxButton = document.querySelector('.tray-pill[data-tray-app-id="fx-rates"]');
|
||||||
|
|
||||||
|
if (!(fxTrayStatusNode instanceof HTMLElement) || !(fxButton instanceof HTMLElement)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fxTrayStatusNode.textContent = 'Kursstatus wird geladen ...';
|
||||||
|
fxButton.setAttribute('title', 'Kursstatus wird geladen ...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/fx-rates/index.php?path=v1/status', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
});
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
const rows = Array.isArray(payload?.data) ? payload.data : [];
|
||||||
|
const latest = rows.slice().sort((left, right) => {
|
||||||
|
const leftTs = new Date(left?.fetched_at || 0).getTime();
|
||||||
|
const rightTs = new Date(right?.fetched_at || 0).getTime();
|
||||||
|
return rightTs - leftTs;
|
||||||
|
})[0] || null;
|
||||||
|
const statusText = latest
|
||||||
|
? `Letzter Abruf: ${latest.fetched_at_display || latest.fetched_at} · ${latest.base_currency} · ${latest.provider}`
|
||||||
|
: 'Noch keine gespeicherten Kurse vorhanden.';
|
||||||
|
|
||||||
|
fxTrayStatusNode.textContent = statusText;
|
||||||
|
fxButton.setAttribute('title', statusText);
|
||||||
|
return latest;
|
||||||
|
} catch (_error) {
|
||||||
|
const errorText = 'Kursstatus konnte nicht geladen werden.';
|
||||||
|
fxTrayStatusNode.textContent = errorText;
|
||||||
|
fxButton.setAttribute('title', errorText);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const runFxTrayRefresh = async (force) => {
|
||||||
|
if (!(fxTrayStatusNode instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fxTrayStatusNode.textContent = 'Aktualisierung laeuft ...';
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/fx-rates/index.php?path=v1/refresh', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
force: force === true,
|
||||||
|
trigger_source: 'tray',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(payload?.context?.message || payload?.error || `HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = payload?.data || {};
|
||||||
|
fxTrayStatusNode.textContent = result.reused
|
||||||
|
? 'Kein neuer API-Abruf. Snapshot ist noch jung genug.'
|
||||||
|
: `Kurse aktualisiert. ${result.updated_count || 0} Werte gespeichert.`;
|
||||||
|
await loadFxTrayStatus();
|
||||||
|
} catch (error) {
|
||||||
|
fxTrayStatusNode.textContent = error instanceof Error ? error.message : 'Aktualisierung fehlgeschlagen.';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const findAppById = (appId) => payload.apps.find((app) => app.app_id === appId) || null;
|
const findAppById = (appId) => payload.apps.find((app) => app.app_id === appId) || null;
|
||||||
|
|
||||||
const dispatchUserSettingsUpdate = (detail) => {
|
const dispatchUserSettingsUpdate = (detail) => {
|
||||||
@@ -1711,12 +1795,17 @@ if (payloadNode) {
|
|||||||
if (!(event.target instanceof Element) || !event.target.closest('.tray-pill[data-tray-id="account"], #tray-account-menu')) {
|
if (!(event.target instanceof Element) || !event.target.closest('.tray-pill[data-tray-id="account"], #tray-account-menu')) {
|
||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(event.target instanceof Element) || !event.target.closest('.tray-pill[data-tray-app-id="fx-rates"], #tray-fx-menu')) {
|
||||||
|
setFxTrayMenuOpen(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
closeDesktopContextMenu();
|
closeDesktopContextMenu();
|
||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
|
setFxTrayMenuOpen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1729,6 +1818,7 @@ if (payloadNode) {
|
|||||||
const isClosed = startMenu.hasAttribute('hidden');
|
const isClosed = startMenu.hasAttribute('hidden');
|
||||||
setStartMenuOpen(isClosed);
|
setStartMenuOpen(isClosed);
|
||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
|
setFxTrayMenuOpen(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.tray-pill[data-tray-id="account"]')?.addEventListener('click', () => {
|
document.querySelector('.tray-pill[data-tray-id="account"]')?.addEventListener('click', () => {
|
||||||
@@ -1738,20 +1828,50 @@ if (payloadNode) {
|
|||||||
|
|
||||||
setStartMenuOpen(false);
|
setStartMenuOpen(false);
|
||||||
setAccountMenuOpen(accountMenu.hidden);
|
setAccountMenuOpen(accountMenu.hidden);
|
||||||
|
setFxTrayMenuOpen(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.tray-pill[data-tray-app-id]').forEach((button) => {
|
document.querySelectorAll('.tray-pill[data-tray-app-id]').forEach((button) => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const appId = button.getAttribute('data-tray-app-id') || '';
|
const appId = button.getAttribute('data-tray-app-id') || '';
|
||||||
|
if (appId === 'fx-rates') {
|
||||||
|
setStartMenuOpen(false);
|
||||||
|
setAccountMenuOpen(false);
|
||||||
|
setFxTrayMenuOpen(fxTrayMenu instanceof HTMLElement ? fxTrayMenu.hidden : false);
|
||||||
|
void loadFxTrayStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const app = findAppById(appId);
|
const app = findAppById(appId);
|
||||||
if (app) {
|
if (app) {
|
||||||
openApp(app);
|
openApp(app);
|
||||||
setStartMenuOpen(false);
|
setStartMenuOpen(false);
|
||||||
setAccountMenuOpen(false);
|
setAccountMenuOpen(false);
|
||||||
|
setFxTrayMenuOpen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fxTrayMenu?.addEventListener('click', (event) => {
|
||||||
|
const target = event.target instanceof Element ? event.target.closest('[data-fx-action]') : null;
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = target.getAttribute('data-fx-action');
|
||||||
|
|
||||||
|
if (action === 'refresh') {
|
||||||
|
void runFxTrayRefresh(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'force-refresh') {
|
||||||
|
void runFxTrayRefresh(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
void loadFxTrayStatus();
|
||||||
|
|
||||||
debugToggleNode?.addEventListener('click', () => {
|
debugToggleNode?.addEventListener('click', () => {
|
||||||
toggleDebugWindow();
|
toggleDebugWindow();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user