@forgecharts/sdk 1.5.82 → 1.5.84
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/api/EventBus.d.ts +1 -1
- package/dist/api/{TChart.d.ts → ForgeCharts.d.ts} +47 -4
- package/dist/api/ForgeCharts.d.ts.map +1 -0
- package/dist/api/createChart.d.ts +4 -4
- package/dist/api/createChart.d.ts.map +1 -1
- package/dist/core/Chart.d.ts +5 -5
- package/dist/core/Chart.d.ts.map +1 -1
- package/dist/core/Series.d.ts +14 -0
- package/dist/core/Series.d.ts.map +1 -1
- package/dist/core/__tests__/seriesCutoff.test.d.ts +2 -0
- package/dist/core/__tests__/seriesCutoff.test.d.ts.map +1 -0
- package/dist/datafeed/DatafeedConnector.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +520 -27
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +520 -27
- package/dist/internal.js.map +1 -1
- package/dist/licensing/packageFeatures.d.ts.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/pixi/PixiCandlestickRenderer.d.ts +4 -0
- package/dist/pixi/PixiCandlestickRenderer.d.ts.map +1 -1
- package/dist/pixi/PixiChart.d.ts +18 -2
- package/dist/pixi/PixiChart.d.ts.map +1 -1
- package/dist/pixi/PixiHistogramRenderer.d.ts +2 -0
- package/dist/pixi/PixiHistogramRenderer.d.ts.map +1 -1
- package/dist/pixi/PixiLineRenderer.d.ts +2 -0
- package/dist/pixi/PixiLineRenderer.d.ts.map +1 -1
- package/dist/react/canvas/ChartCanvas.d.ts +20 -2
- package/dist/react/canvas/ChartCanvas.d.ts.map +1 -1
- package/dist/react/hooks/useUserPackage.d.ts.map +1 -1
- package/dist/react/index.js +807 -63
- package/dist/react/index.js.map +1 -1
- package/dist/react/internal.js +814 -83
- package/dist/react/internal.js.map +1 -1
- package/dist/react/shell/AlertsDrawer.d.ts +11 -0
- package/dist/react/shell/AlertsDrawer.d.ts.map +1 -0
- package/dist/react/shell/ManagedAppShell.d.ts +1 -1
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
- package/dist/react/workspace/ChartWorkspace.d.ts +10 -1
- package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -1
- package/dist/react/workspace/LayoutMenu.d.ts +3 -1
- package/dist/react/workspace/LayoutMenu.d.ts.map +1 -1
- package/dist/react/workspace/toolbars/TopToolbar.d.ts +7 -1
- package/dist/react/workspace/toolbars/TopToolbar.d.ts.map +1 -1
- package/dist/types/ChartBackend.d.ts +17 -4
- package/dist/types/ChartBackend.d.ts.map +1 -1
- package/dist/types/ISeries.d.ts +5 -0
- package/dist/types/ISeries.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/api/TChart.d.ts.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React11, { forwardRef, useRef, useState, useEffect, useImperativeHandle, useCallback, createContext, useMemo, useContext, useReducer } from 'react';
|
|
2
|
-
import { TextStyle, Application,
|
|
2
|
+
import { TextStyle, Application, Graphics, Container, Text, FillGradient, CanvasTextMetrics } from 'pixi.js';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
5
5
|
|
|
@@ -1410,6 +1410,7 @@ var Series = class _Series {
|
|
|
1410
1410
|
// ─── ISeries implementation ──────────────────────────────────────────────────
|
|
1411
1411
|
setData(data) {
|
|
1412
1412
|
this._data = [...data].sort((a, b) => a.time - b.time);
|
|
1413
|
+
this._cutoffView = null;
|
|
1413
1414
|
this.onDataChanged?.();
|
|
1414
1415
|
}
|
|
1415
1416
|
/**
|
|
@@ -1449,11 +1450,47 @@ var Series = class _Series {
|
|
|
1449
1450
|
this._data = this._data.slice(this._data.length - 1e5);
|
|
1450
1451
|
}
|
|
1451
1452
|
}
|
|
1453
|
+
this._cutoffView = null;
|
|
1452
1454
|
this.onDataChanged?.();
|
|
1453
1455
|
}
|
|
1454
|
-
|
|
1456
|
+
/**
|
|
1457
|
+
* Bar-replay display cutoff. When set, data() returns only bars at or
|
|
1458
|
+
* before this time while the FULL tape keeps flowing underneath — the
|
|
1459
|
+
* datafeed keeps writing live bars via setData/update, and everything
|
|
1460
|
+
* downstream (renderers, indicators, crosshair, last-price) naturally sees
|
|
1461
|
+
* the truncated past. Replay is not a mode the pipeline knows about; it is
|
|
1462
|
+
* this clamp plus a controller that moves it.
|
|
1463
|
+
*/
|
|
1464
|
+
_cutoffTime = null;
|
|
1465
|
+
_cutoffView = null;
|
|
1466
|
+
setCutoffTime(time) {
|
|
1467
|
+
this._cutoffTime = time;
|
|
1468
|
+
this._cutoffView = null;
|
|
1469
|
+
this.onDataChanged?.();
|
|
1470
|
+
}
|
|
1471
|
+
cutoffTime() {
|
|
1472
|
+
return this._cutoffTime;
|
|
1473
|
+
}
|
|
1474
|
+
/** The complete tape, ignoring any replay cutoff. Datafeed-side reads only. */
|
|
1475
|
+
fullData() {
|
|
1455
1476
|
return this._data;
|
|
1456
1477
|
}
|
|
1478
|
+
data() {
|
|
1479
|
+
if (this._cutoffTime === null) return this._data;
|
|
1480
|
+
const len = this._data.length;
|
|
1481
|
+
const cached = this._cutoffView;
|
|
1482
|
+
if (cached && cached.len === len && cached.cutoff === this._cutoffTime) return cached.view;
|
|
1483
|
+
let lo = 0;
|
|
1484
|
+
let hi = len;
|
|
1485
|
+
while (lo < hi) {
|
|
1486
|
+
const mid = lo + hi >>> 1;
|
|
1487
|
+
if (this._data[mid].time <= this._cutoffTime) lo = mid + 1;
|
|
1488
|
+
else hi = mid;
|
|
1489
|
+
}
|
|
1490
|
+
const view = this._data.slice(0, lo);
|
|
1491
|
+
this._cutoffView = { len, cutoff: this._cutoffTime, view };
|
|
1492
|
+
return view;
|
|
1493
|
+
}
|
|
1457
1494
|
applyOptions(options) {
|
|
1458
1495
|
this._options = { ...this._options, ...options };
|
|
1459
1496
|
}
|
|
@@ -4730,14 +4767,14 @@ var Chart = class {
|
|
|
4730
4767
|
_crosshairEnabled = true;
|
|
4731
4768
|
_magnetMode = "snap-xy";
|
|
4732
4769
|
// ── Trading overlay ───────────────────────────────────────────────────────────
|
|
4733
|
-
/** Current set of order lines supplied by
|
|
4770
|
+
/** Current set of order lines supplied by ForgeCharts for canvas rendering. */
|
|
4734
4771
|
_tradingOrders = [];
|
|
4735
4772
|
/** Called whenever a drawing is committed (created or updated). */
|
|
4736
4773
|
onDrawingCommitted;
|
|
4737
4774
|
/** Fired when an EXISTING drawing is modified via drag (handle or body move). */
|
|
4738
4775
|
onDrawingEdited;
|
|
4739
4776
|
_drawingEditDirty = false;
|
|
4740
|
-
/** Ctrl+Z / Ctrl+Y — drawing history is owned by
|
|
4777
|
+
/** Ctrl+Z / Ctrl+Y — drawing history is owned by ForgeCharts. */
|
|
4741
4778
|
onUndoRequest;
|
|
4742
4779
|
onRedoRequest;
|
|
4743
4780
|
/** Called whenever a drawing is deleted. */
|
|
@@ -4753,7 +4790,7 @@ var Chart = class {
|
|
|
4753
4790
|
/** Called when the user drags a TP/SL pill off an entry line to create a bracket leg. */
|
|
4754
4791
|
onTpSlCreate;
|
|
4755
4792
|
/**
|
|
4756
|
-
* Called after every pan or zoom interaction so external code (e.g.
|
|
4793
|
+
* Called after every pan or zoom interaction so external code (e.g. ForgeCharts)
|
|
4757
4794
|
* can check whether the visible time range now extends past the oldest
|
|
4758
4795
|
* loaded bar and trigger a lazy history fetch.
|
|
4759
4796
|
*/
|
|
@@ -5256,11 +5293,11 @@ var Chart = class {
|
|
|
5256
5293
|
}
|
|
5257
5294
|
ctx.restore();
|
|
5258
5295
|
}
|
|
5259
|
-
/** Provides the overlay renderer with the latest order list from
|
|
5296
|
+
/** Provides the overlay renderer with the latest order list from ForgeCharts. */
|
|
5260
5297
|
setTradingOrders(orders) {
|
|
5261
5298
|
this._tradingOrders = orders;
|
|
5262
5299
|
}
|
|
5263
|
-
/** Provides the overlay renderer with the latest position list from
|
|
5300
|
+
/** Provides the overlay renderer with the latest position list from ForgeCharts. */
|
|
5264
5301
|
setTradingPositions(_positions) {
|
|
5265
5302
|
}
|
|
5266
5303
|
/** Sets contract switch markers for continuous futures roll dates. */
|
|
@@ -6077,9 +6114,17 @@ var PixiCandlestickRenderer = class {
|
|
|
6077
6114
|
this._gfx = new Graphics();
|
|
6078
6115
|
layer.addChild(this._gfx);
|
|
6079
6116
|
}
|
|
6117
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
6118
|
+
clear() {
|
|
6119
|
+
this._gfx.clear();
|
|
6120
|
+
}
|
|
6080
6121
|
draw(bars, transform, opts) {
|
|
6081
6122
|
const gfx = this._gfx;
|
|
6082
6123
|
gfx.clear();
|
|
6124
|
+
if (opts?.type === "bar") {
|
|
6125
|
+
this._drawBars(bars, transform, opts);
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6083
6128
|
if (bars.length === 0) return;
|
|
6084
6129
|
const { plotWidth: pw, plotHeight: ph } = transform;
|
|
6085
6130
|
const upColor = hexToNum(opts && "upColor" in opts ? opts.upColor : void 0, UP_COLOR);
|
|
@@ -6210,6 +6255,36 @@ var PixiCandlestickRenderer = class {
|
|
|
6210
6255
|
/**
|
|
6211
6256
|
* Converts OHLCV bars to Heikin-Ashi values before drawing.
|
|
6212
6257
|
*/
|
|
6258
|
+
/** Classic OHLC bars: high-low spine, open tick left, close tick right. */
|
|
6259
|
+
_drawBars(bars, transform, opts) {
|
|
6260
|
+
const gfx = this._gfx;
|
|
6261
|
+
if (bars.length === 0) return;
|
|
6262
|
+
const upColor = hexToNum(opts && "upColor" in opts ? opts.upColor : void 0, UP_COLOR);
|
|
6263
|
+
const downColor = hexToNum(opts && "downColor" in opts ? opts.downColor : void 0, DOWN_COLOR);
|
|
6264
|
+
const { plotWidth: pw } = transform;
|
|
6265
|
+
let barPitch = pw / Math.max(1, bars.length);
|
|
6266
|
+
if (bars.length >= 2) {
|
|
6267
|
+
const dx = Math.abs(transform.timeToX(bars[1].time) - transform.timeToX(bars[0].time));
|
|
6268
|
+
if (dx > 0) barPitch = dx;
|
|
6269
|
+
}
|
|
6270
|
+
const tick = Math.max(1.5, Math.min(barPitch * 0.35, 6));
|
|
6271
|
+
const lineW = barPitch < 3 ? 1 : 1.5;
|
|
6272
|
+
for (const bar of bars) {
|
|
6273
|
+
const x = transform.timeToX(bar.time);
|
|
6274
|
+
if (x < -barPitch || x > pw + barPitch) continue;
|
|
6275
|
+
const yHigh = transform.priceToY(bar.high);
|
|
6276
|
+
const yLow = transform.priceToY(bar.low);
|
|
6277
|
+
const yOpen = transform.priceToY(bar.open);
|
|
6278
|
+
const yClose = transform.priceToY(bar.close);
|
|
6279
|
+
const color = bar.close >= bar.open ? upColor : downColor;
|
|
6280
|
+
gfx.moveTo(x, yHigh).lineTo(x, yLow);
|
|
6281
|
+
if (barPitch >= 3) {
|
|
6282
|
+
gfx.moveTo(x - tick, yOpen).lineTo(x, yOpen);
|
|
6283
|
+
gfx.moveTo(x, yClose).lineTo(x + tick, yClose);
|
|
6284
|
+
}
|
|
6285
|
+
gfx.stroke({ color, width: lineW });
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6213
6288
|
drawHeikinAshi(bars, transform, opts) {
|
|
6214
6289
|
const first = bars[0];
|
|
6215
6290
|
const last = bars[bars.length - 1];
|
|
@@ -7532,6 +7607,10 @@ var PixiLineRenderer = class {
|
|
|
7532
7607
|
this._gradientCache = null;
|
|
7533
7608
|
this._gfx.destroy(true);
|
|
7534
7609
|
}
|
|
7610
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
7611
|
+
clear() {
|
|
7612
|
+
this._gfx.clear();
|
|
7613
|
+
}
|
|
7535
7614
|
draw(bars, transform, options) {
|
|
7536
7615
|
const gfx = this._gfx;
|
|
7537
7616
|
gfx.clear();
|
|
@@ -7626,6 +7705,10 @@ var PixiHistogramRenderer = class {
|
|
|
7626
7705
|
destroy() {
|
|
7627
7706
|
this._gfx.destroy(true);
|
|
7628
7707
|
}
|
|
7708
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
7709
|
+
clear() {
|
|
7710
|
+
this._gfx.clear();
|
|
7711
|
+
}
|
|
7629
7712
|
draw(bars, transform) {
|
|
7630
7713
|
const gfx = this._gfx;
|
|
7631
7714
|
gfx.clear();
|
|
@@ -8605,7 +8688,7 @@ var PixiChart = class {
|
|
|
8605
8688
|
/** Fired when an EXISTING drawing is modified via drag (handle or body move). */
|
|
8606
8689
|
onDrawingEdited;
|
|
8607
8690
|
_drawingEditDirty = false;
|
|
8608
|
-
/** Ctrl+Z / Ctrl+Y — drawing history is owned by
|
|
8691
|
+
/** Ctrl+Z / Ctrl+Y — drawing history is owned by ForgeCharts. */
|
|
8609
8692
|
onUndoRequest;
|
|
8610
8693
|
onRedoRequest;
|
|
8611
8694
|
/** Called whenever a drawing is deleted. */
|
|
@@ -8620,7 +8703,7 @@ var PixiChart = class {
|
|
|
8620
8703
|
onDrawingContextMenu;
|
|
8621
8704
|
/** Called when the user double-clicks a drawing. */
|
|
8622
8705
|
onDrawingDoubleClick;
|
|
8623
|
-
/** Called after every pan/zoom so
|
|
8706
|
+
/** Called after every pan/zoom so ForgeCharts can trigger lazy history loading. */
|
|
8624
8707
|
onViewportChanged;
|
|
8625
8708
|
constructor(container, options = {}) {
|
|
8626
8709
|
this._container = container;
|
|
@@ -8727,6 +8810,12 @@ var PixiChart = class {
|
|
|
8727
8810
|
this.onViewportChanged?.();
|
|
8728
8811
|
},
|
|
8729
8812
|
onCrosshairMove: (x, y) => {
|
|
8813
|
+
if (this._replaySelect) {
|
|
8814
|
+
const t = this._cursorBucketTime(x);
|
|
8815
|
+
this._replayHoverTime = t;
|
|
8816
|
+
this._replaySelect.onHover(t);
|
|
8817
|
+
this._drawReplayDim(x);
|
|
8818
|
+
}
|
|
8730
8819
|
if (!this._crosshairEnabled) return;
|
|
8731
8820
|
if (this._magnetMode === "off") {
|
|
8732
8821
|
this._crosshairRenderer.update(x, y, this._cursorBucketTime(x));
|
|
@@ -9016,7 +9105,7 @@ var PixiChart = class {
|
|
|
9016
9105
|
}).catch(() => {
|
|
9017
9106
|
});
|
|
9018
9107
|
}
|
|
9019
|
-
// ─── Drawing objects (used by PixiChart directly;
|
|
9108
|
+
// ─── Drawing objects (used by PixiChart directly; ForgeCharts uses DrawingManager) ─
|
|
9020
9109
|
/** @internal Used by PixiChart when DrawingManager pushes an update. */
|
|
9021
9110
|
setDrawings(drawings) {
|
|
9022
9111
|
this._drawings = [...drawings];
|
|
@@ -9025,6 +9114,47 @@ var PixiChart = class {
|
|
|
9025
9114
|
// ─── Rendering ───────────────────────────────────────────────────────────────
|
|
9026
9115
|
/** Styles whose renderer has already logged a failure this session. */
|
|
9027
9116
|
_renderErrorLogged = /* @__PURE__ */ new Set();
|
|
9117
|
+
// ── Bar-replay anchor selection ─────────────────────────────────────────
|
|
9118
|
+
_replaySelect = null;
|
|
9119
|
+
_replayHoverTime = null;
|
|
9120
|
+
_replayDimGfx = null;
|
|
9121
|
+
_replayClickHandler = null;
|
|
9122
|
+
setReplaySelect(cb) {
|
|
9123
|
+
this._replaySelect = cb;
|
|
9124
|
+
this._replayHoverTime = null;
|
|
9125
|
+
if (cb) {
|
|
9126
|
+
this._replayClickHandler = () => {
|
|
9127
|
+
if (this._replayHoverTime !== null) this._replaySelect?.onCommit(this._replayHoverTime);
|
|
9128
|
+
};
|
|
9129
|
+
this._app.canvas.addEventListener("click", this._replayClickHandler);
|
|
9130
|
+
this._app.canvas.style.cursor = "crosshair";
|
|
9131
|
+
} else {
|
|
9132
|
+
if (this._replayClickHandler) {
|
|
9133
|
+
this._app.canvas.removeEventListener("click", this._replayClickHandler);
|
|
9134
|
+
this._replayClickHandler = null;
|
|
9135
|
+
}
|
|
9136
|
+
this._app.canvas.style.cursor = "";
|
|
9137
|
+
if (this._replayDimGfx) {
|
|
9138
|
+
this._replayDimGfx.clear();
|
|
9139
|
+
this._replayDimGfx.destroy();
|
|
9140
|
+
this._replayDimGfx = null;
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
/** Translucent veil over the bars a replay-anchor click would remove. */
|
|
9145
|
+
_drawReplayDim(x) {
|
|
9146
|
+
if (!this._replayDimGfx) {
|
|
9147
|
+
this._replayDimGfx = new Graphics();
|
|
9148
|
+
this._layers.get(5 /* Interaction */).addChildAt(this._replayDimGfx, 0);
|
|
9149
|
+
}
|
|
9150
|
+
const gfx = this._replayDimGfx;
|
|
9151
|
+
const { plotWidth, plotHeight } = this._transform;
|
|
9152
|
+
gfx.clear();
|
|
9153
|
+
if (x >= 0 && x < plotWidth) {
|
|
9154
|
+
gfx.rect(x, 0, plotWidth - x, plotHeight).fill({ color: 8421520, alpha: 0.35 });
|
|
9155
|
+
}
|
|
9156
|
+
this._layers.dirty.mark(5 /* Interaction */);
|
|
9157
|
+
}
|
|
9028
9158
|
_renderFrame() {
|
|
9029
9159
|
if (!this._ready || this._destroyed) return;
|
|
9030
9160
|
const t = this._transform;
|
|
@@ -9034,6 +9164,9 @@ var PixiChart = class {
|
|
|
9034
9164
|
if (dirty.isDirty(1 /* PriceSeries */)) {
|
|
9035
9165
|
this._autoFitPriceRange();
|
|
9036
9166
|
dirty.mark(0 /* Background */);
|
|
9167
|
+
this._candleRenderer.clear();
|
|
9168
|
+
this._lineRenderer.clear();
|
|
9169
|
+
this._histogramRenderer.clear();
|
|
9037
9170
|
for (const series of this._series) {
|
|
9038
9171
|
const opts = series.options();
|
|
9039
9172
|
if (opts.visible === false) continue;
|
|
@@ -9246,6 +9379,16 @@ var PixiChart = class {
|
|
|
9246
9379
|
}
|
|
9247
9380
|
this._markViewportDirty();
|
|
9248
9381
|
}
|
|
9382
|
+
/**
|
|
9383
|
+
* Pans the viewport so the bar at `time` sits at the horizontal center.
|
|
9384
|
+
* Used by bar replay: the anchor candle the trader picked becomes the
|
|
9385
|
+
* chart's center, with the (empty, dimmed-away) future to its right.
|
|
9386
|
+
*/
|
|
9387
|
+
centerOnTime(time) {
|
|
9388
|
+
const px = this._transform.timeToX(time);
|
|
9389
|
+
this._transform.pan(-(px - this._transform.plotWidth / 2), 0);
|
|
9390
|
+
this._markViewportDirty();
|
|
9391
|
+
}
|
|
9249
9392
|
/** Pans the viewport so the latest bar sits near the right edge. */
|
|
9250
9393
|
_scrollToLatestBar() {
|
|
9251
9394
|
let latestTime = -Infinity;
|
|
@@ -11596,6 +11739,8 @@ var resolver = () => ChartRuntimeResolver.getInstance();
|
|
|
11596
11739
|
var canUseChartTypes = () => resolver().canUseChartTypes();
|
|
11597
11740
|
var canUseExtendedDrawings = () => resolver().canUseExtendedDrawings();
|
|
11598
11741
|
var canUseMultiPane = () => resolver().canUseMultiPane();
|
|
11742
|
+
var canUseBarReplay = () => resolver().canUseBarReplay();
|
|
11743
|
+
var canRenderOverlays = () => resolver().canRenderOverlays();
|
|
11599
11744
|
var canUseDraggableOrders = () => resolver().canUseDraggableOrders();
|
|
11600
11745
|
var canUseBracketOrders = () => resolver().canUseBracketOrders();
|
|
11601
11746
|
var canUsePositionsOverlay = () => resolver().canUsePositionsOverlay();
|
|
@@ -11912,8 +12057,8 @@ var ManagedTradingController = class {
|
|
|
11912
12057
|
}
|
|
11913
12058
|
};
|
|
11914
12059
|
|
|
11915
|
-
// src/api/
|
|
11916
|
-
var
|
|
12060
|
+
// src/api/ForgeCharts.ts
|
|
12061
|
+
var ForgeCharts = class _ForgeCharts {
|
|
11917
12062
|
_core;
|
|
11918
12063
|
_bus;
|
|
11919
12064
|
_indicators;
|
|
@@ -12304,7 +12449,7 @@ var TChart = class _TChart {
|
|
|
12304
12449
|
"[ForgeCharts] ForgeScript indicators are not enabled on the active license. Contact your license provider to enable the forgeScript feature."
|
|
12305
12450
|
);
|
|
12306
12451
|
}
|
|
12307
|
-
let finalConfig =
|
|
12452
|
+
let finalConfig = _ForgeCharts.OVERLAY_ONLY.has(config.type) && config.overlay !== true ? { ...config, overlay: true } : config;
|
|
12308
12453
|
if (finalConfig.overlay !== true && !canUseMultiPane()) {
|
|
12309
12454
|
console.warn("[ForgeCharts] multiPane is not enabled on the active license \u2014 indicator added as a price-pane overlay instead.");
|
|
12310
12455
|
finalConfig = { ...finalConfig, overlay: true };
|
|
@@ -12482,7 +12627,7 @@ var TChart = class _TChart {
|
|
|
12482
12627
|
*/
|
|
12483
12628
|
startDrawingTool(type) {
|
|
12484
12629
|
this._assertAlive();
|
|
12485
|
-
if (
|
|
12630
|
+
if (_ForgeCharts.EXTENDED_DRAWING_TOOLS.has(type) && !canUseExtendedDrawings()) {
|
|
12486
12631
|
console.warn(`[ForgeCharts] Drawing tool '${type}' requires the extendedDrawings feature, which is not enabled on the active license.`);
|
|
12487
12632
|
return;
|
|
12488
12633
|
}
|
|
@@ -12520,6 +12665,14 @@ var TChart = class _TChart {
|
|
|
12520
12665
|
/** Set contract switch roll markers for continuous futures. */
|
|
12521
12666
|
setContractSwitches(switches) {
|
|
12522
12667
|
this._assertAlive();
|
|
12668
|
+
if (!canRenderOverlays()) {
|
|
12669
|
+
if (switches.length > 0) {
|
|
12670
|
+
console.warn("[ForgeCharts] Overlay rendering (overlays) is not enabled on the active plan \u2014 contract switch markers suppressed.");
|
|
12671
|
+
}
|
|
12672
|
+
this._core.setContractSwitches([]);
|
|
12673
|
+
this._core.markDirty();
|
|
12674
|
+
return;
|
|
12675
|
+
}
|
|
12523
12676
|
this._core.setContractSwitches(switches);
|
|
12524
12677
|
this._core.markDirty();
|
|
12525
12678
|
}
|
|
@@ -12563,7 +12716,7 @@ var TChart = class _TChart {
|
|
|
12563
12716
|
const snap = this._drawings.all().map((d) => ({ ...d, points: [...d.points] }));
|
|
12564
12717
|
this._drawHistory.splice(this._drawHistIdx + 1);
|
|
12565
12718
|
this._drawHistory.push(snap);
|
|
12566
|
-
if (this._drawHistory.length >
|
|
12719
|
+
if (this._drawHistory.length > _ForgeCharts._HIST_MAX) this._drawHistory.shift();
|
|
12567
12720
|
this._drawHistIdx = this._drawHistory.length - 1;
|
|
12568
12721
|
}
|
|
12569
12722
|
/** Steps drawing state one edit back (Ctrl+Z). */
|
|
@@ -12928,6 +13081,186 @@ var TChart = class _TChart {
|
|
|
12928
13081
|
}
|
|
12929
13082
|
return this._unmanagedIngestion;
|
|
12930
13083
|
}
|
|
13084
|
+
// ─── Bar replay ───────────────────────────────────────────────────────────────
|
|
13085
|
+
//
|
|
13086
|
+
// Replay is a display cutoff on the primary series (Series.setCutoffTime)
|
|
13087
|
+
// plus this controller moving it. The full tape keeps flowing underneath —
|
|
13088
|
+
// live bars land in fullData() and stay hidden until stepped over or the
|
|
13089
|
+
// replay exits — so no datafeed, indicator, or renderer knows replay
|
|
13090
|
+
// exists. Selection (the hover shadow of what a click would remove) is a
|
|
13091
|
+
// backend surface only Pixi implements; ForgeCharts refuses replay elsewhere.
|
|
13092
|
+
/** Playback speeds, in bars per second. */
|
|
13093
|
+
static REPLAY_SPEEDS = [0.5, 1, 2, 5, 10, 15, 20];
|
|
13094
|
+
_replay = {
|
|
13095
|
+
selecting: false,
|
|
13096
|
+
active: false,
|
|
13097
|
+
playing: false,
|
|
13098
|
+
speedIdx: 1,
|
|
13099
|
+
timer: null
|
|
13100
|
+
};
|
|
13101
|
+
_emitReplayState() {
|
|
13102
|
+
this._bus.emit("replayStateChanged", {
|
|
13103
|
+
selecting: this._replay.selecting,
|
|
13104
|
+
active: this._replay.active,
|
|
13105
|
+
playing: this._replay.playing,
|
|
13106
|
+
speed: _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx],
|
|
13107
|
+
cutoffTime: this._series.cutoffTime()
|
|
13108
|
+
});
|
|
13109
|
+
}
|
|
13110
|
+
/** Current replay state — the control strip's render source. */
|
|
13111
|
+
replayState() {
|
|
13112
|
+
return {
|
|
13113
|
+
selecting: this._replay.selecting,
|
|
13114
|
+
active: this._replay.active,
|
|
13115
|
+
playing: this._replay.playing,
|
|
13116
|
+
speed: _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx],
|
|
13117
|
+
cutoffTime: this._series.cutoffTime()
|
|
13118
|
+
};
|
|
13119
|
+
}
|
|
13120
|
+
/**
|
|
13121
|
+
* Enter replay-anchor selection: the cursor shadows everything a click
|
|
13122
|
+
* would remove; clicking commits that bucket as the replay head. Returns
|
|
13123
|
+
* false (with a warning) when the plan lacks bar_replay or the backend
|
|
13124
|
+
* has no selection surface.
|
|
13125
|
+
*/
|
|
13126
|
+
replayEnterSelection() {
|
|
13127
|
+
this._assertAlive();
|
|
13128
|
+
if (!canUseBarReplay()) {
|
|
13129
|
+
console.warn("[ForgeCharts] Bar replay (bar_replay) is not enabled on the active plan.");
|
|
13130
|
+
return false;
|
|
13131
|
+
}
|
|
13132
|
+
if (!this._core.setReplaySelect) {
|
|
13133
|
+
console.warn("[ForgeCharts] Bar replay requires the Pixi render backend.");
|
|
13134
|
+
return false;
|
|
13135
|
+
}
|
|
13136
|
+
this._replayStopTimer();
|
|
13137
|
+
this._replay.selecting = true;
|
|
13138
|
+
this._replay.playing = false;
|
|
13139
|
+
this._core.setReplaySelect({
|
|
13140
|
+
onHover: () => {
|
|
13141
|
+
},
|
|
13142
|
+
onCommit: (time) => this._replayCommit(time)
|
|
13143
|
+
});
|
|
13144
|
+
this._emitReplayState();
|
|
13145
|
+
return true;
|
|
13146
|
+
}
|
|
13147
|
+
/** Leave selection without committing (existing replay, if any, survives). */
|
|
13148
|
+
replayCancelSelection() {
|
|
13149
|
+
this._assertAlive();
|
|
13150
|
+
if (!this._replay.selecting) return;
|
|
13151
|
+
this._replay.selecting = false;
|
|
13152
|
+
this._core.setReplaySelect?.(null);
|
|
13153
|
+
this._emitReplayState();
|
|
13154
|
+
}
|
|
13155
|
+
_replayCommit(time) {
|
|
13156
|
+
this._replay.selecting = false;
|
|
13157
|
+
this._core.setReplaySelect?.(null);
|
|
13158
|
+
this._series.setCutoffTime(time);
|
|
13159
|
+
this._replay.active = true;
|
|
13160
|
+
this._replay.playing = false;
|
|
13161
|
+
this._core.centerOnTime?.(time);
|
|
13162
|
+
this._core.markDirty();
|
|
13163
|
+
this._emitReplayState();
|
|
13164
|
+
}
|
|
13165
|
+
replayPlay() {
|
|
13166
|
+
this._assertAlive();
|
|
13167
|
+
if (!this._replay.active || this._replay.playing) return;
|
|
13168
|
+
this._replay.playing = true;
|
|
13169
|
+
this._replayStartTimer();
|
|
13170
|
+
this._emitReplayState();
|
|
13171
|
+
}
|
|
13172
|
+
replayPause() {
|
|
13173
|
+
this._assertAlive();
|
|
13174
|
+
if (!this._replay.playing) return;
|
|
13175
|
+
this._replay.playing = false;
|
|
13176
|
+
this._replayStopTimer();
|
|
13177
|
+
this._emitReplayState();
|
|
13178
|
+
}
|
|
13179
|
+
/** Advance the replay head n bars. Pauses at the end of the tape. */
|
|
13180
|
+
replayStepForward(n = 1) {
|
|
13181
|
+
this._assertAlive();
|
|
13182
|
+
if (!this._replay.active) return;
|
|
13183
|
+
const full = this._series.fullData();
|
|
13184
|
+
const shown = this._series.data().length;
|
|
13185
|
+
const nextIdx = Math.min(shown + n, full.length) - 1;
|
|
13186
|
+
if (nextIdx < shown) {
|
|
13187
|
+
this.replayPause();
|
|
13188
|
+
return;
|
|
13189
|
+
}
|
|
13190
|
+
this._series.setCutoffTime(full[nextIdx].time);
|
|
13191
|
+
this._core.markDirty();
|
|
13192
|
+
if (nextIdx >= full.length - 1) this.replayPause();
|
|
13193
|
+
this._emitReplayState();
|
|
13194
|
+
}
|
|
13195
|
+
/** Rewind the replay head n bars back in time (default 10). */
|
|
13196
|
+
replayRewind(n = 10) {
|
|
13197
|
+
this._assertAlive();
|
|
13198
|
+
if (!this._replay.active) return;
|
|
13199
|
+
const full = this._series.fullData();
|
|
13200
|
+
const shown = this._series.data().length;
|
|
13201
|
+
const idx = Math.max(shown - 1 - n, 0);
|
|
13202
|
+
if (full.length === 0) return;
|
|
13203
|
+
this._series.setCutoffTime(full[idx].time);
|
|
13204
|
+
this._core.markDirty();
|
|
13205
|
+
this._emitReplayState();
|
|
13206
|
+
}
|
|
13207
|
+
/** Set an exact playback speed (nearest catalogued value). */
|
|
13208
|
+
replaySetSpeed(speed) {
|
|
13209
|
+
this._assertAlive();
|
|
13210
|
+
let best = 0;
|
|
13211
|
+
for (let i = 0; i < _ForgeCharts.REPLAY_SPEEDS.length; i++) {
|
|
13212
|
+
if (Math.abs(_ForgeCharts.REPLAY_SPEEDS[i] - speed) < Math.abs(_ForgeCharts.REPLAY_SPEEDS[best] - speed)) best = i;
|
|
13213
|
+
}
|
|
13214
|
+
this._replay.speedIdx = best;
|
|
13215
|
+
if (this._replay.playing) this._replayStartTimer();
|
|
13216
|
+
this._emitReplayState();
|
|
13217
|
+
}
|
|
13218
|
+
/**
|
|
13219
|
+
* Restore a persisted replay session (browser refresh mid-replay). Applies
|
|
13220
|
+
* the same gate as entering selection; the cutoff simply lands where it
|
|
13221
|
+
* was, paused, with the anchor centered. A cutoff at or beyond the tape's
|
|
13222
|
+
* head means the replay had effectively finished — nothing is restored.
|
|
13223
|
+
*/
|
|
13224
|
+
replayRestore(cutoffTime, speed) {
|
|
13225
|
+
this._assertAlive();
|
|
13226
|
+
if (!canUseBarReplay()) return false;
|
|
13227
|
+
const full = this._series.fullData();
|
|
13228
|
+
const last = full[full.length - 1];
|
|
13229
|
+
if (!last || cutoffTime >= last.time) return false;
|
|
13230
|
+
if (speed !== void 0) this.replaySetSpeed(speed);
|
|
13231
|
+
this._replayCommit(cutoffTime);
|
|
13232
|
+
return true;
|
|
13233
|
+
}
|
|
13234
|
+
/** Cycle to the next playback speed; restarts the timer when playing. */
|
|
13235
|
+
replayCycleSpeed() {
|
|
13236
|
+
this._assertAlive();
|
|
13237
|
+
this._replay.speedIdx = (this._replay.speedIdx + 1) % _ForgeCharts.REPLAY_SPEEDS.length;
|
|
13238
|
+
if (this._replay.playing) this._replayStartTimer();
|
|
13239
|
+
this._emitReplayState();
|
|
13240
|
+
return _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
|
|
13241
|
+
}
|
|
13242
|
+
/** Exit replay entirely: the full tape (live head included) returns. */
|
|
13243
|
+
replayExit() {
|
|
13244
|
+
this._assertAlive();
|
|
13245
|
+
this._replayStopTimer();
|
|
13246
|
+
this._replay.selecting = false;
|
|
13247
|
+
this._replay.active = false;
|
|
13248
|
+
this._replay.playing = false;
|
|
13249
|
+
this._core.setReplaySelect?.(null);
|
|
13250
|
+
this._series.setCutoffTime(null);
|
|
13251
|
+
this._core.markDirty();
|
|
13252
|
+
this._core.scrollToEnd();
|
|
13253
|
+
this._emitReplayState();
|
|
13254
|
+
}
|
|
13255
|
+
_replayStartTimer() {
|
|
13256
|
+
this._replayStopTimer();
|
|
13257
|
+
const speed = _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
|
|
13258
|
+
this._replay.timer = setInterval(() => this.replayStepForward(1), Math.max(1e3 / speed, 16));
|
|
13259
|
+
}
|
|
13260
|
+
_replayStopTimer() {
|
|
13261
|
+
if (this._replay.timer) clearInterval(this._replay.timer);
|
|
13262
|
+
this._replay.timer = null;
|
|
13263
|
+
}
|
|
12931
13264
|
// ─── Datafeed ─────────────────────────────────────────────────────────────────
|
|
12932
13265
|
_buildConnector(datafeed) {
|
|
12933
13266
|
return new DatafeedConnector(datafeed, this._series, {
|
|
@@ -18146,7 +18479,7 @@ var DEMONSTRATION_STROKE = "rgba(255, 200, 50, 0.7)";
|
|
|
18146
18479
|
var DEMONSTRATION_COLOR = "var(--crosshair-overlay, rgba(255,255,255,0.75))";
|
|
18147
18480
|
|
|
18148
18481
|
// src/version.ts
|
|
18149
|
-
var FORGECHARTS_VERSION = "1.5.
|
|
18482
|
+
var FORGECHARTS_VERSION = "1.5.84" ;
|
|
18150
18483
|
function applyRuntimeConfig(caps, config) {
|
|
18151
18484
|
if (!config) return caps;
|
|
18152
18485
|
const orderEntry = config.enableOrderEntry === false ? false : caps.orderEntry;
|
|
@@ -18248,10 +18581,7 @@ function useHostLicense(apiUrl, getAuthToken) {
|
|
|
18248
18581
|
function useUserPackage(apiUrl, getAuthToken) {
|
|
18249
18582
|
useEffect(() => {
|
|
18250
18583
|
const resolver3 = ChartRuntimeResolver.getInstance();
|
|
18251
|
-
if (!apiUrl || !getAuthToken)
|
|
18252
|
-
resolver3.setUserPackageFeatures(null);
|
|
18253
|
-
return;
|
|
18254
|
-
}
|
|
18584
|
+
if (!apiUrl || !getAuthToken) return;
|
|
18255
18585
|
let cancelled = false;
|
|
18256
18586
|
void (async () => {
|
|
18257
18587
|
try {
|
|
@@ -22991,6 +23321,8 @@ var ChartCanvas = forwardRef(
|
|
|
22991
23321
|
onPointerToolChange,
|
|
22992
23322
|
initialPointerTool,
|
|
22993
23323
|
initialSeriesType,
|
|
23324
|
+
initialReplay,
|
|
23325
|
+
onReplayStateChange,
|
|
22994
23326
|
initialMagnetMode,
|
|
22995
23327
|
onMagnetModeChange,
|
|
22996
23328
|
drawingFavorites: drawingFavoritesProp,
|
|
@@ -23110,6 +23442,14 @@ var ChartCanvas = forwardRef(
|
|
|
23110
23442
|
},
|
|
23111
23443
|
addIndicator: (config) => chartRef.current?.addIndicator(config) ?? null,
|
|
23112
23444
|
setSeriesType: (type) => chartRef.current?.setSeriesType(type),
|
|
23445
|
+
toggleReplay: () => {
|
|
23446
|
+
const chart = chartRef.current;
|
|
23447
|
+
if (!chart) return;
|
|
23448
|
+
const st = chart.replayState();
|
|
23449
|
+
if (st.selecting) chart.replayCancelSelection();
|
|
23450
|
+
else if (st.active) chart.replayExit();
|
|
23451
|
+
else chart.replayEnterSelection();
|
|
23452
|
+
},
|
|
23113
23453
|
removeIndicator: (id) => chartRef.current?.removeIndicator(id),
|
|
23114
23454
|
moveIndicatorToOverlay: (id) => chartRef.current?.moveIndicatorToOverlay(id),
|
|
23115
23455
|
moveIndicatorToPane: (id, targetPaneId) => chartRef.current?.moveIndicatorToPane(id, targetPaneId),
|
|
@@ -23157,7 +23497,7 @@ var ChartCanvas = forwardRef(
|
|
|
23157
23497
|
useEffect(() => {
|
|
23158
23498
|
const container = priceRef.current;
|
|
23159
23499
|
if (!container) return;
|
|
23160
|
-
const chart = new
|
|
23500
|
+
const chart = new ForgeCharts({
|
|
23161
23501
|
container,
|
|
23162
23502
|
symbol,
|
|
23163
23503
|
interval: timeframe,
|
|
@@ -23171,6 +23511,10 @@ var ChartCanvas = forwardRef(
|
|
|
23171
23511
|
for (const config of initialIndicators) {
|
|
23172
23512
|
chart.addIndicator(config);
|
|
23173
23513
|
}
|
|
23514
|
+
chart.on("replayStateChanged", (s) => {
|
|
23515
|
+
setReplayState(s.selecting || s.active ? s : null);
|
|
23516
|
+
onReplayStateChangeRef.current?.(s);
|
|
23517
|
+
});
|
|
23174
23518
|
transformRef.current = chart.getTransform();
|
|
23175
23519
|
const syncChartState = () => {
|
|
23176
23520
|
setBars([...chart.getBars()]);
|
|
@@ -24130,6 +24474,37 @@ var ChartCanvas = forwardRef(
|
|
|
24130
24474
|
selectPointerToolRef.current = handleToolSelect;
|
|
24131
24475
|
const initialPointerAppliedRef = useRef(false);
|
|
24132
24476
|
const initialSeriesAppliedRef = useRef(false);
|
|
24477
|
+
const [replayState, setReplayState] = useState(null);
|
|
24478
|
+
const [replayBarOffset, setReplayBarOffset] = useState(0);
|
|
24479
|
+
const replayDragRef = useRef(null);
|
|
24480
|
+
const onReplayStateChangeRef = useRef(onReplayStateChange);
|
|
24481
|
+
useEffect(() => {
|
|
24482
|
+
onReplayStateChangeRef.current = onReplayStateChange;
|
|
24483
|
+
}, [onReplayStateChange]);
|
|
24484
|
+
const initialReplayAppliedRef = useRef(false);
|
|
24485
|
+
useEffect(() => {
|
|
24486
|
+
if (initialReplayAppliedRef.current || !initialReplay || bars.length === 0) return;
|
|
24487
|
+
initialReplayAppliedRef.current = true;
|
|
24488
|
+
chartRef.current?.replayRestore(initialReplay.cutoffTime, initialReplay.speed);
|
|
24489
|
+
}, [initialReplay, bars]);
|
|
24490
|
+
useEffect(() => {
|
|
24491
|
+
const move = (e) => {
|
|
24492
|
+
const d = replayDragRef.current;
|
|
24493
|
+
if (!d) return;
|
|
24494
|
+
const host = outerRef.current;
|
|
24495
|
+
const max = host ? Math.max(0, host.clientHeight - 80) : 400;
|
|
24496
|
+
setReplayBarOffset(Math.min(max, Math.max(0, d.startOffset + (d.startY - e.clientY))));
|
|
24497
|
+
};
|
|
24498
|
+
const up = () => {
|
|
24499
|
+
replayDragRef.current = null;
|
|
24500
|
+
};
|
|
24501
|
+
window.addEventListener("pointermove", move);
|
|
24502
|
+
window.addEventListener("pointerup", up);
|
|
24503
|
+
return () => {
|
|
24504
|
+
window.removeEventListener("pointermove", move);
|
|
24505
|
+
window.removeEventListener("pointerup", up);
|
|
24506
|
+
};
|
|
24507
|
+
}, []);
|
|
24133
24508
|
useEffect(() => {
|
|
24134
24509
|
if (!initialSeriesAppliedRef.current && initialSeriesType && chartRef.current) {
|
|
24135
24510
|
initialSeriesAppliedRef.current = true;
|
|
@@ -24138,7 +24513,7 @@ var ChartCanvas = forwardRef(
|
|
|
24138
24513
|
if (initialPointerAppliedRef.current || !initialPointerTool) return;
|
|
24139
24514
|
initialPointerAppliedRef.current = true;
|
|
24140
24515
|
selectPointerToolRef.current(initialPointerTool);
|
|
24141
|
-
}, [initialPointerTool]);
|
|
24516
|
+
}, [initialPointerTool, initialSeriesType]);
|
|
24142
24517
|
const handleMagnetModeChange = useCallback((mode) => {
|
|
24143
24518
|
setMagnetMode(mode);
|
|
24144
24519
|
chartRef.current?.setMagnetMode(mode);
|
|
@@ -24234,6 +24609,124 @@ var ChartCanvas = forwardRef(
|
|
|
24234
24609
|
crosshairXRef.current = null;
|
|
24235
24610
|
},
|
|
24236
24611
|
children: [
|
|
24612
|
+
replayState && (() => {
|
|
24613
|
+
const dark = effectiveTheme !== "light";
|
|
24614
|
+
const bg = dark ? "#000000" : "#ffffff";
|
|
24615
|
+
const fg = dark ? "#ffffff" : "#000000";
|
|
24616
|
+
const line = dark ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.22)";
|
|
24617
|
+
const btn = {
|
|
24618
|
+
background: "transparent",
|
|
24619
|
+
border: "none",
|
|
24620
|
+
color: fg,
|
|
24621
|
+
cursor: "pointer",
|
|
24622
|
+
padding: "6px 10px",
|
|
24623
|
+
borderRadius: 6,
|
|
24624
|
+
fontSize: 12,
|
|
24625
|
+
display: "inline-flex",
|
|
24626
|
+
alignItems: "center",
|
|
24627
|
+
gap: 5,
|
|
24628
|
+
lineHeight: 1
|
|
24629
|
+
};
|
|
24630
|
+
const sep = /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "6px 4px" } });
|
|
24631
|
+
const call = (fn) => () => {
|
|
24632
|
+
if (chartRef.current) fn(chartRef.current);
|
|
24633
|
+
};
|
|
24634
|
+
return /* @__PURE__ */ jsxs("div", { style: {
|
|
24635
|
+
position: "absolute",
|
|
24636
|
+
left: 0,
|
|
24637
|
+
right: 0,
|
|
24638
|
+
bottom: replayBarOffset,
|
|
24639
|
+
zIndex: 8,
|
|
24640
|
+
display: "flex",
|
|
24641
|
+
alignItems: "center",
|
|
24642
|
+
gap: 2,
|
|
24643
|
+
height: 38,
|
|
24644
|
+
padding: "0 8px",
|
|
24645
|
+
boxSizing: "border-box",
|
|
24646
|
+
background: bg,
|
|
24647
|
+
color: fg,
|
|
24648
|
+
borderTop: `1px solid ${line}`,
|
|
24649
|
+
borderBottom: `1px solid ${line}`,
|
|
24650
|
+
userSelect: "none"
|
|
24651
|
+
}, children: [
|
|
24652
|
+
/* @__PURE__ */ jsx(
|
|
24653
|
+
"span",
|
|
24654
|
+
{
|
|
24655
|
+
title: "Drag to move",
|
|
24656
|
+
onPointerDown: (e) => {
|
|
24657
|
+
e.preventDefault();
|
|
24658
|
+
replayDragRef.current = { startY: e.clientY, startOffset: replayBarOffset };
|
|
24659
|
+
},
|
|
24660
|
+
style: { cursor: "ns-resize", padding: "6px 6px", color: fg, opacity: 0.55, fontSize: 12, letterSpacing: 1 },
|
|
24661
|
+
children: "\u2059\u2059"
|
|
24662
|
+
}
|
|
24663
|
+
),
|
|
24664
|
+
sep,
|
|
24665
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, fontWeight: 600, letterSpacing: 0.3, padding: "0 6px" }, children: "Replay" }),
|
|
24666
|
+
sep,
|
|
24667
|
+
replayState.selecting ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, padding: "0 8px", opacity: 0.85 }, children: "Click a bar to start replay from there" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24668
|
+
/* @__PURE__ */ jsx(
|
|
24669
|
+
"button",
|
|
24670
|
+
{
|
|
24671
|
+
style: btn,
|
|
24672
|
+
title: "Pick a new starting bar",
|
|
24673
|
+
onClick: call((c) => c.replayEnterSelection()),
|
|
24674
|
+
children: "\u2506\u2190 Select bar"
|
|
24675
|
+
}
|
|
24676
|
+
),
|
|
24677
|
+
sep,
|
|
24678
|
+
/* @__PURE__ */ jsx(
|
|
24679
|
+
"button",
|
|
24680
|
+
{
|
|
24681
|
+
style: btn,
|
|
24682
|
+
title: "Rewind 10 bars",
|
|
24683
|
+
onClick: call((c) => c.replayRewind(10)),
|
|
24684
|
+
children: "\u23EA"
|
|
24685
|
+
}
|
|
24686
|
+
),
|
|
24687
|
+
/* @__PURE__ */ jsx(
|
|
24688
|
+
"button",
|
|
24689
|
+
{
|
|
24690
|
+
style: btn,
|
|
24691
|
+
title: replayState.playing ? "Pause" : "Play",
|
|
24692
|
+
onClick: call((c) => replayState.playing ? c.replayPause() : c.replayPlay()),
|
|
24693
|
+
children: replayState.playing ? "\u23F8" : "\u25B6"
|
|
24694
|
+
}
|
|
24695
|
+
),
|
|
24696
|
+
/* @__PURE__ */ jsx(
|
|
24697
|
+
"button",
|
|
24698
|
+
{
|
|
24699
|
+
style: btn,
|
|
24700
|
+
title: "Forward one bar",
|
|
24701
|
+
onClick: call((c) => c.replayStepForward(1)),
|
|
24702
|
+
children: "\u23E9"
|
|
24703
|
+
}
|
|
24704
|
+
),
|
|
24705
|
+
/* @__PURE__ */ jsxs(
|
|
24706
|
+
"button",
|
|
24707
|
+
{
|
|
24708
|
+
style: { ...btn, fontVariantNumeric: "tabular-nums", minWidth: 44, justifyContent: "center" },
|
|
24709
|
+
title: "Playback speed (0.5\u201320 bars per second)",
|
|
24710
|
+
onClick: call((c) => c.replayCycleSpeed()),
|
|
24711
|
+
children: [
|
|
24712
|
+
replayState.speed,
|
|
24713
|
+
"x"
|
|
24714
|
+
]
|
|
24715
|
+
}
|
|
24716
|
+
)
|
|
24717
|
+
] }),
|
|
24718
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
|
|
24719
|
+
/* @__PURE__ */ jsx(
|
|
24720
|
+
"button",
|
|
24721
|
+
{
|
|
24722
|
+
style: btn,
|
|
24723
|
+
title: replayState.selecting ? "Cancel selection" : "Exit replay",
|
|
24724
|
+
onClick: call((c) => replayState.selecting ? c.replayCancelSelection() : c.replayExit()),
|
|
24725
|
+
children: "\u2715"
|
|
24726
|
+
}
|
|
24727
|
+
)
|
|
24728
|
+
] });
|
|
24729
|
+
})(),
|
|
24237
24730
|
/* @__PURE__ */ jsx(
|
|
24238
24731
|
"div",
|
|
24239
24732
|
{
|
|
@@ -24321,7 +24814,7 @@ var ChartCanvas = forwardRef(
|
|
|
24321
24814
|
symbol,
|
|
24322
24815
|
timeframe,
|
|
24323
24816
|
bars,
|
|
24324
|
-
theme,
|
|
24817
|
+
theme: effectiveTheme,
|
|
24325
24818
|
indicators,
|
|
24326
24819
|
overlayColors: OVERLAY_COLORS,
|
|
24327
24820
|
onRemove: handleRemoveIndicator,
|
|
@@ -24762,6 +25255,7 @@ function LayoutMenu({
|
|
|
24762
25255
|
currentName,
|
|
24763
25256
|
currentLayoutId,
|
|
24764
25257
|
autoSave,
|
|
25258
|
+
saveState,
|
|
24765
25259
|
onFetchLayouts,
|
|
24766
25260
|
onSave,
|
|
24767
25261
|
onLoad,
|
|
@@ -24903,7 +25397,9 @@ function LayoutMenu({
|
|
|
24903
25397
|
] }),
|
|
24904
25398
|
currentName ?? "Layout",
|
|
24905
25399
|
" \u25BE",
|
|
24906
|
-
|
|
25400
|
+
saveState === "saving" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, opacity: 0.75, fontStyle: "italic" }, children: "Saving\u2026" }),
|
|
25401
|
+
saveState === "saved" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, color: "var(--up)" }, children: "Saved \u2713" }),
|
|
25402
|
+
saveState !== "saving" && saveState !== "saved" && autoSave && isSaved && /* @__PURE__ */ jsx("span", { className: "layout-autosave-dot", title: "Auto-save enabled" })
|
|
24907
25403
|
]
|
|
24908
25404
|
}
|
|
24909
25405
|
),
|
|
@@ -26275,28 +26771,32 @@ function RollRuleDropdown({
|
|
|
26275
26771
|
))
|
|
26276
26772
|
] });
|
|
26277
26773
|
}
|
|
26278
|
-
var
|
|
26279
|
-
{
|
|
26280
|
-
/* @__PURE__ */
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
/* @__PURE__ */ jsx("
|
|
26297
|
-
/* @__PURE__ */
|
|
26298
|
-
|
|
26774
|
+
var SERIES_STYLE_GROUPS = [
|
|
26775
|
+
{ label: "Bars", items: [
|
|
26776
|
+
{ id: "bar", label: "Bars", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", strokeWidth: "1.4", fill: "none", children: [
|
|
26777
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "2", x2: "5", y2: "13" }),
|
|
26778
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "5", x2: "5", y2: "5" }),
|
|
26779
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "10", x2: "7", y2: "10" }),
|
|
26780
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "4", x2: "11", y2: "14" }),
|
|
26781
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "7", x2: "11", y2: "7" }),
|
|
26782
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "12", x2: "13", y2: "12" })
|
|
26783
|
+
] }) },
|
|
26784
|
+
{ id: "candlestick", label: "Candles", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
|
|
26785
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "3", height: "6", rx: "0.5" }),
|
|
26786
|
+
/* @__PURE__ */ jsx("line", { x1: "4.5", y1: "2", x2: "4.5", y2: "14", stroke: "currentColor", strokeWidth: "1" }),
|
|
26787
|
+
/* @__PURE__ */ jsx("rect", { x: "10", y: "3", width: "3", height: "7", rx: "0.5" }),
|
|
26788
|
+
/* @__PURE__ */ jsx("line", { x1: "11.5", y1: "1", x2: "11.5", y2: "13", stroke: "currentColor", strokeWidth: "1" })
|
|
26789
|
+
] }) }
|
|
26790
|
+
] },
|
|
26791
|
+
{ label: "Lines", items: [
|
|
26792
|
+
{ id: "line", label: "Line", icon: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: /* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4" }) }) },
|
|
26793
|
+
{ id: "area", label: "Area", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", children: [
|
|
26794
|
+
/* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
26795
|
+
/* @__PURE__ */ jsx("polygon", { points: "2,12 6,7 10,10 14,4 14,14 2,14", fill: "currentColor", opacity: "0.35" })
|
|
26796
|
+
] }) }
|
|
26797
|
+
] }
|
|
26299
26798
|
];
|
|
26799
|
+
var ALL_SERIES_STYLES = SERIES_STYLE_GROUPS.flatMap((g) => g.items);
|
|
26300
26800
|
function TopToolbar({
|
|
26301
26801
|
symbol,
|
|
26302
26802
|
timeframe,
|
|
@@ -26310,6 +26810,9 @@ function TopToolbar({
|
|
|
26310
26810
|
onFavoritesChange,
|
|
26311
26811
|
seriesType,
|
|
26312
26812
|
onSeriesTypeChange,
|
|
26813
|
+
onToggleReplay,
|
|
26814
|
+
onToggleAlerts,
|
|
26815
|
+
alertsOpen,
|
|
26313
26816
|
onAddIndicator,
|
|
26314
26817
|
maxIndicators,
|
|
26315
26818
|
indicatorsOpen,
|
|
@@ -26347,7 +26850,8 @@ function TopToolbar({
|
|
|
26347
26850
|
showSavedLayouts,
|
|
26348
26851
|
showCustomTimeframes,
|
|
26349
26852
|
showDataExport,
|
|
26350
|
-
showMultiChartLayout
|
|
26853
|
+
showMultiChartLayout,
|
|
26854
|
+
layoutSaveState
|
|
26351
26855
|
}) {
|
|
26352
26856
|
const [tfOpen, setTfOpen] = useState(false);
|
|
26353
26857
|
const [styleOpen, setStyleOpen] = useState(false);
|
|
@@ -26505,27 +27009,60 @@ function TopToolbar({
|
|
|
26505
27009
|
onClick: () => setStyleOpen((o) => !o),
|
|
26506
27010
|
title: "Chart style",
|
|
26507
27011
|
children: [
|
|
26508
|
-
(
|
|
26509
|
-
(
|
|
27012
|
+
(ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).icon,
|
|
27013
|
+
(ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).label
|
|
26510
27014
|
]
|
|
26511
27015
|
}
|
|
26512
27016
|
),
|
|
26513
|
-
styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth:
|
|
26514
|
-
"
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
27017
|
+
styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth: 165 }, children: SERIES_STYLE_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
27018
|
+
/* @__PURE__ */ jsx("div", { style: {
|
|
27019
|
+
padding: "6px 12px 3px",
|
|
27020
|
+
fontSize: 10.5,
|
|
27021
|
+
fontWeight: 600,
|
|
27022
|
+
letterSpacing: 1,
|
|
27023
|
+
textTransform: "uppercase",
|
|
27024
|
+
opacity: 0.55
|
|
27025
|
+
}, children: group.label }),
|
|
27026
|
+
group.items.map((style) => /* @__PURE__ */ jsxs(
|
|
27027
|
+
"button",
|
|
27028
|
+
{
|
|
27029
|
+
className: "tf-dropdown-row",
|
|
27030
|
+
style: style.id === seriesType ? { fontWeight: 600 } : void 0,
|
|
27031
|
+
onClick: () => {
|
|
27032
|
+
onSeriesTypeChange(style.id);
|
|
27033
|
+
setStyleOpen(false);
|
|
27034
|
+
},
|
|
27035
|
+
children: [
|
|
27036
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
|
|
27037
|
+
style.icon,
|
|
27038
|
+
style.label
|
|
27039
|
+
] }),
|
|
27040
|
+
style.id === seriesType && /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { opacity: 0.9 }, children: "\u2713" })
|
|
27041
|
+
]
|
|
26521
27042
|
},
|
|
26522
|
-
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26528
|
-
|
|
27043
|
+
style.id
|
|
27044
|
+
))
|
|
27045
|
+
] }, group.label)) })
|
|
27046
|
+
] })
|
|
27047
|
+
] }),
|
|
27048
|
+
onToggleAlerts !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27049
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
27050
|
+
/* @__PURE__ */ jsxs("button", { className: `ind-trigger${alertsOpen ? " active" : ""}`, onClick: onToggleAlerts, title: "Price alerts", children: [
|
|
27051
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
27052
|
+
/* @__PURE__ */ jsx("path", { d: "M8 2.5a4 4 0 0 0-4 4v2.5l-1.4 2.3a.5.5 0 0 0 .43.75h9.94a.5.5 0 0 0 .43-.75L12 9V6.5a4 4 0 0 0-4-4z" }),
|
|
27053
|
+
/* @__PURE__ */ jsx("path", { d: "M6.6 12.5a1.5 1.5 0 0 0 2.8 0" })
|
|
27054
|
+
] }),
|
|
27055
|
+
"Alerts"
|
|
27056
|
+
] })
|
|
27057
|
+
] }),
|
|
27058
|
+
onToggleReplay !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27059
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
27060
|
+
/* @__PURE__ */ jsxs("button", { className: "ind-trigger", onClick: onToggleReplay, title: "Bar replay", children: [
|
|
27061
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
27062
|
+
/* @__PURE__ */ jsx("polygon", { points: "6,4 12,8 6,12", fill: "currentColor", stroke: "none" }),
|
|
27063
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "3", x2: "3", y2: "13" })
|
|
27064
|
+
] }),
|
|
27065
|
+
"Replay"
|
|
26529
27066
|
] })
|
|
26530
27067
|
] }),
|
|
26531
27068
|
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
@@ -26594,6 +27131,7 @@ function TopToolbar({
|
|
|
26594
27131
|
currentName: currentLayoutName,
|
|
26595
27132
|
currentLayoutId,
|
|
26596
27133
|
autoSave,
|
|
27134
|
+
...layoutSaveState !== void 0 ? { saveState: layoutSaveState } : {},
|
|
26597
27135
|
onFetchLayouts,
|
|
26598
27136
|
onSave: onSaveLayout,
|
|
26599
27137
|
onLoad: onLoadLayout,
|
|
@@ -31346,6 +31884,194 @@ function WatchlistDrawer({ onClose, onSelectSymbol, symbolResolver, apiUrl, getA
|
|
|
31346
31884
|
}
|
|
31347
31885
|
);
|
|
31348
31886
|
}
|
|
31887
|
+
function AlertsDrawer({ apiUrl, getAuthToken, symbol, onClose }) {
|
|
31888
|
+
const [alerts, setAlerts] = useState(null);
|
|
31889
|
+
const [error, setError] = useState("");
|
|
31890
|
+
const [busy, setBusy] = useState(false);
|
|
31891
|
+
const [formSymbol, setFormSymbol] = useState(symbol);
|
|
31892
|
+
const [condition, setCondition] = useState("above");
|
|
31893
|
+
const [priceStr, setPriceStr] = useState("");
|
|
31894
|
+
useEffect(() => {
|
|
31895
|
+
setFormSymbol(symbol);
|
|
31896
|
+
}, [symbol]);
|
|
31897
|
+
const call = useCallback(async (path, init) => {
|
|
31898
|
+
const token = getAuthToken ? await getAuthToken() : "";
|
|
31899
|
+
return fetch(`${apiUrl.replace(/\/$/, "")}/api/alerts${path}`, {
|
|
31900
|
+
...init,
|
|
31901
|
+
headers: {
|
|
31902
|
+
"Content-Type": "application/json",
|
|
31903
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
31904
|
+
...init?.headers ?? {}
|
|
31905
|
+
}
|
|
31906
|
+
});
|
|
31907
|
+
}, [apiUrl, getAuthToken]);
|
|
31908
|
+
const load = useCallback(() => {
|
|
31909
|
+
call("").then(async (r) => {
|
|
31910
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
31911
|
+
setAlerts(await r.json());
|
|
31912
|
+
}).catch((e) => setError(`Failed to load alerts: ${e instanceof Error ? e.message : e}`));
|
|
31913
|
+
}, [call]);
|
|
31914
|
+
useEffect(() => {
|
|
31915
|
+
load();
|
|
31916
|
+
const t = setInterval(load, 15e3);
|
|
31917
|
+
return () => clearInterval(t);
|
|
31918
|
+
}, [load]);
|
|
31919
|
+
async function create() {
|
|
31920
|
+
const price = parseFloat(priceStr);
|
|
31921
|
+
if (!(price > 0) || !formSymbol.trim()) return;
|
|
31922
|
+
setBusy(true);
|
|
31923
|
+
setError("");
|
|
31924
|
+
try {
|
|
31925
|
+
const r = await call("", {
|
|
31926
|
+
method: "POST",
|
|
31927
|
+
body: JSON.stringify({ symbol: formSymbol.trim(), condition, price })
|
|
31928
|
+
});
|
|
31929
|
+
const d = await r.json().catch(() => ({}));
|
|
31930
|
+
if (!r.ok) throw new Error(d.error ?? `HTTP ${r.status}`);
|
|
31931
|
+
setPriceStr("");
|
|
31932
|
+
load();
|
|
31933
|
+
} catch (e) {
|
|
31934
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
31935
|
+
} finally {
|
|
31936
|
+
setBusy(false);
|
|
31937
|
+
}
|
|
31938
|
+
}
|
|
31939
|
+
async function remove(id) {
|
|
31940
|
+
try {
|
|
31941
|
+
await call(`/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
31942
|
+
setAlerts((prev) => prev?.filter((a) => a.id !== id) ?? null);
|
|
31943
|
+
} catch {
|
|
31944
|
+
}
|
|
31945
|
+
}
|
|
31946
|
+
return /* @__PURE__ */ jsxs("div", { style: {
|
|
31947
|
+
width: 280,
|
|
31948
|
+
flexShrink: 0,
|
|
31949
|
+
display: "flex",
|
|
31950
|
+
flexDirection: "column",
|
|
31951
|
+
background: "var(--surface)",
|
|
31952
|
+
border: "1px solid var(--border)",
|
|
31953
|
+
borderRadius: 8,
|
|
31954
|
+
overflow: "hidden",
|
|
31955
|
+
color: "var(--text)"
|
|
31956
|
+
}, children: [
|
|
31957
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
31958
|
+
display: "flex",
|
|
31959
|
+
alignItems: "center",
|
|
31960
|
+
justifyContent: "space-between",
|
|
31961
|
+
padding: "10px 14px",
|
|
31962
|
+
borderBottom: "1px solid var(--border)"
|
|
31963
|
+
}, children: [
|
|
31964
|
+
/* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Alerts" }),
|
|
31965
|
+
/* @__PURE__ */ jsx(
|
|
31966
|
+
"button",
|
|
31967
|
+
{
|
|
31968
|
+
onClick: onClose,
|
|
31969
|
+
title: "Close",
|
|
31970
|
+
style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 14 },
|
|
31971
|
+
children: "\u2715"
|
|
31972
|
+
}
|
|
31973
|
+
)
|
|
31974
|
+
] }),
|
|
31975
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "10px 14px", borderBottom: "1px solid var(--border)", display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
31976
|
+
/* @__PURE__ */ jsx(
|
|
31977
|
+
"input",
|
|
31978
|
+
{
|
|
31979
|
+
value: formSymbol,
|
|
31980
|
+
onChange: (e) => setFormSymbol(e.target.value),
|
|
31981
|
+
placeholder: "Symbol",
|
|
31982
|
+
style: { fontSize: 12.5, padding: "5px 8px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 }
|
|
31983
|
+
}
|
|
31984
|
+
),
|
|
31985
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
31986
|
+
/* @__PURE__ */ jsxs(
|
|
31987
|
+
"select",
|
|
31988
|
+
{
|
|
31989
|
+
value: condition,
|
|
31990
|
+
onChange: (e) => setCondition(e.target.value),
|
|
31991
|
+
style: { fontSize: 12.5, padding: "5px 6px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 },
|
|
31992
|
+
children: [
|
|
31993
|
+
/* @__PURE__ */ jsx("option", { value: "above", children: "Crosses above" }),
|
|
31994
|
+
/* @__PURE__ */ jsx("option", { value: "below", children: "Crosses below" })
|
|
31995
|
+
]
|
|
31996
|
+
}
|
|
31997
|
+
),
|
|
31998
|
+
/* @__PURE__ */ jsx(
|
|
31999
|
+
"input",
|
|
32000
|
+
{
|
|
32001
|
+
value: priceStr,
|
|
32002
|
+
onChange: (e) => setPriceStr(e.target.value.replace(/[^0-9.]/g, "")),
|
|
32003
|
+
placeholder: "Price",
|
|
32004
|
+
inputMode: "decimal",
|
|
32005
|
+
style: { flex: 1, minWidth: 0, fontSize: 12.5, padding: "5px 8px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6, fontVariantNumeric: "tabular-nums" },
|
|
32006
|
+
onKeyDown: (e) => e.key === "Enter" && !busy && create()
|
|
32007
|
+
}
|
|
32008
|
+
)
|
|
32009
|
+
] }),
|
|
32010
|
+
/* @__PURE__ */ jsx(
|
|
32011
|
+
"button",
|
|
32012
|
+
{
|
|
32013
|
+
onClick: create,
|
|
32014
|
+
disabled: busy || !(parseFloat(priceStr) > 0),
|
|
32015
|
+
style: {
|
|
32016
|
+
fontSize: 12.5,
|
|
32017
|
+
padding: "6px 0",
|
|
32018
|
+
borderRadius: 6,
|
|
32019
|
+
border: "none",
|
|
32020
|
+
cursor: "pointer",
|
|
32021
|
+
background: "var(--primary, #5b5bd6)",
|
|
32022
|
+
color: "#fff",
|
|
32023
|
+
opacity: busy || !(parseFloat(priceStr) > 0) ? 0.55 : 1
|
|
32024
|
+
},
|
|
32025
|
+
children: busy ? "Creating\u2026" : "Create alert"
|
|
32026
|
+
}
|
|
32027
|
+
),
|
|
32028
|
+
error && /* @__PURE__ */ jsx("div", { style: { fontSize: 11.5, color: "var(--down, #e5484d)" }, children: error })
|
|
32029
|
+
] }),
|
|
32030
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, overflowY: "auto" }, children: [
|
|
32031
|
+
alerts === null && !error && /* @__PURE__ */ jsx("div", { style: { padding: 14, fontSize: 12.5, color: "var(--text-muted)" }, children: "Loading\u2026" }),
|
|
32032
|
+
alerts !== null && alerts.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: 14, fontSize: 12.5, color: "var(--text-muted)" }, children: "No alerts yet. They fire server-side and email you \u2014 the chart doesn't need to stay open." }),
|
|
32033
|
+
alerts?.map((a) => /* @__PURE__ */ jsxs("div", { style: {
|
|
32034
|
+
display: "flex",
|
|
32035
|
+
alignItems: "center",
|
|
32036
|
+
gap: 8,
|
|
32037
|
+
padding: "8px 14px",
|
|
32038
|
+
borderBottom: "1px solid var(--border)",
|
|
32039
|
+
fontSize: 12.5,
|
|
32040
|
+
opacity: a.status === "triggered" ? 0.75 : 1
|
|
32041
|
+
}, children: [
|
|
32042
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
32043
|
+
width: 7,
|
|
32044
|
+
height: 7,
|
|
32045
|
+
borderRadius: "50%",
|
|
32046
|
+
flexShrink: 0,
|
|
32047
|
+
background: a.status === "active" ? "var(--up, #22a06b)" : "var(--text-muted)"
|
|
32048
|
+
}, title: a.status }),
|
|
32049
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
32050
|
+
/* @__PURE__ */ jsxs("div", { style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: [
|
|
32051
|
+
/* @__PURE__ */ jsx("strong", { children: a.symbol }),
|
|
32052
|
+
" ",
|
|
32053
|
+
a.condition === "above" ? "\u2265" : "\u2264",
|
|
32054
|
+
" ",
|
|
32055
|
+
/* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: a.price })
|
|
32056
|
+
] }),
|
|
32057
|
+
a.status === "triggered" && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: "var(--text-muted)" }, children: [
|
|
32058
|
+
"fired at ",
|
|
32059
|
+
a.trigger_price
|
|
32060
|
+
] })
|
|
32061
|
+
] }),
|
|
32062
|
+
/* @__PURE__ */ jsx(
|
|
32063
|
+
"button",
|
|
32064
|
+
{
|
|
32065
|
+
onClick: () => remove(a.id),
|
|
32066
|
+
title: "Remove alert",
|
|
32067
|
+
style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 13 },
|
|
32068
|
+
children: "\u2715"
|
|
32069
|
+
}
|
|
32070
|
+
)
|
|
32071
|
+
] }, a.id))
|
|
32072
|
+
] })
|
|
32073
|
+
] });
|
|
32074
|
+
}
|
|
31349
32075
|
function ChartWorkspace({
|
|
31350
32076
|
// TabBar
|
|
31351
32077
|
tabs,
|
|
@@ -31396,6 +32122,9 @@ function ChartWorkspace({
|
|
|
31396
32122
|
seriesType,
|
|
31397
32123
|
onSeriesTypeChange,
|
|
31398
32124
|
showSeriesTypes,
|
|
32125
|
+
onToggleReplay,
|
|
32126
|
+
showReplay,
|
|
32127
|
+
layoutSaveState,
|
|
31399
32128
|
extraTfGroups,
|
|
31400
32129
|
// RightToolbar
|
|
31401
32130
|
watchlistOpen,
|
|
@@ -31506,6 +32235,9 @@ function ChartWorkspace({
|
|
|
31506
32235
|
const effShowMultiChartLayout = gate(showMultiChartLayout, capabilities.multiChartLayout);
|
|
31507
32236
|
const effShowMultipleTabs = gate(showMultipleTabs, capabilities.multipleWorkspaces);
|
|
31508
32237
|
const effShowSeriesTypes = gate(showSeriesTypes, capabilities.chartTypes) && onSeriesTypeChange !== void 0;
|
|
32238
|
+
const effShowReplay = gate(showReplay, capabilities.barReplay) && onToggleReplay !== void 0;
|
|
32239
|
+
const effShowAlerts = gate(void 0, capabilities.alerts) && hostApiUrl !== void 0;
|
|
32240
|
+
const [alertsOpen, setAlertsOpen] = React11.useState(false);
|
|
31509
32241
|
const tabItems = tabs.map((t) => ({
|
|
31510
32242
|
id: t.id,
|
|
31511
32243
|
label: t.label,
|
|
@@ -31584,6 +32316,8 @@ function ChartWorkspace({
|
|
|
31584
32316
|
onSymbolChange,
|
|
31585
32317
|
onTimeframeChange,
|
|
31586
32318
|
...effShowSeriesTypes ? { seriesType: seriesType ?? "candlestick", onSeriesTypeChange } : {},
|
|
32319
|
+
...effShowReplay ? { onToggleReplay } : {},
|
|
32320
|
+
...effShowAlerts ? { onToggleAlerts: () => setAlertsOpen((o) => !o), alertsOpen } : {},
|
|
31587
32321
|
onAddCustomTimeframe,
|
|
31588
32322
|
onRemoveCustomTimeframe,
|
|
31589
32323
|
onFavoritesChange: onFavoriteTfsChange,
|
|
@@ -31599,6 +32333,7 @@ function ChartWorkspace({
|
|
|
31599
32333
|
currentLayoutName,
|
|
31600
32334
|
currentLayoutId,
|
|
31601
32335
|
autoSave,
|
|
32336
|
+
...layoutSaveState !== void 0 ? { layoutSaveState } : {},
|
|
31602
32337
|
onFetchLayouts,
|
|
31603
32338
|
onSaveLayout,
|
|
31604
32339
|
onLoadLayout,
|
|
@@ -31630,6 +32365,15 @@ function ChartWorkspace({
|
|
|
31630
32365
|
/* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", gap: 4, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, display: "flex", gap: 4, minWidth: 0, minHeight: 0 }, children: [
|
|
31631
32366
|
(dataBlocked || isLicensed === false) && /* @__PURE__ */ jsx(LicenseRequiredOverlay, {}),
|
|
31632
32367
|
/* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minWidth: 0, minHeight: 0 }, children: chartSlots }),
|
|
32368
|
+
effShowAlerts && alertsOpen && /* @__PURE__ */ jsx(
|
|
32369
|
+
AlertsDrawer,
|
|
32370
|
+
{
|
|
32371
|
+
apiUrl: hostApiUrl,
|
|
32372
|
+
getAuthToken,
|
|
32373
|
+
symbol: activeSymbol ?? "",
|
|
32374
|
+
onClose: () => setAlertsOpen(false)
|
|
32375
|
+
}
|
|
32376
|
+
),
|
|
31633
32377
|
autoTrading && !onToggleOrderEntry && effOrderEntryOpen && capabilities.orderEntry && tradingBridge && /* @__PURE__ */ jsx(
|
|
31634
32378
|
OrderTicket,
|
|
31635
32379
|
{
|