@forgecharts/sdk 1.3.8 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/internal.js +6 -3
- package/dist/internal.js.map +1 -1
- package/dist/licensing/LicenseManager.d.ts.map +1 -1
- package/dist/react/index.js +894 -6
- package/dist/react/index.js.map +1 -1
- package/dist/react/internal.js +3151 -3048
- package/dist/react/internal.js.map +1 -1
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
- package/dist/react/shell/WatchlistDrawer.d.ts +8 -1
- package/dist/react/shell/WatchlistDrawer.d.ts.map +1 -1
- package/dist/react/shell/useWatchlistQuotes.d.ts +1 -1
- package/dist/react/shell/useWatchlistQuotes.d.ts.map +1 -1
- package/dist/react/shell/watchlistApi.d.ts +3 -0
- package/dist/react/shell/watchlistApi.d.ts.map +1 -1
- package/dist/react/workspace/ChartWorkspace.d.ts +15 -3
- package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -1
- package/dist/server/index.d.ts +22 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1051 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/knowledge.d.ts +11 -0
- package/dist/server/knowledge.d.ts.map +1 -0
- package/dist/server/systemPrompt.d.ts +13 -0
- package/dist/server/systemPrompt.d.ts.map +1 -0
- package/dist/server/tools.d.ts +24 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/package.json +6 -1
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React11, { forwardRef, useRef, useState, useImperativeHandle, useEffect, useCallback, createContext, useMemo, useContext } from 'react';
|
|
1
|
+
import React11, { forwardRef, useRef, useState, useImperativeHandle, useEffect, useCallback, createContext, useMemo, useContext, useReducer } from 'react';
|
|
2
2
|
import { TextStyle, Application, Container, Graphics, Text, FillGradient } from 'pixi.js';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import ReactDOM from 'react-dom';
|
|
@@ -10726,6 +10726,7 @@ function validateLicensePolicy(payload) {
|
|
|
10726
10726
|
}
|
|
10727
10727
|
|
|
10728
10728
|
// src/licensing/LicenseManager.ts
|
|
10729
|
+
var GLOBAL_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("@forgecharts/sdk:LicenseManager");
|
|
10729
10730
|
var DEFAULT_MODE = "unmanaged";
|
|
10730
10731
|
var DEFAULT_PRODUCT_TIER = "core";
|
|
10731
10732
|
var DEFAULT_FEATURES = {};
|
|
@@ -10760,10 +10761,12 @@ var LicenseManager = class _LicenseManager {
|
|
|
10760
10761
|
});
|
|
10761
10762
|
}
|
|
10762
10763
|
static getInstance() {
|
|
10763
|
-
|
|
10764
|
-
|
|
10764
|
+
const registry = globalThis;
|
|
10765
|
+
if (!registry[GLOBAL_INSTANCE_KEY]) {
|
|
10766
|
+
registry[GLOBAL_INSTANCE_KEY] = _LicenseManager.instance ?? new _LicenseManager();
|
|
10765
10767
|
}
|
|
10766
|
-
|
|
10768
|
+
_LicenseManager.instance = registry[GLOBAL_INSTANCE_KEY];
|
|
10769
|
+
return registry[GLOBAL_INSTANCE_KEY];
|
|
10767
10770
|
}
|
|
10768
10771
|
/**
|
|
10769
10772
|
* Validate a license key against the licensing endpoint.
|
|
@@ -17838,7 +17841,143 @@ var logo_dark_default = "../logo_dark-B2KCSRPJ.png";
|
|
|
17838
17841
|
|
|
17839
17842
|
// src/react/assets/logo_light.png
|
|
17840
17843
|
var logo_light_default = "../logo_light-NAWNBY4G.png";
|
|
17844
|
+
var _ws = null;
|
|
17845
|
+
var _listeners = /* @__PURE__ */ new Set();
|
|
17846
|
+
var _subscribed = /* @__PURE__ */ new Set();
|
|
17847
|
+
var _reconnectTimer = null;
|
|
17848
|
+
var _authToken = null;
|
|
17841
17849
|
function setWsAuthToken(token) {
|
|
17850
|
+
_authToken = token;
|
|
17851
|
+
}
|
|
17852
|
+
function _wsUrl() {
|
|
17853
|
+
const base = typeof window === "undefined" ? "ws://localhost:4001/ws" : `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/ws`;
|
|
17854
|
+
return _authToken ? `${base}?token=${encodeURIComponent(_authToken)}` : base;
|
|
17855
|
+
}
|
|
17856
|
+
function _send(msg) {
|
|
17857
|
+
if (_ws?.readyState === WebSocket.OPEN) {
|
|
17858
|
+
_ws.send(JSON.stringify(msg));
|
|
17859
|
+
}
|
|
17860
|
+
}
|
|
17861
|
+
function _ensureConnected() {
|
|
17862
|
+
if (_ws && (_ws.readyState === WebSocket.CONNECTING || _ws.readyState === WebSocket.OPEN)) return;
|
|
17863
|
+
if (_reconnectTimer) {
|
|
17864
|
+
clearTimeout(_reconnectTimer);
|
|
17865
|
+
_reconnectTimer = null;
|
|
17866
|
+
}
|
|
17867
|
+
_ws = new WebSocket(_wsUrl());
|
|
17868
|
+
_ws.onopen = () => {
|
|
17869
|
+
for (const sym of _subscribed) {
|
|
17870
|
+
_send({ type: "subscribe_quote", symbol: sym });
|
|
17871
|
+
}
|
|
17872
|
+
};
|
|
17873
|
+
_ws.onmessage = (event) => {
|
|
17874
|
+
try {
|
|
17875
|
+
const msg = JSON.parse(event.data);
|
|
17876
|
+
if (msg.type === "quote") {
|
|
17877
|
+
for (const listener of _listeners) {
|
|
17878
|
+
listener(msg.payload);
|
|
17879
|
+
}
|
|
17880
|
+
}
|
|
17881
|
+
} catch {
|
|
17882
|
+
}
|
|
17883
|
+
};
|
|
17884
|
+
_ws.onclose = () => {
|
|
17885
|
+
_ws = null;
|
|
17886
|
+
if (_subscribed.size > 0) {
|
|
17887
|
+
_reconnectTimer = setTimeout(_ensureConnected, 3e3);
|
|
17888
|
+
}
|
|
17889
|
+
};
|
|
17890
|
+
}
|
|
17891
|
+
function _addListener(fn) {
|
|
17892
|
+
_listeners.add(fn);
|
|
17893
|
+
}
|
|
17894
|
+
function _removeListener(fn) {
|
|
17895
|
+
_listeners.delete(fn);
|
|
17896
|
+
}
|
|
17897
|
+
function _subscribe(symbols) {
|
|
17898
|
+
_ensureConnected();
|
|
17899
|
+
for (const sym of symbols) {
|
|
17900
|
+
if (!_subscribed.has(sym)) {
|
|
17901
|
+
_subscribed.add(sym);
|
|
17902
|
+
_send({ type: "subscribe_quote", symbol: sym });
|
|
17903
|
+
}
|
|
17904
|
+
}
|
|
17905
|
+
}
|
|
17906
|
+
function _unsubscribe(symbols) {
|
|
17907
|
+
for (const sym of symbols) {
|
|
17908
|
+
_subscribed.delete(sym);
|
|
17909
|
+
_send({ type: "unsubscribe_quote", symbol: sym });
|
|
17910
|
+
}
|
|
17911
|
+
if (_subscribed.size === 0 && _ws) {
|
|
17912
|
+
_ws.close();
|
|
17913
|
+
_ws = null;
|
|
17914
|
+
}
|
|
17915
|
+
}
|
|
17916
|
+
function quoteReducer(state, action) {
|
|
17917
|
+
if (action.type === "tick") {
|
|
17918
|
+
const prices = new Map(state.prices);
|
|
17919
|
+
prices.set(action.symbol, action.last);
|
|
17920
|
+
const volumes = action.volume !== void 0 ? new Map(state.volumes).set(action.symbol, action.volume) : state.volumes;
|
|
17921
|
+
if (action.dir) {
|
|
17922
|
+
const dirs = new Map(state.dirs);
|
|
17923
|
+
dirs.set(action.symbol, action.dir);
|
|
17924
|
+
return { prices, dirs, volumes };
|
|
17925
|
+
}
|
|
17926
|
+
return { prices, dirs: state.dirs, volumes };
|
|
17927
|
+
}
|
|
17928
|
+
if (action.type === "clear_dir") {
|
|
17929
|
+
if (!state.dirs.has(action.symbol)) return state;
|
|
17930
|
+
const dirs = new Map(state.dirs);
|
|
17931
|
+
dirs.delete(action.symbol);
|
|
17932
|
+
return { ...state, dirs };
|
|
17933
|
+
}
|
|
17934
|
+
return state;
|
|
17935
|
+
}
|
|
17936
|
+
var FLASH_MS = 300;
|
|
17937
|
+
function useWatchlistQuotes(symbols, enabled = true) {
|
|
17938
|
+
const [state, dispatch] = useReducer(quoteReducer, void 0, () => ({
|
|
17939
|
+
prices: /* @__PURE__ */ new Map(),
|
|
17940
|
+
dirs: /* @__PURE__ */ new Map(),
|
|
17941
|
+
volumes: /* @__PURE__ */ new Map()
|
|
17942
|
+
}));
|
|
17943
|
+
const symbolsKey = symbols.slice().sort().join(",");
|
|
17944
|
+
const flashTimers = useRef(/* @__PURE__ */ new Map());
|
|
17945
|
+
const listenerRef = useRef(null);
|
|
17946
|
+
const onQuote = useCallback((payload) => {
|
|
17947
|
+
if (!_subscribed.has(payload.symbol)) return;
|
|
17948
|
+
const tickAction = { type: "tick", symbol: payload.symbol, last: payload.last, dir: payload.dir };
|
|
17949
|
+
if (payload.volume !== void 0) tickAction.volume = payload.volume;
|
|
17950
|
+
dispatch(tickAction);
|
|
17951
|
+
if (payload.dir) {
|
|
17952
|
+
const existing = flashTimers.current.get(payload.symbol);
|
|
17953
|
+
if (existing !== void 0) clearTimeout(existing);
|
|
17954
|
+
const t = setTimeout(() => {
|
|
17955
|
+
dispatch({ type: "clear_dir", symbol: payload.symbol });
|
|
17956
|
+
flashTimers.current.delete(payload.symbol);
|
|
17957
|
+
}, FLASH_MS);
|
|
17958
|
+
flashTimers.current.set(payload.symbol, t);
|
|
17959
|
+
}
|
|
17960
|
+
}, []);
|
|
17961
|
+
useEffect(() => {
|
|
17962
|
+
if (listenerRef.current) _removeListener(listenerRef.current);
|
|
17963
|
+
listenerRef.current = onQuote;
|
|
17964
|
+
_addListener(onQuote);
|
|
17965
|
+
return () => {
|
|
17966
|
+
if (listenerRef.current) _removeListener(listenerRef.current);
|
|
17967
|
+
listenerRef.current = null;
|
|
17968
|
+
for (const t of flashTimers.current.values()) clearTimeout(t);
|
|
17969
|
+
flashTimers.current.clear();
|
|
17970
|
+
};
|
|
17971
|
+
}, [onQuote]);
|
|
17972
|
+
useEffect(() => {
|
|
17973
|
+
if (!enabled) return;
|
|
17974
|
+
const syms = symbols.filter(Boolean);
|
|
17975
|
+
if (syms.length > 0) _subscribe(syms);
|
|
17976
|
+
return () => {
|
|
17977
|
+
if (syms.length > 0) _unsubscribe(syms);
|
|
17978
|
+
};
|
|
17979
|
+
}, [symbolsKey, enabled]);
|
|
17980
|
+
return { prices: state.prices, dirs: state.dirs, volumes: state.volumes };
|
|
17842
17981
|
}
|
|
17843
17982
|
|
|
17844
17983
|
// src/react/assets/Rithmic.png
|
|
@@ -29685,6 +29824,741 @@ function ScriptDrawer({ onClose, onAddIndicator, apiUrl, getAuthToken }) {
|
|
|
29685
29824
|
] })
|
|
29686
29825
|
] });
|
|
29687
29826
|
}
|
|
29827
|
+
|
|
29828
|
+
// src/react/shell/watchlistApi.ts
|
|
29829
|
+
var _apiBase = "/api";
|
|
29830
|
+
var _memToken = null;
|
|
29831
|
+
var _getAuthToken = null;
|
|
29832
|
+
async function _resolveToken() {
|
|
29833
|
+
if (_getAuthToken) {
|
|
29834
|
+
try {
|
|
29835
|
+
return await _getAuthToken();
|
|
29836
|
+
} catch {
|
|
29837
|
+
}
|
|
29838
|
+
}
|
|
29839
|
+
if (_memToken) return _memToken;
|
|
29840
|
+
if (typeof localStorage !== "undefined") return localStorage.getItem("forgecharts-token");
|
|
29841
|
+
return null;
|
|
29842
|
+
}
|
|
29843
|
+
async function _authHeader() {
|
|
29844
|
+
const token = await _resolveToken();
|
|
29845
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
29846
|
+
}
|
|
29847
|
+
async function apiFetch(method, path, body) {
|
|
29848
|
+
const bodyInit = body !== void 0 ? JSON.stringify(body) : null;
|
|
29849
|
+
const res = await fetch(`${_apiBase}${path}`, {
|
|
29850
|
+
method,
|
|
29851
|
+
headers: { "Content-Type": "application/json", ...await _authHeader() },
|
|
29852
|
+
...bodyInit !== null ? { body: bodyInit } : {}
|
|
29853
|
+
});
|
|
29854
|
+
if (!res.ok) {
|
|
29855
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
29856
|
+
throw new Error(err.error ?? res.statusText);
|
|
29857
|
+
}
|
|
29858
|
+
if (res.status === 204) return void 0;
|
|
29859
|
+
return res.json();
|
|
29860
|
+
}
|
|
29861
|
+
var watchlistApi = {
|
|
29862
|
+
configure(cfg) {
|
|
29863
|
+
if (cfg.apiBase) _apiBase = cfg.apiBase;
|
|
29864
|
+
if (cfg.getAuthToken) _getAuthToken = cfg.getAuthToken;
|
|
29865
|
+
if (cfg.token) _memToken = cfg.token;
|
|
29866
|
+
},
|
|
29867
|
+
setToken(token) {
|
|
29868
|
+
_memToken = token;
|
|
29869
|
+
},
|
|
29870
|
+
getAll() {
|
|
29871
|
+
return apiFetch("GET", "/watchlist");
|
|
29872
|
+
},
|
|
29873
|
+
addItem(symbol, group_id) {
|
|
29874
|
+
return apiFetch("POST", "/watchlist/items", { symbol, group_id });
|
|
29875
|
+
},
|
|
29876
|
+
removeItem(id) {
|
|
29877
|
+
return apiFetch("DELETE", `/watchlist/items/${encodeURIComponent(id)}`);
|
|
29878
|
+
},
|
|
29879
|
+
moveItemToGroup(id, group_id) {
|
|
29880
|
+
return apiFetch("PATCH", `/watchlist/items/${encodeURIComponent(id)}`, { group_id });
|
|
29881
|
+
},
|
|
29882
|
+
addGroup(name) {
|
|
29883
|
+
return apiFetch("POST", "/watchlist/groups", { name });
|
|
29884
|
+
},
|
|
29885
|
+
removeGroup(id) {
|
|
29886
|
+
return apiFetch("DELETE", `/watchlist/groups/${encodeURIComponent(id)}`);
|
|
29887
|
+
},
|
|
29888
|
+
reorderItems(ids) {
|
|
29889
|
+
return apiFetch("PUT", "/watchlist/items/reorder", { ids });
|
|
29890
|
+
}
|
|
29891
|
+
};
|
|
29892
|
+
var LOCAL_KEY = "forgecharts:watchlist";
|
|
29893
|
+
function _localRead() {
|
|
29894
|
+
try {
|
|
29895
|
+
const raw = typeof localStorage !== "undefined" ? localStorage.getItem(LOCAL_KEY) : null;
|
|
29896
|
+
if (raw) {
|
|
29897
|
+
const parsed = JSON.parse(raw);
|
|
29898
|
+
if (Array.isArray(parsed.groups) && Array.isArray(parsed.items)) return parsed;
|
|
29899
|
+
}
|
|
29900
|
+
} catch {
|
|
29901
|
+
}
|
|
29902
|
+
return { groups: [], items: [] };
|
|
29903
|
+
}
|
|
29904
|
+
function _localWrite(data) {
|
|
29905
|
+
try {
|
|
29906
|
+
if (typeof localStorage !== "undefined") localStorage.setItem(LOCAL_KEY, JSON.stringify(data));
|
|
29907
|
+
} catch {
|
|
29908
|
+
}
|
|
29909
|
+
}
|
|
29910
|
+
function _localId() {
|
|
29911
|
+
return `wl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
29912
|
+
}
|
|
29913
|
+
var localWatchlistApi = {
|
|
29914
|
+
async getAll() {
|
|
29915
|
+
return _localRead();
|
|
29916
|
+
},
|
|
29917
|
+
async addItem(symbol, group_id) {
|
|
29918
|
+
const data = _localRead();
|
|
29919
|
+
const existing = data.items.find((i) => i.symbol === symbol && i.group_id === (group_id ?? null));
|
|
29920
|
+
if (existing) return existing;
|
|
29921
|
+
const item = {
|
|
29922
|
+
id: _localId(),
|
|
29923
|
+
group_id: group_id ?? null,
|
|
29924
|
+
symbol,
|
|
29925
|
+
position: data.items.length,
|
|
29926
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
29927
|
+
};
|
|
29928
|
+
data.items.push(item);
|
|
29929
|
+
_localWrite(data);
|
|
29930
|
+
return item;
|
|
29931
|
+
},
|
|
29932
|
+
async removeItem(id) {
|
|
29933
|
+
const data = _localRead();
|
|
29934
|
+
data.items = data.items.filter((i) => i.id !== id);
|
|
29935
|
+
_localWrite(data);
|
|
29936
|
+
},
|
|
29937
|
+
async moveItemToGroup(id, group_id) {
|
|
29938
|
+
const data = _localRead();
|
|
29939
|
+
const item = data.items.find((i) => i.id === id);
|
|
29940
|
+
if (!item) throw new Error("Watchlist item not found");
|
|
29941
|
+
item.group_id = group_id;
|
|
29942
|
+
_localWrite(data);
|
|
29943
|
+
return item;
|
|
29944
|
+
},
|
|
29945
|
+
async addGroup(name) {
|
|
29946
|
+
const data = _localRead();
|
|
29947
|
+
const group = {
|
|
29948
|
+
id: _localId(),
|
|
29949
|
+
name,
|
|
29950
|
+
position: data.groups.length,
|
|
29951
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
29952
|
+
};
|
|
29953
|
+
data.groups.push(group);
|
|
29954
|
+
_localWrite(data);
|
|
29955
|
+
return group;
|
|
29956
|
+
},
|
|
29957
|
+
async removeGroup(id) {
|
|
29958
|
+
const data = _localRead();
|
|
29959
|
+
data.groups = data.groups.filter((g) => g.id !== id);
|
|
29960
|
+
for (const item of data.items) {
|
|
29961
|
+
if (item.group_id === id) item.group_id = null;
|
|
29962
|
+
}
|
|
29963
|
+
_localWrite(data);
|
|
29964
|
+
},
|
|
29965
|
+
async reorderItems(ids) {
|
|
29966
|
+
const data = _localRead();
|
|
29967
|
+
const order = new Map(ids.map((id, idx) => [id, idx]));
|
|
29968
|
+
data.items.sort((a, b) => (order.get(a.id) ?? a.position) - (order.get(b.id) ?? b.position));
|
|
29969
|
+
data.items.forEach((item, idx) => {
|
|
29970
|
+
item.position = idx;
|
|
29971
|
+
});
|
|
29972
|
+
_localWrite(data);
|
|
29973
|
+
}
|
|
29974
|
+
};
|
|
29975
|
+
function fmtPrice2(v) {
|
|
29976
|
+
const abs = Math.abs(v);
|
|
29977
|
+
const dec = abs >= 1e3 ? 2 : abs >= 1 ? 2 : abs >= 1e-4 ? 4 : 6;
|
|
29978
|
+
return v.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
|
|
29979
|
+
}
|
|
29980
|
+
function fmtVolume(v) {
|
|
29981
|
+
if (v >= 1e9) return (v / 1e9).toFixed(2) + "B";
|
|
29982
|
+
if (v >= 1e6) return (v / 1e6).toFixed(2) + "M";
|
|
29983
|
+
if (v >= 1e3) return (v / 1e3).toFixed(1) + "K";
|
|
29984
|
+
return v.toLocaleString("en-US");
|
|
29985
|
+
}
|
|
29986
|
+
function SymbolRow({
|
|
29987
|
+
item,
|
|
29988
|
+
quote,
|
|
29989
|
+
flash,
|
|
29990
|
+
dragOver,
|
|
29991
|
+
highlighted,
|
|
29992
|
+
logoUrl,
|
|
29993
|
+
onSelect,
|
|
29994
|
+
onRemove,
|
|
29995
|
+
onContextMenu,
|
|
29996
|
+
onDragStart,
|
|
29997
|
+
onDragOver,
|
|
29998
|
+
onDrop,
|
|
29999
|
+
onDragEnd
|
|
30000
|
+
}) {
|
|
30001
|
+
const liveColor = flash === "up" ? "var(--wl-up)" : flash === "down" ? "var(--wl-down)" : quote ? quote.change >= 0 ? "var(--wl-up)" : "var(--wl-down)" : "var(--text-muted)";
|
|
30002
|
+
const changeColor = quote ? quote.change >= 0 ? "var(--wl-up)" : "var(--wl-down)" : "var(--text-muted)";
|
|
30003
|
+
return /* @__PURE__ */ jsxs(
|
|
30004
|
+
"div",
|
|
30005
|
+
{
|
|
30006
|
+
className: `wl-row${dragOver ? " wl-drag-over" : ""}${highlighted ? " agent-highlight" : ""}`,
|
|
30007
|
+
draggable: true,
|
|
30008
|
+
onDragStart,
|
|
30009
|
+
onDragOver,
|
|
30010
|
+
onDrop,
|
|
30011
|
+
onDragEnd,
|
|
30012
|
+
onClick: onSelect,
|
|
30013
|
+
onContextMenu,
|
|
30014
|
+
title: item.symbol,
|
|
30015
|
+
children: [
|
|
30016
|
+
/* @__PURE__ */ jsx("div", { className: "wl-drag-handle", title: "Drag to reorder", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 8 12", width: "8", height: "12", fill: "currentColor", children: [
|
|
30017
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: "2", height: "2", rx: "1" }),
|
|
30018
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "5", width: "2", height: "2", rx: "1" }),
|
|
30019
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "10", width: "2", height: "2", rx: "1" }),
|
|
30020
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "0", width: "2", height: "2", rx: "1" }),
|
|
30021
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "2", height: "2", rx: "1" }),
|
|
30022
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "10", width: "2", height: "2", rx: "1" })
|
|
30023
|
+
] }) }),
|
|
30024
|
+
item.symbol.includes(":") && /* @__PURE__ */ jsx(
|
|
30025
|
+
"img",
|
|
30026
|
+
{
|
|
30027
|
+
className: "wl-logo",
|
|
30028
|
+
src: logoUrl ?? `/api/logos/${encodeURIComponent(item.symbol.split(":")[0].toUpperCase())}`,
|
|
30029
|
+
alt: "",
|
|
30030
|
+
onError: (e) => {
|
|
30031
|
+
const img = e.currentTarget;
|
|
30032
|
+
if (logoUrl && !img.src.includes("/api/logos/")) {
|
|
30033
|
+
img.src = `/api/logos/${encodeURIComponent(item.symbol.split(":")[0].toUpperCase())}`;
|
|
30034
|
+
} else {
|
|
30035
|
+
img.style.display = "none";
|
|
30036
|
+
}
|
|
30037
|
+
}
|
|
30038
|
+
}
|
|
30039
|
+
),
|
|
30040
|
+
/* @__PURE__ */ jsx("span", { className: "wl-symbol", children: item.symbol }),
|
|
30041
|
+
quote ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30042
|
+
/* @__PURE__ */ jsx(
|
|
30043
|
+
"span",
|
|
30044
|
+
{
|
|
30045
|
+
className: `wl-last${flash ? ` wl-flash-${flash}` : ""}`,
|
|
30046
|
+
style: { color: liveColor },
|
|
30047
|
+
children: fmtPrice2(quote.last)
|
|
30048
|
+
}
|
|
30049
|
+
),
|
|
30050
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-change", style: { color: changeColor }, children: [
|
|
30051
|
+
quote.change >= 0 ? "+" : "",
|
|
30052
|
+
fmtPrice2(quote.change)
|
|
30053
|
+
] }),
|
|
30054
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-pct", style: { color: changeColor }, children: [
|
|
30055
|
+
quote.change >= 0 ? "+" : "",
|
|
30056
|
+
quote.changePct.toFixed(2),
|
|
30057
|
+
"%"
|
|
30058
|
+
] }),
|
|
30059
|
+
/* @__PURE__ */ jsx("span", { className: "wl-vol", children: quote.volume !== void 0 ? fmtVolume(quote.volume) : "\u2014" })
|
|
30060
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "wl-no-data", children: "\u2014" }),
|
|
30061
|
+
/* @__PURE__ */ jsx(
|
|
30062
|
+
"button",
|
|
30063
|
+
{
|
|
30064
|
+
className: "wl-remove-btn",
|
|
30065
|
+
title: "Remove",
|
|
30066
|
+
onClick: (e) => {
|
|
30067
|
+
e.stopPropagation();
|
|
30068
|
+
onRemove();
|
|
30069
|
+
},
|
|
30070
|
+
children: "\u2715"
|
|
30071
|
+
}
|
|
30072
|
+
)
|
|
30073
|
+
]
|
|
30074
|
+
}
|
|
30075
|
+
);
|
|
30076
|
+
}
|
|
30077
|
+
function WatchlistDrawer({ onClose, onSelectSymbol, symbolResolver, mode = "managed" }) {
|
|
30078
|
+
const api = mode === "local" ? localWatchlistApi : watchlistApi;
|
|
30079
|
+
const [groups, setGroups] = useState([]);
|
|
30080
|
+
const [items, setItems] = useState([]);
|
|
30081
|
+
const [loading, setLoading] = useState(true);
|
|
30082
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
30083
|
+
const [addError, setAddError] = useState("");
|
|
30084
|
+
const [ctxMenu, setCtxMenu] = useState(null);
|
|
30085
|
+
const [groupDialog, setGroupDialog] = useState(null);
|
|
30086
|
+
const [groupName, setGroupName] = useState("");
|
|
30087
|
+
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
30088
|
+
const [logoMap, setLogoMap] = useState(/* @__PURE__ */ new Map());
|
|
30089
|
+
const [drawerWidth, setDrawerWidth] = useState(360);
|
|
30090
|
+
const [colWidths, setColWidths] = useState({ sym: 120, last: 70, chg: 70, pct: 60, vol: 52 });
|
|
30091
|
+
const [dragItemId, setDragItemId] = useState(null);
|
|
30092
|
+
const [dropTargetId, setDropTargetId] = useState(null);
|
|
30093
|
+
const [highlightedSymbol, setHighlightedSymbol] = useState(null);
|
|
30094
|
+
const ctxRef = useRef(null);
|
|
30095
|
+
const drawerRef = useRef(null);
|
|
30096
|
+
const dragState = useRef(null);
|
|
30097
|
+
const colDragRef = useRef(null);
|
|
30098
|
+
useAgentUIEvent("watchlist:highlight", ({ symbol }) => {
|
|
30099
|
+
setHighlightedSymbol(symbol);
|
|
30100
|
+
setTimeout(() => setHighlightedSymbol(null), 1800);
|
|
30101
|
+
});
|
|
30102
|
+
const onColResizeMouseDown = (e, left, right) => {
|
|
30103
|
+
e.preventDefault();
|
|
30104
|
+
e.stopPropagation();
|
|
30105
|
+
colDragRef.current = {
|
|
30106
|
+
left,
|
|
30107
|
+
right,
|
|
30108
|
+
startX: e.clientX,
|
|
30109
|
+
startLW: colWidths[left],
|
|
30110
|
+
startRW: right ? colWidths[right] : 0
|
|
30111
|
+
};
|
|
30112
|
+
const onMove = (ev) => {
|
|
30113
|
+
if (!colDragRef.current) return;
|
|
30114
|
+
const { left: l, right: r, startX, startLW, startRW } = colDragRef.current;
|
|
30115
|
+
const delta = ev.clientX - startX;
|
|
30116
|
+
const maxGrow = r ? startRW - 36 : Infinity;
|
|
30117
|
+
const maxShrink = startLW - 36;
|
|
30118
|
+
const clamped = Math.min(maxGrow, Math.max(-maxShrink, delta));
|
|
30119
|
+
setColWidths((prev) => {
|
|
30120
|
+
const next = { ...prev, [l]: startLW + clamped };
|
|
30121
|
+
if (r) next[r] = startRW - clamped;
|
|
30122
|
+
return next;
|
|
30123
|
+
});
|
|
30124
|
+
};
|
|
30125
|
+
const onUp = () => {
|
|
30126
|
+
colDragRef.current = null;
|
|
30127
|
+
window.removeEventListener("mousemove", onMove);
|
|
30128
|
+
window.removeEventListener("mouseup", onUp);
|
|
30129
|
+
};
|
|
30130
|
+
window.addEventListener("mousemove", onMove);
|
|
30131
|
+
window.addEventListener("mouseup", onUp);
|
|
30132
|
+
};
|
|
30133
|
+
const onResizeMouseDown = (e) => {
|
|
30134
|
+
e.preventDefault();
|
|
30135
|
+
dragState.current = { startX: e.clientX, startW: drawerWidth };
|
|
30136
|
+
const onMove = (ev) => {
|
|
30137
|
+
if (!dragState.current) return;
|
|
30138
|
+
const delta = dragState.current.startX - ev.clientX;
|
|
30139
|
+
const next = Math.min(600, Math.max(240, dragState.current.startW + delta));
|
|
30140
|
+
setDrawerWidth(next);
|
|
30141
|
+
};
|
|
30142
|
+
const onUp = () => {
|
|
30143
|
+
dragState.current = null;
|
|
30144
|
+
window.removeEventListener("mousemove", onMove);
|
|
30145
|
+
window.removeEventListener("mouseup", onUp);
|
|
30146
|
+
};
|
|
30147
|
+
window.addEventListener("mousemove", onMove);
|
|
30148
|
+
window.addEventListener("mouseup", onUp);
|
|
30149
|
+
};
|
|
30150
|
+
const reload = useCallback(async () => {
|
|
30151
|
+
try {
|
|
30152
|
+
const data = await api.getAll();
|
|
30153
|
+
setGroups(data.groups);
|
|
30154
|
+
setItems(data.items);
|
|
30155
|
+
} catch {
|
|
30156
|
+
} finally {
|
|
30157
|
+
setLoading(false);
|
|
30158
|
+
}
|
|
30159
|
+
}, []);
|
|
30160
|
+
useEffect(() => {
|
|
30161
|
+
reload();
|
|
30162
|
+
}, [reload]);
|
|
30163
|
+
useEffect(() => {
|
|
30164
|
+
if (items.length === 0 || mode === "local") return;
|
|
30165
|
+
const syms = [...new Set(items.map((i) => i.symbol))].join(",");
|
|
30166
|
+
fetch(`/api/reference/symbols/logos?symbols=${encodeURIComponent(syms)}`).then((r) => r.ok ? r.json() : {}).then((map) => setLogoMap(new Map(Object.entries(map)))).catch(() => {
|
|
30167
|
+
});
|
|
30168
|
+
}, [items, mode]);
|
|
30169
|
+
const symbols = items.map((i) => i.symbol);
|
|
30170
|
+
const { prices: livePrices, dirs: liveDirs, volumes: liveVolumes } = useWatchlistQuotes(symbols, mode !== "local");
|
|
30171
|
+
const sessionOpenRef = useRef(/* @__PURE__ */ new Map());
|
|
30172
|
+
const quotes = useRef(/* @__PURE__ */ new Map()).current;
|
|
30173
|
+
quotes.clear();
|
|
30174
|
+
for (const [sym, last] of livePrices) {
|
|
30175
|
+
if (!sessionOpenRef.current.has(sym)) {
|
|
30176
|
+
sessionOpenRef.current.set(sym, last);
|
|
30177
|
+
}
|
|
30178
|
+
const open = sessionOpenRef.current.get(sym);
|
|
30179
|
+
const change = last - open;
|
|
30180
|
+
const q = {
|
|
30181
|
+
last,
|
|
30182
|
+
change,
|
|
30183
|
+
changePct: open !== 0 ? change / open * 100 : 0
|
|
30184
|
+
};
|
|
30185
|
+
const vol = liveVolumes.get(sym);
|
|
30186
|
+
if (vol !== void 0) q.volume = vol;
|
|
30187
|
+
quotes.set(sym, q);
|
|
30188
|
+
}
|
|
30189
|
+
const prevItemsRef = useRef([]);
|
|
30190
|
+
if (prevItemsRef.current !== items) {
|
|
30191
|
+
const prevSyms = new Set(prevItemsRef.current.map((i) => i.symbol));
|
|
30192
|
+
const nextSyms = new Set(items.map((i) => i.symbol));
|
|
30193
|
+
for (const s of prevSyms) {
|
|
30194
|
+
if (!nextSyms.has(s)) sessionOpenRef.current.delete(s);
|
|
30195
|
+
}
|
|
30196
|
+
prevItemsRef.current = items;
|
|
30197
|
+
}
|
|
30198
|
+
useEffect(() => {
|
|
30199
|
+
if (!ctxMenu) return;
|
|
30200
|
+
const handler = (e) => {
|
|
30201
|
+
if (ctxRef.current && !ctxRef.current.contains(e.target)) setCtxMenu(null);
|
|
30202
|
+
};
|
|
30203
|
+
document.addEventListener("mousedown", handler);
|
|
30204
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
30205
|
+
}, [ctxMenu]);
|
|
30206
|
+
const handleAddFromSearch = useCallback(async (symbol) => {
|
|
30207
|
+
setSearchOpen(false);
|
|
30208
|
+
setAddError("");
|
|
30209
|
+
try {
|
|
30210
|
+
await api.addItem(symbol);
|
|
30211
|
+
await reload();
|
|
30212
|
+
} catch (err) {
|
|
30213
|
+
setAddError(err instanceof Error ? err.message : "Failed to add");
|
|
30214
|
+
}
|
|
30215
|
+
}, [reload]);
|
|
30216
|
+
const handleRemove = async (id) => {
|
|
30217
|
+
await api.removeItem(id);
|
|
30218
|
+
await reload();
|
|
30219
|
+
};
|
|
30220
|
+
const openCtxMenu = (e, item) => {
|
|
30221
|
+
e.preventDefault();
|
|
30222
|
+
setCtxMenu({ x: e.clientX, y: e.clientY, itemId: item.id, symbol: item.symbol });
|
|
30223
|
+
};
|
|
30224
|
+
const openGroupDialog = (itemId) => {
|
|
30225
|
+
setCtxMenu(null);
|
|
30226
|
+
setGroupName("");
|
|
30227
|
+
setGroupDialog({ itemId });
|
|
30228
|
+
};
|
|
30229
|
+
const handleCreateGroup = async () => {
|
|
30230
|
+
const name = groupName.trim();
|
|
30231
|
+
if (!name || !groupDialog) return;
|
|
30232
|
+
const group = await api.addGroup(name);
|
|
30233
|
+
await api.moveItemToGroup(groupDialog.itemId, group.id);
|
|
30234
|
+
setGroupDialog(null);
|
|
30235
|
+
await reload();
|
|
30236
|
+
};
|
|
30237
|
+
const handleMoveToExisting = async (itemId, groupId) => {
|
|
30238
|
+
setCtxMenu(null);
|
|
30239
|
+
await api.moveItemToGroup(itemId, groupId);
|
|
30240
|
+
await reload();
|
|
30241
|
+
};
|
|
30242
|
+
const handleUngroup = async (itemId) => {
|
|
30243
|
+
setCtxMenu(null);
|
|
30244
|
+
await api.moveItemToGroup(itemId, null);
|
|
30245
|
+
await reload();
|
|
30246
|
+
};
|
|
30247
|
+
const handleDragStart = (e, itemId) => {
|
|
30248
|
+
setDragItemId(itemId);
|
|
30249
|
+
e.dataTransfer.effectAllowed = "move";
|
|
30250
|
+
};
|
|
30251
|
+
const handleDragOver = (e, targetId) => {
|
|
30252
|
+
e.preventDefault();
|
|
30253
|
+
e.dataTransfer.dropEffect = "move";
|
|
30254
|
+
if (targetId !== dragItemId) setDropTargetId(targetId);
|
|
30255
|
+
};
|
|
30256
|
+
const handleDrop = async (e, targetId) => {
|
|
30257
|
+
e.preventDefault();
|
|
30258
|
+
if (!dragItemId || dragItemId === targetId) return;
|
|
30259
|
+
setDragItemId(null);
|
|
30260
|
+
setDropTargetId(null);
|
|
30261
|
+
const dragItem = items.find((i) => i.id === dragItemId);
|
|
30262
|
+
const targetItem = items.find((i) => i.id === targetId);
|
|
30263
|
+
if (!dragItem || !targetItem) return;
|
|
30264
|
+
const sameGroup = dragItem.group_id === targetItem.group_id;
|
|
30265
|
+
if (!sameGroup) {
|
|
30266
|
+
try {
|
|
30267
|
+
await api.moveItemToGroup(dragItem.id, targetItem.group_id);
|
|
30268
|
+
} catch {
|
|
30269
|
+
await reload();
|
|
30270
|
+
return;
|
|
30271
|
+
}
|
|
30272
|
+
const updated = items.map(
|
|
30273
|
+
(i) => i.id === dragItem.id ? { ...i, group_id: targetItem.group_id } : i
|
|
30274
|
+
);
|
|
30275
|
+
const targetGroup = updated.filter((i) => i.group_id === targetItem.group_id);
|
|
30276
|
+
const fromIdx = targetGroup.findIndex((i) => i.id === dragItem.id);
|
|
30277
|
+
const toIdx = targetGroup.findIndex((i) => i.id === targetId);
|
|
30278
|
+
if (fromIdx !== -1 && toIdx !== -1 && fromIdx !== toIdx) {
|
|
30279
|
+
const [moved] = targetGroup.splice(fromIdx, 1);
|
|
30280
|
+
targetGroup.splice(toIdx, 0, moved);
|
|
30281
|
+
}
|
|
30282
|
+
const rest = updated.filter((i) => i.group_id !== targetItem.group_id);
|
|
30283
|
+
const newItems = [...rest, ...targetGroup];
|
|
30284
|
+
setItems(newItems);
|
|
30285
|
+
try {
|
|
30286
|
+
await api.reorderItems(newItems.map((i) => i.id));
|
|
30287
|
+
} catch {
|
|
30288
|
+
await reload();
|
|
30289
|
+
}
|
|
30290
|
+
} else {
|
|
30291
|
+
const groupItems = items.filter((i) => i.group_id === dragItem.group_id);
|
|
30292
|
+
const fromIdx = groupItems.findIndex((i) => i.id === dragItemId);
|
|
30293
|
+
const toIdx = groupItems.findIndex((i) => i.id === targetId);
|
|
30294
|
+
if (fromIdx === -1 || toIdx === -1) return;
|
|
30295
|
+
const reordered = [...groupItems];
|
|
30296
|
+
const [moved] = reordered.splice(fromIdx, 1);
|
|
30297
|
+
reordered.splice(toIdx, 0, moved);
|
|
30298
|
+
const rest = items.filter((i) => i.group_id !== dragItem.group_id);
|
|
30299
|
+
const newItems = [...rest, ...reordered];
|
|
30300
|
+
setItems(newItems);
|
|
30301
|
+
try {
|
|
30302
|
+
await api.reorderItems(newItems.map((i) => i.id));
|
|
30303
|
+
} catch {
|
|
30304
|
+
await reload();
|
|
30305
|
+
}
|
|
30306
|
+
}
|
|
30307
|
+
};
|
|
30308
|
+
const handleDragEnd = () => {
|
|
30309
|
+
setDragItemId(null);
|
|
30310
|
+
setDropTargetId(null);
|
|
30311
|
+
};
|
|
30312
|
+
const handleRemoveGroup = async (groupId) => {
|
|
30313
|
+
await api.removeGroup(groupId);
|
|
30314
|
+
await reload();
|
|
30315
|
+
};
|
|
30316
|
+
const toggleCollapse = (groupId) => {
|
|
30317
|
+
setCollapsed((prev) => {
|
|
30318
|
+
const next = new Set(prev);
|
|
30319
|
+
next.has(groupId) ? next.delete(groupId) : next.add(groupId);
|
|
30320
|
+
return next;
|
|
30321
|
+
});
|
|
30322
|
+
};
|
|
30323
|
+
const ungrouped = items.filter((i) => i.group_id === null);
|
|
30324
|
+
return /* @__PURE__ */ jsxs(
|
|
30325
|
+
"div",
|
|
30326
|
+
{
|
|
30327
|
+
ref: drawerRef,
|
|
30328
|
+
className: "wl-drawer",
|
|
30329
|
+
style: {
|
|
30330
|
+
width: drawerWidth,
|
|
30331
|
+
"--wl-sym": `${colWidths.sym}px`,
|
|
30332
|
+
"--wl-last": `${colWidths.last}px`,
|
|
30333
|
+
"--wl-chg": `${colWidths.chg}px`,
|
|
30334
|
+
"--wl-pct": `${colWidths.pct}px`,
|
|
30335
|
+
"--wl-vol": `${colWidths.vol}px`
|
|
30336
|
+
},
|
|
30337
|
+
children: [
|
|
30338
|
+
/* @__PURE__ */ jsx("div", { className: "wl-resize-handle", onMouseDown: onResizeMouseDown }),
|
|
30339
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-header", children: [
|
|
30340
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-header-title", children: [
|
|
30341
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
30342
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
30343
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "7", width: "9", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
30344
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "11", width: "10", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" })
|
|
30345
|
+
] }),
|
|
30346
|
+
"Watchlist"
|
|
30347
|
+
] }),
|
|
30348
|
+
/* @__PURE__ */ jsx("button", { className: "wl-close-btn", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 14 14", width: "12", height: "12", stroke: "currentColor", strokeWidth: "1.8", children: [
|
|
30349
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "12", y2: "12" }),
|
|
30350
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "2", x2: "2", y2: "12" })
|
|
30351
|
+
] }) })
|
|
30352
|
+
] }),
|
|
30353
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-add-row", children: [
|
|
30354
|
+
/* @__PURE__ */ jsx(
|
|
30355
|
+
"input",
|
|
30356
|
+
{
|
|
30357
|
+
readOnly: true,
|
|
30358
|
+
className: "wl-add-input",
|
|
30359
|
+
placeholder: "Add symbol e.g. BINANCE:BTCUSDT",
|
|
30360
|
+
onClick: () => setSearchOpen(true),
|
|
30361
|
+
onFocus: () => setSearchOpen(true),
|
|
30362
|
+
style: { cursor: "pointer" }
|
|
30363
|
+
}
|
|
30364
|
+
),
|
|
30365
|
+
/* @__PURE__ */ jsx("button", { className: "wl-add-btn", onClick: () => setSearchOpen(true), title: "Add", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 14 14", width: "12", height: "12", stroke: "currentColor", strokeWidth: "2", children: [
|
|
30366
|
+
/* @__PURE__ */ jsx("line", { x1: "7", y1: "1", x2: "7", y2: "13" }),
|
|
30367
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "7", x2: "13", y2: "7" })
|
|
30368
|
+
] }) })
|
|
30369
|
+
] }),
|
|
30370
|
+
addError && /* @__PURE__ */ jsx("div", { className: "wl-add-error", children: addError }),
|
|
30371
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-col-headers", children: [
|
|
30372
|
+
/* @__PURE__ */ jsx("span", {}),
|
|
30373
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-symbol", children: [
|
|
30374
|
+
"Symbol",
|
|
30375
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "sym", "last") })
|
|
30376
|
+
] }),
|
|
30377
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-last", children: [
|
|
30378
|
+
"Last",
|
|
30379
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "last", "chg") })
|
|
30380
|
+
] }),
|
|
30381
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-chg", children: [
|
|
30382
|
+
"Chg",
|
|
30383
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "chg", "pct") })
|
|
30384
|
+
] }),
|
|
30385
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-pct", children: [
|
|
30386
|
+
"Chg%",
|
|
30387
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "pct", "vol") })
|
|
30388
|
+
] }),
|
|
30389
|
+
/* @__PURE__ */ jsx("span", { className: "wl-col-vol", children: "Vol" })
|
|
30390
|
+
] }),
|
|
30391
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-list", children: [
|
|
30392
|
+
loading && /* @__PURE__ */ jsx("div", { className: "wl-empty", children: "Loading\u2026" }),
|
|
30393
|
+
groups.map((group) => {
|
|
30394
|
+
const groupItems = items.filter((i) => i.group_id === group.id);
|
|
30395
|
+
const isCollapsed = collapsed.has(group.id);
|
|
30396
|
+
return /* @__PURE__ */ jsxs("div", { className: "wl-group", children: [
|
|
30397
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-group-header", children: [
|
|
30398
|
+
/* @__PURE__ */ jsx(
|
|
30399
|
+
"button",
|
|
30400
|
+
{
|
|
30401
|
+
className: "wl-group-toggle",
|
|
30402
|
+
onClick: () => toggleCollapse(group.id),
|
|
30403
|
+
title: isCollapsed ? "Expand" : "Collapse",
|
|
30404
|
+
children: /* @__PURE__ */ jsx(
|
|
30405
|
+
"svg",
|
|
30406
|
+
{
|
|
30407
|
+
viewBox: "0 0 10 10",
|
|
30408
|
+
width: "8",
|
|
30409
|
+
height: "8",
|
|
30410
|
+
stroke: "currentColor",
|
|
30411
|
+
fill: "none",
|
|
30412
|
+
strokeWidth: "1.5",
|
|
30413
|
+
style: { transform: isCollapsed ? "rotate(-90deg)" : "none", transition: "transform 0.15s" },
|
|
30414
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "2,3 5,7 8,3" })
|
|
30415
|
+
}
|
|
30416
|
+
)
|
|
30417
|
+
}
|
|
30418
|
+
),
|
|
30419
|
+
/* @__PURE__ */ jsx("span", { className: "wl-group-name", children: group.name }),
|
|
30420
|
+
/* @__PURE__ */ jsx("span", { className: "wl-group-count", children: groupItems.length }),
|
|
30421
|
+
/* @__PURE__ */ jsx(
|
|
30422
|
+
"button",
|
|
30423
|
+
{
|
|
30424
|
+
className: "wl-remove-btn wl-group-del",
|
|
30425
|
+
title: "Remove group (items become ungrouped)",
|
|
30426
|
+
onClick: () => handleRemoveGroup(group.id),
|
|
30427
|
+
children: "\u2715"
|
|
30428
|
+
}
|
|
30429
|
+
)
|
|
30430
|
+
] }),
|
|
30431
|
+
!isCollapsed && groupItems.map((item) => /* @__PURE__ */ jsx(
|
|
30432
|
+
SymbolRow,
|
|
30433
|
+
{
|
|
30434
|
+
item,
|
|
30435
|
+
quote: quotes.get(item.symbol),
|
|
30436
|
+
flash: liveDirs.get(item.symbol),
|
|
30437
|
+
dragOver: dropTargetId === item.id,
|
|
30438
|
+
highlighted: highlightedSymbol === item.symbol,
|
|
30439
|
+
...logoMap.get(item.symbol) !== void 0 && { logoUrl: logoMap.get(item.symbol) },
|
|
30440
|
+
onSelect: () => onSelectSymbol(item.symbol),
|
|
30441
|
+
onRemove: () => handleRemove(item.id),
|
|
30442
|
+
onContextMenu: (e) => openCtxMenu(e, item),
|
|
30443
|
+
onDragStart: (e) => handleDragStart(e, item.id),
|
|
30444
|
+
onDragOver: (e) => handleDragOver(e, item.id),
|
|
30445
|
+
onDrop: (e) => handleDrop(e, item.id),
|
|
30446
|
+
onDragEnd: handleDragEnd
|
|
30447
|
+
},
|
|
30448
|
+
item.id
|
|
30449
|
+
))
|
|
30450
|
+
] }, group.id);
|
|
30451
|
+
}),
|
|
30452
|
+
ungrouped.length > 0 && groups.length > 0 && /* @__PURE__ */ jsx("div", { className: "wl-group-label-ungrouped", children: "Other" }),
|
|
30453
|
+
ungrouped.map((item) => /* @__PURE__ */ jsx(
|
|
30454
|
+
SymbolRow,
|
|
30455
|
+
{
|
|
30456
|
+
item,
|
|
30457
|
+
quote: quotes.get(item.symbol),
|
|
30458
|
+
flash: liveDirs.get(item.symbol),
|
|
30459
|
+
dragOver: dropTargetId === item.id,
|
|
30460
|
+
highlighted: highlightedSymbol === item.symbol,
|
|
30461
|
+
...logoMap.get(item.symbol) !== void 0 && { logoUrl: logoMap.get(item.symbol) },
|
|
30462
|
+
onSelect: () => onSelectSymbol(item.symbol),
|
|
30463
|
+
onRemove: () => handleRemove(item.id),
|
|
30464
|
+
onContextMenu: (e) => openCtxMenu(e, item),
|
|
30465
|
+
onDragStart: (e) => handleDragStart(e, item.id),
|
|
30466
|
+
onDragOver: (e) => handleDragOver(e, item.id),
|
|
30467
|
+
onDrop: (e) => handleDrop(e, item.id),
|
|
30468
|
+
onDragEnd: handleDragEnd
|
|
30469
|
+
},
|
|
30470
|
+
item.id
|
|
30471
|
+
)),
|
|
30472
|
+
!loading && items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "wl-empty", children: [
|
|
30473
|
+
"No symbols yet.",
|
|
30474
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
30475
|
+
"Type a symbol above and press Enter."
|
|
30476
|
+
] })
|
|
30477
|
+
] }),
|
|
30478
|
+
ctxMenu && /* @__PURE__ */ jsxs(
|
|
30479
|
+
"div",
|
|
30480
|
+
{
|
|
30481
|
+
ref: ctxRef,
|
|
30482
|
+
className: "wl-ctx-menu",
|
|
30483
|
+
style: { top: ctxMenu.y, left: ctxMenu.x },
|
|
30484
|
+
children: [
|
|
30485
|
+
groups.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
30486
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-section", children: "Move to group" }),
|
|
30487
|
+
groups.map((g) => /* @__PURE__ */ jsx(
|
|
30488
|
+
"button",
|
|
30489
|
+
{
|
|
30490
|
+
className: "wl-ctx-item",
|
|
30491
|
+
onClick: () => handleMoveToExisting(ctxMenu.itemId, g.id),
|
|
30492
|
+
children: g.name
|
|
30493
|
+
},
|
|
30494
|
+
g.id
|
|
30495
|
+
)),
|
|
30496
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-divider" })
|
|
30497
|
+
] }),
|
|
30498
|
+
/* @__PURE__ */ jsx(
|
|
30499
|
+
"button",
|
|
30500
|
+
{
|
|
30501
|
+
className: "wl-ctx-item",
|
|
30502
|
+
onClick: () => openGroupDialog(ctxMenu.itemId),
|
|
30503
|
+
children: "New group\u2026"
|
|
30504
|
+
}
|
|
30505
|
+
),
|
|
30506
|
+
items.find((i) => i.id === ctxMenu.itemId)?.group_id && /* @__PURE__ */ jsx(
|
|
30507
|
+
"button",
|
|
30508
|
+
{
|
|
30509
|
+
className: "wl-ctx-item",
|
|
30510
|
+
onClick: () => handleUngroup(ctxMenu.itemId),
|
|
30511
|
+
children: "Remove from group"
|
|
30512
|
+
}
|
|
30513
|
+
),
|
|
30514
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-divider" }),
|
|
30515
|
+
/* @__PURE__ */ jsx(
|
|
30516
|
+
"button",
|
|
30517
|
+
{
|
|
30518
|
+
className: "wl-ctx-item wl-ctx-item--danger",
|
|
30519
|
+
onClick: () => {
|
|
30520
|
+
setCtxMenu(null);
|
|
30521
|
+
handleRemove(ctxMenu.itemId);
|
|
30522
|
+
},
|
|
30523
|
+
children: "Remove symbol"
|
|
30524
|
+
}
|
|
30525
|
+
)
|
|
30526
|
+
]
|
|
30527
|
+
}
|
|
30528
|
+
),
|
|
30529
|
+
groupDialog && /* @__PURE__ */ jsx("div", { className: "wl-dialog-backdrop", children: /* @__PURE__ */ jsxs("div", { className: "wl-dialog", children: [
|
|
30530
|
+
/* @__PURE__ */ jsx("div", { className: "wl-dialog-title", children: "Add to group" }),
|
|
30531
|
+
/* @__PURE__ */ jsx(
|
|
30532
|
+
"input",
|
|
30533
|
+
{
|
|
30534
|
+
className: "wl-dialog-input",
|
|
30535
|
+
placeholder: "Group name",
|
|
30536
|
+
value: groupName,
|
|
30537
|
+
onChange: (e) => setGroupName(e.target.value),
|
|
30538
|
+
onKeyDown: (e) => {
|
|
30539
|
+
if (e.key === "Enter") handleCreateGroup();
|
|
30540
|
+
},
|
|
30541
|
+
autoFocus: true
|
|
30542
|
+
}
|
|
30543
|
+
),
|
|
30544
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-dialog-actions", children: [
|
|
30545
|
+
/* @__PURE__ */ jsx("button", { className: "wl-dialog-cancel", onClick: () => setGroupDialog(null), children: "Cancel" }),
|
|
30546
|
+
/* @__PURE__ */ jsx("button", { className: "wl-dialog-confirm", onClick: handleCreateGroup, disabled: !groupName.trim(), children: "Save" })
|
|
30547
|
+
] })
|
|
30548
|
+
] }) }),
|
|
30549
|
+
searchOpen && /* @__PURE__ */ jsx(
|
|
30550
|
+
SymbolSearchDialog,
|
|
30551
|
+
{
|
|
30552
|
+
current: "",
|
|
30553
|
+
onSelect: handleAddFromSearch,
|
|
30554
|
+
onClose: () => setSearchOpen(false),
|
|
30555
|
+
symbolResolver
|
|
30556
|
+
}
|
|
30557
|
+
)
|
|
30558
|
+
]
|
|
30559
|
+
}
|
|
30560
|
+
);
|
|
30561
|
+
}
|
|
29688
30562
|
function ChartWorkspace({
|
|
29689
30563
|
// TabBar
|
|
29690
30564
|
tabs,
|
|
@@ -29734,6 +30608,7 @@ function ChartWorkspace({
|
|
|
29734
30608
|
// RightToolbar
|
|
29735
30609
|
watchlistOpen,
|
|
29736
30610
|
onToggleWatchlist,
|
|
30611
|
+
builtinWatchlistDrawer,
|
|
29737
30612
|
orderEntryOpen,
|
|
29738
30613
|
onToggleOrderEntry,
|
|
29739
30614
|
showOrderEntry,
|
|
@@ -29797,6 +30672,10 @@ function ChartWorkspace({
|
|
|
29797
30672
|
const effOnToggleScriptDrawer = onToggleScriptDrawer ?? (() => setAutoScriptOpen((o) => !o));
|
|
29798
30673
|
const effScriptDrawerOpen = onToggleScriptDrawer ? scriptDrawerOpen ?? false : autoScriptOpen;
|
|
29799
30674
|
const closeScriptDrawer = onToggleScriptDrawer ?? (() => setAutoScriptOpen(false));
|
|
30675
|
+
const [autoWatchlistOpen, setAutoWatchlistOpen] = React11.useState(false);
|
|
30676
|
+
const effOnToggleWatchlist = onToggleWatchlist ?? (() => setAutoWatchlistOpen((o) => !o));
|
|
30677
|
+
const effWatchlistOpen = onToggleWatchlist ? watchlistOpen ?? false : autoWatchlistOpen;
|
|
30678
|
+
const closeWatchlist = onToggleWatchlist ?? (() => setAutoWatchlistOpen(false));
|
|
29800
30679
|
const gate = (hostProp, licensed) => hostProp !== false && licensed;
|
|
29801
30680
|
const effShowTradeButton = gate(showTradeButton, capabilities.orderEntry) && effOnToggleTradeDrawer !== void 0;
|
|
29802
30681
|
const effShowOrderEntry = gate(showOrderEntry, capabilities.orderEntry) && effOnToggleOrderEntry !== void 0;
|
|
@@ -29969,13 +30848,22 @@ function ChartWorkspace({
|
|
|
29969
30848
|
...getAuthToken !== void 0 ? { getAuthToken } : {}
|
|
29970
30849
|
}
|
|
29971
30850
|
),
|
|
30851
|
+
builtinWatchlistDrawer !== false && effWatchlistOpen && capabilities.watchlists && /* @__PURE__ */ jsx(
|
|
30852
|
+
WatchlistDrawer,
|
|
30853
|
+
{
|
|
30854
|
+
mode: "local",
|
|
30855
|
+
onClose: closeWatchlist,
|
|
30856
|
+
onSelectSymbol: onSymbolChange,
|
|
30857
|
+
symbolResolver
|
|
30858
|
+
}
|
|
30859
|
+
),
|
|
29972
30860
|
leftDrawers,
|
|
29973
30861
|
drawers,
|
|
29974
30862
|
/* @__PURE__ */ jsx(
|
|
29975
30863
|
RightToolbar,
|
|
29976
30864
|
{
|
|
29977
|
-
watchlistOpen,
|
|
29978
|
-
onToggleWatchlist,
|
|
30865
|
+
watchlistOpen: effWatchlistOpen,
|
|
30866
|
+
onToggleWatchlist: effOnToggleWatchlist,
|
|
29979
30867
|
orderEntryOpen: effOrderEntryOpen,
|
|
29980
30868
|
onToggleOrderEntry: effOnToggleOrderEntry ?? (() => {
|
|
29981
30869
|
}),
|