@forgecharts/sdk 1.1.29 → 1.1.31
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/react/internal.js +3007 -1069
- package/dist/react/internal.js.map +1 -1
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
- package/dist/react/shell/ScriptDrawer.d.ts +7 -0
- package/dist/react/shell/ScriptDrawer.d.ts.map +1 -0
- package/dist/react/shell/TradeDrawer.d.ts +5 -0
- package/dist/react/shell/TradeDrawer.d.ts.map +1 -0
- package/dist/react/shell/WatchlistDrawer.d.ts +8 -0
- package/dist/react/shell/WatchlistDrawer.d.ts.map +1 -0
- package/dist/react/shell/useWatchlistQuotes.d.ts +6 -0
- package/dist/react/shell/useWatchlistQuotes.d.ts.map +1 -0
- package/dist/react/shell/watchlistApi.d.ts +38 -0
- package/dist/react/shell/watchlistApi.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/react/internal.js
CHANGED
|
@@ -6943,6 +6943,924 @@ var IndicatorDAG = class {
|
|
|
6943
6943
|
}
|
|
6944
6944
|
};
|
|
6945
6945
|
|
|
6946
|
+
// src/pine/pine-lexer.ts
|
|
6947
|
+
var PineLexer = class {
|
|
6948
|
+
_src;
|
|
6949
|
+
_pos = 0;
|
|
6950
|
+
_line = 1;
|
|
6951
|
+
_col = 1;
|
|
6952
|
+
/** Version extracted from `//@version=N` */
|
|
6953
|
+
version = 5;
|
|
6954
|
+
constructor(src) {
|
|
6955
|
+
this._src = src.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
6956
|
+
}
|
|
6957
|
+
tokenize() {
|
|
6958
|
+
const raw = this._rawTokenize();
|
|
6959
|
+
return this._injectIndents(raw);
|
|
6960
|
+
}
|
|
6961
|
+
// ─── Raw tokenisation ────────────────────────────────────────────────────────
|
|
6962
|
+
_rawTokenize() {
|
|
6963
|
+
const tokens = [];
|
|
6964
|
+
while (this._pos < this._src.length) {
|
|
6965
|
+
const ch = this._src[this._pos];
|
|
6966
|
+
if (ch === "\n") {
|
|
6967
|
+
tokens.push(this._make("NEWLINE", "\n"));
|
|
6968
|
+
this._pos++;
|
|
6969
|
+
this._line++;
|
|
6970
|
+
this._col = 1;
|
|
6971
|
+
continue;
|
|
6972
|
+
}
|
|
6973
|
+
if (ch === " " || ch === " ") {
|
|
6974
|
+
this._pos++;
|
|
6975
|
+
this._col++;
|
|
6976
|
+
continue;
|
|
6977
|
+
}
|
|
6978
|
+
if (ch === "/" && this._peek(1) === "/") {
|
|
6979
|
+
const commentStart = this._pos;
|
|
6980
|
+
while (this._pos < this._src.length && this._src[this._pos] !== "\n") {
|
|
6981
|
+
this._pos++;
|
|
6982
|
+
this._col++;
|
|
6983
|
+
}
|
|
6984
|
+
const comment = this._src.slice(commentStart, this._pos);
|
|
6985
|
+
const vMatch = comment.match(/\/\/@version\s*=\s*(\d+)/);
|
|
6986
|
+
if (vMatch) this.version = parseInt(vMatch[1], 10);
|
|
6987
|
+
continue;
|
|
6988
|
+
}
|
|
6989
|
+
if (ch >= "0" && ch <= "9" || ch === "." && this._isDigit(this._peek(1))) {
|
|
6990
|
+
tokens.push(this._readNumber());
|
|
6991
|
+
continue;
|
|
6992
|
+
}
|
|
6993
|
+
if (ch === '"' || ch === "'") {
|
|
6994
|
+
tokens.push(this._readString(ch));
|
|
6995
|
+
continue;
|
|
6996
|
+
}
|
|
6997
|
+
if (this._isAlpha(ch)) {
|
|
6998
|
+
const tok = this._readIdent();
|
|
6999
|
+
tokens.push(tok);
|
|
7000
|
+
continue;
|
|
7001
|
+
}
|
|
7002
|
+
const two = this._src.slice(this._pos, this._pos + 2);
|
|
7003
|
+
if (two === "<=") {
|
|
7004
|
+
tokens.push(this._make("LTE", two));
|
|
7005
|
+
this._adv(2);
|
|
7006
|
+
continue;
|
|
7007
|
+
}
|
|
7008
|
+
if (two === ">=") {
|
|
7009
|
+
tokens.push(this._make("GTE", two));
|
|
7010
|
+
this._adv(2);
|
|
7011
|
+
continue;
|
|
7012
|
+
}
|
|
7013
|
+
if (two === "==") {
|
|
7014
|
+
tokens.push(this._make("EQEQ", two));
|
|
7015
|
+
this._adv(2);
|
|
7016
|
+
continue;
|
|
7017
|
+
}
|
|
7018
|
+
if (two === "!=") {
|
|
7019
|
+
tokens.push(this._make("NEQ", two));
|
|
7020
|
+
this._adv(2);
|
|
7021
|
+
continue;
|
|
7022
|
+
}
|
|
7023
|
+
if (two === ":=") {
|
|
7024
|
+
tokens.push(this._make("REASSIGN", two));
|
|
7025
|
+
this._adv(2);
|
|
7026
|
+
continue;
|
|
7027
|
+
}
|
|
7028
|
+
if (two === "=>") {
|
|
7029
|
+
tokens.push(this._make("ARROW", two));
|
|
7030
|
+
this._adv(2);
|
|
7031
|
+
continue;
|
|
7032
|
+
}
|
|
7033
|
+
const singleOps = {
|
|
7034
|
+
"+": "PLUS",
|
|
7035
|
+
"-": "MINUS",
|
|
7036
|
+
"*": "STAR",
|
|
7037
|
+
"/": "SLASH",
|
|
7038
|
+
"%": "PERCENT",
|
|
7039
|
+
"<": "LT",
|
|
7040
|
+
">": "GT",
|
|
7041
|
+
"?": "QMARK",
|
|
7042
|
+
":": "COLON",
|
|
7043
|
+
"(": "LPAREN",
|
|
7044
|
+
")": "RPAREN",
|
|
7045
|
+
"[": "LBRACKET",
|
|
7046
|
+
"]": "RBRACKET",
|
|
7047
|
+
",": "COMMA",
|
|
7048
|
+
".": "DOT",
|
|
7049
|
+
"=": "ASSIGN"
|
|
7050
|
+
};
|
|
7051
|
+
if (ch in singleOps) {
|
|
7052
|
+
tokens.push(this._make(singleOps[ch], ch));
|
|
7053
|
+
this._pos++;
|
|
7054
|
+
this._col++;
|
|
7055
|
+
continue;
|
|
7056
|
+
}
|
|
7057
|
+
throw new SyntaxError(`[Pine] Unexpected character '${ch}' at ${this._line}:${this._col}`);
|
|
7058
|
+
}
|
|
7059
|
+
tokens.push(this._make("EOF", ""));
|
|
7060
|
+
return tokens;
|
|
7061
|
+
}
|
|
7062
|
+
// ─── Indent/Dedent injection ─────────────────────────────────────────────────
|
|
7063
|
+
_injectIndents(raw) {
|
|
7064
|
+
const out = [];
|
|
7065
|
+
const indentStack = [0];
|
|
7066
|
+
let i = 0;
|
|
7067
|
+
while (i < raw.length) {
|
|
7068
|
+
const tok = raw[i];
|
|
7069
|
+
if (tok.kind !== "NEWLINE") {
|
|
7070
|
+
out.push(tok);
|
|
7071
|
+
i++;
|
|
7072
|
+
continue;
|
|
7073
|
+
}
|
|
7074
|
+
out.push(tok);
|
|
7075
|
+
i++;
|
|
7076
|
+
let spaces = 0;
|
|
7077
|
+
let j = i;
|
|
7078
|
+
while (j < raw.length && raw[j].kind === "NEWLINE") {
|
|
7079
|
+
out.push(raw[j]);
|
|
7080
|
+
j++;
|
|
7081
|
+
}
|
|
7082
|
+
i = j;
|
|
7083
|
+
if (i >= raw.length || raw[i].kind === "EOF") break;
|
|
7084
|
+
const nextTok = raw[i];
|
|
7085
|
+
spaces = nextTok.col - 1;
|
|
7086
|
+
const currentIndent = indentStack[indentStack.length - 1];
|
|
7087
|
+
if (spaces > currentIndent) {
|
|
7088
|
+
indentStack.push(spaces);
|
|
7089
|
+
out.push({ kind: "INDENT", value: "", line: nextTok.line, col: 1 });
|
|
7090
|
+
} else {
|
|
7091
|
+
while (spaces < indentStack[indentStack.length - 1]) {
|
|
7092
|
+
indentStack.pop();
|
|
7093
|
+
out.push({ kind: "DEDENT", value: "", line: nextTok.line, col: 1 });
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
while (indentStack.length > 1) {
|
|
7098
|
+
indentStack.pop();
|
|
7099
|
+
out.push({ kind: "DEDENT", value: "", line: 0, col: 0 });
|
|
7100
|
+
}
|
|
7101
|
+
out.push({ kind: "EOF", value: "", line: this._line, col: this._col });
|
|
7102
|
+
return out;
|
|
7103
|
+
}
|
|
7104
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
7105
|
+
_make(kind, value) {
|
|
7106
|
+
return { kind, value, line: this._line, col: this._col };
|
|
7107
|
+
}
|
|
7108
|
+
_peek(offset) {
|
|
7109
|
+
return this._src[this._pos + offset] ?? "";
|
|
7110
|
+
}
|
|
7111
|
+
_adv(n = 1) {
|
|
7112
|
+
this._pos += n;
|
|
7113
|
+
this._col += n;
|
|
7114
|
+
}
|
|
7115
|
+
_isDigit(ch) {
|
|
7116
|
+
return ch >= "0" && ch <= "9";
|
|
7117
|
+
}
|
|
7118
|
+
_isAlpha(ch) {
|
|
7119
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_";
|
|
7120
|
+
}
|
|
7121
|
+
_isAlphaNum(ch) {
|
|
7122
|
+
return this._isAlpha(ch) || this._isDigit(ch);
|
|
7123
|
+
}
|
|
7124
|
+
_readNumber() {
|
|
7125
|
+
const start = this._pos;
|
|
7126
|
+
const startCol = this._col;
|
|
7127
|
+
while (this._pos < this._src.length) {
|
|
7128
|
+
const c = this._src[this._pos];
|
|
7129
|
+
if (this._isDigit(c) || c === ".") {
|
|
7130
|
+
this._pos++;
|
|
7131
|
+
this._col++;
|
|
7132
|
+
} else break;
|
|
7133
|
+
}
|
|
7134
|
+
return { kind: "NUMBER", value: this._src.slice(start, this._pos), line: this._line, col: startCol };
|
|
7135
|
+
}
|
|
7136
|
+
_readString(quote) {
|
|
7137
|
+
const startCol = this._col;
|
|
7138
|
+
this._pos++;
|
|
7139
|
+
this._col++;
|
|
7140
|
+
let result = "";
|
|
7141
|
+
while (this._pos < this._src.length && this._src[this._pos] !== quote) {
|
|
7142
|
+
if (this._src[this._pos] === "\\") {
|
|
7143
|
+
this._pos++;
|
|
7144
|
+
this._col++;
|
|
7145
|
+
const esc = this._src[this._pos] ?? "";
|
|
7146
|
+
result += esc === "n" ? "\n" : esc === "t" ? " " : esc;
|
|
7147
|
+
} else {
|
|
7148
|
+
result += this._src[this._pos];
|
|
7149
|
+
}
|
|
7150
|
+
this._pos++;
|
|
7151
|
+
this._col++;
|
|
7152
|
+
}
|
|
7153
|
+
this._pos++;
|
|
7154
|
+
this._col++;
|
|
7155
|
+
return { kind: "STRING", value: result, line: this._line, col: startCol };
|
|
7156
|
+
}
|
|
7157
|
+
_readIdent() {
|
|
7158
|
+
const start = this._pos;
|
|
7159
|
+
const startCol = this._col;
|
|
7160
|
+
while (this._pos < this._src.length && this._isAlphaNum(this._src[this._pos])) {
|
|
7161
|
+
this._pos++;
|
|
7162
|
+
this._col++;
|
|
7163
|
+
}
|
|
7164
|
+
const name = this._src.slice(start, this._pos);
|
|
7165
|
+
if (name === "true" || name === "false") {
|
|
7166
|
+
return { kind: "BOOL", value: name, line: this._line, col: startCol };
|
|
7167
|
+
}
|
|
7168
|
+
return { kind: "IDENT", value: name, line: this._line, col: startCol };
|
|
7169
|
+
}
|
|
7170
|
+
};
|
|
7171
|
+
|
|
7172
|
+
// src/pine/pine-parser.ts
|
|
7173
|
+
var PineParser = class {
|
|
7174
|
+
_tokens;
|
|
7175
|
+
_pos = 0;
|
|
7176
|
+
version;
|
|
7177
|
+
constructor(src) {
|
|
7178
|
+
const lexer = new PineLexer(src);
|
|
7179
|
+
this._tokens = lexer.tokenize();
|
|
7180
|
+
this.version = lexer.version;
|
|
7181
|
+
}
|
|
7182
|
+
parse() {
|
|
7183
|
+
const stmts = [];
|
|
7184
|
+
this._skipNL();
|
|
7185
|
+
while (!this._check("EOF")) {
|
|
7186
|
+
const s = this._stmt();
|
|
7187
|
+
if (s !== null) stmts.push(s);
|
|
7188
|
+
this._skipNL();
|
|
7189
|
+
}
|
|
7190
|
+
return { kind: "PineProgram", version: this.version, stmts };
|
|
7191
|
+
}
|
|
7192
|
+
// ─── Statements ─────────────────────────────────────────────────────────────
|
|
7193
|
+
_stmt() {
|
|
7194
|
+
const t = this._cur();
|
|
7195
|
+
const loc = { line: t.line, col: t.col };
|
|
7196
|
+
if (t.kind === "IDENT" && (t.value === "var" || t.value === "varip")) {
|
|
7197
|
+
return this._varDecl();
|
|
7198
|
+
}
|
|
7199
|
+
if (t.kind === "IDENT" && t.value === "if") {
|
|
7200
|
+
return this._ifStmt();
|
|
7201
|
+
}
|
|
7202
|
+
if (t.kind === "IDENT") {
|
|
7203
|
+
const p1 = this._peekKind(1);
|
|
7204
|
+
const p2 = this._peekKind(2);
|
|
7205
|
+
const isAssign = p1 === "ASSIGN" || p1 === "REASSIGN";
|
|
7206
|
+
const isTyped = p1 === "IDENT" && (p2 === "ASSIGN" || p2 === "REASSIGN");
|
|
7207
|
+
if (isAssign || isTyped) {
|
|
7208
|
+
return this._assignStmt();
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
if (t.kind === "IDENT" && t.value === "indicator") {
|
|
7212
|
+
return this._indicatorDecl();
|
|
7213
|
+
}
|
|
7214
|
+
const expr = this._expr();
|
|
7215
|
+
this._consumeNLorEOF();
|
|
7216
|
+
return { kind: "PineExprStmt", expr, loc };
|
|
7217
|
+
}
|
|
7218
|
+
_varDecl() {
|
|
7219
|
+
const tok = this._advance();
|
|
7220
|
+
const modifier = tok.value;
|
|
7221
|
+
const loc = { line: tok.line, col: tok.col };
|
|
7222
|
+
let typeHint;
|
|
7223
|
+
if (this._check("IDENT") && this._peekKind(1) === "ASSIGN") {
|
|
7224
|
+
const maybeType = this._cur().value;
|
|
7225
|
+
if (["float", "int", "bool", "string", "color", "series", "simple", "const"].includes(maybeType)) {
|
|
7226
|
+
typeHint = maybeType;
|
|
7227
|
+
this._advance();
|
|
7228
|
+
}
|
|
7229
|
+
}
|
|
7230
|
+
const name = this._consume("IDENT", "Expected variable name after 'var'").value;
|
|
7231
|
+
this._consume("ASSIGN", "Expected '=' in var declaration");
|
|
7232
|
+
const value = this._expr();
|
|
7233
|
+
this._consumeNLorEOF();
|
|
7234
|
+
return typeHint !== void 0 ? { kind: "PineVarDecl", modifier, name, typeHint, value, loc } : { kind: "PineVarDecl", modifier, name, value, loc };
|
|
7235
|
+
}
|
|
7236
|
+
_assignStmt() {
|
|
7237
|
+
const nameTok = this._advance();
|
|
7238
|
+
const loc = { line: nameTok.line, col: nameTok.col };
|
|
7239
|
+
const name = nameTok.value;
|
|
7240
|
+
let op = "=";
|
|
7241
|
+
if (this._check("IDENT")) {
|
|
7242
|
+
this._advance();
|
|
7243
|
+
}
|
|
7244
|
+
if (this._check("REASSIGN")) {
|
|
7245
|
+
op = ":=";
|
|
7246
|
+
this._advance();
|
|
7247
|
+
} else {
|
|
7248
|
+
this._consume("ASSIGN", "Expected '=' or ':='");
|
|
7249
|
+
}
|
|
7250
|
+
const value = this._expr();
|
|
7251
|
+
this._consumeNLorEOF();
|
|
7252
|
+
return { kind: "PineAssign", name, op, value, loc };
|
|
7253
|
+
}
|
|
7254
|
+
_indicatorDecl() {
|
|
7255
|
+
const tok = this._advance();
|
|
7256
|
+
const loc = { line: tok.line, col: tok.col };
|
|
7257
|
+
this._consume("LPAREN", "Expected '(' after 'indicator'");
|
|
7258
|
+
const { args, namedArgs } = this._argList();
|
|
7259
|
+
this._consume("RPAREN", "Expected ')' after indicator args");
|
|
7260
|
+
this._consumeNLorEOF();
|
|
7261
|
+
return { kind: "PineIndicatorDecl", args, namedArgs, loc };
|
|
7262
|
+
}
|
|
7263
|
+
_ifStmt() {
|
|
7264
|
+
const tok = this._advance();
|
|
7265
|
+
const loc = { line: tok.line, col: tok.col };
|
|
7266
|
+
const condition = this._expr();
|
|
7267
|
+
this._consumeNLorEOF();
|
|
7268
|
+
const then = this._indentedBlock();
|
|
7269
|
+
const else_ = [];
|
|
7270
|
+
if (this._check("IDENT") && this._cur().value === "else") {
|
|
7271
|
+
this._advance();
|
|
7272
|
+
this._consumeNLorEOF();
|
|
7273
|
+
else_.push(...this._indentedBlock());
|
|
7274
|
+
}
|
|
7275
|
+
return { kind: "PineIf", condition, then, else_, loc };
|
|
7276
|
+
}
|
|
7277
|
+
_indentedBlock() {
|
|
7278
|
+
const stmts = [];
|
|
7279
|
+
if (!this._check("INDENT")) return stmts;
|
|
7280
|
+
this._advance();
|
|
7281
|
+
this._skipNL();
|
|
7282
|
+
while (!this._check("DEDENT") && !this._check("EOF")) {
|
|
7283
|
+
const s = this._stmt();
|
|
7284
|
+
if (s !== null) stmts.push(s);
|
|
7285
|
+
this._skipNL();
|
|
7286
|
+
}
|
|
7287
|
+
if (this._check("DEDENT")) this._advance();
|
|
7288
|
+
return stmts;
|
|
7289
|
+
}
|
|
7290
|
+
// ─── Expressions ────────────────────────────────────────────────────────────
|
|
7291
|
+
_expr() {
|
|
7292
|
+
return this._ternary();
|
|
7293
|
+
}
|
|
7294
|
+
_ternary() {
|
|
7295
|
+
let left = this._or();
|
|
7296
|
+
if (this._match("QMARK")) {
|
|
7297
|
+
const consequent = this._expr();
|
|
7298
|
+
this._consume("COLON", "Expected ':' in ternary");
|
|
7299
|
+
const alternate = this._expr();
|
|
7300
|
+
const node = {
|
|
7301
|
+
kind: "PineTernary",
|
|
7302
|
+
condition: left,
|
|
7303
|
+
consequent,
|
|
7304
|
+
alternate,
|
|
7305
|
+
loc: this._locOf(left)
|
|
7306
|
+
};
|
|
7307
|
+
return node;
|
|
7308
|
+
}
|
|
7309
|
+
return left;
|
|
7310
|
+
}
|
|
7311
|
+
_or() {
|
|
7312
|
+
let left = this._and();
|
|
7313
|
+
while (this._checkIdent("or")) {
|
|
7314
|
+
this._advance();
|
|
7315
|
+
const right = this._and();
|
|
7316
|
+
left = this._bin("or", left, right);
|
|
7317
|
+
}
|
|
7318
|
+
return left;
|
|
7319
|
+
}
|
|
7320
|
+
_and() {
|
|
7321
|
+
let left = this._not();
|
|
7322
|
+
while (this._checkIdent("and")) {
|
|
7323
|
+
this._advance();
|
|
7324
|
+
const right = this._not();
|
|
7325
|
+
left = this._bin("and", left, right);
|
|
7326
|
+
}
|
|
7327
|
+
return left;
|
|
7328
|
+
}
|
|
7329
|
+
_not() {
|
|
7330
|
+
if (this._checkIdent("not")) {
|
|
7331
|
+
const loc = this._locOfCur();
|
|
7332
|
+
this._advance();
|
|
7333
|
+
const operand = this._comparison();
|
|
7334
|
+
const node = { kind: "PineUnary", op: "not", operand, loc };
|
|
7335
|
+
return node;
|
|
7336
|
+
}
|
|
7337
|
+
return this._comparison();
|
|
7338
|
+
}
|
|
7339
|
+
_comparison() {
|
|
7340
|
+
let left = this._addition();
|
|
7341
|
+
while (true) {
|
|
7342
|
+
if (this._match("LTE")) {
|
|
7343
|
+
left = this._bin("<=", left, this._addition());
|
|
7344
|
+
continue;
|
|
7345
|
+
}
|
|
7346
|
+
if (this._match("GTE")) {
|
|
7347
|
+
left = this._bin(">=", left, this._addition());
|
|
7348
|
+
continue;
|
|
7349
|
+
}
|
|
7350
|
+
if (this._match("EQEQ")) {
|
|
7351
|
+
left = this._bin("==", left, this._addition());
|
|
7352
|
+
continue;
|
|
7353
|
+
}
|
|
7354
|
+
if (this._match("NEQ")) {
|
|
7355
|
+
left = this._bin("!=", left, this._addition());
|
|
7356
|
+
continue;
|
|
7357
|
+
}
|
|
7358
|
+
if (this._match("LT")) {
|
|
7359
|
+
left = this._bin("<", left, this._addition());
|
|
7360
|
+
continue;
|
|
7361
|
+
}
|
|
7362
|
+
if (this._match("GT")) {
|
|
7363
|
+
left = this._bin(">", left, this._addition());
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
break;
|
|
7367
|
+
}
|
|
7368
|
+
return left;
|
|
7369
|
+
}
|
|
7370
|
+
_addition() {
|
|
7371
|
+
let left = this._multiply();
|
|
7372
|
+
while (true) {
|
|
7373
|
+
if (this._match("PLUS")) {
|
|
7374
|
+
left = this._bin("+", left, this._multiply());
|
|
7375
|
+
continue;
|
|
7376
|
+
}
|
|
7377
|
+
if (this._match("MINUS")) {
|
|
7378
|
+
left = this._bin("-", left, this._multiply());
|
|
7379
|
+
continue;
|
|
7380
|
+
}
|
|
7381
|
+
break;
|
|
7382
|
+
}
|
|
7383
|
+
return left;
|
|
7384
|
+
}
|
|
7385
|
+
_multiply() {
|
|
7386
|
+
let left = this._unary();
|
|
7387
|
+
while (true) {
|
|
7388
|
+
if (this._match("STAR")) {
|
|
7389
|
+
left = this._bin("*", left, this._unary());
|
|
7390
|
+
continue;
|
|
7391
|
+
}
|
|
7392
|
+
if (this._match("SLASH")) {
|
|
7393
|
+
left = this._bin("/", left, this._unary());
|
|
7394
|
+
continue;
|
|
7395
|
+
}
|
|
7396
|
+
if (this._match("PERCENT")) {
|
|
7397
|
+
left = this._bin("%", left, this._unary());
|
|
7398
|
+
continue;
|
|
7399
|
+
}
|
|
7400
|
+
break;
|
|
7401
|
+
}
|
|
7402
|
+
return left;
|
|
7403
|
+
}
|
|
7404
|
+
_unary() {
|
|
7405
|
+
if (this._match("MINUS")) {
|
|
7406
|
+
const operand = this._unary();
|
|
7407
|
+
const node = { kind: "PineUnary", op: "-", operand, loc: this._locOfCur() };
|
|
7408
|
+
return node;
|
|
7409
|
+
}
|
|
7410
|
+
return this._postfix();
|
|
7411
|
+
}
|
|
7412
|
+
_postfix() {
|
|
7413
|
+
let expr = this._primary();
|
|
7414
|
+
while (this._check("LBRACKET")) {
|
|
7415
|
+
const loc = this._locOfCur();
|
|
7416
|
+
this._advance();
|
|
7417
|
+
const index = this._expr();
|
|
7418
|
+
this._consume("RBRACKET", "Expected ']'");
|
|
7419
|
+
const node = { kind: "PineIndex", series: expr, index, loc };
|
|
7420
|
+
expr = node;
|
|
7421
|
+
}
|
|
7422
|
+
return expr;
|
|
7423
|
+
}
|
|
7424
|
+
_primary() {
|
|
7425
|
+
const t = this._cur();
|
|
7426
|
+
const loc = { line: t.line, col: t.col };
|
|
7427
|
+
if (t.kind === "NUMBER") {
|
|
7428
|
+
this._advance();
|
|
7429
|
+
const node = { kind: "PineNumberLit", value: parseFloat(t.value), loc };
|
|
7430
|
+
return node;
|
|
7431
|
+
}
|
|
7432
|
+
if (t.kind === "STRING") {
|
|
7433
|
+
this._advance();
|
|
7434
|
+
const node = { kind: "PineStringLit", value: t.value, loc };
|
|
7435
|
+
return node;
|
|
7436
|
+
}
|
|
7437
|
+
if (t.kind === "BOOL") {
|
|
7438
|
+
this._advance();
|
|
7439
|
+
const node = { kind: "PineBoolLit", value: t.value === "true", loc };
|
|
7440
|
+
return node;
|
|
7441
|
+
}
|
|
7442
|
+
if (t.kind === "IDENT" && t.value === "na") {
|
|
7443
|
+
this._advance();
|
|
7444
|
+
const node = { kind: "PineNa", loc };
|
|
7445
|
+
return node;
|
|
7446
|
+
}
|
|
7447
|
+
if (t.kind === "IDENT" && this._peekKind(1) === "DOT") {
|
|
7448
|
+
const ns = t.value;
|
|
7449
|
+
this._advance();
|
|
7450
|
+
this._advance();
|
|
7451
|
+
const member = this._consume("IDENT", "Expected member name after '.'").value;
|
|
7452
|
+
if (this._check("LPAREN")) {
|
|
7453
|
+
this._advance();
|
|
7454
|
+
const { args, namedArgs } = this._argList();
|
|
7455
|
+
this._consume("RPAREN", "Expected ')'");
|
|
7456
|
+
const node2 = { kind: "PineNsCall", namespace: ns, fn: member, args, namedArgs, loc };
|
|
7457
|
+
return node2;
|
|
7458
|
+
}
|
|
7459
|
+
const node = { kind: "PineMember", object: ns, prop: member, loc };
|
|
7460
|
+
return node;
|
|
7461
|
+
}
|
|
7462
|
+
if (t.kind === "IDENT" && this._peekKind(1) === "LPAREN") {
|
|
7463
|
+
const fn = t.value;
|
|
7464
|
+
this._advance();
|
|
7465
|
+
this._advance();
|
|
7466
|
+
const { args, namedArgs } = this._argList();
|
|
7467
|
+
this._consume("RPAREN", "Expected ')'");
|
|
7468
|
+
const node = { kind: "PineCall", fn, args, namedArgs, loc };
|
|
7469
|
+
return node;
|
|
7470
|
+
}
|
|
7471
|
+
if (t.kind === "IDENT") {
|
|
7472
|
+
this._advance();
|
|
7473
|
+
const node = { kind: "PineIdent", name: t.value, loc };
|
|
7474
|
+
return node;
|
|
7475
|
+
}
|
|
7476
|
+
if (t.kind === "LPAREN") {
|
|
7477
|
+
this._advance();
|
|
7478
|
+
const inner = this._expr();
|
|
7479
|
+
this._consume("RPAREN", "Expected ')'");
|
|
7480
|
+
return inner;
|
|
7481
|
+
}
|
|
7482
|
+
throw new SyntaxError(`[Pine] Unexpected token '${t.value}' (${t.kind}) at ${t.line}:${t.col}`);
|
|
7483
|
+
}
|
|
7484
|
+
// ─── Arg list helper ────────────────────────────────────────────────────────
|
|
7485
|
+
_argList() {
|
|
7486
|
+
const args = [];
|
|
7487
|
+
const namedArgs = /* @__PURE__ */ new Map();
|
|
7488
|
+
if (this._check("RPAREN")) return { args, namedArgs };
|
|
7489
|
+
this._parseOneArg(args, namedArgs);
|
|
7490
|
+
while (this._match("COMMA")) {
|
|
7491
|
+
if (this._check("RPAREN")) break;
|
|
7492
|
+
this._parseOneArg(args, namedArgs);
|
|
7493
|
+
}
|
|
7494
|
+
return { args, namedArgs };
|
|
7495
|
+
}
|
|
7496
|
+
_parseOneArg(args, namedArgs) {
|
|
7497
|
+
if (this._check("IDENT") && this._peekKind(1) === "ASSIGN" && this._peekKind(2) !== "ASSIGN") {
|
|
7498
|
+
const key = this._advance().value;
|
|
7499
|
+
this._advance();
|
|
7500
|
+
const val = this._expr();
|
|
7501
|
+
namedArgs.set(key, val);
|
|
7502
|
+
} else {
|
|
7503
|
+
args.push(this._expr());
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7506
|
+
// ─── Token helpers ───────────────────────────────────────────────────────────
|
|
7507
|
+
_cur() {
|
|
7508
|
+
return this._tokens[this._pos];
|
|
7509
|
+
}
|
|
7510
|
+
_advance() {
|
|
7511
|
+
return this._tokens[this._pos++];
|
|
7512
|
+
}
|
|
7513
|
+
_check(kind) {
|
|
7514
|
+
return this._cur().kind === kind;
|
|
7515
|
+
}
|
|
7516
|
+
_checkIdent(name) {
|
|
7517
|
+
const t = this._cur();
|
|
7518
|
+
return t.kind === "IDENT" && t.value === name;
|
|
7519
|
+
}
|
|
7520
|
+
_peekKind(offset) {
|
|
7521
|
+
return this._tokens[this._pos + offset]?.kind ?? "EOF";
|
|
7522
|
+
}
|
|
7523
|
+
_match(kind) {
|
|
7524
|
+
if (this._check(kind)) {
|
|
7525
|
+
this._advance();
|
|
7526
|
+
return true;
|
|
7527
|
+
}
|
|
7528
|
+
return false;
|
|
7529
|
+
}
|
|
7530
|
+
_consume(kind, msg) {
|
|
7531
|
+
if (this._check(kind)) return this._advance();
|
|
7532
|
+
const t = this._cur();
|
|
7533
|
+
throw new SyntaxError(`[Pine] ${msg} \u2014 got '${t.value}' (${t.kind}) at ${t.line}:${t.col}`);
|
|
7534
|
+
}
|
|
7535
|
+
_skipNL() {
|
|
7536
|
+
while (this._check("NEWLINE")) this._advance();
|
|
7537
|
+
}
|
|
7538
|
+
_consumeNLorEOF() {
|
|
7539
|
+
if (this._check("NEWLINE") || this._check("EOF")) {
|
|
7540
|
+
if (this._check("NEWLINE")) this._advance();
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7543
|
+
_locOfCur() {
|
|
7544
|
+
return { line: this._cur().line, col: this._cur().col };
|
|
7545
|
+
}
|
|
7546
|
+
_locOf(expr) {
|
|
7547
|
+
return expr.loc;
|
|
7548
|
+
}
|
|
7549
|
+
_bin(op, left, right) {
|
|
7550
|
+
return { kind: "PineBinary", op, left, right, loc: this._locOf(left) };
|
|
7551
|
+
}
|
|
7552
|
+
};
|
|
7553
|
+
|
|
7554
|
+
// src/pine/pine-transpiler.ts
|
|
7555
|
+
var TA_MAP = {
|
|
7556
|
+
sma: "sma",
|
|
7557
|
+
ema: "ema",
|
|
7558
|
+
rsi: "rsi",
|
|
7559
|
+
wma: "wma",
|
|
7560
|
+
rma: "rma",
|
|
7561
|
+
stdev: "stdev",
|
|
7562
|
+
highest: "highest",
|
|
7563
|
+
lowest: "lowest",
|
|
7564
|
+
change: "change",
|
|
7565
|
+
mom: "mom",
|
|
7566
|
+
atr: "atr",
|
|
7567
|
+
crossover: "crossover",
|
|
7568
|
+
crossunder: "crossunder",
|
|
7569
|
+
barssince: "barssince"
|
|
7570
|
+
};
|
|
7571
|
+
var MATH_MAP = {
|
|
7572
|
+
abs: "abs",
|
|
7573
|
+
max: "max",
|
|
7574
|
+
min: "min",
|
|
7575
|
+
round: "round",
|
|
7576
|
+
floor: "floor",
|
|
7577
|
+
ceil: "ceil",
|
|
7578
|
+
sqrt: "sqrt",
|
|
7579
|
+
log: "log",
|
|
7580
|
+
pow: "pow",
|
|
7581
|
+
sign: "sign",
|
|
7582
|
+
exp: "exp"
|
|
7583
|
+
};
|
|
7584
|
+
var COLOR_MAP = {
|
|
7585
|
+
red: '"#f23645"',
|
|
7586
|
+
green: '"#26a69a"',
|
|
7587
|
+
blue: '"#2196f3"',
|
|
7588
|
+
white: '"#ffffff"',
|
|
7589
|
+
black: '"#000000"',
|
|
7590
|
+
yellow: '"#ffeb3b"',
|
|
7591
|
+
orange: '"#ff9800"',
|
|
7592
|
+
purple: '"#9c27b0"',
|
|
7593
|
+
aqua: '"#00bcd4"',
|
|
7594
|
+
gray: '"#787b86"',
|
|
7595
|
+
silver: '"#b2b5be"',
|
|
7596
|
+
lime: '"#4caf50"',
|
|
7597
|
+
maroon: '"#880000"',
|
|
7598
|
+
navy: '"#000080"',
|
|
7599
|
+
olive: '"#808000"',
|
|
7600
|
+
teal: '"#008080"',
|
|
7601
|
+
fuchsia: '"#ff00ff"'
|
|
7602
|
+
};
|
|
7603
|
+
var UNSUPPORTED_FN = /* @__PURE__ */ new Set([
|
|
7604
|
+
"plotshape",
|
|
7605
|
+
"plotarrow",
|
|
7606
|
+
"plotbar",
|
|
7607
|
+
"plotcandle",
|
|
7608
|
+
"barcolor",
|
|
7609
|
+
"bgcolor",
|
|
7610
|
+
"alertcondition",
|
|
7611
|
+
"alert",
|
|
7612
|
+
"strategy",
|
|
7613
|
+
"label",
|
|
7614
|
+
"line",
|
|
7615
|
+
"box",
|
|
7616
|
+
"table",
|
|
7617
|
+
"request"
|
|
7618
|
+
]);
|
|
7619
|
+
var PineTranspiler = class {
|
|
7620
|
+
_diag;
|
|
7621
|
+
_indent = 0;
|
|
7622
|
+
_out = [];
|
|
7623
|
+
constructor(diag) {
|
|
7624
|
+
this._diag = diag;
|
|
7625
|
+
}
|
|
7626
|
+
transpile(prog) {
|
|
7627
|
+
this._out = [];
|
|
7628
|
+
this._indent = 0;
|
|
7629
|
+
for (const stmt of prog.stmts) {
|
|
7630
|
+
this._stmt(stmt);
|
|
7631
|
+
}
|
|
7632
|
+
return this._out.join("");
|
|
7633
|
+
}
|
|
7634
|
+
// ─── Statements ─────────────────────────────────────────────────────────────
|
|
7635
|
+
_stmt(s) {
|
|
7636
|
+
switch (s.kind) {
|
|
7637
|
+
case "PineIndicatorDecl":
|
|
7638
|
+
return this._indicatorDecl(s);
|
|
7639
|
+
case "PineVarDecl":
|
|
7640
|
+
return this._varDecl(s);
|
|
7641
|
+
case "PineAssign":
|
|
7642
|
+
return this._assign(s);
|
|
7643
|
+
case "PineExprStmt":
|
|
7644
|
+
return this._exprStmt(s);
|
|
7645
|
+
case "PineIf":
|
|
7646
|
+
return this._ifStmt(s);
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7649
|
+
_indicatorDecl(s) {
|
|
7650
|
+
const title = s.args[0];
|
|
7651
|
+
const titleStr = title ? this._expr(title) : '"Script"';
|
|
7652
|
+
this._line(`indicator(${titleStr})`);
|
|
7653
|
+
}
|
|
7654
|
+
_varDecl(s) {
|
|
7655
|
+
this._line(`${s.name} = ${this._expr(s.value)}`);
|
|
7656
|
+
}
|
|
7657
|
+
_assign(s) {
|
|
7658
|
+
this._line(`${s.name} = ${this._expr(s.value)}`);
|
|
7659
|
+
}
|
|
7660
|
+
_exprStmt(s) {
|
|
7661
|
+
const expr = s.expr;
|
|
7662
|
+
if ((expr.kind === "PineCall" || expr.kind === "PineNsCall") && UNSUPPORTED_FN.has(expr.kind === "PineCall" ? expr.fn : expr.fn)) {
|
|
7663
|
+
const name = expr.kind === "PineCall" ? expr.fn : `${expr.namespace}.${expr.fn}`;
|
|
7664
|
+
this._diag.add("warning", `'${name}()' is not supported and will be ignored`, s.loc, "PINE_UNSUPPORTED_FN");
|
|
7665
|
+
this._line(`// ${name}() not supported`);
|
|
7666
|
+
return;
|
|
7667
|
+
}
|
|
7668
|
+
this._line(this._expr(expr));
|
|
7669
|
+
}
|
|
7670
|
+
_ifStmt(s) {
|
|
7671
|
+
this._line(`if ${this._expr(s.condition)}`);
|
|
7672
|
+
this._indent++;
|
|
7673
|
+
for (const st of s.then) this._stmt(st);
|
|
7674
|
+
if (s.then.length === 0) this._line("0");
|
|
7675
|
+
this._indent--;
|
|
7676
|
+
if (s.else_.length > 0) {
|
|
7677
|
+
this._line("else");
|
|
7678
|
+
this._indent++;
|
|
7679
|
+
for (const st of s.else_) this._stmt(st);
|
|
7680
|
+
this._indent--;
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
// ─── Expressions ────────────────────────────────────────────────────────────
|
|
7684
|
+
_expr(e) {
|
|
7685
|
+
switch (e.kind) {
|
|
7686
|
+
case "PineNumberLit":
|
|
7687
|
+
return this._numLit(e);
|
|
7688
|
+
case "PineStringLit":
|
|
7689
|
+
return `"${e.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
7690
|
+
case "PineBoolLit":
|
|
7691
|
+
return e.value ? "true" : "false";
|
|
7692
|
+
case "PineNa":
|
|
7693
|
+
return "na";
|
|
7694
|
+
case "PineIdent":
|
|
7695
|
+
return e.name;
|
|
7696
|
+
case "PineNsCall":
|
|
7697
|
+
return this._nsCall(e);
|
|
7698
|
+
case "PineCall":
|
|
7699
|
+
return this._call(e);
|
|
7700
|
+
case "PineIndex":
|
|
7701
|
+
return `${this._expr(e.series)}[${this._expr(e.index)}]`;
|
|
7702
|
+
case "PineBinary":
|
|
7703
|
+
return this._binary(e);
|
|
7704
|
+
case "PineUnary":
|
|
7705
|
+
return this._unary(e);
|
|
7706
|
+
case "PineTernary":
|
|
7707
|
+
return `${this._expr(e.condition)} ? ${this._expr(e.consequent)} : ${this._expr(e.alternate)}`;
|
|
7708
|
+
case "PineMember":
|
|
7709
|
+
return this._member(e);
|
|
7710
|
+
// PineColorLit is resolved via PineMember for color.red etc
|
|
7711
|
+
default:
|
|
7712
|
+
return "0";
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
_numLit(e) {
|
|
7716
|
+
return Number.isInteger(e.value) ? String(e.value) : String(e.value);
|
|
7717
|
+
}
|
|
7718
|
+
_nsCall(e) {
|
|
7719
|
+
const { namespace: ns, fn } = e;
|
|
7720
|
+
if (ns === "ta") {
|
|
7721
|
+
const mapped = TA_MAP[fn];
|
|
7722
|
+
if (mapped) {
|
|
7723
|
+
const args = this._positionalArgs(e.args);
|
|
7724
|
+
return `${mapped}(${args})`;
|
|
7725
|
+
}
|
|
7726
|
+
this._diag.add("warning", `'ta.${fn}()' is not supported`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7727
|
+
return `/* ta.${fn} not supported */ 0`;
|
|
7728
|
+
}
|
|
7729
|
+
if (ns === "math") {
|
|
7730
|
+
const mapped = MATH_MAP[fn];
|
|
7731
|
+
if (mapped) {
|
|
7732
|
+
const args = this._positionalArgs(e.args);
|
|
7733
|
+
return `${mapped}(${args})`;
|
|
7734
|
+
}
|
|
7735
|
+
this._diag.add("warning", `'math.${fn}()' is not supported`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7736
|
+
return `/* math.${fn} not supported */ 0`;
|
|
7737
|
+
}
|
|
7738
|
+
if (ns === "input") {
|
|
7739
|
+
return this._inputNsCall(e);
|
|
7740
|
+
}
|
|
7741
|
+
if (ns === "strategy" || ns === "label" || ns === "line" || ns === "box" || ns === "table" || ns === "request") {
|
|
7742
|
+
this._diag.add("warning", `'${ns}.${fn}()' is not supported`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7743
|
+
return `/* ${ns}.${fn} not supported */ 0`;
|
|
7744
|
+
}
|
|
7745
|
+
this._diag.add("warning", `'${ns}.${fn}()' is not supported`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7746
|
+
return `/* ${ns}.${fn} not supported */ 0`;
|
|
7747
|
+
}
|
|
7748
|
+
_inputNsCall(e) {
|
|
7749
|
+
if (e.fn === "source") {
|
|
7750
|
+
return e.args[0] ? this._expr(e.args[0]) : "close";
|
|
7751
|
+
}
|
|
7752
|
+
const defVal = e.args[0] ?? e.namedArgs.get("defval");
|
|
7753
|
+
return defVal ? `input(${this._expr(defVal)})` : "input(0)";
|
|
7754
|
+
}
|
|
7755
|
+
_call(e) {
|
|
7756
|
+
if (UNSUPPORTED_FN.has(e.fn)) {
|
|
7757
|
+
this._diag.add("warning", `'${e.fn}()' is not supported and will be ignored`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7758
|
+
return `/* ${e.fn} not supported */ 0`;
|
|
7759
|
+
}
|
|
7760
|
+
if (e.fn === "input") {
|
|
7761
|
+
const defVal = e.args[0] ?? e.namedArgs.get("defval");
|
|
7762
|
+
return defVal ? `input(${this._expr(defVal)})` : "input(0)";
|
|
7763
|
+
}
|
|
7764
|
+
if (e.fn === "plot") {
|
|
7765
|
+
return this._plotCall(e);
|
|
7766
|
+
}
|
|
7767
|
+
if (e.fn === "indicator") {
|
|
7768
|
+
const title = e.args[0];
|
|
7769
|
+
const titleStr = title ? this._expr(title) : '"Script"';
|
|
7770
|
+
return `indicator(${titleStr})`;
|
|
7771
|
+
}
|
|
7772
|
+
const args = this._positionalArgs(e.args);
|
|
7773
|
+
return `${e.fn}(${args})`;
|
|
7774
|
+
}
|
|
7775
|
+
_plotCall(e) {
|
|
7776
|
+
const series = e.args[0];
|
|
7777
|
+
if (!series) return "/* plot() missing series */";
|
|
7778
|
+
return `plot(${this._expr(series)})`;
|
|
7779
|
+
}
|
|
7780
|
+
_member(e) {
|
|
7781
|
+
if (e.object === "color") {
|
|
7782
|
+
const hex = COLOR_MAP[e.prop];
|
|
7783
|
+
if (hex) return hex;
|
|
7784
|
+
this._diag.add("info", `'color.${e.prop}' is not a known color`, e.loc, "PINE_UNKNOWN_COLOR");
|
|
7785
|
+
return '"#888888"';
|
|
7786
|
+
}
|
|
7787
|
+
if (e.object === "line" || e.object === "label" || e.object === "box") {
|
|
7788
|
+
return `"${e.prop}"`;
|
|
7789
|
+
}
|
|
7790
|
+
if (e.object === "timeframe" || e.object === "syminfo" || e.object === "session") {
|
|
7791
|
+
this._diag.add("info", `'${e.object}.${e.prop}' is not supported`, e.loc, "PINE_UNSUPPORTED_FN");
|
|
7792
|
+
return "0";
|
|
7793
|
+
}
|
|
7794
|
+
return `${e.object}_${e.prop}`;
|
|
7795
|
+
}
|
|
7796
|
+
_binary(e) {
|
|
7797
|
+
const l = this._expr(e.left);
|
|
7798
|
+
const r = this._expr(e.right);
|
|
7799
|
+
return `(${l} ${e.op} ${r})`;
|
|
7800
|
+
}
|
|
7801
|
+
_unary(e) {
|
|
7802
|
+
if (e.op === "not") return `not ${this._expr(e.operand)}`;
|
|
7803
|
+
return `${e.op}${this._expr(e.operand)}`;
|
|
7804
|
+
}
|
|
7805
|
+
_positionalArgs(args) {
|
|
7806
|
+
return args.map((a) => this._expr(a)).join(", ");
|
|
7807
|
+
}
|
|
7808
|
+
// ─── Output helpers ─────────────────────────────────────────────────────────
|
|
7809
|
+
_line(code) {
|
|
7810
|
+
const pad = " ".repeat(this._indent);
|
|
7811
|
+
this._out.push(`${pad}${code}
|
|
7812
|
+
`);
|
|
7813
|
+
}
|
|
7814
|
+
};
|
|
7815
|
+
|
|
7816
|
+
// src/pine/diagnostics.ts
|
|
7817
|
+
var DiagnosticBag = class {
|
|
7818
|
+
_diags = [];
|
|
7819
|
+
add(severity, message, loc, code) {
|
|
7820
|
+
this._diags.push({ severity, message, line: loc.line, col: loc.col, code });
|
|
7821
|
+
}
|
|
7822
|
+
get diagnostics() {
|
|
7823
|
+
return this._diags;
|
|
7824
|
+
}
|
|
7825
|
+
hasErrors() {
|
|
7826
|
+
return this._diags.some((d) => d.severity === "error");
|
|
7827
|
+
}
|
|
7828
|
+
};
|
|
7829
|
+
|
|
7830
|
+
// src/pine/PineCompiler.ts
|
|
7831
|
+
var PineCompiler = class _PineCompiler {
|
|
7832
|
+
/**
|
|
7833
|
+
* Compile Pine Script source to TScript.
|
|
7834
|
+
* Parse errors throw a `SyntaxError`; semantic problems populate diagnostics.
|
|
7835
|
+
*/
|
|
7836
|
+
compile(pineSource) {
|
|
7837
|
+
const diag = new DiagnosticBag();
|
|
7838
|
+
const parser = new PineParser(pineSource);
|
|
7839
|
+
const program = parser.parse();
|
|
7840
|
+
const transpiler = new PineTranspiler(diag);
|
|
7841
|
+
const tscript = transpiler.transpile(program);
|
|
7842
|
+
return {
|
|
7843
|
+
tscript,
|
|
7844
|
+
diagnostics: diag.diagnostics,
|
|
7845
|
+
ok: !diag.hasErrors()
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
/**
|
|
7849
|
+
* Convenience: compile + run against a bar series.
|
|
7850
|
+
* Returns the plot outputs and any diagnostics.
|
|
7851
|
+
*/
|
|
7852
|
+
static run(pineSource, bars) {
|
|
7853
|
+
const compiler = new _PineCompiler();
|
|
7854
|
+
const result = compiler.compile(pineSource);
|
|
7855
|
+
if (!result.ok) {
|
|
7856
|
+
return { plots: [], diagnostics: result.diagnostics };
|
|
7857
|
+
}
|
|
7858
|
+
const indicator = new TScriptIndicator(result.tscript);
|
|
7859
|
+
const plots = indicator.run(bars);
|
|
7860
|
+
return { plots, diagnostics: result.diagnostics };
|
|
7861
|
+
}
|
|
7862
|
+
};
|
|
7863
|
+
|
|
6946
7864
|
// src/api/drawing tools/pointers menu/dot.ts
|
|
6947
7865
|
var DOT_RADIUS = 3;
|
|
6948
7866
|
var DOT_COLOR = "var(--crosshair-overlay, rgba(255,255,255,0.75))";
|
|
@@ -10414,1146 +11332,2181 @@ function IndicatorsDialog({ onAdd, onClose }) {
|
|
|
10414
11332
|
/* @__PURE__ */ jsx(
|
|
10415
11333
|
"input",
|
|
10416
11334
|
{
|
|
10417
|
-
ref: inputRef,
|
|
10418
|
-
className: "ind-search-input",
|
|
11335
|
+
ref: inputRef,
|
|
11336
|
+
className: "ind-search-input",
|
|
11337
|
+
type: "text",
|
|
11338
|
+
value: query,
|
|
11339
|
+
onChange: (e) => setQuery(e.target.value),
|
|
11340
|
+
placeholder: "Search indicators\u2026",
|
|
11341
|
+
autoComplete: "off",
|
|
11342
|
+
spellCheck: false
|
|
11343
|
+
}
|
|
11344
|
+
),
|
|
11345
|
+
query && /* @__PURE__ */ jsx("button", { className: "ind-search-clear", onClick: () => setQuery(""), "aria-label": "Clear", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 12 12", width: "10", height: "10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", fill: "none", children: [
|
|
11346
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "10", y2: "10" }),
|
|
11347
|
+
/* @__PURE__ */ jsx("line", { x1: "10", y1: "2", x2: "2", y2: "10" })
|
|
11348
|
+
] }) })
|
|
11349
|
+
] }),
|
|
11350
|
+
/* @__PURE__ */ jsx("div", { className: "ind-tabs", children: CATEGORIES.map((cat) => /* @__PURE__ */ jsx(
|
|
11351
|
+
"button",
|
|
11352
|
+
{
|
|
11353
|
+
className: `ind-tab${cat === category ? " active" : ""}`,
|
|
11354
|
+
onClick: () => setCategory(cat),
|
|
11355
|
+
children: cat
|
|
11356
|
+
},
|
|
11357
|
+
cat
|
|
11358
|
+
)) }),
|
|
11359
|
+
/* @__PURE__ */ jsx("div", { className: "ind-list-wrap", children: visible.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "ind-empty", children: [
|
|
11360
|
+
'No indicators match "',
|
|
11361
|
+
query,
|
|
11362
|
+
'"'
|
|
11363
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11364
|
+
/* @__PURE__ */ jsxs("div", { className: "ind-section-hdr", children: [
|
|
11365
|
+
/* @__PURE__ */ jsx("span", { children: "Built-in" }),
|
|
11366
|
+
/* @__PURE__ */ jsx("span", { className: "ind-section-count", children: visible.length })
|
|
11367
|
+
] }),
|
|
11368
|
+
visible.map((entry) => {
|
|
11369
|
+
const isAdded = added === entry.type;
|
|
11370
|
+
return /* @__PURE__ */ jsxs(
|
|
11371
|
+
"button",
|
|
11372
|
+
{
|
|
11373
|
+
className: `ind-list-row${isAdded ? " added" : ""}`,
|
|
11374
|
+
onClick: () => handleAdd(entry),
|
|
11375
|
+
children: [
|
|
11376
|
+
/* @__PURE__ */ jsxs("div", { className: "ind-list-info", children: [
|
|
11377
|
+
/* @__PURE__ */ jsx("span", { className: "ind-list-name", children: entry.label }),
|
|
11378
|
+
/* @__PURE__ */ jsxs("span", { className: "ind-list-meta", children: [
|
|
11379
|
+
entry.author,
|
|
11380
|
+
" \xB7 ",
|
|
11381
|
+
entry.category
|
|
11382
|
+
] })
|
|
11383
|
+
] }),
|
|
11384
|
+
/* @__PURE__ */ jsx("span", { className: `ind-list-badge${entry.defaultConfig.overlay ? " overlay" : " pane"}`, children: entry.defaultConfig.overlay ? "Overlay" : "Pane" }),
|
|
11385
|
+
/* @__PURE__ */ jsx("span", { className: "ind-list-add", children: isAdded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11386
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 10", width: "11", height: "10", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", children: /* @__PURE__ */ jsx("polyline", { points: "1,5 4,8 11,1" }) }),
|
|
11387
|
+
"Added"
|
|
11388
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11389
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 12 12", width: "11", height: "11", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", fill: "none", children: [
|
|
11390
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "1", x2: "6", y2: "11" }),
|
|
11391
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "6", x2: "11", y2: "6" })
|
|
11392
|
+
] }),
|
|
11393
|
+
"Add to Chart"
|
|
11394
|
+
] }) })
|
|
11395
|
+
]
|
|
11396
|
+
},
|
|
11397
|
+
entry.type
|
|
11398
|
+
);
|
|
11399
|
+
})
|
|
11400
|
+
] }) })
|
|
11401
|
+
] }) });
|
|
11402
|
+
}
|
|
11403
|
+
var DEFAULT_FAVORITES = ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"];
|
|
11404
|
+
var TF_LABELS = {
|
|
11405
|
+
"1m": "1 minute",
|
|
11406
|
+
"2m": "2 minutes",
|
|
11407
|
+
"3m": "3 minutes",
|
|
11408
|
+
"4m": "4 minutes",
|
|
11409
|
+
"5m": "5 minutes",
|
|
11410
|
+
"10m": "10 minutes",
|
|
11411
|
+
"15m": "15 minutes",
|
|
11412
|
+
"20m": "20 minutes",
|
|
11413
|
+
"30m": "30 minutes",
|
|
11414
|
+
"45m": "45 minutes",
|
|
11415
|
+
"1h": "1 hour",
|
|
11416
|
+
"2h": "2 hours",
|
|
11417
|
+
"3h": "3 hours",
|
|
11418
|
+
"4h": "4 hours",
|
|
11419
|
+
"6h": "6 hours",
|
|
11420
|
+
"8h": "8 hours",
|
|
11421
|
+
"12h": "12 hours",
|
|
11422
|
+
"1d": "1 day",
|
|
11423
|
+
"2d": "2 days",
|
|
11424
|
+
"3d": "3 days",
|
|
11425
|
+
"1w": "1 week",
|
|
11426
|
+
"2w": "2 weeks",
|
|
11427
|
+
"1M": "1 month",
|
|
11428
|
+
"3M": "3 months",
|
|
11429
|
+
"6M": "6 months",
|
|
11430
|
+
"12M": "12 months"
|
|
11431
|
+
};
|
|
11432
|
+
var TF_GROUPS_ALL = [
|
|
11433
|
+
{ label: "Minutes", items: ["1m", "2m", "3m", "4m", "5m", "10m", "15m", "20m", "30m", "45m"] },
|
|
11434
|
+
{ label: "Hours", items: ["1h", "2h", "3h", "4h", "6h", "8h", "12h"] },
|
|
11435
|
+
{ label: "Days", items: ["1d", "2d", "3d", "1w", "2w", "1M", "3M", "6M", "12M"] }
|
|
11436
|
+
];
|
|
11437
|
+
function TimeframeDropdown({
|
|
11438
|
+
timeframe,
|
|
11439
|
+
customTimeframes,
|
|
11440
|
+
favorites,
|
|
11441
|
+
onSelect,
|
|
11442
|
+
onToggleFavorite,
|
|
11443
|
+
onAddCustom,
|
|
11444
|
+
onClose
|
|
11445
|
+
}) {
|
|
11446
|
+
const [collapsed, setCollapsed] = useState({});
|
|
11447
|
+
const [addingCustom, setAddingCustom] = useState(false);
|
|
11448
|
+
const [input, setInput] = useState("");
|
|
11449
|
+
const ref = useRef(null);
|
|
11450
|
+
useEffect(() => {
|
|
11451
|
+
const handler = (e) => {
|
|
11452
|
+
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
11453
|
+
};
|
|
11454
|
+
document.addEventListener("mousedown", handler);
|
|
11455
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
11456
|
+
}, [onClose]);
|
|
11457
|
+
const commit = () => {
|
|
11458
|
+
const val = input.trim();
|
|
11459
|
+
if (!val) return;
|
|
11460
|
+
onAddCustom(val);
|
|
11461
|
+
onSelect(val);
|
|
11462
|
+
setInput("");
|
|
11463
|
+
onClose();
|
|
11464
|
+
};
|
|
11465
|
+
const toggleGroup = (label) => setCollapsed((prev) => ({ ...prev, [label]: !prev[label] }));
|
|
11466
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "tf-dropdown", children: [
|
|
11467
|
+
/* @__PURE__ */ jsx(
|
|
11468
|
+
"button",
|
|
11469
|
+
{
|
|
11470
|
+
className: "tf-dropdown-add-custom",
|
|
11471
|
+
onClick: () => setAddingCustom((a) => !a),
|
|
11472
|
+
children: "+ Add custom interval..."
|
|
11473
|
+
}
|
|
11474
|
+
),
|
|
11475
|
+
addingCustom && /* @__PURE__ */ jsxs("div", { className: "tf-dropdown-add-row", children: [
|
|
11476
|
+
/* @__PURE__ */ jsx(
|
|
11477
|
+
"input",
|
|
11478
|
+
{
|
|
11479
|
+
className: "tf-dropdown-input",
|
|
10419
11480
|
type: "text",
|
|
10420
|
-
value:
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
11481
|
+
value: input,
|
|
11482
|
+
placeholder: "e.g. 2h, 45m",
|
|
11483
|
+
onChange: (e) => setInput(e.target.value),
|
|
11484
|
+
onKeyDown: (e) => {
|
|
11485
|
+
if (e.key === "Enter") commit();
|
|
11486
|
+
},
|
|
11487
|
+
autoFocus: true
|
|
10425
11488
|
}
|
|
10426
11489
|
),
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
|
|
11490
|
+
/* @__PURE__ */ jsx(
|
|
11491
|
+
"button",
|
|
11492
|
+
{
|
|
11493
|
+
className: "tf-dropdown-add-btn",
|
|
11494
|
+
disabled: !input.trim(),
|
|
11495
|
+
onClick: commit,
|
|
11496
|
+
children: "Add"
|
|
11497
|
+
}
|
|
11498
|
+
)
|
|
10431
11499
|
] }),
|
|
10432
|
-
/* @__PURE__ */
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
11500
|
+
TF_GROUPS_ALL.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
11501
|
+
/* @__PURE__ */ jsxs(
|
|
11502
|
+
"button",
|
|
11503
|
+
{
|
|
11504
|
+
className: "tf-dropdown-group-hdr",
|
|
11505
|
+
onClick: () => toggleGroup(group.label),
|
|
11506
|
+
children: [
|
|
11507
|
+
/* @__PURE__ */ jsx("span", { children: group.label }),
|
|
11508
|
+
/* @__PURE__ */ jsx("span", { className: `tf-dropdown-chevron${collapsed[group.label] ? " collapsed" : ""}`, children: "\u2227" })
|
|
11509
|
+
]
|
|
11510
|
+
}
|
|
11511
|
+
),
|
|
11512
|
+
!collapsed[group.label] && group.items.map((tf) => /* @__PURE__ */ jsxs(
|
|
11513
|
+
"button",
|
|
11514
|
+
{
|
|
11515
|
+
className: "tf-dropdown-row",
|
|
11516
|
+
onClick: () => {
|
|
11517
|
+
onSelect(tf);
|
|
11518
|
+
onClose();
|
|
11519
|
+
},
|
|
11520
|
+
children: [
|
|
11521
|
+
/* @__PURE__ */ jsx("span", { children: TF_LABELS[tf] ?? tf }),
|
|
11522
|
+
/* @__PURE__ */ jsx(
|
|
11523
|
+
"button",
|
|
11524
|
+
{
|
|
11525
|
+
className: "tf-star-btn",
|
|
11526
|
+
title: favorites.includes(tf) ? "Remove from toolbar" : "Add to toolbar",
|
|
11527
|
+
onClick: (e) => {
|
|
11528
|
+
e.stopPropagation();
|
|
11529
|
+
onToggleFavorite(tf);
|
|
11530
|
+
},
|
|
11531
|
+
children: /* @__PURE__ */ jsx("span", { className: `tf-star${favorites.includes(tf) ? " favorited" : ""}`, children: "\u2605" })
|
|
11532
|
+
}
|
|
11533
|
+
)
|
|
11534
|
+
]
|
|
11535
|
+
},
|
|
11536
|
+
tf
|
|
11537
|
+
))
|
|
11538
|
+
] }, group.label)),
|
|
11539
|
+
customTimeframes.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
11540
|
+
/* @__PURE__ */ jsxs(
|
|
11541
|
+
"button",
|
|
11542
|
+
{
|
|
11543
|
+
className: "tf-dropdown-group-hdr",
|
|
11544
|
+
onClick: () => toggleGroup("Custom"),
|
|
11545
|
+
children: [
|
|
11546
|
+
/* @__PURE__ */ jsx("span", { children: "Custom" }),
|
|
11547
|
+
/* @__PURE__ */ jsx("span", { className: `tf-dropdown-chevron${collapsed["Custom"] ? " collapsed" : ""}`, children: "\u2227" })
|
|
11548
|
+
]
|
|
11549
|
+
}
|
|
11550
|
+
),
|
|
11551
|
+
!collapsed["Custom"] && customTimeframes.map((tf) => /* @__PURE__ */ jsx(
|
|
11552
|
+
"button",
|
|
11553
|
+
{
|
|
11554
|
+
className: "tf-dropdown-row",
|
|
11555
|
+
onClick: () => {
|
|
11556
|
+
onSelect(tf);
|
|
11557
|
+
onClose();
|
|
11558
|
+
},
|
|
11559
|
+
children: /* @__PURE__ */ jsx("span", { children: tf })
|
|
11560
|
+
},
|
|
11561
|
+
tf
|
|
11562
|
+
))
|
|
11563
|
+
] })
|
|
11564
|
+
] });
|
|
11565
|
+
}
|
|
11566
|
+
function TopToolbar({
|
|
11567
|
+
symbol,
|
|
11568
|
+
timeframe,
|
|
11569
|
+
theme,
|
|
11570
|
+
customTimeframes,
|
|
11571
|
+
favorites,
|
|
11572
|
+
onSymbolChange,
|
|
11573
|
+
onTimeframeChange,
|
|
11574
|
+
onAddCustomTimeframe,
|
|
11575
|
+
onFavoritesChange,
|
|
11576
|
+
onAddIndicator,
|
|
11577
|
+
onToggleTheme,
|
|
11578
|
+
onCopyScreenshot,
|
|
11579
|
+
onDownloadScreenshot,
|
|
11580
|
+
onFullscreen,
|
|
11581
|
+
isFullscreen,
|
|
11582
|
+
currentLayoutName,
|
|
11583
|
+
currentLayoutId,
|
|
11584
|
+
autoSave,
|
|
11585
|
+
onSaveLayout,
|
|
11586
|
+
onLoadLayout,
|
|
11587
|
+
onRenameLayout,
|
|
11588
|
+
onCopyLayout,
|
|
11589
|
+
onToggleAutoSave,
|
|
11590
|
+
onDeleteLayout,
|
|
11591
|
+
onOpenLayoutInNewTab,
|
|
11592
|
+
onFetchLayouts,
|
|
11593
|
+
symbolResolver,
|
|
11594
|
+
showTradeButton,
|
|
11595
|
+
tradeDrawerOpen,
|
|
11596
|
+
onToggleTradeDrawer
|
|
11597
|
+
}) {
|
|
11598
|
+
const [tfOpen, setTfOpen] = useState(false);
|
|
11599
|
+
const [symOpen, setSymOpen] = useState(false);
|
|
11600
|
+
const [indOpen, setIndOpen] = useState(false);
|
|
11601
|
+
const [ssOpen, setSsOpen] = useState(false);
|
|
11602
|
+
const ssRef = useRef(null);
|
|
11603
|
+
const tfRef = useRef(null);
|
|
11604
|
+
useEffect(() => {
|
|
11605
|
+
if (!ssOpen) return;
|
|
11606
|
+
const handler = (e) => {
|
|
11607
|
+
if (ssRef.current && !ssRef.current.contains(e.target)) setSsOpen(false);
|
|
11608
|
+
};
|
|
11609
|
+
document.addEventListener("mousedown", handler);
|
|
11610
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
11611
|
+
}, [ssOpen]);
|
|
11612
|
+
return /* @__PURE__ */ jsxs(
|
|
11613
|
+
"div",
|
|
11614
|
+
{
|
|
11615
|
+
style: {
|
|
11616
|
+
display: "flex",
|
|
11617
|
+
alignItems: "center",
|
|
11618
|
+
gap: 8,
|
|
11619
|
+
padding: "6px 12px",
|
|
11620
|
+
background: "var(--toolbar-bg)",
|
|
11621
|
+
borderRadius: 8,
|
|
11622
|
+
flexShrink: 0
|
|
10438
11623
|
},
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
query,
|
|
10444
|
-
'"'
|
|
10445
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10446
|
-
/* @__PURE__ */ jsxs("div", { className: "ind-section-hdr", children: [
|
|
10447
|
-
/* @__PURE__ */ jsx("span", { children: "Built-in" }),
|
|
10448
|
-
/* @__PURE__ */ jsx("span", { className: "ind-section-count", children: visible.length })
|
|
10449
|
-
] }),
|
|
10450
|
-
visible.map((entry) => {
|
|
10451
|
-
const isAdded = added === entry.type;
|
|
10452
|
-
return /* @__PURE__ */ jsxs(
|
|
11624
|
+
children: [
|
|
11625
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 700, letterSpacing: "-0.5px", color: "var(--accent)" }, children: "ForgeCharts" }),
|
|
11626
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
11627
|
+
/* @__PURE__ */ jsxs(
|
|
10453
11628
|
"button",
|
|
10454
11629
|
{
|
|
10455
|
-
className:
|
|
10456
|
-
onClick: () =>
|
|
11630
|
+
className: "sym-trigger",
|
|
11631
|
+
onClick: () => setSymOpen(true),
|
|
11632
|
+
title: "Search symbols",
|
|
11633
|
+
children: [
|
|
11634
|
+
/* @__PURE__ */ jsxs(
|
|
11635
|
+
"svg",
|
|
11636
|
+
{
|
|
11637
|
+
viewBox: "0 0 14 14",
|
|
11638
|
+
width: "12",
|
|
11639
|
+
height: "12",
|
|
11640
|
+
stroke: "currentColor",
|
|
11641
|
+
fill: "none",
|
|
11642
|
+
strokeWidth: "1.8",
|
|
11643
|
+
strokeLinecap: "round",
|
|
11644
|
+
children: [
|
|
11645
|
+
/* @__PURE__ */ jsx("circle", { cx: "5.5", cy: "5.5", r: "4" }),
|
|
11646
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "9", x2: "13", y2: "13" })
|
|
11647
|
+
]
|
|
11648
|
+
}
|
|
11649
|
+
),
|
|
11650
|
+
/* @__PURE__ */ jsx("span", { children: symbol }),
|
|
11651
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 6", width: "8", height: "6", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M0 0l5 6 5-6z" }) })
|
|
11652
|
+
]
|
|
11653
|
+
}
|
|
11654
|
+
),
|
|
11655
|
+
symOpen && /* @__PURE__ */ jsx(
|
|
11656
|
+
SymbolSearchDialog,
|
|
11657
|
+
{
|
|
11658
|
+
current: symbol,
|
|
11659
|
+
onSelect: onSymbolChange,
|
|
11660
|
+
onClose: () => setSymOpen(false),
|
|
11661
|
+
symbolResolver
|
|
11662
|
+
}
|
|
11663
|
+
),
|
|
11664
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
11665
|
+
/* @__PURE__ */ jsxs("div", { ref: tfRef, style: { display: "flex", gap: 2, position: "relative" }, children: [
|
|
11666
|
+
favorites.map((tf) => /* @__PURE__ */ jsx(
|
|
11667
|
+
"button",
|
|
11668
|
+
{
|
|
11669
|
+
className: tf === timeframe ? "active" : void 0,
|
|
11670
|
+
onClick: () => onTimeframeChange(tf),
|
|
11671
|
+
children: tf
|
|
11672
|
+
},
|
|
11673
|
+
tf
|
|
11674
|
+
)),
|
|
11675
|
+
!favorites.includes(timeframe) && timeframe && /* @__PURE__ */ jsx("button", { className: "active", style: { fontStyle: "italic" }, children: timeframe }),
|
|
11676
|
+
/* @__PURE__ */ jsx(
|
|
11677
|
+
"button",
|
|
11678
|
+
{
|
|
11679
|
+
className: tfOpen ? "active" : void 0,
|
|
11680
|
+
onClick: () => setTfOpen((o) => !o),
|
|
11681
|
+
title: "More timeframes",
|
|
11682
|
+
style: { fontSize: 11, padding: "4px 7px" },
|
|
11683
|
+
children: "\u25BE"
|
|
11684
|
+
}
|
|
11685
|
+
),
|
|
11686
|
+
tfOpen && /* @__PURE__ */ jsx(
|
|
11687
|
+
TimeframeDropdown,
|
|
11688
|
+
{
|
|
11689
|
+
timeframe,
|
|
11690
|
+
customTimeframes,
|
|
11691
|
+
favorites,
|
|
11692
|
+
onSelect: onTimeframeChange,
|
|
11693
|
+
onToggleFavorite: (tf) => {
|
|
11694
|
+
const next = favorites.includes(tf) ? favorites.filter((f) => f !== tf) : [...favorites, tf];
|
|
11695
|
+
onFavoritesChange(next);
|
|
11696
|
+
},
|
|
11697
|
+
onAddCustom: onAddCustomTimeframe,
|
|
11698
|
+
onClose: () => setTfOpen(false)
|
|
11699
|
+
}
|
|
11700
|
+
)
|
|
11701
|
+
] }),
|
|
11702
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
11703
|
+
/* @__PURE__ */ jsxs(
|
|
11704
|
+
"button",
|
|
11705
|
+
{
|
|
11706
|
+
className: `ind-trigger${indOpen ? " active" : ""}`,
|
|
11707
|
+
onClick: () => setIndOpen((o) => !o),
|
|
11708
|
+
title: "Indicators",
|
|
10457
11709
|
children: [
|
|
10458
|
-
/* @__PURE__ */ jsxs("
|
|
10459
|
-
/* @__PURE__ */ jsx("
|
|
10460
|
-
/* @__PURE__ */
|
|
10461
|
-
|
|
10462
|
-
" \xB7 ",
|
|
10463
|
-
entry.category
|
|
10464
|
-
] })
|
|
11710
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", fill: "none", strokeWidth: "1.6", strokeLinecap: "round", children: [
|
|
11711
|
+
/* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 9,10 14,4" }),
|
|
11712
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "2", x2: "14", y2: "2" }),
|
|
11713
|
+
/* @__PURE__ */ jsx("line", { x1: "14", y1: "2", x2: "14", y2: "5" })
|
|
10465
11714
|
] }),
|
|
10466
|
-
|
|
10467
|
-
/* @__PURE__ */ jsx("span", { className: "ind-list-add", children: isAdded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10468
|
-
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 10", width: "11", height: "10", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", children: /* @__PURE__ */ jsx("polyline", { points: "1,5 4,8 11,1" }) }),
|
|
10469
|
-
"Added"
|
|
10470
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10471
|
-
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 12 12", width: "11", height: "11", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", fill: "none", children: [
|
|
10472
|
-
/* @__PURE__ */ jsx("line", { x1: "6", y1: "1", x2: "6", y2: "11" }),
|
|
10473
|
-
/* @__PURE__ */ jsx("line", { x1: "1", y1: "6", x2: "11", y2: "6" })
|
|
10474
|
-
] }),
|
|
10475
|
-
"Add to Chart"
|
|
10476
|
-
] }) })
|
|
11715
|
+
"Indicators"
|
|
10477
11716
|
]
|
|
10478
|
-
}
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
document.addEventListener("mousedown", handler);
|
|
10537
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
10538
|
-
}, [onClose]);
|
|
10539
|
-
const commit = () => {
|
|
10540
|
-
const val = input.trim();
|
|
10541
|
-
if (!val) return;
|
|
10542
|
-
onAddCustom(val);
|
|
10543
|
-
onSelect(val);
|
|
10544
|
-
setInput("");
|
|
10545
|
-
onClose();
|
|
10546
|
-
};
|
|
10547
|
-
const toggleGroup = (label) => setCollapsed((prev) => ({ ...prev, [label]: !prev[label] }));
|
|
10548
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "tf-dropdown", children: [
|
|
10549
|
-
/* @__PURE__ */ jsx(
|
|
10550
|
-
"button",
|
|
10551
|
-
{
|
|
10552
|
-
className: "tf-dropdown-add-custom",
|
|
10553
|
-
onClick: () => setAddingCustom((a) => !a),
|
|
10554
|
-
children: "+ Add custom interval..."
|
|
10555
|
-
}
|
|
10556
|
-
),
|
|
10557
|
-
addingCustom && /* @__PURE__ */ jsxs("div", { className: "tf-dropdown-add-row", children: [
|
|
10558
|
-
/* @__PURE__ */ jsx(
|
|
10559
|
-
"input",
|
|
10560
|
-
{
|
|
10561
|
-
className: "tf-dropdown-input",
|
|
10562
|
-
type: "text",
|
|
10563
|
-
value: input,
|
|
10564
|
-
placeholder: "e.g. 2h, 45m",
|
|
10565
|
-
onChange: (e) => setInput(e.target.value),
|
|
10566
|
-
onKeyDown: (e) => {
|
|
10567
|
-
if (e.key === "Enter") commit();
|
|
10568
|
-
},
|
|
10569
|
-
autoFocus: true
|
|
10570
|
-
}
|
|
10571
|
-
),
|
|
10572
|
-
/* @__PURE__ */ jsx(
|
|
10573
|
-
"button",
|
|
10574
|
-
{
|
|
10575
|
-
className: "tf-dropdown-add-btn",
|
|
10576
|
-
disabled: !input.trim(),
|
|
10577
|
-
onClick: commit,
|
|
10578
|
-
children: "Add"
|
|
10579
|
-
}
|
|
10580
|
-
)
|
|
10581
|
-
] }),
|
|
10582
|
-
TF_GROUPS_ALL.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
10583
|
-
/* @__PURE__ */ jsxs(
|
|
10584
|
-
"button",
|
|
10585
|
-
{
|
|
10586
|
-
className: "tf-dropdown-group-hdr",
|
|
10587
|
-
onClick: () => toggleGroup(group.label),
|
|
10588
|
-
children: [
|
|
10589
|
-
/* @__PURE__ */ jsx("span", { children: group.label }),
|
|
10590
|
-
/* @__PURE__ */ jsx("span", { className: `tf-dropdown-chevron${collapsed[group.label] ? " collapsed" : ""}`, children: "\u2227" })
|
|
10591
|
-
]
|
|
10592
|
-
}
|
|
10593
|
-
),
|
|
10594
|
-
!collapsed[group.label] && group.items.map((tf) => /* @__PURE__ */ jsxs(
|
|
10595
|
-
"button",
|
|
10596
|
-
{
|
|
10597
|
-
className: "tf-dropdown-row",
|
|
10598
|
-
onClick: () => {
|
|
10599
|
-
onSelect(tf);
|
|
10600
|
-
onClose();
|
|
10601
|
-
},
|
|
10602
|
-
children: [
|
|
10603
|
-
/* @__PURE__ */ jsx("span", { children: TF_LABELS[tf] ?? tf }),
|
|
11717
|
+
}
|
|
11718
|
+
),
|
|
11719
|
+
indOpen && /* @__PURE__ */ jsx(
|
|
11720
|
+
IndicatorsDialog,
|
|
11721
|
+
{
|
|
11722
|
+
onAdd: (config) => {
|
|
11723
|
+
onAddIndicator(config);
|
|
11724
|
+
},
|
|
11725
|
+
onClose: () => setIndOpen(false)
|
|
11726
|
+
}
|
|
11727
|
+
),
|
|
11728
|
+
/* @__PURE__ */ jsxs("div", { style: { marginLeft: "auto", display: "flex", alignItems: "center", gap: 4 }, children: [
|
|
11729
|
+
showTradeButton && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11730
|
+
/* @__PURE__ */ jsxs(
|
|
11731
|
+
"button",
|
|
11732
|
+
{
|
|
11733
|
+
className: `top-trade-btn${tradeDrawerOpen ? " active" : ""}`,
|
|
11734
|
+
onClick: onToggleTradeDrawer,
|
|
11735
|
+
title: "Trade \u2014 Connect your broker",
|
|
11736
|
+
style: { display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 8px", fontSize: 12 },
|
|
11737
|
+
children: [
|
|
11738
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: "1.6", children: [
|
|
11739
|
+
/* @__PURE__ */ jsx("rect", { x: "1.5", y: "3", width: "13", height: "10", rx: "1.5" }),
|
|
11740
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "8", x2: "11", y2: "8" }),
|
|
11741
|
+
/* @__PURE__ */ jsx("line", { x1: "8", y1: "5", x2: "8", y2: "11" })
|
|
11742
|
+
] }),
|
|
11743
|
+
"Trade"
|
|
11744
|
+
]
|
|
11745
|
+
}
|
|
11746
|
+
),
|
|
11747
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" })
|
|
11748
|
+
] }),
|
|
11749
|
+
/* @__PURE__ */ jsx(
|
|
11750
|
+
LayoutMenu,
|
|
11751
|
+
{
|
|
11752
|
+
currentName: currentLayoutName,
|
|
11753
|
+
currentLayoutId,
|
|
11754
|
+
autoSave,
|
|
11755
|
+
onFetchLayouts,
|
|
11756
|
+
onSave: onSaveLayout,
|
|
11757
|
+
onLoad: onLoadLayout,
|
|
11758
|
+
onRename: onRenameLayout,
|
|
11759
|
+
onCopy: onCopyLayout,
|
|
11760
|
+
onToggleAutoSave,
|
|
11761
|
+
onDelete: onDeleteLayout,
|
|
11762
|
+
onOpenInNewTab: onOpenLayoutInNewTab
|
|
11763
|
+
}
|
|
11764
|
+
),
|
|
11765
|
+
/* @__PURE__ */ jsx(
|
|
11766
|
+
"button",
|
|
11767
|
+
{
|
|
11768
|
+
onClick: onToggleTheme,
|
|
11769
|
+
title: `Switch to ${theme === "dark" ? "light" : "dark"} mode`,
|
|
11770
|
+
style: { fontSize: 14, padding: "4px 8px" },
|
|
11771
|
+
children: theme === "dark" ? "\u2600" : "\u263E"
|
|
11772
|
+
}
|
|
11773
|
+
),
|
|
11774
|
+
/* @__PURE__ */ jsxs("div", { ref: ssRef, style: { position: "relative" }, children: [
|
|
10604
11775
|
/* @__PURE__ */ jsx(
|
|
10605
11776
|
"button",
|
|
10606
11777
|
{
|
|
10607
|
-
className: "
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
11778
|
+
className: ssOpen ? "active" : void 0,
|
|
11779
|
+
onClick: () => setSsOpen((o) => !o),
|
|
11780
|
+
title: "Screenshot",
|
|
11781
|
+
style: { display: "inline-flex", alignItems: "center", padding: "4px 7px" },
|
|
11782
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
11783
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "12", height: "9", rx: "1" }),
|
|
11784
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "8.5", r: "2.5" }),
|
|
11785
|
+
/* @__PURE__ */ jsx("path", { d: "M6 4l1-2h2l1 2" })
|
|
11786
|
+
] })
|
|
10614
11787
|
}
|
|
10615
|
-
)
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
11788
|
+
),
|
|
11789
|
+
ssOpen && /* @__PURE__ */ jsxs("div", { className: "ss-dropdown", children: [
|
|
11790
|
+
/* @__PURE__ */ jsxs("button", { className: "ss-dropdown-item", onClick: () => {
|
|
11791
|
+
setSsOpen(false);
|
|
11792
|
+
onCopyScreenshot();
|
|
11793
|
+
}, children: [
|
|
11794
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
11795
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1" }),
|
|
11796
|
+
/* @__PURE__ */ jsx("path", { d: "M3 11H2a1 1 0 01-1-1V2a1 1 0 011-1h8a1 1 0 011 1v1" })
|
|
11797
|
+
] }),
|
|
11798
|
+
"Copy to Clipboard"
|
|
11799
|
+
] }),
|
|
11800
|
+
/* @__PURE__ */ jsxs("button", { className: "ss-dropdown-item", onClick: () => {
|
|
11801
|
+
setSsOpen(false);
|
|
11802
|
+
onDownloadScreenshot();
|
|
11803
|
+
}, children: [
|
|
11804
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", fill: "none", strokeWidth: "1.5", strokeLinecap: "round", children: [
|
|
11805
|
+
/* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "11" }),
|
|
11806
|
+
/* @__PURE__ */ jsx("polyline", { points: "4,7 8,11 12,7" }),
|
|
11807
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "14", x2: "14", y2: "14" })
|
|
11808
|
+
] }),
|
|
11809
|
+
"Download PNG"
|
|
11810
|
+
] })
|
|
11811
|
+
] })
|
|
11812
|
+
] }),
|
|
11813
|
+
/* @__PURE__ */ jsx(
|
|
11814
|
+
"button",
|
|
11815
|
+
{
|
|
11816
|
+
onClick: onFullscreen,
|
|
11817
|
+
title: isFullscreen ? "Exit Fullscreen" : "Fullscreen",
|
|
11818
|
+
style: { display: "inline-flex", alignItems: "center", padding: "4px 7px" },
|
|
11819
|
+
children: isFullscreen ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: /* @__PURE__ */ jsx("path", { d: "M6 2v4H2M10 2v4h4M10 14v-4h4M6 14v-4H2" }) }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: /* @__PURE__ */ jsx("path", { d: "M2 6V2h4M10 2h4v4M14 10v4h-4M6 14H2v-4" }) })
|
|
11820
|
+
}
|
|
11821
|
+
)
|
|
11822
|
+
] })
|
|
11823
|
+
]
|
|
11824
|
+
}
|
|
11825
|
+
);
|
|
11826
|
+
}
|
|
11827
|
+
var WatchListIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "16", height: "16", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
11828
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
11829
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "7", width: "9", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
11830
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "11", width: "10", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
11831
|
+
/* @__PURE__ */ jsx("circle", { cx: "13", cy: "11.75", r: "2.5" }),
|
|
11832
|
+
/* @__PURE__ */ jsx("line", { x1: "13", y1: "10.75", x2: "13", y2: "12.75", strokeWidth: "1.2" }),
|
|
11833
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "11.75", x2: "14", y2: "11.75", strokeWidth: "1.2" })
|
|
11834
|
+
] });
|
|
11835
|
+
function RightToolbar({ watchlistOpen, onToggleWatchlist }) {
|
|
11836
|
+
return /* @__PURE__ */ jsx(
|
|
11837
|
+
"div",
|
|
11838
|
+
{
|
|
11839
|
+
style: {
|
|
11840
|
+
display: "flex",
|
|
11841
|
+
flexDirection: "column",
|
|
11842
|
+
alignItems: "center",
|
|
11843
|
+
gap: 2,
|
|
11844
|
+
padding: "6px 4px",
|
|
11845
|
+
background: "var(--toolbar-bg)",
|
|
11846
|
+
borderRadius: 8,
|
|
11847
|
+
width: 40,
|
|
11848
|
+
flexShrink: 0,
|
|
11849
|
+
userSelect: "none"
|
|
11850
|
+
},
|
|
11851
|
+
children: /* @__PURE__ */ jsx(
|
|
10623
11852
|
"button",
|
|
10624
11853
|
{
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
/* @__PURE__ */ jsx("span", { className: `tf-dropdown-chevron${collapsed["Custom"] ? " collapsed" : ""}`, children: "\u2227" })
|
|
10630
|
-
]
|
|
11854
|
+
title: "Watch List",
|
|
11855
|
+
className: `drawing-tool-btn${watchlistOpen ? " is-active" : ""}`,
|
|
11856
|
+
onClick: onToggleWatchlist,
|
|
11857
|
+
children: /* @__PURE__ */ jsx(WatchListIcon, {})
|
|
10631
11858
|
}
|
|
10632
|
-
)
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
11859
|
+
)
|
|
11860
|
+
}
|
|
11861
|
+
);
|
|
11862
|
+
}
|
|
11863
|
+
var TIMEZONE_OPTIONS = [
|
|
11864
|
+
{ label: "UTC", value: "UTC" },
|
|
11865
|
+
{ label: "Exchange", value: "exchange" },
|
|
11866
|
+
// Americas
|
|
11867
|
+
{ label: "(UTC-10) Honolulu", value: "Pacific/Honolulu" },
|
|
11868
|
+
{ label: "(UTC-9) Anchorage", value: "America/Anchorage" },
|
|
11869
|
+
{ label: "(UTC-9) Juneau", value: "America/Juneau" },
|
|
11870
|
+
{ label: "(UTC-8) Los Angeles", value: "America/Los_Angeles" },
|
|
11871
|
+
{ label: "(UTC-8) Vancouver", value: "America/Vancouver" },
|
|
11872
|
+
{ label: "(UTC-7) Denver", value: "America/Denver" },
|
|
11873
|
+
{ label: "(UTC-7) Phoenix", value: "America/Phoenix" },
|
|
11874
|
+
{ label: "(UTC-6) Chicago", value: "America/Chicago" },
|
|
11875
|
+
{ label: "(UTC-6) Mexico City", value: "America/Mexico_City" },
|
|
11876
|
+
{ label: "(UTC-6) San Salvador", value: "America/El_Salvador" },
|
|
11877
|
+
{ label: "(UTC-5) Bogota", value: "America/Bogota" },
|
|
11878
|
+
{ label: "(UTC-5) Lima", value: "America/Lima" },
|
|
11879
|
+
{ label: "(UTC-5) New York", value: "America/New_York" },
|
|
11880
|
+
{ label: "(UTC-5) Toronto", value: "America/Toronto" },
|
|
11881
|
+
{ label: "(UTC-4) Caracas", value: "America/Caracas" },
|
|
11882
|
+
{ label: "(UTC-4) Halifax", value: "America/Halifax" },
|
|
11883
|
+
{ label: "(UTC-3) Buenos Aires", value: "America/Argentina/Buenos_Aires" },
|
|
11884
|
+
{ label: "(UTC-3) Santiago", value: "America/Santiago" },
|
|
11885
|
+
{ label: "(UTC-3) Sao Paulo", value: "America/Sao_Paulo" },
|
|
11886
|
+
{ label: "(UTC-2) Mid-Atlantic", value: "Atlantic/South_Georgia" },
|
|
11887
|
+
{ label: "(UTC-1) Azores", value: "Atlantic/Azores" },
|
|
11888
|
+
// Europe / Africa (UTC / UTC+0)
|
|
11889
|
+
{ label: "(UTC) Casablanca", value: "Africa/Casablanca" },
|
|
11890
|
+
{ label: "(UTC) Dublin", value: "Europe/Dublin" },
|
|
11891
|
+
{ label: "(UTC) Lisbon", value: "Europe/Lisbon" },
|
|
11892
|
+
{ label: "(UTC) London", value: "Europe/London" },
|
|
11893
|
+
{ label: "(UTC) Reykjavik", value: "Atlantic/Reykjavik" },
|
|
11894
|
+
// UTC+1
|
|
11895
|
+
{ label: "(UTC+1) Amsterdam", value: "Europe/Amsterdam" },
|
|
11896
|
+
{ label: "(UTC+1) Belgrade", value: "Europe/Belgrade" },
|
|
11897
|
+
{ label: "(UTC+1) Berlin", value: "Europe/Berlin" },
|
|
11898
|
+
{ label: "(UTC+1) Bratislava", value: "Europe/Bratislava" },
|
|
11899
|
+
{ label: "(UTC+1) Brussels", value: "Europe/Brussels" },
|
|
11900
|
+
{ label: "(UTC+1) Budapest", value: "Europe/Budapest" },
|
|
11901
|
+
{ label: "(UTC+1) Copenhagen", value: "Europe/Copenhagen" },
|
|
11902
|
+
{ label: "(UTC+1) Lagos", value: "Africa/Lagos" },
|
|
11903
|
+
{ label: "(UTC+1) Luxembourg", value: "Europe/Luxembourg" },
|
|
11904
|
+
{ label: "(UTC+1) Madrid", value: "Europe/Madrid" },
|
|
11905
|
+
{ label: "(UTC+1) Malta", value: "Europe/Malta" },
|
|
11906
|
+
{ label: "(UTC+1) Oslo", value: "Europe/Oslo" },
|
|
11907
|
+
{ label: "(UTC+1) Paris", value: "Europe/Paris" },
|
|
11908
|
+
{ label: "(UTC+1) Prague", value: "Europe/Prague" },
|
|
11909
|
+
{ label: "(UTC+1) Rome", value: "Europe/Rome" },
|
|
11910
|
+
{ label: "(UTC+1) Sarajevo", value: "Europe/Sarajevo" },
|
|
11911
|
+
{ label: "(UTC+1) Stockholm", value: "Europe/Stockholm" },
|
|
11912
|
+
{ label: "(UTC+1) Vienna", value: "Europe/Vienna" },
|
|
11913
|
+
{ label: "(UTC+1) Warsaw", value: "Europe/Warsaw" },
|
|
11914
|
+
{ label: "(UTC+1) Zurich", value: "Europe/Zurich" },
|
|
11915
|
+
// UTC+2
|
|
11916
|
+
{ label: "(UTC+2) Athens", value: "Europe/Athens" },
|
|
11917
|
+
{ label: "(UTC+2) Bucharest", value: "Europe/Bucharest" },
|
|
11918
|
+
{ label: "(UTC+2) Cairo", value: "Africa/Cairo" },
|
|
11919
|
+
{ label: "(UTC+2) Helsinki", value: "Europe/Helsinki" },
|
|
11920
|
+
{ label: "(UTC+2) Istanbul", value: "Europe/Istanbul" },
|
|
11921
|
+
{ label: "(UTC+2) Jerusalem", value: "Asia/Jerusalem" },
|
|
11922
|
+
{ label: "(UTC+2) Johannesburg", value: "Africa/Johannesburg" },
|
|
11923
|
+
{ label: "(UTC+2) Kyiv", value: "Europe/Kiev" },
|
|
11924
|
+
{ label: "(UTC+2) Riga", value: "Europe/Riga" },
|
|
11925
|
+
{ label: "(UTC+2) Sofia", value: "Europe/Sofia" },
|
|
11926
|
+
{ label: "(UTC+2) Tallinn", value: "Europe/Tallinn" },
|
|
11927
|
+
{ label: "(UTC+2) Vilnius", value: "Europe/Vilnius" },
|
|
11928
|
+
// UTC+3
|
|
11929
|
+
{ label: "(UTC+3) Baghdad", value: "Asia/Baghdad" },
|
|
11930
|
+
{ label: "(UTC+3) Kuwait", value: "Asia/Kuwait" },
|
|
11931
|
+
{ label: "(UTC+3) Moscow", value: "Europe/Moscow" },
|
|
11932
|
+
{ label: "(UTC+3) Nairobi", value: "Africa/Nairobi" },
|
|
11933
|
+
{ label: "(UTC+3) Qatar", value: "Asia/Qatar" },
|
|
11934
|
+
{ label: "(UTC+3) Riyadh", value: "Asia/Riyadh" },
|
|
11935
|
+
// UTC+3:30 / UTC+4
|
|
11936
|
+
{ label: "(UTC+3:30) Tehran", value: "Asia/Tehran" },
|
|
11937
|
+
{ label: "(UTC+4) Abu Dhabi", value: "Asia/Dubai" },
|
|
11938
|
+
{ label: "(UTC+4) Baku", value: "Asia/Baku" },
|
|
11939
|
+
{ label: "(UTC+4) Dubai", value: "Asia/Dubai" },
|
|
11940
|
+
{ label: "(UTC+4) Muscat", value: "Asia/Muscat" },
|
|
11941
|
+
{ label: "(UTC+4) Tbilisi", value: "Asia/Tbilisi" },
|
|
11942
|
+
{ label: "(UTC+4:30) Kabul", value: "Asia/Kabul" },
|
|
11943
|
+
// UTC+5
|
|
11944
|
+
{ label: "(UTC+5) Islamabad", value: "Asia/Karachi" },
|
|
11945
|
+
{ label: "(UTC+5) Karachi", value: "Asia/Karachi" },
|
|
11946
|
+
{ label: "(UTC+5) Tashkent", value: "Asia/Tashkent" },
|
|
11947
|
+
{ label: "(UTC+5:30) Chennai", value: "Asia/Kolkata" },
|
|
11948
|
+
{ label: "(UTC+5:30) Mumbai", value: "Asia/Kolkata" },
|
|
11949
|
+
{ label: "(UTC+5:30) New Delhi", value: "Asia/Kolkata" },
|
|
11950
|
+
{ label: "(UTC+5:45) Kathmandu", value: "Asia/Kathmandu" },
|
|
11951
|
+
// UTC+6
|
|
11952
|
+
{ label: "(UTC+6) Almaty", value: "Asia/Almaty" },
|
|
11953
|
+
{ label: "(UTC+6) Dhaka", value: "Asia/Dhaka" },
|
|
11954
|
+
// UTC+7
|
|
11955
|
+
{ label: "(UTC+7) Bangkok", value: "Asia/Bangkok" },
|
|
11956
|
+
{ label: "(UTC+7) Hanoi", value: "Asia/Ho_Chi_Minh" },
|
|
11957
|
+
{ label: "(UTC+7) Jakarta", value: "Asia/Jakarta" },
|
|
11958
|
+
// UTC+8
|
|
11959
|
+
{ label: "(UTC+8) Beijing", value: "Asia/Shanghai" },
|
|
11960
|
+
{ label: "(UTC+8) Hong Kong", value: "Asia/Hong_Kong" },
|
|
11961
|
+
{ label: "(UTC+8) Kuala Lumpur", value: "Asia/Kuala_Lumpur" },
|
|
11962
|
+
{ label: "(UTC+8) Perth", value: "Australia/Perth" },
|
|
11963
|
+
{ label: "(UTC+8) Shanghai", value: "Asia/Shanghai" },
|
|
11964
|
+
{ label: "(UTC+8) Singapore", value: "Asia/Singapore" },
|
|
11965
|
+
{ label: "(UTC+8) Taipei", value: "Asia/Taipei" },
|
|
11966
|
+
// UTC+9
|
|
11967
|
+
{ label: "(UTC+9) Osaka", value: "Asia/Tokyo" },
|
|
11968
|
+
{ label: "(UTC+9) Seoul", value: "Asia/Seoul" },
|
|
11969
|
+
{ label: "(UTC+9) Tokyo", value: "Asia/Tokyo" },
|
|
11970
|
+
{ label: "(UTC+9:30) Adelaide", value: "Australia/Adelaide" },
|
|
11971
|
+
{ label: "(UTC+9:30) Darwin", value: "Australia/Darwin" },
|
|
11972
|
+
// UTC+10
|
|
11973
|
+
{ label: "(UTC+10) Brisbane", value: "Australia/Brisbane" },
|
|
11974
|
+
{ label: "(UTC+10) Canberra", value: "Australia/Sydney" },
|
|
11975
|
+
{ label: "(UTC+10) Melbourne", value: "Australia/Melbourne" },
|
|
11976
|
+
{ label: "(UTC+10) Sydney", value: "Australia/Sydney" },
|
|
11977
|
+
// UTC+12
|
|
11978
|
+
{ label: "(UTC+12) Auckland", value: "Pacific/Auckland" },
|
|
11979
|
+
{ label: "(UTC+12) Fiji", value: "Pacific/Fiji" }
|
|
11980
|
+
];
|
|
11981
|
+
function getUtcOffsetLabel(iana) {
|
|
11982
|
+
try {
|
|
11983
|
+
const parts = new Intl.DateTimeFormat("en", {
|
|
11984
|
+
timeZone: iana,
|
|
11985
|
+
timeZoneName: "shortOffset"
|
|
11986
|
+
}).formatToParts(/* @__PURE__ */ new Date());
|
|
11987
|
+
const tzName = parts.find((p) => p.type === "timeZoneName")?.value ?? "GMT";
|
|
11988
|
+
const offset = tzName.replace(/^GMT/, "");
|
|
11989
|
+
return offset ? `(UTC${offset})` : "(UTC)";
|
|
11990
|
+
} catch {
|
|
11991
|
+
return "(UTC)";
|
|
11992
|
+
}
|
|
11993
|
+
}
|
|
11994
|
+
function getDisplayLabel(opt) {
|
|
11995
|
+
if (opt.value === "UTC" || opt.value === "exchange") return opt.label;
|
|
11996
|
+
const city = opt.label.replace(/^\(UTC[^)]*\)\s*/, "");
|
|
11997
|
+
return `${getUtcOffsetLabel(opt.value)} ${city}`;
|
|
11998
|
+
}
|
|
11999
|
+
var CRYPTO_EXCHANGES = /* @__PURE__ */ new Set([
|
|
12000
|
+
"BINANCE",
|
|
12001
|
+
"BINANCEUS",
|
|
12002
|
+
"BINANCEUSDM",
|
|
12003
|
+
"BINANCECOINM",
|
|
12004
|
+
"COINBASE",
|
|
12005
|
+
"COINBASEPRO",
|
|
12006
|
+
"KRAKEN",
|
|
12007
|
+
"KRAKENFX",
|
|
12008
|
+
"BYBIT",
|
|
12009
|
+
"BITGET",
|
|
12010
|
+
"OKX",
|
|
12011
|
+
"OKEX",
|
|
12012
|
+
"HUOBI",
|
|
12013
|
+
"HTX",
|
|
12014
|
+
"KUCOIN",
|
|
12015
|
+
"GEMINI",
|
|
12016
|
+
"BITSTAMP",
|
|
12017
|
+
"BITFINEX",
|
|
12018
|
+
"MEXC",
|
|
12019
|
+
"GATEIO",
|
|
12020
|
+
"GATE",
|
|
12021
|
+
"BITMEX",
|
|
12022
|
+
"DERIBIT"
|
|
12023
|
+
]);
|
|
12024
|
+
function symbolSupportsSession(symbol) {
|
|
12025
|
+
const exchange = symbol.includes(":") ? symbol.split(":")[0].toUpperCase() : "";
|
|
12026
|
+
return exchange !== "" && !CRYPTO_EXCHANGES.has(exchange);
|
|
12027
|
+
}
|
|
12028
|
+
function useClockTime(timezone) {
|
|
12029
|
+
const [time, setTime] = useState(() => _formatClock(timezone));
|
|
12030
|
+
useEffect(() => {
|
|
12031
|
+
setTime(_formatClock(timezone));
|
|
12032
|
+
const id = setInterval(() => setTime(_formatClock(timezone)), 1e3);
|
|
12033
|
+
return () => clearInterval(id);
|
|
12034
|
+
}, [timezone]);
|
|
12035
|
+
return time;
|
|
12036
|
+
}
|
|
12037
|
+
function _formatClock(timezone) {
|
|
12038
|
+
const tz = timezone === "exchange" ? "UTC" : timezone;
|
|
12039
|
+
try {
|
|
12040
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
12041
|
+
timeZone: tz,
|
|
12042
|
+
hour: "2-digit",
|
|
12043
|
+
minute: "2-digit",
|
|
12044
|
+
second: "2-digit",
|
|
12045
|
+
hour12: false
|
|
12046
|
+
}).format(/* @__PURE__ */ new Date());
|
|
12047
|
+
} catch {
|
|
12048
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
12049
|
+
timeZone: "UTC",
|
|
12050
|
+
hour: "2-digit",
|
|
12051
|
+
minute: "2-digit",
|
|
12052
|
+
second: "2-digit",
|
|
12053
|
+
hour12: false
|
|
12054
|
+
}).format(/* @__PURE__ */ new Date());
|
|
12055
|
+
}
|
|
10647
12056
|
}
|
|
10648
|
-
function
|
|
10649
|
-
symbol
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
onAddCustomTimeframe,
|
|
10657
|
-
onFavoritesChange,
|
|
10658
|
-
onAddIndicator,
|
|
10659
|
-
onToggleTheme,
|
|
10660
|
-
onCopyScreenshot,
|
|
10661
|
-
onDownloadScreenshot,
|
|
10662
|
-
onFullscreen,
|
|
10663
|
-
isFullscreen,
|
|
10664
|
-
currentLayoutName,
|
|
10665
|
-
currentLayoutId,
|
|
10666
|
-
autoSave,
|
|
10667
|
-
onSaveLayout,
|
|
10668
|
-
onLoadLayout,
|
|
10669
|
-
onRenameLayout,
|
|
10670
|
-
onCopyLayout,
|
|
10671
|
-
onToggleAutoSave,
|
|
10672
|
-
onDeleteLayout,
|
|
10673
|
-
onOpenLayoutInNewTab,
|
|
10674
|
-
onFetchLayouts,
|
|
10675
|
-
symbolResolver,
|
|
10676
|
-
showTradeButton,
|
|
10677
|
-
tradeDrawerOpen,
|
|
10678
|
-
onToggleTradeDrawer
|
|
10679
|
-
}) {
|
|
10680
|
-
const [tfOpen, setTfOpen] = useState(false);
|
|
10681
|
-
const [symOpen, setSymOpen] = useState(false);
|
|
10682
|
-
const [indOpen, setIndOpen] = useState(false);
|
|
10683
|
-
const [ssOpen, setSsOpen] = useState(false);
|
|
10684
|
-
const ssRef = useRef(null);
|
|
10685
|
-
const tfRef = useRef(null);
|
|
12057
|
+
function BottomToolbar({ symbol, timezone, onTimezoneChange, session, onSessionChange, onToggleScriptDrawer, scriptDrawerOpen }) {
|
|
12058
|
+
const showSession = symbolSupportsSession(symbol);
|
|
12059
|
+
const [open, setOpen] = useState(false);
|
|
12060
|
+
const [sessionOpen, setSessionOpen] = useState(false);
|
|
12061
|
+
const rootRef = useRef(null);
|
|
12062
|
+
const sessionRef = useRef(null);
|
|
12063
|
+
const clockTime = useClockTime(timezone);
|
|
12064
|
+
const current = TIMEZONE_OPTIONS.find((o) => o.value === timezone) ?? TIMEZONE_OPTIONS[0];
|
|
10686
12065
|
useEffect(() => {
|
|
10687
|
-
if (!
|
|
12066
|
+
if (!open) return;
|
|
10688
12067
|
const handler = (e) => {
|
|
10689
|
-
if (
|
|
12068
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
|
|
10690
12069
|
};
|
|
10691
12070
|
document.addEventListener("mousedown", handler);
|
|
10692
12071
|
return () => document.removeEventListener("mousedown", handler);
|
|
10693
|
-
}, [
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
{
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
12072
|
+
}, [open]);
|
|
12073
|
+
useEffect(() => {
|
|
12074
|
+
if (!sessionOpen) return;
|
|
12075
|
+
const handler = (e) => {
|
|
12076
|
+
if (sessionRef.current && !sessionRef.current.contains(e.target)) setSessionOpen(false);
|
|
12077
|
+
};
|
|
12078
|
+
document.addEventListener("mousedown", handler);
|
|
12079
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
12080
|
+
}, [sessionOpen]);
|
|
12081
|
+
return /* @__PURE__ */ jsxs("div", { className: "bottom-bar", children: [
|
|
12082
|
+
/* @__PURE__ */ jsx("div", { className: "bottom-bar-left", children: /* @__PURE__ */ jsxs(
|
|
12083
|
+
"button",
|
|
12084
|
+
{
|
|
12085
|
+
className: `bottom-bar-btn${scriptDrawerOpen ? " active" : ""}`,
|
|
12086
|
+
onClick: onToggleScriptDrawer,
|
|
12087
|
+
title: "Script Engine",
|
|
12088
|
+
children: [
|
|
12089
|
+
/* @__PURE__ */ jsxs(
|
|
12090
|
+
"svg",
|
|
12091
|
+
{
|
|
12092
|
+
viewBox: "0 0 16 16",
|
|
12093
|
+
width: "11",
|
|
12094
|
+
height: "11",
|
|
12095
|
+
fill: "none",
|
|
12096
|
+
stroke: "currentColor",
|
|
12097
|
+
strokeWidth: "1.6",
|
|
12098
|
+
strokeLinecap: "round",
|
|
12099
|
+
children: [
|
|
12100
|
+
/* @__PURE__ */ jsx("polyline", { points: "4,6 2,8 4,10" }),
|
|
12101
|
+
/* @__PURE__ */ jsx("polyline", { points: "12,6 14,8 12,10" }),
|
|
12102
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "3", x2: "7", y2: "13" })
|
|
12103
|
+
]
|
|
12104
|
+
}
|
|
12105
|
+
),
|
|
12106
|
+
"Script"
|
|
12107
|
+
]
|
|
12108
|
+
}
|
|
12109
|
+
) }),
|
|
12110
|
+
/* @__PURE__ */ jsxs("div", { className: "bottom-bar-right", children: [
|
|
12111
|
+
/* @__PURE__ */ jsxs("div", { ref: rootRef, style: { position: "relative", display: "flex", alignItems: "center" }, children: [
|
|
10709
12112
|
/* @__PURE__ */ jsxs(
|
|
10710
12113
|
"button",
|
|
10711
12114
|
{
|
|
10712
|
-
className:
|
|
10713
|
-
onClick: () =>
|
|
10714
|
-
title: "
|
|
12115
|
+
className: `bottom-bar-btn${open ? " active" : ""}`,
|
|
12116
|
+
onClick: () => setOpen((o) => !o),
|
|
12117
|
+
title: "Chart timezone",
|
|
10715
12118
|
children: [
|
|
10716
|
-
/* @__PURE__ */ jsxs(
|
|
10717
|
-
"
|
|
10718
|
-
{
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
strokeWidth: "1.8",
|
|
10725
|
-
strokeLinecap: "round",
|
|
10726
|
-
children: [
|
|
10727
|
-
/* @__PURE__ */ jsx("circle", { cx: "5.5", cy: "5.5", r: "4" }),
|
|
10728
|
-
/* @__PURE__ */ jsx("line", { x1: "9", y1: "9", x2: "13", y2: "13" })
|
|
10729
|
-
]
|
|
10730
|
-
}
|
|
10731
|
-
),
|
|
10732
|
-
/* @__PURE__ */ jsx("span", { children: symbol }),
|
|
10733
|
-
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 6", width: "8", height: "6", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M0 0l5 6 5-6z" }) })
|
|
12119
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 14 14", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: "1.2", children: [
|
|
12120
|
+
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "5.5" }),
|
|
12121
|
+
/* @__PURE__ */ jsx("path", { d: "M7 1.5C7 1.5 9 4 9 7s-2 5.5-2 5.5M7 1.5C7 1.5 5 4 5 7s2 5.5 2 5.5" }),
|
|
12122
|
+
/* @__PURE__ */ jsx("line", { x1: "1.5", y1: "7", x2: "12.5", y2: "7" }),
|
|
12123
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "4.5", x2: "12", y2: "4.5" }),
|
|
12124
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "9.5", x2: "12", y2: "9.5" })
|
|
12125
|
+
] }),
|
|
12126
|
+
getDisplayLabel(current)
|
|
10734
12127
|
]
|
|
10735
12128
|
}
|
|
10736
12129
|
),
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
{
|
|
10740
|
-
|
|
10741
|
-
onSelect: onSymbolChange,
|
|
10742
|
-
onClose: () => setSymOpen(false),
|
|
10743
|
-
symbolResolver
|
|
10744
|
-
}
|
|
10745
|
-
),
|
|
10746
|
-
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
10747
|
-
/* @__PURE__ */ jsxs("div", { ref: tfRef, style: { display: "flex", gap: 2, position: "relative" }, children: [
|
|
10748
|
-
favorites.map((tf) => /* @__PURE__ */ jsx(
|
|
12130
|
+
/* @__PURE__ */ jsx("span", { className: "bottom-bar-clock", title: `Current time in ${getDisplayLabel(current)}`, children: clockTime }),
|
|
12131
|
+
open && /* @__PURE__ */ jsxs("div", { className: "bottom-bar-dropdown bottom-bar-dropdown--right", children: [
|
|
12132
|
+
/* @__PURE__ */ jsx("div", { className: "bottom-bar-dropdown-section", children: "Timezone" }),
|
|
12133
|
+
TIMEZONE_OPTIONS.map((opt) => /* @__PURE__ */ jsxs(
|
|
10749
12134
|
"button",
|
|
10750
12135
|
{
|
|
10751
|
-
className:
|
|
10752
|
-
onClick: () =>
|
|
10753
|
-
|
|
12136
|
+
className: `bottom-bar-dropdown-item${opt.value === timezone ? " current" : ""}`,
|
|
12137
|
+
onClick: () => {
|
|
12138
|
+
onTimezoneChange(opt.value);
|
|
12139
|
+
setOpen(false);
|
|
12140
|
+
},
|
|
12141
|
+
children: [
|
|
12142
|
+
opt.value === timezone && /* @__PURE__ */ jsx("span", { className: "bottom-bar-check", children: "\xE2\u0153\u201C" }),
|
|
12143
|
+
getDisplayLabel(opt)
|
|
12144
|
+
]
|
|
10754
12145
|
},
|
|
10755
|
-
|
|
10756
|
-
))
|
|
10757
|
-
|
|
10758
|
-
|
|
12146
|
+
opt.value + opt.label
|
|
12147
|
+
))
|
|
12148
|
+
] })
|
|
12149
|
+
] }),
|
|
12150
|
+
showSession && /* @__PURE__ */ jsxs("div", { ref: sessionRef, style: { position: "relative", display: "flex", alignItems: "center" }, children: [
|
|
12151
|
+
/* @__PURE__ */ jsxs(
|
|
12152
|
+
"button",
|
|
12153
|
+
{
|
|
12154
|
+
className: `bottom-bar-btn${sessionOpen ? " active" : ""}`,
|
|
12155
|
+
onClick: () => setSessionOpen((o) => !o),
|
|
12156
|
+
title: "Trading session",
|
|
12157
|
+
children: [
|
|
12158
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 14 14", width: "11", height: "11", fill: "none", stroke: "currentColor", strokeWidth: "1.2", children: [
|
|
12159
|
+
/* @__PURE__ */ jsx("rect", { x: "1.5", y: "2.5", width: "11", height: "9", rx: "1" }),
|
|
12160
|
+
/* @__PURE__ */ jsx("line", { x1: "1.5", y1: "5.5", x2: "12.5", y2: "5.5" }),
|
|
12161
|
+
/* @__PURE__ */ jsx("line", { x1: "4.5", y1: "2.5", x2: "4.5", y2: "1" }),
|
|
12162
|
+
/* @__PURE__ */ jsx("line", { x1: "9.5", y1: "2.5", x2: "9.5", y2: "1" })
|
|
12163
|
+
] }),
|
|
12164
|
+
session === "extended" ? "Extended hours" : "Regular hours"
|
|
12165
|
+
]
|
|
12166
|
+
}
|
|
12167
|
+
),
|
|
12168
|
+
sessionOpen && /* @__PURE__ */ jsxs("div", { className: "bottom-bar-dropdown bottom-bar-dropdown--right", children: [
|
|
12169
|
+
/* @__PURE__ */ jsx("div", { className: "bottom-bar-dropdown-section", children: "Sessions" }),
|
|
12170
|
+
/* @__PURE__ */ jsxs(
|
|
10759
12171
|
"button",
|
|
10760
12172
|
{
|
|
10761
|
-
className:
|
|
10762
|
-
onClick: () =>
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
12173
|
+
className: `bottom-bar-dropdown-item${session === "regular" ? " current" : ""}`,
|
|
12174
|
+
onClick: () => {
|
|
12175
|
+
onSessionChange("regular");
|
|
12176
|
+
setSessionOpen(false);
|
|
12177
|
+
},
|
|
12178
|
+
children: [
|
|
12179
|
+
session === "regular" && /* @__PURE__ */ jsx("span", { className: "bottom-bar-check", children: "\xE2\u0153\u201C" }),
|
|
12180
|
+
/* @__PURE__ */ jsxs("span", { className: "bottom-bar-session-label", children: [
|
|
12181
|
+
/* @__PURE__ */ jsx("strong", { children: "Regular trading hours" }),
|
|
12182
|
+
/* @__PURE__ */ jsx("span", { children: "Exchange session only (RTH) \xE2\u20AC\u201D cleaner signals, higher liquidity" })
|
|
12183
|
+
] })
|
|
12184
|
+
]
|
|
10766
12185
|
}
|
|
10767
12186
|
),
|
|
10768
|
-
|
|
10769
|
-
|
|
12187
|
+
/* @__PURE__ */ jsxs(
|
|
12188
|
+
"button",
|
|
10770
12189
|
{
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
onToggleFavorite: (tf) => {
|
|
10776
|
-
const next = favorites.includes(tf) ? favorites.filter((f) => f !== tf) : [...favorites, tf];
|
|
10777
|
-
onFavoritesChange(next);
|
|
12190
|
+
className: `bottom-bar-dropdown-item${session === "extended" ? " current" : ""}`,
|
|
12191
|
+
onClick: () => {
|
|
12192
|
+
onSessionChange("extended");
|
|
12193
|
+
setSessionOpen(false);
|
|
10778
12194
|
},
|
|
10779
|
-
|
|
10780
|
-
|
|
12195
|
+
children: [
|
|
12196
|
+
session === "extended" && /* @__PURE__ */ jsx("span", { className: "bottom-bar-check", children: "\xE2\u0153\u201C" }),
|
|
12197
|
+
/* @__PURE__ */ jsxs("span", { className: "bottom-bar-session-label", children: [
|
|
12198
|
+
/* @__PURE__ */ jsx("strong", { children: "Extended / electronic hours" }),
|
|
12199
|
+
/* @__PURE__ */ jsx("span", { children: "Includes pre-market & after-hours (ETH) \xE2\u20AC\u201D full price range" })
|
|
12200
|
+
] })
|
|
12201
|
+
]
|
|
10781
12202
|
}
|
|
10782
12203
|
)
|
|
12204
|
+
] })
|
|
12205
|
+
] })
|
|
12206
|
+
] })
|
|
12207
|
+
] });
|
|
12208
|
+
}
|
|
12209
|
+
function ChartWorkspace({
|
|
12210
|
+
// TabBar
|
|
12211
|
+
tabs,
|
|
12212
|
+
activeTabId,
|
|
12213
|
+
onSelectTab,
|
|
12214
|
+
onAddTab,
|
|
12215
|
+
onCloseTab,
|
|
12216
|
+
onRenameTab,
|
|
12217
|
+
// TopToolbar
|
|
12218
|
+
symbol,
|
|
12219
|
+
timeframe,
|
|
12220
|
+
theme,
|
|
12221
|
+
customTimeframes,
|
|
12222
|
+
favoriteTfs,
|
|
12223
|
+
onSymbolChange,
|
|
12224
|
+
onTimeframeChange,
|
|
12225
|
+
onAddCustomTimeframe,
|
|
12226
|
+
onFavoriteTfsChange,
|
|
12227
|
+
onAddIndicator,
|
|
12228
|
+
onToggleTheme,
|
|
12229
|
+
onCopyScreenshot,
|
|
12230
|
+
onDownloadScreenshot,
|
|
12231
|
+
onFullscreen,
|
|
12232
|
+
isFullscreen,
|
|
12233
|
+
currentLayoutName,
|
|
12234
|
+
currentLayoutId,
|
|
12235
|
+
autoSave,
|
|
12236
|
+
onFetchLayouts,
|
|
12237
|
+
onSaveLayout,
|
|
12238
|
+
onLoadLayout,
|
|
12239
|
+
onRenameLayout,
|
|
12240
|
+
onCopyLayout,
|
|
12241
|
+
onToggleAutoSave,
|
|
12242
|
+
onDeleteLayout,
|
|
12243
|
+
onOpenLayoutInNewTab,
|
|
12244
|
+
showTradeButton,
|
|
12245
|
+
tradeDrawerOpen,
|
|
12246
|
+
onToggleTradeDrawer,
|
|
12247
|
+
symbolResolver,
|
|
12248
|
+
// RightToolbar
|
|
12249
|
+
watchlistOpen,
|
|
12250
|
+
onToggleWatchlist,
|
|
12251
|
+
// BottomToolbar
|
|
12252
|
+
activeSymbol,
|
|
12253
|
+
timezone,
|
|
12254
|
+
onTimezoneChange,
|
|
12255
|
+
session,
|
|
12256
|
+
onSessionChange,
|
|
12257
|
+
scriptDrawerOpen,
|
|
12258
|
+
onToggleScriptDrawer,
|
|
12259
|
+
// Chart + state
|
|
12260
|
+
chartSlots,
|
|
12261
|
+
isLicensed,
|
|
12262
|
+
wsLoaded,
|
|
12263
|
+
drawers
|
|
12264
|
+
}) {
|
|
12265
|
+
const tabItems = tabs.map((t) => ({
|
|
12266
|
+
id: t.id,
|
|
12267
|
+
label: t.label,
|
|
12268
|
+
...t.isSaved !== void 0 ? { isSaved: t.isSaved } : {}
|
|
12269
|
+
}));
|
|
12270
|
+
return /* @__PURE__ */ jsxs(
|
|
12271
|
+
"div",
|
|
12272
|
+
{
|
|
12273
|
+
style: {
|
|
12274
|
+
display: "flex",
|
|
12275
|
+
flexDirection: "column",
|
|
12276
|
+
height: "100%",
|
|
12277
|
+
background: "var(--shelf-bg)",
|
|
12278
|
+
padding: 6,
|
|
12279
|
+
gap: 4
|
|
12280
|
+
},
|
|
12281
|
+
children: [
|
|
12282
|
+
!wsLoaded && /* @__PURE__ */ jsxs("div", { style: {
|
|
12283
|
+
position: "absolute",
|
|
12284
|
+
inset: 0,
|
|
12285
|
+
display: "flex",
|
|
12286
|
+
alignItems: "center",
|
|
12287
|
+
justifyContent: "center",
|
|
12288
|
+
background: "var(--bg)",
|
|
12289
|
+
zIndex: 1e3
|
|
12290
|
+
}, children: [
|
|
12291
|
+
/* @__PURE__ */ jsxs("svg", { width: "32", height: "32", viewBox: "0 0 32 32", style: { animation: "spin 0.9s linear infinite" }, children: [
|
|
12292
|
+
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", fill: "none", stroke: "var(--border)", strokeWidth: "3" }),
|
|
12293
|
+
/* @__PURE__ */ jsx("path", { d: "M16 4 A12 12 0 0 1 28 16", fill: "none", stroke: "var(--primary)", strokeWidth: "3", strokeLinecap: "round" })
|
|
12294
|
+
] }),
|
|
12295
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
10783
12296
|
] }),
|
|
10784
|
-
/* @__PURE__ */ jsx(
|
|
10785
|
-
|
|
10786
|
-
"button",
|
|
12297
|
+
/* @__PURE__ */ jsx(
|
|
12298
|
+
TabBar,
|
|
10787
12299
|
{
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
/* @__PURE__ */ jsx("line", { x1: "11", y1: "2", x2: "14", y2: "2" }),
|
|
10795
|
-
/* @__PURE__ */ jsx("line", { x1: "14", y1: "2", x2: "14", y2: "5" })
|
|
10796
|
-
] }),
|
|
10797
|
-
"Indicators"
|
|
10798
|
-
]
|
|
12300
|
+
tabs: tabItems,
|
|
12301
|
+
activeId: activeTabId,
|
|
12302
|
+
onSelect: onSelectTab,
|
|
12303
|
+
onAdd: onAddTab,
|
|
12304
|
+
onClose: onCloseTab,
|
|
12305
|
+
onRename: onRenameTab
|
|
10799
12306
|
}
|
|
10800
12307
|
),
|
|
10801
|
-
|
|
10802
|
-
|
|
12308
|
+
/* @__PURE__ */ jsx(
|
|
12309
|
+
TopToolbar,
|
|
10803
12310
|
{
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
12311
|
+
symbol,
|
|
12312
|
+
timeframe,
|
|
12313
|
+
theme,
|
|
12314
|
+
customTimeframes,
|
|
12315
|
+
favorites: favoriteTfs,
|
|
12316
|
+
onSymbolChange,
|
|
12317
|
+
onTimeframeChange,
|
|
12318
|
+
onAddCustomTimeframe,
|
|
12319
|
+
onFavoritesChange: onFavoriteTfsChange,
|
|
12320
|
+
onAddIndicator,
|
|
12321
|
+
onToggleTheme,
|
|
12322
|
+
onCopyScreenshot,
|
|
12323
|
+
onDownloadScreenshot,
|
|
12324
|
+
onFullscreen,
|
|
12325
|
+
isFullscreen,
|
|
12326
|
+
currentLayoutName,
|
|
12327
|
+
currentLayoutId,
|
|
12328
|
+
autoSave,
|
|
12329
|
+
onFetchLayouts,
|
|
12330
|
+
onSaveLayout,
|
|
12331
|
+
onLoadLayout,
|
|
12332
|
+
onRenameLayout,
|
|
12333
|
+
onCopyLayout,
|
|
12334
|
+
onToggleAutoSave,
|
|
12335
|
+
onDeleteLayout,
|
|
12336
|
+
onOpenLayoutInNewTab,
|
|
12337
|
+
symbolResolver,
|
|
12338
|
+
...showTradeButton !== void 0 ? { showTradeButton } : {},
|
|
12339
|
+
...tradeDrawerOpen !== void 0 ? { tradeDrawerOpen } : {},
|
|
12340
|
+
...onToggleTradeDrawer !== void 0 ? { onToggleTradeDrawer } : {}
|
|
10808
12341
|
}
|
|
10809
12342
|
),
|
|
10810
|
-
/* @__PURE__ */ jsxs("div", { style: {
|
|
10811
|
-
|
|
10812
|
-
/* @__PURE__ */ jsxs(
|
|
10813
|
-
"
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
}
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
LayoutMenu,
|
|
10833
|
-
{
|
|
10834
|
-
currentName: currentLayoutName,
|
|
10835
|
-
currentLayoutId,
|
|
10836
|
-
autoSave,
|
|
10837
|
-
onFetchLayouts,
|
|
10838
|
-
onSave: onSaveLayout,
|
|
10839
|
-
onLoad: onLoadLayout,
|
|
10840
|
-
onRename: onRenameLayout,
|
|
10841
|
-
onCopy: onCopyLayout,
|
|
10842
|
-
onToggleAutoSave,
|
|
10843
|
-
onDelete: onDeleteLayout,
|
|
10844
|
-
onOpenInNewTab: onOpenLayoutInNewTab
|
|
10845
|
-
}
|
|
10846
|
-
),
|
|
10847
|
-
/* @__PURE__ */ jsx(
|
|
10848
|
-
"button",
|
|
10849
|
-
{
|
|
10850
|
-
onClick: onToggleTheme,
|
|
10851
|
-
title: `Switch to ${theme === "dark" ? "light" : "dark"} mode`,
|
|
10852
|
-
style: { fontSize: 14, padding: "4px 8px" },
|
|
10853
|
-
children: theme === "dark" ? "\u2600" : "\u263E"
|
|
10854
|
-
}
|
|
10855
|
-
),
|
|
10856
|
-
/* @__PURE__ */ jsxs("div", { ref: ssRef, style: { position: "relative" }, children: [
|
|
12343
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", gap: 8, minHeight: 0, overflow: "hidden" }, children: [
|
|
12344
|
+
/* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, display: "flex", gap: 8, minWidth: 0, minHeight: 0 }, children: [
|
|
12345
|
+
!isLicensed && /* @__PURE__ */ jsxs("div", { style: {
|
|
12346
|
+
position: "absolute",
|
|
12347
|
+
inset: 0,
|
|
12348
|
+
zIndex: 100,
|
|
12349
|
+
background: "rgba(0,0,0,0.6)",
|
|
12350
|
+
backdropFilter: "blur(4px)",
|
|
12351
|
+
display: "flex",
|
|
12352
|
+
flexDirection: "column",
|
|
12353
|
+
alignItems: "center",
|
|
12354
|
+
justifyContent: "center",
|
|
12355
|
+
gap: 12
|
|
12356
|
+
}, children: [
|
|
12357
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "40", height: "40", fill: "none", stroke: "rgba(255,255,255,0.7)", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
12358
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }),
|
|
12359
|
+
/* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0110 0v4" })
|
|
12360
|
+
] }),
|
|
12361
|
+
/* @__PURE__ */ jsx("div", { style: { color: "#fff", fontWeight: 700, fontSize: 18, letterSpacing: "0.01em" }, children: "Unlicensed" }),
|
|
12362
|
+
/* @__PURE__ */ jsx("div", { style: { color: "rgba(255,255,255,0.65)", fontSize: 13 }, children: "Please activate your license in the admin studio" })
|
|
12363
|
+
] }),
|
|
12364
|
+
/* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minWidth: 0, minHeight: 0 }, children: chartSlots }),
|
|
10857
12365
|
/* @__PURE__ */ jsx(
|
|
10858
|
-
|
|
12366
|
+
RightToolbar,
|
|
10859
12367
|
{
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
title: "Screenshot",
|
|
10863
|
-
style: { display: "inline-flex", alignItems: "center", padding: "4px 7px" },
|
|
10864
|
-
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
10865
|
-
/* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "12", height: "9", rx: "1" }),
|
|
10866
|
-
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "8.5", r: "2.5" }),
|
|
10867
|
-
/* @__PURE__ */ jsx("path", { d: "M6 4l1-2h2l1 2" })
|
|
10868
|
-
] })
|
|
12368
|
+
watchlistOpen,
|
|
12369
|
+
onToggleWatchlist
|
|
10869
12370
|
}
|
|
10870
|
-
)
|
|
10871
|
-
ssOpen && /* @__PURE__ */ jsxs("div", { className: "ss-dropdown", children: [
|
|
10872
|
-
/* @__PURE__ */ jsxs("button", { className: "ss-dropdown-item", onClick: () => {
|
|
10873
|
-
setSsOpen(false);
|
|
10874
|
-
onCopyScreenshot();
|
|
10875
|
-
}, children: [
|
|
10876
|
-
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
10877
|
-
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1" }),
|
|
10878
|
-
/* @__PURE__ */ jsx("path", { d: "M3 11H2a1 1 0 01-1-1V2a1 1 0 011-1h8a1 1 0 011 1v1" })
|
|
10879
|
-
] }),
|
|
10880
|
-
"Copy to Clipboard"
|
|
10881
|
-
] }),
|
|
10882
|
-
/* @__PURE__ */ jsxs("button", { className: "ss-dropdown-item", onClick: () => {
|
|
10883
|
-
setSsOpen(false);
|
|
10884
|
-
onDownloadScreenshot();
|
|
10885
|
-
}, children: [
|
|
10886
|
-
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", fill: "none", strokeWidth: "1.5", strokeLinecap: "round", children: [
|
|
10887
|
-
/* @__PURE__ */ jsx("line", { x1: "8", y1: "2", x2: "8", y2: "11" }),
|
|
10888
|
-
/* @__PURE__ */ jsx("polyline", { points: "4,7 8,11 12,7" }),
|
|
10889
|
-
/* @__PURE__ */ jsx("line", { x1: "2", y1: "14", x2: "14", y2: "14" })
|
|
10890
|
-
] }),
|
|
10891
|
-
"Download PNG"
|
|
10892
|
-
] })
|
|
10893
|
-
] })
|
|
12371
|
+
)
|
|
10894
12372
|
] }),
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
12373
|
+
drawers
|
|
12374
|
+
] }),
|
|
12375
|
+
/* @__PURE__ */ jsx(
|
|
12376
|
+
BottomToolbar,
|
|
12377
|
+
{
|
|
12378
|
+
symbol: activeSymbol,
|
|
12379
|
+
timezone,
|
|
12380
|
+
onTimezoneChange,
|
|
12381
|
+
session,
|
|
12382
|
+
onSessionChange,
|
|
12383
|
+
onToggleScriptDrawer,
|
|
12384
|
+
scriptDrawerOpen
|
|
12385
|
+
}
|
|
12386
|
+
)
|
|
10905
12387
|
]
|
|
10906
12388
|
}
|
|
10907
12389
|
);
|
|
10908
12390
|
}
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
10930
|
-
|
|
10931
|
-
|
|
10932
|
-
},
|
|
10933
|
-
children: /* @__PURE__ */ jsx(
|
|
10934
|
-
"button",
|
|
10935
|
-
{
|
|
10936
|
-
title: "Watch List",
|
|
10937
|
-
className: `drawing-tool-btn${watchlistOpen ? " is-active" : ""}`,
|
|
10938
|
-
onClick: onToggleWatchlist,
|
|
10939
|
-
children: /* @__PURE__ */ jsx(WatchListIcon, {})
|
|
10940
|
-
}
|
|
10941
|
-
)
|
|
10942
|
-
}
|
|
10943
|
-
);
|
|
10944
|
-
}
|
|
10945
|
-
var TIMEZONE_OPTIONS = [
|
|
10946
|
-
{ label: "UTC", value: "UTC" },
|
|
10947
|
-
{ label: "Exchange", value: "exchange" },
|
|
10948
|
-
// Americas
|
|
10949
|
-
{ label: "(UTC-10) Honolulu", value: "Pacific/Honolulu" },
|
|
10950
|
-
{ label: "(UTC-9) Anchorage", value: "America/Anchorage" },
|
|
10951
|
-
{ label: "(UTC-9) Juneau", value: "America/Juneau" },
|
|
10952
|
-
{ label: "(UTC-8) Los Angeles", value: "America/Los_Angeles" },
|
|
10953
|
-
{ label: "(UTC-8) Vancouver", value: "America/Vancouver" },
|
|
10954
|
-
{ label: "(UTC-7) Denver", value: "America/Denver" },
|
|
10955
|
-
{ label: "(UTC-7) Phoenix", value: "America/Phoenix" },
|
|
10956
|
-
{ label: "(UTC-6) Chicago", value: "America/Chicago" },
|
|
10957
|
-
{ label: "(UTC-6) Mexico City", value: "America/Mexico_City" },
|
|
10958
|
-
{ label: "(UTC-6) San Salvador", value: "America/El_Salvador" },
|
|
10959
|
-
{ label: "(UTC-5) Bogota", value: "America/Bogota" },
|
|
10960
|
-
{ label: "(UTC-5) Lima", value: "America/Lima" },
|
|
10961
|
-
{ label: "(UTC-5) New York", value: "America/New_York" },
|
|
10962
|
-
{ label: "(UTC-5) Toronto", value: "America/Toronto" },
|
|
10963
|
-
{ label: "(UTC-4) Caracas", value: "America/Caracas" },
|
|
10964
|
-
{ label: "(UTC-4) Halifax", value: "America/Halifax" },
|
|
10965
|
-
{ label: "(UTC-3) Buenos Aires", value: "America/Argentina/Buenos_Aires" },
|
|
10966
|
-
{ label: "(UTC-3) Santiago", value: "America/Santiago" },
|
|
10967
|
-
{ label: "(UTC-3) Sao Paulo", value: "America/Sao_Paulo" },
|
|
10968
|
-
{ label: "(UTC-2) Mid-Atlantic", value: "Atlantic/South_Georgia" },
|
|
10969
|
-
{ label: "(UTC-1) Azores", value: "Atlantic/Azores" },
|
|
10970
|
-
// Europe / Africa (UTC / UTC+0)
|
|
10971
|
-
{ label: "(UTC) Casablanca", value: "Africa/Casablanca" },
|
|
10972
|
-
{ label: "(UTC) Dublin", value: "Europe/Dublin" },
|
|
10973
|
-
{ label: "(UTC) Lisbon", value: "Europe/Lisbon" },
|
|
10974
|
-
{ label: "(UTC) London", value: "Europe/London" },
|
|
10975
|
-
{ label: "(UTC) Reykjavik", value: "Atlantic/Reykjavik" },
|
|
10976
|
-
// UTC+1
|
|
10977
|
-
{ label: "(UTC+1) Amsterdam", value: "Europe/Amsterdam" },
|
|
10978
|
-
{ label: "(UTC+1) Belgrade", value: "Europe/Belgrade" },
|
|
10979
|
-
{ label: "(UTC+1) Berlin", value: "Europe/Berlin" },
|
|
10980
|
-
{ label: "(UTC+1) Bratislava", value: "Europe/Bratislava" },
|
|
10981
|
-
{ label: "(UTC+1) Brussels", value: "Europe/Brussels" },
|
|
10982
|
-
{ label: "(UTC+1) Budapest", value: "Europe/Budapest" },
|
|
10983
|
-
{ label: "(UTC+1) Copenhagen", value: "Europe/Copenhagen" },
|
|
10984
|
-
{ label: "(UTC+1) Lagos", value: "Africa/Lagos" },
|
|
10985
|
-
{ label: "(UTC+1) Luxembourg", value: "Europe/Luxembourg" },
|
|
10986
|
-
{ label: "(UTC+1) Madrid", value: "Europe/Madrid" },
|
|
10987
|
-
{ label: "(UTC+1) Malta", value: "Europe/Malta" },
|
|
10988
|
-
{ label: "(UTC+1) Oslo", value: "Europe/Oslo" },
|
|
10989
|
-
{ label: "(UTC+1) Paris", value: "Europe/Paris" },
|
|
10990
|
-
{ label: "(UTC+1) Prague", value: "Europe/Prague" },
|
|
10991
|
-
{ label: "(UTC+1) Rome", value: "Europe/Rome" },
|
|
10992
|
-
{ label: "(UTC+1) Sarajevo", value: "Europe/Sarajevo" },
|
|
10993
|
-
{ label: "(UTC+1) Stockholm", value: "Europe/Stockholm" },
|
|
10994
|
-
{ label: "(UTC+1) Vienna", value: "Europe/Vienna" },
|
|
10995
|
-
{ label: "(UTC+1) Warsaw", value: "Europe/Warsaw" },
|
|
10996
|
-
{ label: "(UTC+1) Zurich", value: "Europe/Zurich" },
|
|
10997
|
-
// UTC+2
|
|
10998
|
-
{ label: "(UTC+2) Athens", value: "Europe/Athens" },
|
|
10999
|
-
{ label: "(UTC+2) Bucharest", value: "Europe/Bucharest" },
|
|
11000
|
-
{ label: "(UTC+2) Cairo", value: "Africa/Cairo" },
|
|
11001
|
-
{ label: "(UTC+2) Helsinki", value: "Europe/Helsinki" },
|
|
11002
|
-
{ label: "(UTC+2) Istanbul", value: "Europe/Istanbul" },
|
|
11003
|
-
{ label: "(UTC+2) Jerusalem", value: "Asia/Jerusalem" },
|
|
11004
|
-
{ label: "(UTC+2) Johannesburg", value: "Africa/Johannesburg" },
|
|
11005
|
-
{ label: "(UTC+2) Kyiv", value: "Europe/Kiev" },
|
|
11006
|
-
{ label: "(UTC+2) Riga", value: "Europe/Riga" },
|
|
11007
|
-
{ label: "(UTC+2) Sofia", value: "Europe/Sofia" },
|
|
11008
|
-
{ label: "(UTC+2) Tallinn", value: "Europe/Tallinn" },
|
|
11009
|
-
{ label: "(UTC+2) Vilnius", value: "Europe/Vilnius" },
|
|
11010
|
-
// UTC+3
|
|
11011
|
-
{ label: "(UTC+3) Baghdad", value: "Asia/Baghdad" },
|
|
11012
|
-
{ label: "(UTC+3) Kuwait", value: "Asia/Kuwait" },
|
|
11013
|
-
{ label: "(UTC+3) Moscow", value: "Europe/Moscow" },
|
|
11014
|
-
{ label: "(UTC+3) Nairobi", value: "Africa/Nairobi" },
|
|
11015
|
-
{ label: "(UTC+3) Qatar", value: "Asia/Qatar" },
|
|
11016
|
-
{ label: "(UTC+3) Riyadh", value: "Asia/Riyadh" },
|
|
11017
|
-
// UTC+3:30 / UTC+4
|
|
11018
|
-
{ label: "(UTC+3:30) Tehran", value: "Asia/Tehran" },
|
|
11019
|
-
{ label: "(UTC+4) Abu Dhabi", value: "Asia/Dubai" },
|
|
11020
|
-
{ label: "(UTC+4) Baku", value: "Asia/Baku" },
|
|
11021
|
-
{ label: "(UTC+4) Dubai", value: "Asia/Dubai" },
|
|
11022
|
-
{ label: "(UTC+4) Muscat", value: "Asia/Muscat" },
|
|
11023
|
-
{ label: "(UTC+4) Tbilisi", value: "Asia/Tbilisi" },
|
|
11024
|
-
{ label: "(UTC+4:30) Kabul", value: "Asia/Kabul" },
|
|
11025
|
-
// UTC+5
|
|
11026
|
-
{ label: "(UTC+5) Islamabad", value: "Asia/Karachi" },
|
|
11027
|
-
{ label: "(UTC+5) Karachi", value: "Asia/Karachi" },
|
|
11028
|
-
{ label: "(UTC+5) Tashkent", value: "Asia/Tashkent" },
|
|
11029
|
-
{ label: "(UTC+5:30) Chennai", value: "Asia/Kolkata" },
|
|
11030
|
-
{ label: "(UTC+5:30) Mumbai", value: "Asia/Kolkata" },
|
|
11031
|
-
{ label: "(UTC+5:30) New Delhi", value: "Asia/Kolkata" },
|
|
11032
|
-
{ label: "(UTC+5:45) Kathmandu", value: "Asia/Kathmandu" },
|
|
11033
|
-
// UTC+6
|
|
11034
|
-
{ label: "(UTC+6) Almaty", value: "Asia/Almaty" },
|
|
11035
|
-
{ label: "(UTC+6) Dhaka", value: "Asia/Dhaka" },
|
|
11036
|
-
// UTC+7
|
|
11037
|
-
{ label: "(UTC+7) Bangkok", value: "Asia/Bangkok" },
|
|
11038
|
-
{ label: "(UTC+7) Hanoi", value: "Asia/Ho_Chi_Minh" },
|
|
11039
|
-
{ label: "(UTC+7) Jakarta", value: "Asia/Jakarta" },
|
|
11040
|
-
// UTC+8
|
|
11041
|
-
{ label: "(UTC+8) Beijing", value: "Asia/Shanghai" },
|
|
11042
|
-
{ label: "(UTC+8) Hong Kong", value: "Asia/Hong_Kong" },
|
|
11043
|
-
{ label: "(UTC+8) Kuala Lumpur", value: "Asia/Kuala_Lumpur" },
|
|
11044
|
-
{ label: "(UTC+8) Perth", value: "Australia/Perth" },
|
|
11045
|
-
{ label: "(UTC+8) Shanghai", value: "Asia/Shanghai" },
|
|
11046
|
-
{ label: "(UTC+8) Singapore", value: "Asia/Singapore" },
|
|
11047
|
-
{ label: "(UTC+8) Taipei", value: "Asia/Taipei" },
|
|
11048
|
-
// UTC+9
|
|
11049
|
-
{ label: "(UTC+9) Osaka", value: "Asia/Tokyo" },
|
|
11050
|
-
{ label: "(UTC+9) Seoul", value: "Asia/Seoul" },
|
|
11051
|
-
{ label: "(UTC+9) Tokyo", value: "Asia/Tokyo" },
|
|
11052
|
-
{ label: "(UTC+9:30) Adelaide", value: "Australia/Adelaide" },
|
|
11053
|
-
{ label: "(UTC+9:30) Darwin", value: "Australia/Darwin" },
|
|
11054
|
-
// UTC+10
|
|
11055
|
-
{ label: "(UTC+10) Brisbane", value: "Australia/Brisbane" },
|
|
11056
|
-
{ label: "(UTC+10) Canberra", value: "Australia/Sydney" },
|
|
11057
|
-
{ label: "(UTC+10) Melbourne", value: "Australia/Melbourne" },
|
|
11058
|
-
{ label: "(UTC+10) Sydney", value: "Australia/Sydney" },
|
|
11059
|
-
// UTC+12
|
|
11060
|
-
{ label: "(UTC+12) Auckland", value: "Pacific/Auckland" },
|
|
11061
|
-
{ label: "(UTC+12) Fiji", value: "Pacific/Fiji" }
|
|
11062
|
-
];
|
|
11063
|
-
function getUtcOffsetLabel(iana) {
|
|
11064
|
-
try {
|
|
11065
|
-
const parts = new Intl.DateTimeFormat("en", {
|
|
11066
|
-
timeZone: iana,
|
|
11067
|
-
timeZoneName: "shortOffset"
|
|
11068
|
-
}).formatToParts(/* @__PURE__ */ new Date());
|
|
11069
|
-
const tzName = parts.find((p) => p.type === "timeZoneName")?.value ?? "GMT";
|
|
11070
|
-
const offset = tzName.replace(/^GMT/, "");
|
|
11071
|
-
return offset ? `(UTC${offset})` : "(UTC)";
|
|
11072
|
-
} catch {
|
|
11073
|
-
return "(UTC)";
|
|
11074
|
-
}
|
|
11075
|
-
}
|
|
11076
|
-
function getDisplayLabel(opt) {
|
|
11077
|
-
if (opt.value === "UTC" || opt.value === "exchange") return opt.label;
|
|
11078
|
-
const city = opt.label.replace(/^\(UTC[^)]*\)\s*/, "");
|
|
11079
|
-
return `${getUtcOffsetLabel(opt.value)} ${city}`;
|
|
11080
|
-
}
|
|
11081
|
-
var CRYPTO_EXCHANGES = /* @__PURE__ */ new Set([
|
|
11082
|
-
"BINANCE",
|
|
11083
|
-
"BINANCEUS",
|
|
11084
|
-
"BINANCEUSDM",
|
|
11085
|
-
"BINANCECOINM",
|
|
11086
|
-
"COINBASE",
|
|
11087
|
-
"COINBASEPRO",
|
|
11088
|
-
"KRAKEN",
|
|
11089
|
-
"KRAKENFX",
|
|
11090
|
-
"BYBIT",
|
|
11091
|
-
"BITGET",
|
|
11092
|
-
"OKX",
|
|
11093
|
-
"OKEX",
|
|
11094
|
-
"HUOBI",
|
|
11095
|
-
"HTX",
|
|
11096
|
-
"KUCOIN",
|
|
11097
|
-
"GEMINI",
|
|
11098
|
-
"BITSTAMP",
|
|
11099
|
-
"BITFINEX",
|
|
11100
|
-
"MEXC",
|
|
11101
|
-
"GATEIO",
|
|
11102
|
-
"GATE",
|
|
11103
|
-
"BITMEX",
|
|
11104
|
-
"DERIBIT"
|
|
11105
|
-
]);
|
|
11106
|
-
function symbolSupportsSession(symbol) {
|
|
11107
|
-
const exchange = symbol.includes(":") ? symbol.split(":")[0].toUpperCase() : "";
|
|
11108
|
-
return exchange !== "" && !CRYPTO_EXCHANGES.has(exchange);
|
|
11109
|
-
}
|
|
11110
|
-
function useClockTime(timezone) {
|
|
11111
|
-
const [time, setTime] = useState(() => _formatClock(timezone));
|
|
11112
|
-
useEffect(() => {
|
|
11113
|
-
setTime(_formatClock(timezone));
|
|
11114
|
-
const id = setInterval(() => setTime(_formatClock(timezone)), 1e3);
|
|
11115
|
-
return () => clearInterval(id);
|
|
11116
|
-
}, [timezone]);
|
|
11117
|
-
return time;
|
|
11118
|
-
}
|
|
11119
|
-
function _formatClock(timezone) {
|
|
11120
|
-
const tz = timezone === "exchange" ? "UTC" : timezone;
|
|
11121
|
-
try {
|
|
11122
|
-
return new Intl.DateTimeFormat("en-GB", {
|
|
11123
|
-
timeZone: tz,
|
|
11124
|
-
hour: "2-digit",
|
|
11125
|
-
minute: "2-digit",
|
|
11126
|
-
second: "2-digit",
|
|
11127
|
-
hour12: false
|
|
11128
|
-
}).format(/* @__PURE__ */ new Date());
|
|
11129
|
-
} catch {
|
|
11130
|
-
return new Intl.DateTimeFormat("en-GB", {
|
|
11131
|
-
timeZone: "UTC",
|
|
11132
|
-
hour: "2-digit",
|
|
11133
|
-
minute: "2-digit",
|
|
11134
|
-
second: "2-digit",
|
|
11135
|
-
hour12: false
|
|
11136
|
-
}).format(/* @__PURE__ */ new Date());
|
|
11137
|
-
}
|
|
12391
|
+
function applyRuntimeConfig(caps, config) {
|
|
12392
|
+
if (!config) return caps;
|
|
12393
|
+
const orderEntry = config.enableOrderEntry === false ? false : caps.orderEntry;
|
|
12394
|
+
const draggableOrders = config.enableDraggableOrders === false ? false : caps.draggableOrders;
|
|
12395
|
+
const bracketOrders = config.enableBracketOrders === false ? false : caps.bracketOrders;
|
|
12396
|
+
const managedTrading = config.enableManagedTrading === false ? false : caps.managedTrading;
|
|
12397
|
+
const indicators = config.enableIndicators === false ? false : caps.indicators;
|
|
12398
|
+
return {
|
|
12399
|
+
...caps,
|
|
12400
|
+
orderEntry,
|
|
12401
|
+
draggableOrders,
|
|
12402
|
+
bracketOrders,
|
|
12403
|
+
managedTrading,
|
|
12404
|
+
indicators,
|
|
12405
|
+
// Recalculate derived render flags from the updated feature flags.
|
|
12406
|
+
renderOrderEntry: orderEntry,
|
|
12407
|
+
renderBuySellButtons: orderEntry,
|
|
12408
|
+
renderOrderTicket: orderEntry,
|
|
12409
|
+
renderBracketControls: orderEntry && bracketOrders,
|
|
12410
|
+
renderOrderModificationControls: orderEntry && draggableOrders,
|
|
12411
|
+
renderManagedTradingControls: managedTrading,
|
|
12412
|
+
renderIndicators: indicators
|
|
12413
|
+
};
|
|
11138
12414
|
}
|
|
11139
|
-
function
|
|
11140
|
-
const
|
|
11141
|
-
const [open, setOpen] = useState(false);
|
|
11142
|
-
const [sessionOpen, setSessionOpen] = useState(false);
|
|
11143
|
-
const rootRef = useRef(null);
|
|
11144
|
-
const sessionRef = useRef(null);
|
|
11145
|
-
const clockTime = useClockTime(timezone);
|
|
11146
|
-
const current = TIMEZONE_OPTIONS.find((o) => o.value === timezone) ?? TIMEZONE_OPTIONS[0];
|
|
12415
|
+
function useChartCapabilities(config) {
|
|
12416
|
+
const [licCaps, setLicCaps] = useState(() => getCapabilities());
|
|
11147
12417
|
useEffect(() => {
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
|
|
12418
|
+
const unsub = LicenseManager.getInstance().subscribe(() => {
|
|
12419
|
+
setLicCaps(getCapabilities());
|
|
12420
|
+
});
|
|
12421
|
+
return unsub;
|
|
12422
|
+
}, []);
|
|
12423
|
+
return useMemo(() => applyRuntimeConfig(licCaps, config), [licCaps, config]);
|
|
12424
|
+
}
|
|
12425
|
+
|
|
12426
|
+
// src/react/trading/TradingBridge.ts
|
|
12427
|
+
function createTradingBridgeLogger(callbacks, label) {
|
|
12428
|
+
if (process.env.NODE_ENV === "production") return callbacks;
|
|
12429
|
+
const tag = label ? `[TradingBridge:${label}]` : "[TradingBridge]";
|
|
12430
|
+
return {
|
|
12431
|
+
onPlaceOrderIntent: (intent) => {
|
|
12432
|
+
console.debug(
|
|
12433
|
+
`${tag} PLACE side=%s type=%s qty=%s price=%s stopPrice=%s correlationId=%s`,
|
|
12434
|
+
intent.side,
|
|
12435
|
+
intent.orderType,
|
|
12436
|
+
intent.qty,
|
|
12437
|
+
intent.limitPrice ?? "\u2014",
|
|
12438
|
+
intent.stopPrice ?? "\u2014",
|
|
12439
|
+
intent.correlationId,
|
|
12440
|
+
intent
|
|
12441
|
+
);
|
|
12442
|
+
return callbacks.onPlaceOrderIntent?.(intent);
|
|
12443
|
+
},
|
|
12444
|
+
onModifyOrderIntent: (intent) => {
|
|
12445
|
+
console.debug(
|
|
12446
|
+
`${tag} MODIFY orderId=%s limitPrice=%s stopPrice=%s qty=%s`,
|
|
12447
|
+
intent.orderId,
|
|
12448
|
+
intent.limitPrice ?? "\u2014",
|
|
12449
|
+
intent.stopPrice ?? "\u2014",
|
|
12450
|
+
intent.qty ?? "\u2014",
|
|
12451
|
+
intent
|
|
12452
|
+
);
|
|
12453
|
+
return callbacks.onModifyOrderIntent?.(intent);
|
|
12454
|
+
},
|
|
12455
|
+
onCancelOrderIntent: (intent) => {
|
|
12456
|
+
console.debug(
|
|
12457
|
+
`${tag} CANCEL orderId=%s symbol=%s`,
|
|
12458
|
+
intent.orderId,
|
|
12459
|
+
intent.symbol,
|
|
12460
|
+
intent
|
|
12461
|
+
);
|
|
12462
|
+
return callbacks.onCancelOrderIntent?.(intent);
|
|
12463
|
+
},
|
|
12464
|
+
onBracketAdjustIntent: (intent) => {
|
|
12465
|
+
console.debug(
|
|
12466
|
+
`${tag} BRACKET_ADJUST entry=%s sl=%s tp=%s`,
|
|
12467
|
+
intent.entryOrderId,
|
|
12468
|
+
intent.stopLossPrice ?? "\u2014",
|
|
12469
|
+
intent.takeProfitPrice ?? "\u2014",
|
|
12470
|
+
intent
|
|
12471
|
+
);
|
|
12472
|
+
return callbacks.onBracketAdjustIntent?.(intent);
|
|
12473
|
+
}
|
|
12474
|
+
};
|
|
12475
|
+
}
|
|
12476
|
+
var TA_FUNCTIONS = [
|
|
12477
|
+
{ name: "sma(src, len)", desc: "Simple moving average" },
|
|
12478
|
+
{ name: "ema(src, len)", desc: "Exponential moving average" },
|
|
12479
|
+
{ name: "wma(src, len)", desc: "Weighted moving average" },
|
|
12480
|
+
{ name: "rma(src, len)", desc: "Wilder (RMA) moving average" },
|
|
12481
|
+
{ name: "stdev(src, len)", desc: "Standard deviation" },
|
|
12482
|
+
{ name: "highest(src, len)", desc: "Highest value over N bars" },
|
|
12483
|
+
{ name: "lowest(src, len)", desc: "Lowest value over N bars" },
|
|
12484
|
+
{ name: "atr(len)", desc: "Average true range" },
|
|
12485
|
+
{ name: "change(src)", desc: "Difference from previous bar" },
|
|
12486
|
+
{ name: "mom(src, len)", desc: "Momentum" },
|
|
12487
|
+
{ name: "crossover(a, b)", desc: "True when a crosses above b" },
|
|
12488
|
+
{ name: "crossunder(a, b)", desc: "True when a crosses below b" },
|
|
12489
|
+
{ name: "barssince(cond)", desc: "Bars since condition was true" }
|
|
12490
|
+
];
|
|
12491
|
+
var BUILT_IN_SERIES = [
|
|
12492
|
+
"open",
|
|
12493
|
+
"high",
|
|
12494
|
+
"low",
|
|
12495
|
+
"close",
|
|
12496
|
+
"volume",
|
|
12497
|
+
"hl2",
|
|
12498
|
+
"hlc3",
|
|
12499
|
+
"ohlc4",
|
|
12500
|
+
"bar_index"
|
|
12501
|
+
];
|
|
12502
|
+
var UTILITY_FUNCTIONS = [
|
|
12503
|
+
{ name: 'indicator("Title")', desc: "Declare the indicator name" },
|
|
12504
|
+
{ name: "input(default)", desc: "Create a user-configurable parameter" },
|
|
12505
|
+
{ name: "plot(value)", desc: "Plot a line on the chart" },
|
|
12506
|
+
{ name: "nz(val, rep?)", desc: "Replace NaN with rep (default 0)" },
|
|
12507
|
+
{ name: "na(val)", desc: "Returns true if value is NaN" }
|
|
12508
|
+
];
|
|
12509
|
+
var MATH_FUNCTIONS = ["abs", "max", "min", "round", "floor", "ceil", "sqrt", "log", "pow", "sign"];
|
|
12510
|
+
var TSCRIPT_TEMPLATE = `indicator("My Indicator")
|
|
12511
|
+
|
|
12512
|
+
// Parameters \u2014 shown in the UI
|
|
12513
|
+
length = input(14)
|
|
12514
|
+
fast = input(9)
|
|
12515
|
+
slow = input(21)
|
|
12516
|
+
|
|
12517
|
+
// Calculations using built-in TA functions
|
|
12518
|
+
fast_ma = ema(close, fast)
|
|
12519
|
+
slow_ma = ema(close, slow)
|
|
12520
|
+
signal = crossover(fast_ma, slow_ma)
|
|
12521
|
+
|
|
12522
|
+
// Plot results (overlay: true draws on price pane)
|
|
12523
|
+
plot(fast_ma)
|
|
12524
|
+
plot(slow_ma)
|
|
12525
|
+
`;
|
|
12526
|
+
var PINE_TEMPLATE = `//@version=5
|
|
12527
|
+
indicator("My Pine Indicator", overlay=false)
|
|
12528
|
+
|
|
12529
|
+
length = input.int(14, title="Length")
|
|
12530
|
+
src = input.source(close, title="Source")
|
|
12531
|
+
|
|
12532
|
+
value = ta.rsi(src, length)
|
|
12533
|
+
|
|
12534
|
+
plot(value, title="RSI", color=color.blue)
|
|
12535
|
+
hline(70, "Overbought", color=color.red)
|
|
12536
|
+
hline(30, "Oversold", color=color.green)
|
|
12537
|
+
`;
|
|
12538
|
+
function ScriptDrawer({ onClose, onAddIndicator }) {
|
|
12539
|
+
const [lang, setLang] = useState("tscript");
|
|
12540
|
+
const [code, setCode] = useState(TSCRIPT_TEMPLATE);
|
|
12541
|
+
const [overlay, setOverlay] = useState(false);
|
|
12542
|
+
const [errors, setErrors] = useState([]);
|
|
12543
|
+
const [refOpen, setRefOpen] = useState(false);
|
|
12544
|
+
const [drawerWidth, setDrawerWidth] = useState(380);
|
|
12545
|
+
const dragState = useRef(null);
|
|
12546
|
+
const onResizeMouseDown = (e) => {
|
|
12547
|
+
e.preventDefault();
|
|
12548
|
+
dragState.current = { startX: e.clientX, startW: drawerWidth };
|
|
12549
|
+
const onMove = (ev) => {
|
|
12550
|
+
if (!dragState.current) return;
|
|
12551
|
+
const delta = dragState.current.startX - ev.clientX;
|
|
12552
|
+
setDrawerWidth(Math.min(700, Math.max(280, dragState.current.startW + delta)));
|
|
11151
12553
|
};
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
if (!sessionOpen) return;
|
|
11157
|
-
const handler = (e) => {
|
|
11158
|
-
if (sessionRef.current && !sessionRef.current.contains(e.target)) setSessionOpen(false);
|
|
12554
|
+
const onUp = () => {
|
|
12555
|
+
dragState.current = null;
|
|
12556
|
+
window.removeEventListener("mousemove", onMove);
|
|
12557
|
+
window.removeEventListener("mouseup", onUp);
|
|
11159
12558
|
};
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
}
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
stroke: "currentColor",
|
|
11179
|
-
strokeWidth: "1.6",
|
|
11180
|
-
strokeLinecap: "round",
|
|
11181
|
-
children: [
|
|
11182
|
-
/* @__PURE__ */ jsx("polyline", { points: "4,6 2,8 4,10" }),
|
|
11183
|
-
/* @__PURE__ */ jsx("polyline", { points: "12,6 14,8 12,10" }),
|
|
11184
|
-
/* @__PURE__ */ jsx("line", { x1: "9", y1: "3", x2: "7", y2: "13" })
|
|
11185
|
-
]
|
|
11186
|
-
}
|
|
11187
|
-
),
|
|
11188
|
-
"Script"
|
|
11189
|
-
]
|
|
12559
|
+
window.addEventListener("mousemove", onMove);
|
|
12560
|
+
window.addEventListener("mouseup", onUp);
|
|
12561
|
+
};
|
|
12562
|
+
const handleLangChange = (next) => {
|
|
12563
|
+
setLang(next);
|
|
12564
|
+
setErrors([]);
|
|
12565
|
+
setCode(next === "tscript" ? TSCRIPT_TEMPLATE : PINE_TEMPLATE);
|
|
12566
|
+
};
|
|
12567
|
+
const handleAddToChart = () => {
|
|
12568
|
+
setErrors([]);
|
|
12569
|
+
if (lang === "pine") {
|
|
12570
|
+
const result = new PineCompiler().compile(code);
|
|
12571
|
+
if (!result.ok) {
|
|
12572
|
+
const msgs = result.diagnostics.map(
|
|
12573
|
+
(d) => `Line ${d.line ?? "?"}: ${d.message}`
|
|
12574
|
+
);
|
|
12575
|
+
setErrors(msgs.length ? msgs : ["Compilation failed \u2014 check your Pine Script."]);
|
|
12576
|
+
return;
|
|
11190
12577
|
}
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
12578
|
+
onAddIndicator({ type: "script", script: result.tscript, overlay });
|
|
12579
|
+
} else {
|
|
12580
|
+
onAddIndicator({ type: "script", script: code, overlay });
|
|
12581
|
+
}
|
|
12582
|
+
};
|
|
12583
|
+
return /* @__PURE__ */ jsxs("div", { className: "script-drawer", style: { width: drawerWidth }, children: [
|
|
12584
|
+
/* @__PURE__ */ jsx("div", { className: "script-drawer-resize", onMouseDown: onResizeMouseDown }),
|
|
12585
|
+
/* @__PURE__ */ jsxs("div", { className: "script-drawer-header", children: [
|
|
12586
|
+
/* @__PURE__ */ jsxs("span", { className: "script-drawer-title", children: [
|
|
11194
12587
|
/* @__PURE__ */ jsxs(
|
|
11195
|
-
"
|
|
12588
|
+
"svg",
|
|
11196
12589
|
{
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
12590
|
+
viewBox: "0 0 16 16",
|
|
12591
|
+
width: "14",
|
|
12592
|
+
height: "14",
|
|
12593
|
+
fill: "none",
|
|
12594
|
+
stroke: "currentColor",
|
|
12595
|
+
strokeWidth: "1.6",
|
|
12596
|
+
strokeLinecap: "round",
|
|
12597
|
+
style: { flexShrink: 0 },
|
|
11200
12598
|
children: [
|
|
11201
|
-
/* @__PURE__ */
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
/* @__PURE__ */ jsx("line", { x1: "1.5", y1: "7", x2: "12.5", y2: "7" }),
|
|
11205
|
-
/* @__PURE__ */ jsx("line", { x1: "2", y1: "4.5", x2: "12", y2: "4.5" }),
|
|
11206
|
-
/* @__PURE__ */ jsx("line", { x1: "2", y1: "9.5", x2: "12", y2: "9.5" })
|
|
11207
|
-
] }),
|
|
11208
|
-
getDisplayLabel(current)
|
|
12599
|
+
/* @__PURE__ */ jsx("polyline", { points: "4,6 2,8 4,10" }),
|
|
12600
|
+
/* @__PURE__ */ jsx("polyline", { points: "12,6 14,8 12,10" }),
|
|
12601
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "3", x2: "7", y2: "13" })
|
|
11209
12602
|
]
|
|
11210
12603
|
}
|
|
11211
12604
|
),
|
|
11212
|
-
|
|
11213
|
-
open && /* @__PURE__ */ jsxs("div", { className: "bottom-bar-dropdown bottom-bar-dropdown--right", children: [
|
|
11214
|
-
/* @__PURE__ */ jsx("div", { className: "bottom-bar-dropdown-section", children: "Timezone" }),
|
|
11215
|
-
TIMEZONE_OPTIONS.map((opt) => /* @__PURE__ */ jsxs(
|
|
11216
|
-
"button",
|
|
11217
|
-
{
|
|
11218
|
-
className: `bottom-bar-dropdown-item${opt.value === timezone ? " current" : ""}`,
|
|
11219
|
-
onClick: () => {
|
|
11220
|
-
onTimezoneChange(opt.value);
|
|
11221
|
-
setOpen(false);
|
|
11222
|
-
},
|
|
11223
|
-
children: [
|
|
11224
|
-
opt.value === timezone && /* @__PURE__ */ jsx("span", { className: "bottom-bar-check", children: "\xE2\u0153\u201C" }),
|
|
11225
|
-
getDisplayLabel(opt)
|
|
11226
|
-
]
|
|
11227
|
-
},
|
|
11228
|
-
opt.value + opt.label
|
|
11229
|
-
))
|
|
11230
|
-
] })
|
|
12605
|
+
"Script Engine"
|
|
11231
12606
|
] }),
|
|
11232
|
-
|
|
11233
|
-
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
|
|
11259
|
-
|
|
11260
|
-
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
12607
|
+
/* @__PURE__ */ jsx("button", { className: "script-close-btn", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsxs(
|
|
12608
|
+
"svg",
|
|
12609
|
+
{
|
|
12610
|
+
viewBox: "0 0 12 12",
|
|
12611
|
+
width: "12",
|
|
12612
|
+
height: "12",
|
|
12613
|
+
stroke: "currentColor",
|
|
12614
|
+
strokeWidth: "1.8",
|
|
12615
|
+
strokeLinecap: "round",
|
|
12616
|
+
children: [
|
|
12617
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "11", y2: "11" }),
|
|
12618
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "1", x2: "1", y2: "11" })
|
|
12619
|
+
]
|
|
12620
|
+
}
|
|
12621
|
+
) })
|
|
12622
|
+
] }),
|
|
12623
|
+
/* @__PURE__ */ jsxs("div", { className: "script-lang-tabs", children: [
|
|
12624
|
+
/* @__PURE__ */ jsx(
|
|
12625
|
+
"button",
|
|
12626
|
+
{
|
|
12627
|
+
className: `script-lang-tab${lang === "tscript" ? " active" : ""}`,
|
|
12628
|
+
onClick: () => handleLangChange("tscript"),
|
|
12629
|
+
children: "TScript"
|
|
12630
|
+
}
|
|
12631
|
+
),
|
|
12632
|
+
/* @__PURE__ */ jsx(
|
|
12633
|
+
"button",
|
|
12634
|
+
{
|
|
12635
|
+
className: `script-lang-tab${lang === "pine" ? " active" : ""}`,
|
|
12636
|
+
onClick: () => handleLangChange("pine"),
|
|
12637
|
+
children: "Pine Script\u2122"
|
|
12638
|
+
}
|
|
12639
|
+
)
|
|
12640
|
+
] }),
|
|
12641
|
+
/* @__PURE__ */ jsxs("div", { className: "script-ref-section", children: [
|
|
12642
|
+
/* @__PURE__ */ jsxs(
|
|
12643
|
+
"button",
|
|
12644
|
+
{
|
|
12645
|
+
className: "script-ref-toggle",
|
|
12646
|
+
onClick: () => setRefOpen((o) => !o),
|
|
12647
|
+
children: [
|
|
12648
|
+
/* @__PURE__ */ jsx(
|
|
12649
|
+
"svg",
|
|
12650
|
+
{
|
|
12651
|
+
viewBox: "0 0 10 6",
|
|
12652
|
+
width: "8",
|
|
12653
|
+
height: "6",
|
|
12654
|
+
fill: "currentColor",
|
|
12655
|
+
style: { transform: refOpen ? "rotate(180deg)" : "none", transition: "transform 0.15s" },
|
|
12656
|
+
children: /* @__PURE__ */ jsx("path", { d: "M0 0l5 6 5-6z" })
|
|
12657
|
+
}
|
|
12658
|
+
),
|
|
12659
|
+
"Built-in Reference"
|
|
12660
|
+
]
|
|
12661
|
+
}
|
|
12662
|
+
),
|
|
12663
|
+
refOpen && /* @__PURE__ */ jsxs("div", { className: "script-ref-body", children: [
|
|
12664
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Series" }),
|
|
12665
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: BUILT_IN_SERIES.map((s) => /* @__PURE__ */ jsx("span", { className: "script-feature-pill series", children: s }, s)) }),
|
|
12666
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "TA Functions" }),
|
|
12667
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: TA_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill ta", title: f.desc, children: [
|
|
12668
|
+
f.name.split("(")[0],
|
|
12669
|
+
"()"
|
|
12670
|
+
] }, f.name)) }),
|
|
12671
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Math" }),
|
|
12672
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: MATH_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill math", children: [
|
|
12673
|
+
f,
|
|
12674
|
+
"()"
|
|
12675
|
+
] }, f)) }),
|
|
12676
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-group-label", children: "Utility" }),
|
|
12677
|
+
/* @__PURE__ */ jsx("div", { className: "script-feature-list", children: UTILITY_FUNCTIONS.map((f) => /* @__PURE__ */ jsxs("span", { className: "script-feature-pill util", title: f.desc, children: [
|
|
12678
|
+
f.name.split("(")[0],
|
|
12679
|
+
"()"
|
|
12680
|
+
] }, f.name)) }),
|
|
12681
|
+
/* @__PURE__ */ jsx("div", { className: "script-ref-detail", children: [...TA_FUNCTIONS, ...UTILITY_FUNCTIONS].map((f) => /* @__PURE__ */ jsxs("div", { className: "script-ref-row", children: [
|
|
12682
|
+
/* @__PURE__ */ jsx("code", { className: "script-ref-fn", children: f.name }),
|
|
12683
|
+
/* @__PURE__ */ jsx("span", { className: "script-ref-desc", children: f.desc })
|
|
12684
|
+
] }, f.name)) })
|
|
12685
|
+
] })
|
|
12686
|
+
] }),
|
|
12687
|
+
/* @__PURE__ */ jsx("div", { className: "script-editor-wrap", children: /* @__PURE__ */ jsx(
|
|
12688
|
+
"textarea",
|
|
12689
|
+
{
|
|
12690
|
+
className: "script-editor",
|
|
12691
|
+
spellCheck: false,
|
|
12692
|
+
value: code,
|
|
12693
|
+
onChange: (e) => {
|
|
12694
|
+
setCode(e.target.value);
|
|
12695
|
+
setErrors([]);
|
|
12696
|
+
}
|
|
12697
|
+
}
|
|
12698
|
+
) }),
|
|
12699
|
+
errors.length > 0 && /* @__PURE__ */ jsx("div", { className: "script-errors", children: errors.map((msg, i) => /* @__PURE__ */ jsxs("div", { className: "script-error-line", children: [
|
|
12700
|
+
/* @__PURE__ */ jsxs(
|
|
12701
|
+
"svg",
|
|
12702
|
+
{
|
|
12703
|
+
viewBox: "0 0 12 12",
|
|
12704
|
+
width: "11",
|
|
12705
|
+
height: "11",
|
|
12706
|
+
fill: "none",
|
|
12707
|
+
stroke: "currentColor",
|
|
12708
|
+
strokeWidth: "1.6",
|
|
12709
|
+
strokeLinecap: "round",
|
|
12710
|
+
style: { flexShrink: 0, color: "var(--down, #ef5350)" },
|
|
12711
|
+
children: [
|
|
12712
|
+
/* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5" }),
|
|
12713
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "3.5", x2: "6", y2: "6.5" }),
|
|
12714
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "8", x2: "6", y2: "8.5" })
|
|
12715
|
+
]
|
|
12716
|
+
}
|
|
12717
|
+
),
|
|
12718
|
+
msg
|
|
12719
|
+
] }, i)) }),
|
|
12720
|
+
/* @__PURE__ */ jsxs("div", { className: "script-footer", children: [
|
|
12721
|
+
/* @__PURE__ */ jsxs("label", { className: "script-overlay-label", children: [
|
|
12722
|
+
/* @__PURE__ */ jsx(
|
|
12723
|
+
"input",
|
|
12724
|
+
{
|
|
12725
|
+
type: "checkbox",
|
|
12726
|
+
checked: overlay,
|
|
12727
|
+
onChange: (e) => setOverlay(e.target.checked)
|
|
12728
|
+
}
|
|
12729
|
+
),
|
|
12730
|
+
"Overlay on price"
|
|
12731
|
+
] }),
|
|
12732
|
+
/* @__PURE__ */ jsxs("button", { className: "script-run-btn", onClick: handleAddToChart, children: [
|
|
12733
|
+
/* @__PURE__ */ jsx(
|
|
12734
|
+
"svg",
|
|
12735
|
+
{
|
|
12736
|
+
viewBox: "0 0 12 12",
|
|
12737
|
+
width: "11",
|
|
12738
|
+
height: "11",
|
|
12739
|
+
fill: "currentColor",
|
|
12740
|
+
style: { flexShrink: 0 },
|
|
12741
|
+
children: /* @__PURE__ */ jsx("polygon", { points: "2,1 11,6 2,11" })
|
|
12742
|
+
}
|
|
12743
|
+
),
|
|
12744
|
+
"Add to Chart"
|
|
11287
12745
|
] })
|
|
11288
12746
|
] })
|
|
11289
12747
|
] });
|
|
11290
12748
|
}
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11335
|
-
|
|
11336
|
-
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
12749
|
+
|
|
12750
|
+
// src/react/shell/watchlistApi.ts
|
|
12751
|
+
var _apiBase = "/api";
|
|
12752
|
+
var _memToken = null;
|
|
12753
|
+
var _getAuthToken = null;
|
|
12754
|
+
async function _resolveToken() {
|
|
12755
|
+
if (_getAuthToken) {
|
|
12756
|
+
try {
|
|
12757
|
+
return await _getAuthToken();
|
|
12758
|
+
} catch {
|
|
12759
|
+
}
|
|
12760
|
+
}
|
|
12761
|
+
if (_memToken) return _memToken;
|
|
12762
|
+
if (typeof localStorage !== "undefined") return localStorage.getItem("forgecharts-token");
|
|
12763
|
+
return null;
|
|
12764
|
+
}
|
|
12765
|
+
async function _authHeader() {
|
|
12766
|
+
const token = await _resolveToken();
|
|
12767
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
12768
|
+
}
|
|
12769
|
+
async function apiFetch(method, path, body) {
|
|
12770
|
+
const bodyInit = body !== void 0 ? JSON.stringify(body) : null;
|
|
12771
|
+
const res = await fetch(`${_apiBase}${path}`, {
|
|
12772
|
+
method,
|
|
12773
|
+
headers: { "Content-Type": "application/json", ...await _authHeader() },
|
|
12774
|
+
...bodyInit !== null ? { body: bodyInit } : {}
|
|
12775
|
+
});
|
|
12776
|
+
if (!res.ok) {
|
|
12777
|
+
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
12778
|
+
throw new Error(err.error ?? res.statusText);
|
|
12779
|
+
}
|
|
12780
|
+
if (res.status === 204) return void 0;
|
|
12781
|
+
return res.json();
|
|
12782
|
+
}
|
|
12783
|
+
var watchlistApi = {
|
|
12784
|
+
configure(cfg) {
|
|
12785
|
+
if (cfg.apiBase) _apiBase = cfg.apiBase;
|
|
12786
|
+
if (cfg.getAuthToken) _getAuthToken = cfg.getAuthToken;
|
|
12787
|
+
if (cfg.token) _memToken = cfg.token;
|
|
12788
|
+
},
|
|
12789
|
+
setToken(token) {
|
|
12790
|
+
_memToken = token;
|
|
12791
|
+
},
|
|
12792
|
+
getAll() {
|
|
12793
|
+
return apiFetch("GET", "/watchlist");
|
|
12794
|
+
},
|
|
12795
|
+
addItem(symbol, group_id) {
|
|
12796
|
+
return apiFetch("POST", "/watchlist/items", { symbol, group_id });
|
|
12797
|
+
},
|
|
12798
|
+
removeItem(id) {
|
|
12799
|
+
return apiFetch("DELETE", `/watchlist/items/${encodeURIComponent(id)}`);
|
|
12800
|
+
},
|
|
12801
|
+
moveItemToGroup(id, group_id) {
|
|
12802
|
+
return apiFetch("PATCH", `/watchlist/items/${encodeURIComponent(id)}`, { group_id });
|
|
12803
|
+
},
|
|
12804
|
+
addGroup(name) {
|
|
12805
|
+
return apiFetch("POST", "/watchlist/groups", { name });
|
|
12806
|
+
},
|
|
12807
|
+
removeGroup(id) {
|
|
12808
|
+
return apiFetch("DELETE", `/watchlist/groups/${encodeURIComponent(id)}`);
|
|
12809
|
+
},
|
|
12810
|
+
reorderItems(ids) {
|
|
12811
|
+
return apiFetch("PUT", "/watchlist/items/reorder", { ids });
|
|
12812
|
+
}
|
|
12813
|
+
};
|
|
12814
|
+
var _ws = null;
|
|
12815
|
+
var _listeners = /* @__PURE__ */ new Set();
|
|
12816
|
+
var _subscribed = /* @__PURE__ */ new Set();
|
|
12817
|
+
var _reconnectTimer = null;
|
|
12818
|
+
function _wsUrl() {
|
|
12819
|
+
if (typeof window === "undefined") return "ws://localhost:4001/ws";
|
|
12820
|
+
const proto = window.location.protocol === "https:" ? "wss" : "ws";
|
|
12821
|
+
return `${proto}://${window.location.host}/ws`;
|
|
12822
|
+
}
|
|
12823
|
+
function _send(msg) {
|
|
12824
|
+
if (_ws?.readyState === WebSocket.OPEN) {
|
|
12825
|
+
_ws.send(JSON.stringify(msg));
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
function _ensureConnected() {
|
|
12829
|
+
if (_ws && (_ws.readyState === WebSocket.CONNECTING || _ws.readyState === WebSocket.OPEN)) return;
|
|
12830
|
+
if (_reconnectTimer) {
|
|
12831
|
+
clearTimeout(_reconnectTimer);
|
|
12832
|
+
_reconnectTimer = null;
|
|
12833
|
+
}
|
|
12834
|
+
_ws = new WebSocket(_wsUrl());
|
|
12835
|
+
_ws.onopen = () => {
|
|
12836
|
+
for (const sym of _subscribed) {
|
|
12837
|
+
_send({ type: "subscribe_quote", symbol: sym });
|
|
12838
|
+
}
|
|
12839
|
+
};
|
|
12840
|
+
_ws.onmessage = (event) => {
|
|
12841
|
+
try {
|
|
12842
|
+
const msg = JSON.parse(event.data);
|
|
12843
|
+
if (msg.type === "quote") {
|
|
12844
|
+
for (const listener of _listeners) {
|
|
12845
|
+
listener(msg.payload);
|
|
12846
|
+
}
|
|
12847
|
+
}
|
|
12848
|
+
} catch {
|
|
12849
|
+
}
|
|
12850
|
+
};
|
|
12851
|
+
_ws.onclose = () => {
|
|
12852
|
+
_ws = null;
|
|
12853
|
+
if (_subscribed.size > 0) {
|
|
12854
|
+
_reconnectTimer = setTimeout(_ensureConnected, 3e3);
|
|
12855
|
+
}
|
|
12856
|
+
};
|
|
12857
|
+
}
|
|
12858
|
+
function _addListener(fn) {
|
|
12859
|
+
_listeners.add(fn);
|
|
12860
|
+
}
|
|
12861
|
+
function _removeListener(fn) {
|
|
12862
|
+
_listeners.delete(fn);
|
|
12863
|
+
}
|
|
12864
|
+
function _subscribe(symbols) {
|
|
12865
|
+
_ensureConnected();
|
|
12866
|
+
for (const sym of symbols) {
|
|
12867
|
+
if (!_subscribed.has(sym)) {
|
|
12868
|
+
_subscribed.add(sym);
|
|
12869
|
+
_send({ type: "subscribe_quote", symbol: sym });
|
|
12870
|
+
}
|
|
12871
|
+
}
|
|
12872
|
+
}
|
|
12873
|
+
function _unsubscribe(symbols) {
|
|
12874
|
+
for (const sym of symbols) {
|
|
12875
|
+
_subscribed.delete(sym);
|
|
12876
|
+
_send({ type: "unsubscribe_quote", symbol: sym });
|
|
12877
|
+
}
|
|
12878
|
+
if (_subscribed.size === 0 && _ws) {
|
|
12879
|
+
_ws.close();
|
|
12880
|
+
_ws = null;
|
|
12881
|
+
}
|
|
12882
|
+
}
|
|
12883
|
+
function useWatchlistQuotes(symbols) {
|
|
12884
|
+
const [prices, setPrices] = useState(/* @__PURE__ */ new Map());
|
|
12885
|
+
const [dirs, setDirs] = useState(/* @__PURE__ */ new Map());
|
|
12886
|
+
const symbolsKey = symbols.slice().sort().join(",");
|
|
12887
|
+
const listenerRef = useRef(null);
|
|
12888
|
+
const onQuote = useCallback((payload) => {
|
|
12889
|
+
if (!_subscribed.has(payload.symbol)) return;
|
|
12890
|
+
setPrices((prev) => {
|
|
12891
|
+
const next = new Map(prev);
|
|
12892
|
+
next.set(payload.symbol, payload.last);
|
|
12893
|
+
return next;
|
|
12894
|
+
});
|
|
12895
|
+
if (payload.dir) {
|
|
12896
|
+
const dir = payload.dir;
|
|
12897
|
+
setDirs((prev) => {
|
|
12898
|
+
const next = new Map(prev);
|
|
12899
|
+
next.set(payload.symbol, dir);
|
|
12900
|
+
return next;
|
|
12901
|
+
});
|
|
12902
|
+
setTimeout(() => {
|
|
12903
|
+
setDirs((prev) => {
|
|
12904
|
+
const next = new Map(prev);
|
|
12905
|
+
next.delete(payload.symbol);
|
|
12906
|
+
return next;
|
|
12907
|
+
});
|
|
12908
|
+
}, 650);
|
|
12909
|
+
}
|
|
12910
|
+
}, []);
|
|
12911
|
+
useEffect(() => {
|
|
12912
|
+
if (listenerRef.current) _removeListener(listenerRef.current);
|
|
12913
|
+
listenerRef.current = onQuote;
|
|
12914
|
+
_addListener(onQuote);
|
|
12915
|
+
return () => {
|
|
12916
|
+
if (listenerRef.current) _removeListener(listenerRef.current);
|
|
12917
|
+
listenerRef.current = null;
|
|
12918
|
+
};
|
|
12919
|
+
}, [onQuote]);
|
|
12920
|
+
useEffect(() => {
|
|
12921
|
+
const syms = symbols.filter(Boolean);
|
|
12922
|
+
if (syms.length > 0) _subscribe(syms);
|
|
12923
|
+
return () => {
|
|
12924
|
+
if (syms.length > 0) _unsubscribe(syms);
|
|
12925
|
+
};
|
|
12926
|
+
}, [symbolsKey]);
|
|
12927
|
+
return { prices, dirs };
|
|
12928
|
+
}
|
|
12929
|
+
function fmtPrice(v) {
|
|
12930
|
+
const abs = Math.abs(v);
|
|
12931
|
+
const dec = abs >= 1e3 ? 2 : abs >= 1 ? 2 : abs >= 1e-4 ? 4 : 6;
|
|
12932
|
+
return v.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
|
|
12933
|
+
}
|
|
12934
|
+
function SymbolRow({
|
|
12935
|
+
item,
|
|
12936
|
+
quote,
|
|
12937
|
+
flash,
|
|
12938
|
+
dragOver,
|
|
12939
|
+
logoUrl,
|
|
12940
|
+
onSelect,
|
|
12941
|
+
onRemove,
|
|
12942
|
+
onContextMenu,
|
|
12943
|
+
onDragStart,
|
|
12944
|
+
onDragOver,
|
|
12945
|
+
onDrop,
|
|
12946
|
+
onDragEnd
|
|
11346
12947
|
}) {
|
|
11347
|
-
const
|
|
11348
|
-
|
|
11349
|
-
label: t.label,
|
|
11350
|
-
...t.isSaved !== void 0 ? { isSaved: t.isSaved } : {}
|
|
11351
|
-
}));
|
|
12948
|
+
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)";
|
|
12949
|
+
const changeColor = quote ? quote.change >= 0 ? "var(--wl-up)" : "var(--wl-down)" : "var(--text-muted)";
|
|
11352
12950
|
return /* @__PURE__ */ jsxs(
|
|
11353
12951
|
"div",
|
|
11354
12952
|
{
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
12953
|
+
className: `wl-row${dragOver ? " wl-drag-over" : ""}`,
|
|
12954
|
+
draggable: true,
|
|
12955
|
+
onDragStart,
|
|
12956
|
+
onDragOver,
|
|
12957
|
+
onDrop,
|
|
12958
|
+
onDragEnd,
|
|
12959
|
+
onClick: onSelect,
|
|
12960
|
+
onContextMenu,
|
|
12961
|
+
title: item.symbol,
|
|
11363
12962
|
children: [
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", fill: "none", stroke: "var(--border)", strokeWidth: "3" }),
|
|
11375
|
-
/* @__PURE__ */ jsx("path", { d: "M16 4 A12 12 0 0 1 28 16", fill: "none", stroke: "var(--primary)", strokeWidth: "3", strokeLinecap: "round" })
|
|
11376
|
-
] }),
|
|
11377
|
-
/* @__PURE__ */ jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
11378
|
-
] }),
|
|
11379
|
-
/* @__PURE__ */ jsx(
|
|
11380
|
-
TabBar,
|
|
11381
|
-
{
|
|
11382
|
-
tabs: tabItems,
|
|
11383
|
-
activeId: activeTabId,
|
|
11384
|
-
onSelect: onSelectTab,
|
|
11385
|
-
onAdd: onAddTab,
|
|
11386
|
-
onClose: onCloseTab,
|
|
11387
|
-
onRename: onRenameTab
|
|
11388
|
-
}
|
|
11389
|
-
),
|
|
11390
|
-
/* @__PURE__ */ jsx(
|
|
11391
|
-
TopToolbar,
|
|
12963
|
+
/* @__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: [
|
|
12964
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "0", width: "2", height: "2", rx: "1" }),
|
|
12965
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "5", width: "2", height: "2", rx: "1" }),
|
|
12966
|
+
/* @__PURE__ */ jsx("rect", { x: "0", y: "10", width: "2", height: "2", rx: "1" }),
|
|
12967
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "0", width: "2", height: "2", rx: "1" }),
|
|
12968
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "2", height: "2", rx: "1" }),
|
|
12969
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "10", width: "2", height: "2", rx: "1" })
|
|
12970
|
+
] }) }),
|
|
12971
|
+
item.symbol.includes(":") && /* @__PURE__ */ jsx(
|
|
12972
|
+
"img",
|
|
11392
12973
|
{
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
onCopyScreenshot,
|
|
11405
|
-
onDownloadScreenshot,
|
|
11406
|
-
onFullscreen,
|
|
11407
|
-
isFullscreen,
|
|
11408
|
-
currentLayoutName,
|
|
11409
|
-
currentLayoutId,
|
|
11410
|
-
autoSave,
|
|
11411
|
-
onFetchLayouts,
|
|
11412
|
-
onSaveLayout,
|
|
11413
|
-
onLoadLayout,
|
|
11414
|
-
onRenameLayout,
|
|
11415
|
-
onCopyLayout,
|
|
11416
|
-
onToggleAutoSave,
|
|
11417
|
-
onDeleteLayout,
|
|
11418
|
-
onOpenLayoutInNewTab,
|
|
11419
|
-
symbolResolver,
|
|
11420
|
-
...showTradeButton !== void 0 ? { showTradeButton } : {},
|
|
11421
|
-
...tradeDrawerOpen !== void 0 ? { tradeDrawerOpen } : {},
|
|
11422
|
-
...onToggleTradeDrawer !== void 0 ? { onToggleTradeDrawer } : {}
|
|
12974
|
+
className: "wl-logo",
|
|
12975
|
+
src: logoUrl ?? `/api/logos/${encodeURIComponent(item.symbol.split(":")[0].toUpperCase())}`,
|
|
12976
|
+
alt: "",
|
|
12977
|
+
onError: (e) => {
|
|
12978
|
+
const img = e.currentTarget;
|
|
12979
|
+
if (logoUrl && !img.src.includes("/api/logos/")) {
|
|
12980
|
+
img.src = `/api/logos/${encodeURIComponent(item.symbol.split(":")[0].toUpperCase())}`;
|
|
12981
|
+
} else {
|
|
12982
|
+
img.style.display = "none";
|
|
12983
|
+
}
|
|
12984
|
+
}
|
|
11423
12985
|
}
|
|
11424
12986
|
),
|
|
11425
|
-
/* @__PURE__ */
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
}, children: [
|
|
11439
|
-
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "40", height: "40", fill: "none", stroke: "rgba(255,255,255,0.7)", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
11440
|
-
/* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }),
|
|
11441
|
-
/* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0110 0v4" })
|
|
11442
|
-
] }),
|
|
11443
|
-
/* @__PURE__ */ jsx("div", { style: { color: "#fff", fontWeight: 700, fontSize: 18, letterSpacing: "0.01em" }, children: "Unlicensed" }),
|
|
11444
|
-
/* @__PURE__ */ jsx("div", { style: { color: "rgba(255,255,255,0.65)", fontSize: 13 }, children: "Please activate your license in the admin studio" })
|
|
11445
|
-
] }),
|
|
11446
|
-
/* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minWidth: 0, minHeight: 0 }, children: chartSlots }),
|
|
11447
|
-
/* @__PURE__ */ jsx(
|
|
11448
|
-
RightToolbar,
|
|
11449
|
-
{
|
|
11450
|
-
watchlistOpen,
|
|
11451
|
-
onToggleWatchlist
|
|
11452
|
-
}
|
|
11453
|
-
)
|
|
12987
|
+
/* @__PURE__ */ jsx("span", { className: "wl-symbol", children: item.symbol }),
|
|
12988
|
+
quote ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12989
|
+
/* @__PURE__ */ jsx(
|
|
12990
|
+
"span",
|
|
12991
|
+
{
|
|
12992
|
+
className: `wl-last${flash ? ` wl-flash-${flash}` : ""}`,
|
|
12993
|
+
style: { color: liveColor },
|
|
12994
|
+
children: fmtPrice(quote.last)
|
|
12995
|
+
}
|
|
12996
|
+
),
|
|
12997
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-change", style: { color: changeColor }, children: [
|
|
12998
|
+
quote.change >= 0 ? "+" : "",
|
|
12999
|
+
fmtPrice(quote.change)
|
|
11454
13000
|
] }),
|
|
11455
|
-
|
|
11456
|
-
|
|
13001
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-pct", style: { color: changeColor }, children: [
|
|
13002
|
+
quote.change >= 0 ? "+" : "",
|
|
13003
|
+
quote.changePct.toFixed(2),
|
|
13004
|
+
"%"
|
|
13005
|
+
] })
|
|
13006
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "wl-no-data", children: "\u2014" }),
|
|
11457
13007
|
/* @__PURE__ */ jsx(
|
|
11458
|
-
|
|
13008
|
+
"button",
|
|
11459
13009
|
{
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
13010
|
+
className: "wl-remove-btn",
|
|
13011
|
+
title: "Remove",
|
|
13012
|
+
onClick: (e) => {
|
|
13013
|
+
e.stopPropagation();
|
|
13014
|
+
onRemove();
|
|
13015
|
+
},
|
|
13016
|
+
children: "\u2715"
|
|
11467
13017
|
}
|
|
11468
13018
|
)
|
|
11469
13019
|
]
|
|
11470
13020
|
}
|
|
11471
13021
|
);
|
|
11472
13022
|
}
|
|
11473
|
-
function
|
|
11474
|
-
|
|
11475
|
-
const
|
|
11476
|
-
const
|
|
11477
|
-
const
|
|
11478
|
-
const
|
|
11479
|
-
const
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
13023
|
+
function WatchlistDrawer({ onClose, onSelectSymbol, symbolResolver }) {
|
|
13024
|
+
const [groups, setGroups] = useState([]);
|
|
13025
|
+
const [items, setItems] = useState([]);
|
|
13026
|
+
const [loading, setLoading] = useState(true);
|
|
13027
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
13028
|
+
const [addError, setAddError] = useState("");
|
|
13029
|
+
const [ctxMenu, setCtxMenu] = useState(null);
|
|
13030
|
+
const [groupDialog, setGroupDialog] = useState(null);
|
|
13031
|
+
const [groupName, setGroupName] = useState("");
|
|
13032
|
+
const [collapsed, setCollapsed] = useState(/* @__PURE__ */ new Set());
|
|
13033
|
+
const [logoMap, setLogoMap] = useState(/* @__PURE__ */ new Map());
|
|
13034
|
+
const [drawerWidth, setDrawerWidth] = useState(360);
|
|
13035
|
+
const [colWidths, setColWidths] = useState({ last: 70, chg: 70, pct: 60, vol: 52 });
|
|
13036
|
+
const [dragItemId, setDragItemId] = useState(null);
|
|
13037
|
+
const [dropTargetId, setDropTargetId] = useState(null);
|
|
13038
|
+
const ctxRef = useRef(null);
|
|
13039
|
+
const drawerRef = useRef(null);
|
|
13040
|
+
const dragState = useRef(null);
|
|
13041
|
+
const colDragRef = useRef(null);
|
|
13042
|
+
const onColResizeMouseDown = (e, col) => {
|
|
13043
|
+
e.preventDefault();
|
|
13044
|
+
e.stopPropagation();
|
|
13045
|
+
colDragRef.current = { col, startX: e.clientX, startW: colWidths[col] };
|
|
13046
|
+
const onMove = (ev) => {
|
|
13047
|
+
if (!colDragRef.current) return;
|
|
13048
|
+
const { col: c, startX, startW } = colDragRef.current;
|
|
13049
|
+
const next = Math.max(36, startW + (ev.clientX - startX));
|
|
13050
|
+
setColWidths((prev) => ({ ...prev, [c]: next }));
|
|
13051
|
+
};
|
|
13052
|
+
const onUp = () => {
|
|
13053
|
+
colDragRef.current = null;
|
|
13054
|
+
window.removeEventListener("mousemove", onMove);
|
|
13055
|
+
window.removeEventListener("mouseup", onUp);
|
|
13056
|
+
};
|
|
13057
|
+
window.addEventListener("mousemove", onMove);
|
|
13058
|
+
window.addEventListener("mouseup", onUp);
|
|
11495
13059
|
};
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
13060
|
+
const onResizeMouseDown = (e) => {
|
|
13061
|
+
e.preventDefault();
|
|
13062
|
+
dragState.current = { startX: e.clientX, startW: drawerWidth };
|
|
13063
|
+
const onMove = (ev) => {
|
|
13064
|
+
if (!dragState.current) return;
|
|
13065
|
+
const delta = dragState.current.startX - ev.clientX;
|
|
13066
|
+
const next = Math.min(600, Math.max(240, dragState.current.startW + delta));
|
|
13067
|
+
setDrawerWidth(next);
|
|
13068
|
+
};
|
|
13069
|
+
const onUp = () => {
|
|
13070
|
+
dragState.current = null;
|
|
13071
|
+
window.removeEventListener("mousemove", onMove);
|
|
13072
|
+
window.removeEventListener("mouseup", onUp);
|
|
13073
|
+
};
|
|
13074
|
+
window.addEventListener("mousemove", onMove);
|
|
13075
|
+
window.addEventListener("mouseup", onUp);
|
|
13076
|
+
};
|
|
13077
|
+
const reload = useCallback(async () => {
|
|
13078
|
+
try {
|
|
13079
|
+
const data = await watchlistApi.getAll();
|
|
13080
|
+
setGroups(data.groups);
|
|
13081
|
+
setItems(data.items);
|
|
13082
|
+
} catch {
|
|
13083
|
+
} finally {
|
|
13084
|
+
setLoading(false);
|
|
13085
|
+
}
|
|
13086
|
+
}, []);
|
|
11499
13087
|
useEffect(() => {
|
|
11500
|
-
|
|
11501
|
-
|
|
13088
|
+
reload();
|
|
13089
|
+
}, [reload]);
|
|
13090
|
+
useEffect(() => {
|
|
13091
|
+
if (items.length === 0) return;
|
|
13092
|
+
const syms = [...new Set(items.map((i) => i.symbol))].join(",");
|
|
13093
|
+
fetch(`/api/reference/symbols/logos?symbols=${encodeURIComponent(syms)}`).then((r) => r.ok ? r.json() : {}).then((map) => setLogoMap(new Map(Object.entries(map)))).catch(() => {
|
|
11502
13094
|
});
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
);
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
13095
|
+
}, [items]);
|
|
13096
|
+
const symbols = items.map((i) => i.symbol);
|
|
13097
|
+
const { prices: livePrices, dirs: liveDirs } = useWatchlistQuotes(symbols);
|
|
13098
|
+
const sessionOpenRef = useRef(/* @__PURE__ */ new Map());
|
|
13099
|
+
const quotes = useRef(/* @__PURE__ */ new Map()).current;
|
|
13100
|
+
quotes.clear();
|
|
13101
|
+
for (const [sym, last] of livePrices) {
|
|
13102
|
+
if (!sessionOpenRef.current.has(sym)) {
|
|
13103
|
+
sessionOpenRef.current.set(sym, last);
|
|
13104
|
+
}
|
|
13105
|
+
const open = sessionOpenRef.current.get(sym);
|
|
13106
|
+
const change = last - open;
|
|
13107
|
+
quotes.set(sym, {
|
|
13108
|
+
last,
|
|
13109
|
+
change,
|
|
13110
|
+
changePct: open !== 0 ? change / open * 100 : 0
|
|
13111
|
+
});
|
|
13112
|
+
}
|
|
13113
|
+
const prevItemsRef = useRef([]);
|
|
13114
|
+
if (prevItemsRef.current !== items) {
|
|
13115
|
+
const prevSyms = new Set(prevItemsRef.current.map((i) => i.symbol));
|
|
13116
|
+
const nextSyms = new Set(items.map((i) => i.symbol));
|
|
13117
|
+
for (const s of prevSyms) {
|
|
13118
|
+
if (!nextSyms.has(s)) sessionOpenRef.current.delete(s);
|
|
13119
|
+
}
|
|
13120
|
+
prevItemsRef.current = items;
|
|
13121
|
+
}
|
|
13122
|
+
useEffect(() => {
|
|
13123
|
+
if (!ctxMenu) return;
|
|
13124
|
+
const handler = (e) => {
|
|
13125
|
+
if (ctxRef.current && !ctxRef.current.contains(e.target)) setCtxMenu(null);
|
|
13126
|
+
};
|
|
13127
|
+
document.addEventListener("mousedown", handler);
|
|
13128
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
13129
|
+
}, [ctxMenu]);
|
|
13130
|
+
const handleAddFromSearch = useCallback(async (symbol) => {
|
|
13131
|
+
setSearchOpen(false);
|
|
13132
|
+
setAddError("");
|
|
13133
|
+
try {
|
|
13134
|
+
await watchlistApi.addItem(symbol);
|
|
13135
|
+
await reload();
|
|
13136
|
+
} catch (err) {
|
|
13137
|
+
setAddError(err instanceof Error ? err.message : "Failed to add");
|
|
13138
|
+
}
|
|
13139
|
+
}, [reload]);
|
|
13140
|
+
const handleRemove = async (id) => {
|
|
13141
|
+
await watchlistApi.removeItem(id);
|
|
13142
|
+
await reload();
|
|
13143
|
+
};
|
|
13144
|
+
const openCtxMenu = (e, item) => {
|
|
13145
|
+
e.preventDefault();
|
|
13146
|
+
setCtxMenu({ x: e.clientX, y: e.clientY, itemId: item.id, symbol: item.symbol });
|
|
13147
|
+
};
|
|
13148
|
+
const openGroupDialog = (itemId) => {
|
|
13149
|
+
setCtxMenu(null);
|
|
13150
|
+
setGroupName("");
|
|
13151
|
+
setGroupDialog({ itemId });
|
|
13152
|
+
};
|
|
13153
|
+
const handleCreateGroup = async () => {
|
|
13154
|
+
const name = groupName.trim();
|
|
13155
|
+
if (!name || !groupDialog) return;
|
|
13156
|
+
const group = await watchlistApi.addGroup(name);
|
|
13157
|
+
await watchlistApi.moveItemToGroup(groupDialog.itemId, group.id);
|
|
13158
|
+
setGroupDialog(null);
|
|
13159
|
+
await reload();
|
|
13160
|
+
};
|
|
13161
|
+
const handleMoveToExisting = async (itemId, groupId) => {
|
|
13162
|
+
setCtxMenu(null);
|
|
13163
|
+
await watchlistApi.moveItemToGroup(itemId, groupId);
|
|
13164
|
+
await reload();
|
|
13165
|
+
};
|
|
13166
|
+
const handleUngroup = async (itemId) => {
|
|
13167
|
+
setCtxMenu(null);
|
|
13168
|
+
await watchlistApi.moveItemToGroup(itemId, null);
|
|
13169
|
+
await reload();
|
|
13170
|
+
};
|
|
13171
|
+
const handleDragStart = (e, itemId) => {
|
|
13172
|
+
setDragItemId(itemId);
|
|
13173
|
+
e.dataTransfer.effectAllowed = "move";
|
|
13174
|
+
};
|
|
13175
|
+
const handleDragOver = (e, targetId) => {
|
|
13176
|
+
e.preventDefault();
|
|
13177
|
+
e.dataTransfer.dropEffect = "move";
|
|
13178
|
+
if (targetId !== dragItemId) setDropTargetId(targetId);
|
|
13179
|
+
};
|
|
13180
|
+
const handleDrop = async (e, targetId) => {
|
|
13181
|
+
e.preventDefault();
|
|
13182
|
+
if (!dragItemId || dragItemId === targetId) return;
|
|
13183
|
+
setDragItemId(null);
|
|
13184
|
+
setDropTargetId(null);
|
|
13185
|
+
const reorder = (list) => {
|
|
13186
|
+
const from = list.findIndex((i) => i.id === dragItemId);
|
|
13187
|
+
const to = list.findIndex((i) => i.id === targetId);
|
|
13188
|
+
if (from === -1 || to === -1) return list;
|
|
13189
|
+
const next = [...list];
|
|
13190
|
+
const [moved] = next.splice(from, 1);
|
|
13191
|
+
next.splice(to, 0, moved);
|
|
13192
|
+
return next;
|
|
13193
|
+
};
|
|
13194
|
+
const newItems = (() => {
|
|
13195
|
+
const ungr = items.filter((i) => i.group_id === null);
|
|
13196
|
+
const newUngr = reorder(ungr);
|
|
13197
|
+
if (newUngr !== ungr) {
|
|
13198
|
+
return [...newUngr, ...items.filter((i) => i.group_id !== null)];
|
|
13199
|
+
}
|
|
13200
|
+
const result = [...items];
|
|
13201
|
+
for (const group of groups) {
|
|
13202
|
+
const grp = items.filter((i) => i.group_id === group.id);
|
|
13203
|
+
const newGrp = reorder(grp);
|
|
13204
|
+
if (newGrp !== grp) {
|
|
13205
|
+
const base = result.filter((i) => i.group_id !== group.id);
|
|
13206
|
+
return [...base, ...newGrp];
|
|
13207
|
+
}
|
|
13208
|
+
}
|
|
13209
|
+
return result;
|
|
13210
|
+
})();
|
|
13211
|
+
setItems(newItems);
|
|
13212
|
+
try {
|
|
13213
|
+
await watchlistApi.reorderItems(newItems.map((i) => i.id));
|
|
13214
|
+
} catch {
|
|
13215
|
+
await reload();
|
|
11555
13216
|
}
|
|
11556
13217
|
};
|
|
13218
|
+
const handleDragEnd = () => {
|
|
13219
|
+
setDragItemId(null);
|
|
13220
|
+
setDropTargetId(null);
|
|
13221
|
+
};
|
|
13222
|
+
const handleRemoveGroup = async (groupId) => {
|
|
13223
|
+
await watchlistApi.removeGroup(groupId);
|
|
13224
|
+
await reload();
|
|
13225
|
+
};
|
|
13226
|
+
const toggleCollapse = (groupId) => {
|
|
13227
|
+
setCollapsed((prev) => {
|
|
13228
|
+
const next = new Set(prev);
|
|
13229
|
+
next.has(groupId) ? next.delete(groupId) : next.add(groupId);
|
|
13230
|
+
return next;
|
|
13231
|
+
});
|
|
13232
|
+
};
|
|
13233
|
+
const ungrouped = items.filter((i) => i.group_id === null);
|
|
13234
|
+
return /* @__PURE__ */ jsxs(
|
|
13235
|
+
"div",
|
|
13236
|
+
{
|
|
13237
|
+
ref: drawerRef,
|
|
13238
|
+
className: "wl-drawer",
|
|
13239
|
+
style: {
|
|
13240
|
+
width: drawerWidth,
|
|
13241
|
+
"--wl-last": `${colWidths.last}px`,
|
|
13242
|
+
"--wl-chg": `${colWidths.chg}px`,
|
|
13243
|
+
"--wl-pct": `${colWidths.pct}px`,
|
|
13244
|
+
"--wl-vol": `${colWidths.vol}px`
|
|
13245
|
+
},
|
|
13246
|
+
children: [
|
|
13247
|
+
/* @__PURE__ */ jsx("div", { className: "wl-resize-handle", onMouseDown: onResizeMouseDown }),
|
|
13248
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-header", children: [
|
|
13249
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-header-title", children: [
|
|
13250
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", stroke: "currentColor", fill: "none", strokeWidth: "1.5", children: [
|
|
13251
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
13252
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "7", width: "9", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" }),
|
|
13253
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "11", width: "10", height: "1.5", rx: "0.5", fill: "currentColor", stroke: "none" })
|
|
13254
|
+
] }),
|
|
13255
|
+
"Watchlist"
|
|
13256
|
+
] }),
|
|
13257
|
+
/* @__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: [
|
|
13258
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "12", y2: "12" }),
|
|
13259
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "2", x2: "2", y2: "12" })
|
|
13260
|
+
] }) })
|
|
13261
|
+
] }),
|
|
13262
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-add-row", children: [
|
|
13263
|
+
/* @__PURE__ */ jsx(
|
|
13264
|
+
"input",
|
|
13265
|
+
{
|
|
13266
|
+
readOnly: true,
|
|
13267
|
+
className: "wl-add-input",
|
|
13268
|
+
placeholder: "Add symbol e.g. BINANCE:BTCUSDT",
|
|
13269
|
+
onClick: () => setSearchOpen(true),
|
|
13270
|
+
onFocus: () => setSearchOpen(true),
|
|
13271
|
+
style: { cursor: "pointer" }
|
|
13272
|
+
}
|
|
13273
|
+
),
|
|
13274
|
+
/* @__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: [
|
|
13275
|
+
/* @__PURE__ */ jsx("line", { x1: "7", y1: "1", x2: "7", y2: "13" }),
|
|
13276
|
+
/* @__PURE__ */ jsx("line", { x1: "1", y1: "7", x2: "13", y2: "7" })
|
|
13277
|
+
] }) })
|
|
13278
|
+
] }),
|
|
13279
|
+
addError && /* @__PURE__ */ jsx("div", { className: "wl-add-error", children: addError }),
|
|
13280
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-col-headers", children: [
|
|
13281
|
+
/* @__PURE__ */ jsx("span", {}),
|
|
13282
|
+
/* @__PURE__ */ jsx("span", { className: "wl-col-symbol", children: "Symbol" }),
|
|
13283
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-last", children: [
|
|
13284
|
+
"Last",
|
|
13285
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "last") })
|
|
13286
|
+
] }),
|
|
13287
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-chg", children: [
|
|
13288
|
+
"Chg",
|
|
13289
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "chg") })
|
|
13290
|
+
] }),
|
|
13291
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-pct", children: [
|
|
13292
|
+
"Chg%",
|
|
13293
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "pct") })
|
|
13294
|
+
] }),
|
|
13295
|
+
/* @__PURE__ */ jsxs("span", { className: "wl-col-vol", children: [
|
|
13296
|
+
"Vol",
|
|
13297
|
+
/* @__PURE__ */ jsx("div", { className: "wl-col-resize", onMouseDown: (e) => onColResizeMouseDown(e, "vol") })
|
|
13298
|
+
] })
|
|
13299
|
+
] }),
|
|
13300
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-list", children: [
|
|
13301
|
+
loading && /* @__PURE__ */ jsx("div", { className: "wl-empty", children: "Loading\u2026" }),
|
|
13302
|
+
groups.map((group) => {
|
|
13303
|
+
const groupItems = items.filter((i) => i.group_id === group.id);
|
|
13304
|
+
const isCollapsed = collapsed.has(group.id);
|
|
13305
|
+
return /* @__PURE__ */ jsxs("div", { className: "wl-group", children: [
|
|
13306
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-group-header", children: [
|
|
13307
|
+
/* @__PURE__ */ jsx(
|
|
13308
|
+
"button",
|
|
13309
|
+
{
|
|
13310
|
+
className: "wl-group-toggle",
|
|
13311
|
+
onClick: () => toggleCollapse(group.id),
|
|
13312
|
+
title: isCollapsed ? "Expand" : "Collapse",
|
|
13313
|
+
children: /* @__PURE__ */ jsx(
|
|
13314
|
+
"svg",
|
|
13315
|
+
{
|
|
13316
|
+
viewBox: "0 0 10 10",
|
|
13317
|
+
width: "8",
|
|
13318
|
+
height: "8",
|
|
13319
|
+
stroke: "currentColor",
|
|
13320
|
+
fill: "none",
|
|
13321
|
+
strokeWidth: "1.5",
|
|
13322
|
+
style: { transform: isCollapsed ? "rotate(-90deg)" : "none", transition: "transform 0.15s" },
|
|
13323
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "2,3 5,7 8,3" })
|
|
13324
|
+
}
|
|
13325
|
+
)
|
|
13326
|
+
}
|
|
13327
|
+
),
|
|
13328
|
+
/* @__PURE__ */ jsx("span", { className: "wl-group-name", children: group.name }),
|
|
13329
|
+
/* @__PURE__ */ jsx("span", { className: "wl-group-count", children: groupItems.length }),
|
|
13330
|
+
/* @__PURE__ */ jsx(
|
|
13331
|
+
"button",
|
|
13332
|
+
{
|
|
13333
|
+
className: "wl-remove-btn wl-group-del",
|
|
13334
|
+
title: "Remove group (items become ungrouped)",
|
|
13335
|
+
onClick: () => handleRemoveGroup(group.id),
|
|
13336
|
+
children: "\u2715"
|
|
13337
|
+
}
|
|
13338
|
+
)
|
|
13339
|
+
] }),
|
|
13340
|
+
!isCollapsed && groupItems.map((item) => /* @__PURE__ */ jsx(
|
|
13341
|
+
SymbolRow,
|
|
13342
|
+
{
|
|
13343
|
+
item,
|
|
13344
|
+
quote: quotes.get(item.symbol),
|
|
13345
|
+
flash: liveDirs.get(item.symbol),
|
|
13346
|
+
dragOver: dropTargetId === item.id,
|
|
13347
|
+
...logoMap.get(item.symbol) !== void 0 && { logoUrl: logoMap.get(item.symbol) },
|
|
13348
|
+
onSelect: () => onSelectSymbol(item.symbol),
|
|
13349
|
+
onRemove: () => handleRemove(item.id),
|
|
13350
|
+
onContextMenu: (e) => openCtxMenu(e, item),
|
|
13351
|
+
onDragStart: (e) => handleDragStart(e, item.id),
|
|
13352
|
+
onDragOver: (e) => handleDragOver(e, item.id),
|
|
13353
|
+
onDrop: (e) => handleDrop(e, item.id),
|
|
13354
|
+
onDragEnd: handleDragEnd
|
|
13355
|
+
},
|
|
13356
|
+
item.id
|
|
13357
|
+
))
|
|
13358
|
+
] }, group.id);
|
|
13359
|
+
}),
|
|
13360
|
+
ungrouped.length > 0 && groups.length > 0 && /* @__PURE__ */ jsx("div", { className: "wl-group-label-ungrouped", children: "Other" }),
|
|
13361
|
+
ungrouped.map((item) => /* @__PURE__ */ jsx(
|
|
13362
|
+
SymbolRow,
|
|
13363
|
+
{
|
|
13364
|
+
item,
|
|
13365
|
+
quote: quotes.get(item.symbol),
|
|
13366
|
+
flash: liveDirs.get(item.symbol),
|
|
13367
|
+
dragOver: dropTargetId === item.id,
|
|
13368
|
+
...logoMap.get(item.symbol) !== void 0 && { logoUrl: logoMap.get(item.symbol) },
|
|
13369
|
+
onSelect: () => onSelectSymbol(item.symbol),
|
|
13370
|
+
onRemove: () => handleRemove(item.id),
|
|
13371
|
+
onContextMenu: (e) => openCtxMenu(e, item),
|
|
13372
|
+
onDragStart: (e) => handleDragStart(e, item.id),
|
|
13373
|
+
onDragOver: (e) => handleDragOver(e, item.id),
|
|
13374
|
+
onDrop: (e) => handleDrop(e, item.id),
|
|
13375
|
+
onDragEnd: handleDragEnd
|
|
13376
|
+
},
|
|
13377
|
+
item.id
|
|
13378
|
+
)),
|
|
13379
|
+
!loading && items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "wl-empty", children: [
|
|
13380
|
+
"No symbols yet.",
|
|
13381
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
13382
|
+
"Type a symbol above and press Enter."
|
|
13383
|
+
] })
|
|
13384
|
+
] }),
|
|
13385
|
+
ctxMenu && /* @__PURE__ */ jsxs(
|
|
13386
|
+
"div",
|
|
13387
|
+
{
|
|
13388
|
+
ref: ctxRef,
|
|
13389
|
+
className: "wl-ctx-menu",
|
|
13390
|
+
style: { top: ctxMenu.y, left: ctxMenu.x },
|
|
13391
|
+
children: [
|
|
13392
|
+
groups.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
13393
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-section", children: "Move to group" }),
|
|
13394
|
+
groups.map((g) => /* @__PURE__ */ jsx(
|
|
13395
|
+
"button",
|
|
13396
|
+
{
|
|
13397
|
+
className: "wl-ctx-item",
|
|
13398
|
+
onClick: () => handleMoveToExisting(ctxMenu.itemId, g.id),
|
|
13399
|
+
children: g.name
|
|
13400
|
+
},
|
|
13401
|
+
g.id
|
|
13402
|
+
)),
|
|
13403
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-divider" })
|
|
13404
|
+
] }),
|
|
13405
|
+
/* @__PURE__ */ jsx(
|
|
13406
|
+
"button",
|
|
13407
|
+
{
|
|
13408
|
+
className: "wl-ctx-item",
|
|
13409
|
+
onClick: () => openGroupDialog(ctxMenu.itemId),
|
|
13410
|
+
children: "New group\u2026"
|
|
13411
|
+
}
|
|
13412
|
+
),
|
|
13413
|
+
items.find((i) => i.id === ctxMenu.itemId)?.group_id && /* @__PURE__ */ jsx(
|
|
13414
|
+
"button",
|
|
13415
|
+
{
|
|
13416
|
+
className: "wl-ctx-item",
|
|
13417
|
+
onClick: () => handleUngroup(ctxMenu.itemId),
|
|
13418
|
+
children: "Remove from group"
|
|
13419
|
+
}
|
|
13420
|
+
),
|
|
13421
|
+
/* @__PURE__ */ jsx("div", { className: "wl-ctx-divider" }),
|
|
13422
|
+
/* @__PURE__ */ jsx(
|
|
13423
|
+
"button",
|
|
13424
|
+
{
|
|
13425
|
+
className: "wl-ctx-item wl-ctx-item--danger",
|
|
13426
|
+
onClick: () => {
|
|
13427
|
+
setCtxMenu(null);
|
|
13428
|
+
handleRemove(ctxMenu.itemId);
|
|
13429
|
+
},
|
|
13430
|
+
children: "Remove symbol"
|
|
13431
|
+
}
|
|
13432
|
+
)
|
|
13433
|
+
]
|
|
13434
|
+
}
|
|
13435
|
+
),
|
|
13436
|
+
groupDialog && /* @__PURE__ */ jsx("div", { className: "wl-dialog-backdrop", children: /* @__PURE__ */ jsxs("div", { className: "wl-dialog", children: [
|
|
13437
|
+
/* @__PURE__ */ jsx("div", { className: "wl-dialog-title", children: "Add to group" }),
|
|
13438
|
+
/* @__PURE__ */ jsx(
|
|
13439
|
+
"input",
|
|
13440
|
+
{
|
|
13441
|
+
className: "wl-dialog-input",
|
|
13442
|
+
placeholder: "Group name",
|
|
13443
|
+
value: groupName,
|
|
13444
|
+
onChange: (e) => setGroupName(e.target.value),
|
|
13445
|
+
onKeyDown: (e) => {
|
|
13446
|
+
if (e.key === "Enter") handleCreateGroup();
|
|
13447
|
+
},
|
|
13448
|
+
autoFocus: true
|
|
13449
|
+
}
|
|
13450
|
+
),
|
|
13451
|
+
/* @__PURE__ */ jsxs("div", { className: "wl-dialog-actions", children: [
|
|
13452
|
+
/* @__PURE__ */ jsx("button", { className: "wl-dialog-cancel", onClick: () => setGroupDialog(null), children: "Cancel" }),
|
|
13453
|
+
/* @__PURE__ */ jsx("button", { className: "wl-dialog-confirm", onClick: handleCreateGroup, disabled: !groupName.trim(), children: "Save" })
|
|
13454
|
+
] })
|
|
13455
|
+
] }) }),
|
|
13456
|
+
searchOpen && /* @__PURE__ */ jsx(
|
|
13457
|
+
SymbolSearchDialog,
|
|
13458
|
+
{
|
|
13459
|
+
current: "",
|
|
13460
|
+
onSelect: handleAddFromSearch,
|
|
13461
|
+
onClose: () => setSearchOpen(false),
|
|
13462
|
+
symbolResolver
|
|
13463
|
+
}
|
|
13464
|
+
)
|
|
13465
|
+
]
|
|
13466
|
+
}
|
|
13467
|
+
);
|
|
13468
|
+
}
|
|
13469
|
+
var BROKERS = [
|
|
13470
|
+
{ id: "rithmic", name: "Rithmic", description: "Futures & Forex", status: "available" },
|
|
13471
|
+
{ id: "ibkr", name: "Interactive Brokers", description: "Stocks, Options & More", status: "coming-soon" },
|
|
13472
|
+
{ id: "tradovate", name: "Tradovate", description: "Futures & Options", status: "coming-soon" },
|
|
13473
|
+
{ id: "alpaca", name: "Alpaca", description: "Stocks & Crypto", status: "coming-soon" },
|
|
13474
|
+
{ id: "tradestation", name: "TradeStation", description: "Stocks, Futures & Options", status: "coming-soon" },
|
|
13475
|
+
{ id: "tradier", name: "Tradier", description: "Stocks & Options", status: "coming-soon" },
|
|
13476
|
+
{ id: "ninjabrokerage", name: "NinjaTrader Brokerage", description: "Futures & Forex", status: "coming-soon" },
|
|
13477
|
+
{ id: "tastytrade", name: "tastytrade", description: "Options & Futures", status: "coming-soon" }
|
|
13478
|
+
];
|
|
13479
|
+
function TradeDrawer({ onClose }) {
|
|
13480
|
+
return /* @__PURE__ */ jsxs("div", { className: "trade-drawer", children: [
|
|
13481
|
+
/* @__PURE__ */ jsxs("div", { className: "trade-drawer-header", children: [
|
|
13482
|
+
/* @__PURE__ */ jsxs("span", { className: "trade-drawer-title", children: [
|
|
13483
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
|
|
13484
|
+
/* @__PURE__ */ jsx("rect", { x: "1.5", y: "3", width: "13", height: "10", rx: "1.5" }),
|
|
13485
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "8", x2: "11", y2: "8" }),
|
|
13486
|
+
/* @__PURE__ */ jsx("line", { x1: "8", y1: "5", x2: "8", y2: "11" })
|
|
13487
|
+
] }),
|
|
13488
|
+
"Trade \u2014 Connect a broker"
|
|
13489
|
+
] }),
|
|
13490
|
+
/* @__PURE__ */ jsx("button", { className: "trade-drawer-close", onClick: onClose, title: "Close", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 14 14", width: "12", height: "12", stroke: "currentColor", strokeWidth: "1.8", children: [
|
|
13491
|
+
/* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "12", y2: "12" }),
|
|
13492
|
+
/* @__PURE__ */ jsx("line", { x1: "12", y1: "2", x2: "2", y2: "12" })
|
|
13493
|
+
] }) })
|
|
13494
|
+
] }),
|
|
13495
|
+
/* @__PURE__ */ jsx("div", { className: "trade-drawer-body", children: BROKERS.map((broker) => /* @__PURE__ */ jsxs("div", { className: "broker-card", children: [
|
|
13496
|
+
/* @__PURE__ */ jsx("div", { className: "broker-card-name", children: broker.name }),
|
|
13497
|
+
/* @__PURE__ */ jsx("div", { className: "broker-card-desc", children: broker.description }),
|
|
13498
|
+
/* @__PURE__ */ jsx("span", { className: `broker-card-status broker-card-status--${broker.status}`, children: broker.status === "available" ? "Available" : "Coming Soon" }),
|
|
13499
|
+
/* @__PURE__ */ jsx(
|
|
13500
|
+
"button",
|
|
13501
|
+
{
|
|
13502
|
+
className: "broker-card-connect",
|
|
13503
|
+
disabled: broker.status !== "available",
|
|
13504
|
+
title: broker.status === "available" ? `Connect ${broker.name}` : "Coming soon",
|
|
13505
|
+
children: broker.status === "available" ? "Connect" : "Coming Soon"
|
|
13506
|
+
}
|
|
13507
|
+
)
|
|
13508
|
+
] }, broker.id)) })
|
|
13509
|
+
] });
|
|
11557
13510
|
}
|
|
11558
13511
|
function uid() {
|
|
11559
13512
|
return Math.random().toString(36).slice(2, 10);
|
|
@@ -11642,16 +13595,11 @@ var ManagedAppShell = forwardRef(
|
|
|
11642
13595
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
11643
13596
|
const capabilities = useChartCapabilities(config);
|
|
11644
13597
|
useEffect(() => {
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
capabilities.draggableOrders,
|
|
11651
|
-
capabilities.managedTrading
|
|
11652
|
-
);
|
|
11653
|
-
}
|
|
11654
|
-
}, [capabilities.orderEntry, capabilities.draggableOrders, capabilities.managedTrading]);
|
|
13598
|
+
watchlistApi.configure({
|
|
13599
|
+
apiBase: `${apiUrl ?? ""}/api`,
|
|
13600
|
+
...getAuthToken ? { getAuthToken } : {}
|
|
13601
|
+
});
|
|
13602
|
+
}, [apiUrl, getAuthToken]);
|
|
11655
13603
|
const [isLicensed, setIsLicensed] = useState(() => LicenseManager.getInstance().getLicense() !== null);
|
|
11656
13604
|
useEffect(() => LicenseManager.getInstance().subscribe(() => {
|
|
11657
13605
|
setIsLicensed(LicenseManager.getInstance().getLicense() !== null);
|
|
@@ -11971,21 +13919,11 @@ var ManagedAppShell = forwardRef(
|
|
|
11971
13919
|
tab.id
|
|
11972
13920
|
)) });
|
|
11973
13921
|
const drawers = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11974
|
-
scriptDrawerOpen && renderScriptDrawer
|
|
11975
|
-
|
|
11976
|
-
onAddIndicator: handleAddIndicator
|
|
11977
|
-
}),
|
|
11978
|
-
watchlistOpen && renderWatchlistDrawer?.({
|
|
11979
|
-
onClose: () => setWatchlistOpen(false),
|
|
11980
|
-
onSelectSymbol: (sym) => {
|
|
11981
|
-
handleSymbolChange(sym);
|
|
11982
|
-
}
|
|
11983
|
-
})
|
|
13922
|
+
scriptDrawerOpen && (renderScriptDrawer ? renderScriptDrawer({ onClose: () => setScriptDrawerOpen(false), onAddIndicator: handleAddIndicator }) : /* @__PURE__ */ jsx(ScriptDrawer, { onClose: () => setScriptDrawerOpen(false), onAddIndicator: handleAddIndicator })),
|
|
13923
|
+
watchlistOpen && (renderWatchlistDrawer ? renderWatchlistDrawer({ onClose: () => setWatchlistOpen(false), onSelectSymbol: handleSymbolChange }) : /* @__PURE__ */ jsx(WatchlistDrawer, { onClose: () => setWatchlistOpen(false), onSelectSymbol: handleSymbolChange, symbolResolver }))
|
|
11984
13924
|
] });
|
|
11985
13925
|
return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: { height: "100%" }, children: [
|
|
11986
|
-
tradeDrawerOpen && capabilities.renderManagedTradingControls && renderTradeDrawer
|
|
11987
|
-
onClose: () => setTradeDrawerOpen(false)
|
|
11988
|
-
}),
|
|
13926
|
+
tradeDrawerOpen && capabilities.renderManagedTradingControls && (renderTradeDrawer ? renderTradeDrawer({ onClose: () => setTradeDrawerOpen(false) }) : /* @__PURE__ */ jsx(TradeDrawer, { onClose: () => setTradeDrawerOpen(false) })),
|
|
11989
13927
|
/* @__PURE__ */ jsx(
|
|
11990
13928
|
ChartWorkspace,
|
|
11991
13929
|
{
|