From 03f3e4fc04522b5e5655e755b51e6daaf753eaf5 Mon Sep 17 00:00:00 2001 From: Antonio Pedro Date: Wed, 22 Apr 2026 16:20:33 +0100 Subject: [PATCH 1/5] feat(debug-console): integrate dxscript code editor and align chart components with dxscript/dxlink-dxcharts-lite - Replace @devexperts/dxcharts-lite with @dxscript/dxlink-dxcharts-lite for chart rendering - Integrate @dxscript/dxlink-dxscript-editor for script editing with samples and error display - Refactor CandlesChannelManager and ScriptCandlesChannelManager to use IndiChart and IndiChartHandle - Simplify indicator data handling and chart updates - Remove legacy JSIcon and AceEditor code - Update dependencies and lockfile accordingly --- dxlink-javascript/dxlink-docs/package.json | 6 +- .../src/debug-console/ace-dxscript-mode.ts | 120 ------- .../debug-console/candles-channel-manager.tsx | 80 ++--- .../src/debug-console/candles-chart.ts | 72 ---- .../src/debug-console/dxscript-completions.ts | 154 --------- .../dxlink-docs/src/debug-console/icons.tsx | 7 - .../dxlink-docs/src/debug-console/index.tsx | 1 + .../script-candles-channel-manager.tsx | 317 ++---------------- .../script-candles-subscription.tsx | 188 +---------- .../dxlink-docs/src/dxscript-samples.d.ts | 16 - dxlink-javascript/package-lock.json | 104 ++++-- 11 files changed, 142 insertions(+), 923 deletions(-) delete mode 100644 dxlink-javascript/dxlink-docs/src/debug-console/ace-dxscript-mode.ts delete mode 100644 dxlink-javascript/dxlink-docs/src/debug-console/candles-chart.ts delete mode 100644 dxlink-javascript/dxlink-docs/src/debug-console/dxscript-completions.ts delete mode 100644 dxlink-javascript/dxlink-docs/src/dxscript-samples.d.ts diff --git a/dxlink-javascript/dxlink-docs/package.json b/dxlink-javascript/dxlink-docs/package.json index 97f93a6..ce86a0e 100644 --- a/dxlink-javascript/dxlink-docs/package.json +++ b/dxlink-javascript/dxlink-docs/package.json @@ -11,13 +11,11 @@ }, "dependencies": { "@asyncapi/react-component": "2.5.0", - "@devexperts/dxcharts-lite": "2.5.3", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/js-samples": "^1.5.0", - "ace-builds": "1.35.4", + "@dxscript/dxlink-dxcharts-lite": "^1.0.0-SNAPSHOT", + "@dxscript/dxlink-dxscript-editor": "^1.0.0-SNAPSHOT", "react": "^18.2.0", - "react-ace": "12.0.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", "react-router-dom": "^6.5.0", diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/ace-dxscript-mode.ts b/dxlink-javascript/dxlink-docs/src/debug-console/ace-dxscript-mode.ts deleted file mode 100644 index 5c8f923..0000000 --- a/dxlink-javascript/dxlink-docs/src/debug-console/ace-dxscript-mode.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import ace from 'ace-builds/src-noconflict/ace' - -import 'ace-builds/src-noconflict/ext-language_tools' -import 'ace-builds/src-noconflict/mode-text' -import 'ace-builds/src-noconflict/mode-javascript' -import { dxScriptCompletions } from './dxscript-completions' - -const JavaScriptMode = ace.require('ace/mode/javascript').Mode -const MatchingBraceOutdent = ace.require('ace/mode/matching_brace_outdent').MatchingBraceOutdent -const CstyleBehaviour = ace.require('ace/mode/behaviour/cstyle').CstyleBehaviour -const CstyleFoldMode = ace.require('ace/mode/folding/cstyle').FoldMode -const JavaScriptHighlightRules = ace.require( - 'ace/mode/javascript_highlight_rules' -).JavaScriptHighlightRules -const langTools = ace.require('ace/ext/language_tools') - -// Configure Ace base path for dynamic loading -ace.config.set('basePath', 'https://cdn.jsdelivr.net/npm/ace-builds@1.43.6/src-noconflict/') - -langTools.addCompleter({ - getCompletions: (_editor: any, _session: any, _pos: any, _prefix: any, callback: any) => { - callback(null, dxScriptCompletions) - }, -}) - -class DxScriptHighlightRules extends JavaScriptHighlightRules { - constructor() { - super() - - const candleFields = - 'askVolume|bar_index|bidVolume|close|count|high|hl2|hlc3|impVolatility|low|ohlc4|open|openInterest|time|volume|vwap' - - const builtinFunctions = 'input|output|spline|ts' - - const builtinConstants = - 'BOLD|BoxExtend|ITALIC|LineExtend|LineStyle|NONE|STYLE_AREA|STYLE_AREA_BREAK|STYLE_CIRCLES|STYLE_COLUMNS|STYLE_CROSS|STYLE_HISTOGRAM|STYLE_LINE|STYLE_LINE_BREAK|STYLE_STEPLINE|STYLE_STEPLINE_BREAK|TextAlign|TextHorizontalAlign|TextVerticalAlign|XLocation' - - const colorNames = - 'BLACK|BLUE|CORAL|CRIMSON|CYAN|GOLD|GRAY|GREEN|LIME|MAGENTA|MAROON|NAVY|ORANGE|PINK|PURPLE|RED|SILVER|TEAL|WHITE|YELLOW' - - const namespaces = - 'bar|box|console|hline|input|label|line|Math|output|polyline|session|spline|ta|ts' - - if (this.$rules.start) { - this.$rules.start.unshift({ - token: 'support.type', - regex: String.raw`\b(?:${namespaces})\b`, - }) - - this.$rules.start.unshift({ - token: 'constant.language', - regex: String.raw`\b(?:${builtinConstants})\b`, - }) - - this.$rules.start.unshift({ - token: 'support.function', - regex: String.raw`\b(?:${builtinFunctions})\b`, - }) - - this.$rules.start.unshift({ - token: 'variable.language', - regex: String.raw`\b(?:${candleFields})\b`, - }) - - this.$rules.start.unshift({ - token: ['support.type', 'punctuation.operator', 'constant.language'], - regex: String.raw`\b(color)(\.)(?:(${colorNames}))\b`, - }) - - this.$rules.start.unshift({ - token: 'support.function', - regex: String.raw`\b(?:${namespaces})\.[A-Za-z_$][\w$]*\b`, - }) - } - } -} - -export class DxScriptMode extends JavaScriptMode { - constructor() { - super() - this.HighlightRules = DxScriptHighlightRules - this.$outdent = new MatchingBraceOutdent() - this.$behaviour = new CstyleBehaviour() - this.foldingRules = new CstyleFoldMode() - } - - createWorker(session: any) { - return null - } - - getNextLineIndent(state: any, line: string, tab: string) { - const indent = this.$getIndent(line) - const tokenizedLine = this.getTokenizer().getLineTokens(line, state).tokens - - if (tokenizedLine.length && tokenizedLine[tokenizedLine.length - 1].type === 'comment') { - return indent - } - - if (state === 'start' && /^.*[{([]\s*$/.exec(line)) { - return indent + tab - } - - return indent - } - - checkOutdent(_state: any, line: any, input: any) { - return this.$outdent.checkOutdent(line, input) - } - - autoOutdent(_state: any, doc: any, row: any) { - this.$outdent.autoOutdent(doc, row) - } -} - -// Register the custom mode -ace.define('ace/mode/dxscript', ['require', 'exports', 'module'], (require: any, exports: any) => { - exports.Mode = DxScriptMode -}) diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx b/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx index 0951a52..7bb6e8b 100644 --- a/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx +++ b/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx @@ -1,14 +1,14 @@ -import { createChart, Chart } from '@devexperts/dxcharts-lite' +import type { DXLinkIndiChartCandle } from '@dxfeed/dxlink-api' import { unit } from '@dxfeed/ui-kit/utils' +import { IndiChart, type IndiChartHandle } from '@dxscript/dxlink-dxcharts-lite' import { useEffect, useRef, useState } from 'react' import styled from 'styled-components' -import { candleChartColors } from './candles-chart' import { CandlesSubscription } from './candles-subscription' -import type { DXLinkCandleData, DXLinkCandles } from '../candles/candles' +import type { DXLinkCandleData, DXLinkCandleEvent, DXLinkCandles } from '../candles/candles' import { ContentTemplate } from '../common/content-template' -const ChartContainer = styled.div` +const ChartContainer = styled(IndiChart)` width: 100%; height: 400px; border: 1px solid ${({ theme }) => theme.palette.separator.primary}; @@ -33,68 +33,37 @@ interface CandlesChannelManagerProps { channel: DXLinkCandles } +const toIndiChartCandle = (event: Readonly): DXLinkIndiChartCandle => ({ + eventSymbol: event.eventSymbol, + index: event.index, + time: event.time, + open: event.open, + high: event.high, + low: event.low, + close: event.close, + volume: event.volume, +}) + export function CandlesChannelManager({ channel }: CandlesChannelManagerProps) { const [data, setData] = useState() - const [chart, setChart] = useState() - - const ref = useRef(null) + const ref = useRef(null) useEffect(() => { - const chartInstance = createChart(ref.current!, { - colors: candleChartColors, - components: { - waterMark: { - visible: true, - }, - }, - }) - channel.addListener(setData) - setChart(chartInstance) - return () => { - chartInstance.destroy() channel.removeListener(setData) } }, [channel]) useEffect(() => { - if (!data || !chart) { - return - } - - const candles = data.events.map((event) => ({ - hi: Number(event.high), - lo: Number(event.low), - open: Number(event.open), - close: Number(event.close), - timestamp: event.time, - volume: Number(event.volume), - idx: event.index, - })) - - // If it's a snapshot, we need to set data instead of updating it - if (data.isSnapshot) { - const symbol = data?.events[0]?.eventSymbol ?? 'N/A' - - chart.watermarkComponent.setWaterMarkData({ - firstRow: symbol, - }) - - chart.setData({ - candles, - instrument: { - symbol, - }, - }) + if (!data) { return } - chart.updateData({ - candles, - }) - }, [data, chart]) + const candles = data.events.map(toIndiChartCandle) + ref.current?.pushData(candles, [], data.isSnapshot ? 'candles' : 'update') + }, [data]) return ( <> @@ -104,7 +73,14 @@ export function CandlesChannelManager({ channel }: CandlesChannelManagerProps) { - + { + if (chartError) { + console.error(chartError) + } + }} + /> Chart powered by{' '} diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/candles-chart.ts b/dxlink-javascript/dxlink-docs/src/debug-console/candles-chart.ts deleted file mode 100644 index 8f09f09..0000000 --- a/dxlink-javascript/dxlink-docs/src/debug-console/candles-chart.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { ChartColors } from '@devexperts/dxcharts-lite/dist/chart/chart.config' - -export const candleChartColors: ChartColors = { - candleTheme: { - upColor: '#0fa68a', - downColor: '#e91d25', - downWickColor: '#e91d25', - upWickColor: '#0fa68a', - noneColor: '#4c5458', - }, - barTheme: { - upColor: '#0fa68a', - downColor: '#e91d25', - noneColor: '#4c5458', - }, - lineTheme: { - upColor: '#0fa68a', - downColor: '#e91d25', - noneColor: '#4c5458', - }, - scatterPlot: { - mainColor: '#ffffff', - }, - areaTheme: { - lineColor: 'rgba(226,61,25,1)', - startColor: 'rgba(226,61,25,0.8)', - stopColor: 'rgba(226,61,25,0)', - }, - baseLineTheme: { - lowerSectionStrokeColor: '#D92C40', - upperSectionStrokeColor: '#4D9953', - lowerSectionFillColor: 'rgba(217,44,64,0.07)', - upperSectionFillColor: 'rgba(77,153,83,0.07)', - baselineColor: 'rgba(255, 255, 255, 0.15)', - }, - histogram: { - upCap: 'rgba(51, 153, 51, 0.4)', - upBottom: 'rgba(51, 153, 51, 0.1)', - upBright: 'rgba(77, 153, 83, 1)', - downCap: 'rgba(153, 51, 51, 0.4)', - downBottom: 'rgba(153, 51, 51, 0.1)', - downBright: 'rgba(217, 44, 64, 1)', - noneCap: 'rgba(255,255,255,0.4)', - noneBottom: 'rgba(255,255,255,0.1)', - noneBright: 'rgba(255,255,255,1)', - }, - chartAreaTheme: { - backgroundMode: 'regular', - backgroundGradientTopColor: '#ffffff', - backgroundGradientBottomColor: '#ffffff', - backgroundColor: '#ffffff', - gridColor: '#373d40', - }, - crossTool: { - lineColor: 'rgb(59, 59, 59)', - labelBoxColor: 'rgb(59, 59, 59)', - labelTextColor: '#aeb1b3', - }, - waterMarkTheme: { - firstRowColor: 'rgba(0, 0, 0, .3)', - secondRowColor: 'rgba(0, 0, 0, .3)', - thirdRowColor: 'rgba(0, 0, 0, .3)', - }, - xAxis: { - backgroundColor: '#ffffff', - labelTextColor: '#318b6f', - }, - yAxis: { - backgroundColor: '#ffffff', - labelTextColor: '#99922a', - }, -} diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/dxscript-completions.ts b/dxlink-javascript/dxlink-docs/src/debug-console/dxscript-completions.ts deleted file mode 100644 index b0caf17..0000000 --- a/dxlink-javascript/dxlink-docs/src/debug-console/dxscript-completions.ts +++ /dev/null @@ -1,154 +0,0 @@ -export const dxScriptCompletions = [ - // Built-in series / special vars - { caption: 'time', value: 'time', meta: 'series' }, - { caption: 'open', value: 'open', meta: 'series' }, - { caption: 'high', value: 'high', meta: 'series' }, - { caption: 'low', value: 'low', meta: 'series' }, - { caption: 'close', value: 'close', meta: 'series' }, - { caption: 'volume', value: 'volume', meta: 'series' }, - { caption: 'bidVolume', value: 'bidVolume', meta: 'series' }, - { caption: 'askVolume', value: 'askVolume', meta: 'series' }, - { caption: 'count', value: 'count', meta: 'series' }, - { caption: 'vwap', value: 'vwap', meta: 'series' }, - { caption: 'impVolatility', value: 'impVolatility', meta: 'series' }, - { caption: 'openInterest', value: 'openInterest', meta: 'series' }, - { caption: 'hl2', value: 'hl2', meta: 'series' }, - { caption: 'hlc3', value: 'hlc3', meta: 'series' }, - { caption: 'ohlc4', value: 'ohlc4', meta: 'series' }, - { caption: 'bar_index', value: 'bar_index', meta: 'variable' }, - - // Script lifecycle - { caption: 'onTick', value: 'onTick', meta: 'lifecycle' }, - - // Namespaces / modules - { caption: 'bar', value: 'bar', meta: 'module' }, - { caption: 'color', value: 'color', meta: 'module' }, - { caption: 'input', value: 'input', meta: 'module' }, - { caption: 'session', value: 'session', meta: 'module' }, - { caption: 'ta', value: 'ta', meta: 'module' }, - { caption: 'box', value: 'box', meta: 'module' }, - { caption: 'line', value: 'line', meta: 'module' }, - { caption: 'polyline', value: 'polyline', meta: 'module' }, - { caption: 'label', value: 'label', meta: 'module' }, - { caption: 'hline', value: 'hline', meta: 'module' }, - - // Core / outputs - { caption: 'ts', value: 'ts', meta: 'function' }, - { caption: 'spline', value: 'spline', meta: 'function' }, - { caption: 'output', value: 'output', meta: 'function' }, - - // bar.* - { caption: 'bar.index', value: 'bar.index', meta: 'function' }, - { caption: 'bar.isNew', value: 'bar.isNew', meta: 'function' }, - { caption: 'bar.isConfirmed', value: 'bar.isConfirmed', meta: 'function' }, - { caption: 'bar.isHistory', value: 'bar.isHistory', meta: 'function' }, - { caption: 'bar.isNewDay', value: 'bar.isNewDay', meta: 'function' }, - { caption: 'bar.isNewWeek', value: 'bar.isNewWeek', meta: 'function' }, - { caption: 'bar.isNewMonth', value: 'bar.isNewMonth', meta: 'function' }, - - // input.* - { caption: 'input', value: 'input', meta: 'function' }, - { caption: 'input.bool', value: 'input.bool', meta: 'function' }, - { caption: 'input.double', value: 'input.double', meta: 'function' }, - { caption: 'input.string', value: 'input.string', meta: 'function' }, - { caption: 'input.source', value: 'input.source', meta: 'function' }, - { caption: 'input.color', value: 'input.color', meta: 'function' }, - { caption: 'input.session', value: 'input.session', meta: 'function' }, - { caption: 'input.map', value: 'input.map', meta: 'function' }, - - // color.* - { caption: 'color.create', value: 'color.create', meta: 'function' }, - - // session object - { caption: 'session.contains', value: 'session.contains', meta: 'method' }, - - // series object - { caption: 'series.set', value: 'series.set', meta: 'method' }, - - // ta.* - { caption: 'ta.sum', value: 'ta.sum', meta: 'function' }, - { caption: 'ta.cum', value: 'ta.cum', meta: 'function' }, - { caption: 'ta.sma', value: 'ta.sma', meta: 'function' }, - { caption: 'ta.ema', value: 'ta.ema', meta: 'function' }, - { caption: 'ta.wma', value: 'ta.wma', meta: 'function' }, - { caption: 'ta.wima', value: 'ta.wima', meta: 'function' }, - { caption: 'ta.highest', value: 'ta.highest', meta: 'function' }, - { caption: 'ta.highestBar', value: 'ta.highestBar', meta: 'function' }, - { caption: 'ta.lowest', value: 'ta.lowest', meta: 'function' }, - { caption: 'ta.lowestBar', value: 'ta.lowestBar', meta: 'function' }, - { caption: 'ta.pivotHigh', value: 'ta.pivotHigh', meta: 'function' }, - { caption: 'ta.pivotLow', value: 'ta.pivotLow', meta: 'function' }, - - // drawing modules - { caption: 'box.create', value: 'box.create', meta: 'function' }, - { caption: 'box.allBoxes', value: 'box.allBoxes', meta: 'function' }, - { caption: 'line.create', value: 'line.create', meta: 'function' }, - { caption: 'line.allLines', value: 'line.allLines', meta: 'function' }, - { caption: 'polyline.create', value: 'polyline.create', meta: 'function' }, - { caption: 'polyline.allPolylines', value: 'polyline.allPolylines', meta: 'function' }, - { caption: 'label.create', value: 'label.create', meta: 'function' }, - { caption: 'label.allLabels', value: 'label.allLabels', meta: 'function' }, - { caption: 'hline.create', value: 'hline.create', meta: 'function' }, - { caption: 'hline.allLines', value: 'hline.allLines', meta: 'function' }, - - // Color constants (color.*) - { caption: 'color.BLACK', value: 'color.BLACK', meta: 'constant' }, - { caption: 'color.WHITE', value: 'color.WHITE', meta: 'constant' }, - { caption: 'color.RED', value: 'color.RED', meta: 'constant' }, - { caption: 'color.GREEN', value: 'color.GREEN', meta: 'constant' }, - { caption: 'color.BLUE', value: 'color.BLUE', meta: 'constant' }, - { caption: 'color.YELLOW', value: 'color.YELLOW', meta: 'constant' }, - { caption: 'color.ORANGE', value: 'color.ORANGE', meta: 'constant' }, - { caption: 'color.PURPLE', value: 'color.PURPLE', meta: 'constant' }, - { caption: 'color.GRAY', value: 'color.GRAY', meta: 'constant' }, - { caption: 'color.SILVER', value: 'color.SILVER', meta: 'constant' }, - { caption: 'color.MAROON', value: 'color.MAROON', meta: 'constant' }, - { caption: 'color.LIME', value: 'color.LIME', meta: 'constant' }, - { caption: 'color.NAVY', value: 'color.NAVY', meta: 'constant' }, - { caption: 'color.TEAL', value: 'color.TEAL', meta: 'constant' }, - { caption: 'color.CYAN', value: 'color.CYAN', meta: 'constant' }, - { caption: 'color.MAGENTA', value: 'color.MAGENTA', meta: 'constant' }, - { caption: 'color.PINK', value: 'color.PINK', meta: 'constant' }, - { caption: 'color.GOLD', value: 'color.GOLD', meta: 'constant' }, - { caption: 'color.CORAL', value: 'color.CORAL', meta: 'constant' }, - { caption: 'color.CRIMSON', value: 'color.CRIMSON', meta: 'constant' }, - - // spline.STYLE_* - { caption: 'spline.STYLE_LINE', value: 'spline.STYLE_LINE', meta: 'constant' }, - { caption: 'spline.STYLE_STEPLINE', value: 'spline.STYLE_STEPLINE', meta: 'constant' }, - { caption: 'spline.STYLE_HISTOGRAM', value: 'spline.STYLE_HISTOGRAM', meta: 'constant' }, - { caption: 'spline.STYLE_CROSS', value: 'spline.STYLE_CROSS', meta: 'constant' }, - { caption: 'spline.STYLE_AREA', value: 'spline.STYLE_AREA', meta: 'constant' }, - { caption: 'spline.STYLE_COLUMNS', value: 'spline.STYLE_COLUMNS', meta: 'constant' }, - { caption: 'spline.STYLE_CIRCLES', value: 'spline.STYLE_CIRCLES', meta: 'constant' }, - { caption: 'spline.STYLE_LINE_BREAK', value: 'spline.STYLE_LINE_BREAK', meta: 'constant' }, - { caption: 'spline.STYLE_AREA_BREAK', value: 'spline.STYLE_AREA_BREAK', meta: 'constant' }, - { - caption: 'spline.STYLE_STEPLINE_BREAK', - value: 'spline.STYLE_STEPLINE_BREAK', - meta: 'constant', - }, - - // Enum/string values used in options (LineStyle / LineExtend / BoxExtend / Align / XLocation / TextFormatting) - { caption: 'SOLID', value: 'SOLID', meta: 'enum' }, - { caption: 'DOTTED', value: 'DOTTED', meta: 'enum' }, - { caption: 'DASHED', value: 'DASHED', meta: 'enum' }, - { caption: 'ARROW_LEFT', value: 'ARROW_LEFT', meta: 'enum' }, - { caption: 'ARROW_RIGHT', value: 'ARROW_RIGHT', meta: 'enum' }, - { caption: 'ARROW_BOTH', value: 'ARROW_BOTH', meta: 'enum' }, - - { caption: 'NONE', value: 'NONE', meta: 'enum' }, - { caption: 'LEFT', value: 'LEFT', meta: 'enum' }, - { caption: 'RIGHT', value: 'RIGHT', meta: 'enum' }, - { caption: 'BOTH', value: 'BOTH', meta: 'enum' }, - - { caption: 'INDEX', value: 'INDEX', meta: 'enum' }, - { caption: 'TIME', value: 'TIME', meta: 'enum' }, - - { caption: 'TOP', value: 'TOP', meta: 'enum' }, - { caption: 'CENTER', value: 'CENTER', meta: 'enum' }, - { caption: 'BOTTOM', value: 'BOTTOM', meta: 'enum' }, - - { caption: 'BOLD', value: 'BOLD', meta: 'enum' }, - { caption: 'ITALIC', value: 'ITALIC', meta: 'enum' }, -] diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/icons.tsx b/dxlink-javascript/dxlink-docs/src/debug-console/icons.tsx index f2505c6..b262f69 100644 --- a/dxlink-javascript/dxlink-docs/src/debug-console/icons.tsx +++ b/dxlink-javascript/dxlink-docs/src/debug-console/icons.tsx @@ -1,12 +1,5 @@ import { type SVGProps } from 'react' -export const JSIcon = (props: SVGProps) => ( - - - - -) - export const ErrorIcon = (props: SVGProps) => ( theme.palette.separator.primary}; @@ -43,302 +39,33 @@ interface ScriptCandlesChannelManagerProps { channel: ChartHolder } -const stringToColour = (str: string) => { - let hash = 0 - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash) - } - let colour = '#' - for (let i = 0; i < 3; i++) { - const value = (hash >> (i * 8)) & 0xff - colour += ('00' + value.toString(16)).substr(-2) - } - return colour -} - -// Metadata cache for indicator series (title, color, offset) -interface SeriesMetadata { - title: string - color?: string - offset: number -} - -// Helper to extract numeric value from indicator data point -// Values can be either a number or an object with {value, title?, color?, offset?} -const extractValue = (val: unknown): number => { - if (val && typeof val === 'object' && 'value' in val) { - return Number((val as { value: unknown }).value) - } - return Number(val) -} - -// Helper to extract metadata from the first element -const extractMetadata = ( - values: unknown[], - key: string, - cache: Record -): SeriesMetadata => { - const first = values[0] - if (first && typeof first === 'object') { - const obj = first as { title?: string; color?: { value?: string } | string; offset?: number } - if (obj.title) { - // Color can be either {value: "green"} or just "green" string - let colorValue: string | undefined - if (obj.color) { - if (typeof obj.color === 'object' && 'value' in obj.color) { - colorValue = obj.color.value?.toLowerCase() - } else if (typeof obj.color === 'string') { - colorValue = obj.color.toLowerCase() - } - } - cache[key] = { - title: obj.title, - color: colorValue, - offset: obj.offset || 0, - } - } - } - return cache[key] || { title: key, offset: 0 } -} - -// Helper to process indicators and map them to candle timestamps -const processIndicators = ( - indicators: DXLinkIndiChartIndicatorsData[], - candles: { timestamp: number; idx: number; close: number }[], - seriesMetadataRef: React.MutableRefObject> -): Record => { - const results: Record = {} - - // Structure: indicators = [{current: {output: {}, spline: {adl: [...], zero: [...]}}}] - // Level 1: indicators array - // Level 2: indicator object (e.g. {current: ...}) - // Level 3: output types (e.g. {output: {}, spline: {...}}) - // Level 4: actual series data (e.g. {adl: [...], zero: [...]}) - for (const data of indicators) { - for (const outputTypes of Object.values(data)) { - // outputTypes = {output: {}, spline: {...}} - for (const seriesData of Object.values(outputTypes)) { - // seriesData = {} or {adl: [...], zero: [...]} - if (typeof seriesData !== 'object' || seriesData === null) continue - - Object.keys(seriesData).forEach((key) => { - const values = (seriesData as unknown as Record)[key] - if (!Array.isArray(values) || values.length === 0) return - - // Extract metadata (title, color, offset) from first element - const metadata = extractMetadata(values, key, seriesMetadataRef.current) - const seriesTitle = metadata.title - const seriesOffset = metadata.offset - - const base = results[seriesTitle] ?? [] - const offset = base.length - - results[seriesTitle] = values.reduce<{ timestamp: number; idx: number; close: number }[]>( - (acc, value, index) => { - const candleIndex = offset + index + seriesOffset - const candle = candles[candleIndex] - if (candle === undefined) { - console.error('Illegal state, candle not found at index', candleIndex) - return acc - } - - acc.push({ - timestamp: candle.timestamp, - idx: candle.idx, - close: extractValue(value), - }) - - return acc - }, - base - ) - }) - } - } - } - - return results -} - export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelManagerProps) { const [available, setAvailable] = useState(false) const [error, setError] = useState(undefined) const [inParameters, setInParameters] = useState([]) - const ref = useRef(null) - const chartRef = useRef() - const seriesMetadataRef = useRef>({}) - // Store candles for indicator snapshot processing - const candlesRef = useRef<{ timestamp: number; idx: number; close: number }[]>([]) + const chartRef = useRef(null) - const handleDataUpdate = ( - events: DXLinkIndiChartCandle[], - indicators: DXLinkIndiChartIndicatorsData[], - dataType: ChartDataType, - chart: Chart + const handleSet = ( + subscription: DXLinkIndiChartSubscription, + indicator: DXLinkIndiChartIndicator ) => { - const candles = events.map((event) => ({ - hi: Number(event.high), - lo: Number(event.low), - open: Number(event.open), - close: Number(event.close), - timestamp: event.time, - volume: Number(event.volume), - idx: event.index, - })) - - // Handle candles snapshot - draw candles only - if (dataType === 'candles') { - // Reset metadata cache on candles snapshot - seriesMetadataRef.current = {} - // Store candles for later indicator processing - candlesRef.current = candles - - const symbol = events[0]?.eventSymbol ?? 'N/A' - - chart.watermarkComponent.setWaterMarkData({ - firstRow: symbol, - }) - - chart.setData({ - candles, - instrument: { - symbol, - }, - }) - - // Remove existing indicator panes (except CHART) - Object.keys(chart.paneManager.panes).forEach((pane) => { - if (pane !== 'CHART') { - chart.paneManager.removePane(pane) - } - }) - - setAvailable(true) - return - } - - // Handle indicators snapshot - draw indicators using stored candles - if (dataType === 'indicators') { - const storedCandles = candlesRef.current - if (storedCandles.length === 0) { - console.error('No candles available for indicator processing') - return - } - - const results = processIndicators(indicators, storedCandles, seriesMetadataRef) - const resultKeys = Object.keys(results) - - for (const key of resultKeys) { - let pane = chart.paneManager.panes[key] - if (pane === undefined) { - pane = chart.paneManager.createPane(key, { - cursor: key, - }) - pane.yAxis.changeLabelsDescriptionVisibility(true) - } - - let series = pane.dataSeries[0] - if (series === undefined) { - series = pane.createDataSeries() - series.name = key - - // Find seriesInfo from metadata cache where title matches the key - const seriesInfo = Object.values(seriesMetadataRef.current).find( - (meta) => meta.title === key - ) - - const paintConfig = series.config.paintConfig[0] - if (paintConfig !== undefined) { - // Use color from metadata if available, otherwise use generated color - paintConfig.color = seriesInfo?.color || stringToColour(key) - } - series.config.visible = true - series.config.labelLastValue = 'series' - series.config.labelMode = 'line-label' - series.config.labelAppearanceType = 'badge' - pane.yAxis.registerYAxisLabelsProvider(series.yAxisLabelProvider) - } else { - // Update color if it changed in metadata - const seriesInfo = Object.values(seriesMetadataRef.current).find( - (meta) => meta.title === key - ) - const paintConfig = series.config.paintConfig[0] - if (paintConfig !== undefined && seriesInfo?.color) { - paintConfig.color = seriesInfo.color - } - } - - series.setDataPoints(results[key]!) - } - - // Remove panes that are no longer in results - Object.keys(chart.paneManager.panes).forEach((pane) => { - if (!resultKeys.includes(pane) && pane !== 'CHART') { - chart.paneManager.removePane(pane) - } - }) - - return - } - - // Handle regular update - update both candles and indicators - const results = processIndicators(indicators, candles, seriesMetadataRef) - const resultKeys = Object.keys(results) - - chart.updateData({ - candles, - }) - - for (const key of resultKeys) { - try { - const pane = chart.paneManager.panes[key] - if (pane === undefined) return - - const points = pane.dataSeries[0]?.dataPoints ?? [] - const sortedPoints = SortedList.from(points, (a, b) => a.timestamp - b.timestamp) - - for (const point of results[key]!) { - sortedPoints.insert(point) - } - - const newPoints = Array.from(sortedPoints.toArray()) - - pane.dataSeries[0]?.setDataPoints(newPoints) - pane.yAxis.model.fancyLabelsModel.updateLabels() - } catch (e) { - console.error(e) - } - } - } - - useEffect(() => { - const chart = createChart(ref.current!, { - colors: candleChartColors, - components: { - waterMark: { - visible: true, - }, - }, - }) - - chartRef.current = chart - - return () => { - chart.destroy() - } - }, [channel]) - - const handleSet = (sub: DXLinkIndiChartSubscription, indicator: DXLinkIndiChartIndicator) => { setError(undefined) + setAvailable(false) + setInParameters([]) + chartRef.current?.reset() channel.update( - sub, + subscription, indicator, (candles, indicators, dataType) => { - handleDataUpdate(candles, indicators, dataType, chartRef.current!) + chartRef.current?.pushData(candles, indicators, dataType) + + if (dataType === 'candles' && candles.length > 0) { + setAvailable(true) + } }, (params) => setInParameters(params), - setError + (nextError) => setError(nextError) ) } @@ -348,9 +75,12 @@ export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelMan const handleReset = () => { channel.clear() + chartRef.current?.reset() setInParameters([]) + setError(undefined) setAvailable(false) } + return ( <> @@ -367,7 +97,10 @@ export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelMan - + setError(chartError ?? undefined)} + /> Chart powered by{' '} diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-subscription.tsx b/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-subscription.tsx index a545a0c..e4e7f83 100644 --- a/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-subscription.tsx +++ b/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-subscription.tsx @@ -1,27 +1,13 @@ import type { DXLinkIndiChartIndicator, DXLinkIndiChartSubscription } from '@dxfeed/dxlink-api' import { Button } from '@dxfeed/ui-kit/Button' import { HelperMessage } from '@dxfeed/ui-kit/HelperMessage' -import { IconButton } from '@dxfeed/ui-kit/IconButton' -import { Search } from '@dxfeed/ui-kit/Icons' -import { Menu, MenuItem } from '@dxfeed/ui-kit/Menu' -import { Notification } from '@dxfeed/ui-kit/Notification' -import { Text } from '@dxfeed/ui-kit/Text' import { TextField } from '@dxfeed/ui-kit/TextField' -import { TextInput } from '@dxfeed/ui-kit/TextInput' -import { ToggleButton } from '@dxfeed/ui-kit/ToggleButton' -import { Tooltip } from '@dxfeed/ui-kit/Tooltip' import { unit } from '@dxfeed/ui-kit/utils' -import Samples from '@dxscript/js-samples' +import { DxScriptEditor } from '@dxscript/dxlink-dxscript-editor' import { useState } from 'react' -import AceEditor from 'react-ace' import styled from 'styled-components' -import { ErrorIcon, JSIcon } from './icons' import { ContentTemplate } from '../common/content-template' -import 'ace-builds/src-noconflict/mode-python' -import 'ace-builds/src-noconflict/mode-javascript' -import 'ace-builds/src-noconflict/theme-textmate' -import './ace-dxscript-mode' type Lang = 'dxscript-js' @@ -39,56 +25,12 @@ const FieldWrapper = styled.div` width: 100%; ` -const TopPanel = styled.div` - display: flex; - flex-direction: row; - border-top: 1px solid ${({ theme }) => theme.palette.separator.primary}; - padding-bottom: ${unit(1)}; - padding-top: ${unit(1)}; -` - -const ErrorWrapper = styled.div`` - -const LangWrapper = styled.div` - flex-grow: 1; - display: flex; - flex-direction: row; -` - -const LangButton = styled(ToggleButton)` - margin-right: ${unit(1)}; - display: flex; - align-items: center; -` - -const ExampleButton = styled(Button)` - margin-top: ${unit(1)}; -` - -const ExampleItem = styled(MenuItem)` - cursor: pointer; -` - const CodeEditorGroup = styled.div` display: flex; flex-direction: column; padding-bottom: ${unit(1)}; width: 100%; ` -const CodeEditorInput = styled.div` - width: 100%; - - .ace_custom-keyword { - color: #ff6347; /* Красный цвет для подсветки */ - font-weight: bold; - } -` - -const CodeEditorHelp = styled.div` - width: 100%; - display: flex; - justify-content: end; -` const Actions = styled.div` display: flex; @@ -101,28 +43,6 @@ const ActionGroup = styled.div` padding-right: ${unit(1)}; ` -const ErrorButton = styled(IconButton)` - color: ${({ theme }) => theme.palette.red.main}; -` - -const JSLogo = styled(JSIcon)` - width: 16px; - height: 16px; - margin-right: 4px; - margin-bottom: 2px; -` - -const ExampleText = styled(Text)` - display: flex; - flex-grow: 1; - justify-content: space-between; - min-width: 300px; -` - -const ExampleDoc = styled.a` - margin-left: ${unit(2)}; -` - export interface ScriptCandlesSubscriptionProps { error?: string onSet(subscription: DXLinkIndiChartSubscription, indicator: DXLinkIndiChartIndicator): void @@ -138,12 +58,7 @@ export function ScriptCandlesSubscription({ }: ScriptCandlesSubscriptionProps) { const [symbol, setSymbol] = useState('AAPL{=d}') const [fromTime, setFromTime] = useState('0') - - const [exampleId, setExampleId] = useState(Samples.list()[0]?.name ?? '') - const [script, setScript] = useState(Samples.get(exampleId)?.content ?? '') - const [search, setSearch] = useState('') - - const [anchorEl, setAnchorEl] = useState(null) + const [script, setScript] = useState('') const handleSet = () => { onSet( @@ -158,10 +73,6 @@ export function ScriptCandlesSubscription({ ) } - const examples = search - ? Samples.list().filter((item) => item.name.toLowerCase().includes(search.toLowerCase())) - : Samples.list() - return (
@@ -200,94 +111,15 @@ export function ScriptCandlesSubscription({ - - - - dxScript/JS - - - - {error && ( - - {(triggerProps) => ( - - - - )} - - )} - - - - setScript(value)} - fontSize={14} - lineHeight={18} - showPrintMargin={true} - showGutter={true} - highlightActiveLine={true} - value={script} - width="100%" - height="320px" - setOptions={{ - enableBasicAutocompletion: true, - enableLiveAutocompletion: true, - enableSnippets: false, - showLineNumbers: true, - tabSize: 2, - }} - /> - - - setAnchorEl(event.currentTarget)}> - Try examples - - setAnchorEl(null)} - head={ - } - value={search} - onChange={(event) => setSearch(event.target.value)} - /> - } - > - {examples.length > 0 ? ( - examples.map((example) => ( - { - setAnchorEl(null) - setExampleId(example.name) - setScript(Samples.get(example.name)?.content ?? '') - }} - key={example.title} - > - - {example.title} - {example.docs && ( - - [Docs] - - )} - - - )) - ) : ( - - Nothing found - - )} - - + diff --git a/dxlink-javascript/dxlink-docs/src/dxscript-samples.d.ts b/dxlink-javascript/dxlink-docs/src/dxscript-samples.d.ts deleted file mode 100644 index 2f30b82..0000000 --- a/dxlink-javascript/dxlink-docs/src/dxscript-samples.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -declare module '@dxscript/js-samples' { - export interface Sample { - name: string - title: string - docs: string - content?: string - } - - export interface SamplesModule { - list(): Sample[] - get(name: string): Sample | undefined - } - - const samples: SamplesModule - export default samples -} diff --git a/dxlink-javascript/package-lock.json b/dxlink-javascript/package-lock.json index f963631..25f4cc3 100644 --- a/dxlink-javascript/package-lock.json +++ b/dxlink-javascript/package-lock.json @@ -58,13 +58,11 @@ "version": "0.0.1", "dependencies": { "@asyncapi/react-component": "2.5.0", - "@devexperts/dxcharts-lite": "2.5.3", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/js-samples": "^1.5.0", - "ace-builds": "1.35.4", + "@dxscript/dxlink-dxcharts-lite": "^1.0.0-SNAPSHOT", + "@dxscript/dxlink-dxscript-editor": "^1.0.0-SNAPSHOT", "react": "^18.2.0", - "react-ace": "12.0.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", "react-router-dom": "^6.5.0", @@ -84,20 +82,6 @@ "vite": "^4.5.0" } }, - "dxlink-docs/node_modules/@devexperts/dxcharts-lite": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@devexperts/dxcharts-lite/-/dxcharts-lite-2.5.3.tgz", - "integrity": "sha512-+e3ZXxP6cCrMg82sb1+cNutcNw2vNcPYZYqRB3v8h4Vc+uh1RsEX+4YNfdSqcw3UlCsOLrqvwZSGmexmSdqq9Q==", - "license": "MPL 2.0", - "dependencies": { - "color": "^4.2.3", - "date-fns": "^2.30.0", - "rxjs": "^7.5.7" - }, - "peerDependencies": { - "rxjs": ">=6.5.2" - } - }, "dxlink-dom": { "name": "@dxfeed/dxlink-dom", "version": "0.8.0", @@ -154,6 +138,7 @@ }, "node_modules/@ampproject/remapping": { "version": "2.2.1", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -290,6 +275,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -299,6 +285,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -327,6 +314,7 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -373,6 +361,7 @@ "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.27.2", @@ -387,6 +376,7 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -529,6 +519,7 @@ "version": "7.27.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", @@ -649,6 +640,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -671,6 +663,7 @@ "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -2575,6 +2568,20 @@ "node": ">= 4.0.0" } }, + "node_modules/@devexperts/dxcharts-lite": { + "version": "2.7.29", + "resolved": "https://registry.npmjs.org/@devexperts/dxcharts-lite/-/dxcharts-lite-2.7.29.tgz", + "integrity": "sha512-dFbohXiSmFpEXDYuGWj2JgFvDdOH5KI2TONk2asuG+646rPVNlsa5QWR/BR3lx/5tEbnWPTylgNo3IltYDePCw==", + "license": "MPL 2.0", + "dependencies": { + "color": "4.2.3", + "date-fns": "4.1.0", + "rxjs": "7.8.2" + }, + "peerDependencies": { + "rxjs": ">=6.5.2" + } + }, "node_modules/@dxfeed/dxlink-api": { "resolved": "dxlink-api", "link": true @@ -2629,6 +2636,34 @@ } } }, + "node_modules/@dxscript/dxlink-dxcharts-lite": { + "version": "1.0.0-SNAPSHOT", + "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxcharts-lite/-/dxlink-dxcharts-lite-1.0.0-SNAPSHOT.tgz", + "integrity": "sha512-Gb5gd4K0MLSLJHlVBbjAauGWIj1AOfOMvu+xoSMEzAD2OskBmzevTZjFIe5qhPBil6uKcADWpYMwd5hRhCqjkg==", + "dependencies": { + "@devexperts/dxcharts-lite": "^2.7.25" + }, + "peerDependencies": { + "@dxfeed/dxlink-api": "^0.6.1", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@dxscript/dxlink-dxscript-editor": { + "version": "1.0.0-SNAPSHOT", + "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxscript-editor/-/dxlink-dxscript-editor-1.0.0-SNAPSHOT.tgz", + "integrity": "sha512-0BTPO3AYDG7aANaZNB5k7rMpmXw2kUMQwPBx+rTcG5g2zaQ72e0Rq+FgFYPbSRvx7SlHBYlQ3BirkwEu3FqvZA==", + "dependencies": { + "@dxscript/js-samples": "^1.5.0", + "ace-builds": "1.35.4", + "react-ace": "12.0.0" + }, + "peerDependencies": { + "@dxfeed/ui-kit": "*", + "react": ">=18", + "styled-components": ">=5" + } + }, "node_modules/@dxscript/js-samples": { "version": "1.5.0", "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/js-samples/-/js-samples-1.5.0.tgz", @@ -4120,6 +4155,7 @@ }, "node_modules/@types/prop-types": { "version": "15.7.10", + "dev": true, "license": "MIT" }, "node_modules/@types/protocol-buffers-schema": { @@ -4132,6 +4168,7 @@ }, "node_modules/@types/react": { "version": "18.2.37", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4165,6 +4202,7 @@ }, "node_modules/@types/scheduler": { "version": "0.16.6", + "dev": true, "license": "MIT" }, "node_modules/@types/semver": { @@ -4922,6 +4960,7 @@ "version": "4.25.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5057,6 +5096,7 @@ "version": "1.0.30001727", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5221,6 +5261,7 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "dev": true, "license": "MIT" }, "node_modules/core-js-compat": { @@ -5438,6 +5479,7 @@ }, "node_modules/csstype": { "version": "3.1.2", + "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -5501,17 +5543,13 @@ } }, "node_modules/date-fns": { - "version": "2.30.0", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/debug": { @@ -5759,6 +5797,7 @@ "version": "1.5.188", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.188.tgz", "integrity": "sha512-pfEx5CBFAocOKNrc+i5fSvhDaI1Vr9R9aT5uX1IzM3hhdL6k649wfuUcdUd9EZnmbE1xdfA51CwqQ61CO3Xl3g==", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -5994,6 +6033,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6698,6 +6738,7 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -7873,6 +7914,7 @@ }, "node_modules/json5": { "version": "2.2.3", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8067,6 +8109,7 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -8976,6 +9019,7 @@ "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, "license": "MIT" }, "node_modules/normalize-range": { @@ -10794,7 +10838,9 @@ } }, "node_modules/rxjs": { - "version": "7.8.1", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -11986,6 +12032,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -12391,6 +12438,7 @@ }, "node_modules/yallist": { "version": "3.1.1", + "dev": true, "license": "ISC" }, "node_modules/yaml": { From fa0703dc91a26cb9e4e724fbeb7c193109eaaf53 Mon Sep 17 00:00:00 2001 From: Antonio Pedro Date: Mon, 27 Apr 2026 17:50:58 +0100 Subject: [PATCH 2/5] chore(deps): bump @dxscript/dxlink-dxcharts-lite and @dxscript/dxlink-dxscript-editor to 1.0.0 --- dxlink-javascript/dxlink-docs/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dxlink-javascript/dxlink-docs/package.json b/dxlink-javascript/dxlink-docs/package.json index ce86a0e..c076977 100644 --- a/dxlink-javascript/dxlink-docs/package.json +++ b/dxlink-javascript/dxlink-docs/package.json @@ -13,8 +13,8 @@ "@asyncapi/react-component": "2.5.0", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/dxlink-dxcharts-lite": "^1.0.0-SNAPSHOT", - "@dxscript/dxlink-dxscript-editor": "^1.0.0-SNAPSHOT", + "@dxscript/dxlink-dxcharts-lite": "^1.0.0", + "@dxscript/dxlink-dxscript-editor": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", From 42b9a480aa0876800383013a74f3799d5e3b127a Mon Sep 17 00:00:00 2001 From: Antonio Pedro Date: Wed, 29 Apr 2026 13:05:33 +0100 Subject: [PATCH 3/5] feat(debug-console): implement chart reset logic --- .../src/debug-console/candles-channel-manager.tsx | 11 +++++++++-- .../debug-console/script-candles-channel-manager.tsx | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx b/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx index 7bb6e8b..7ba97ef 100644 --- a/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx +++ b/dxlink-javascript/dxlink-docs/src/debug-console/candles-channel-manager.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react' import styled from 'styled-components' import { CandlesSubscription } from './candles-subscription' -import type { DXLinkCandleData, DXLinkCandleEvent, DXLinkCandles } from '../candles/candles' +import type { DXLinkCandleData, DXLinkCandleEvent, DXLinkCandles, DXLinkCandleSubscription } from '../candles/candles' import { ContentTemplate } from '../common/content-template' const ChartContainer = styled(IndiChart)` @@ -46,8 +46,14 @@ const toIndiChartCandle = (event: Readonly): DXLinkIndiChartC export function CandlesChannelManager({ channel }: CandlesChannelManagerProps) { const [data, setData] = useState() + const [chartResetKey, setChartResetKey] = useState(0) const ref = useRef(null) + const handleSet = (subscription: DXLinkCandleSubscription) => { + setChartResetKey((k) => k + 1) + channel.setSubscription(subscription) + } + useEffect(() => { channel.addListener(setData) @@ -68,13 +74,14 @@ export function CandlesChannelManager({ channel }: CandlesChannelManagerProps) { return ( <> - {' '} + {' '} { if (chartError) { console.error(chartError) diff --git a/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-channel-manager.tsx b/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-channel-manager.tsx index f25602a..eca5511 100644 --- a/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-channel-manager.tsx +++ b/dxlink-javascript/dxlink-docs/src/debug-console/script-candles-channel-manager.tsx @@ -43,6 +43,7 @@ export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelMan const [available, setAvailable] = useState(false) const [error, setError] = useState(undefined) const [inParameters, setInParameters] = useState([]) + const [chartResetKey, setChartResetKey] = useState(0) const chartRef = useRef(null) const handleSet = ( @@ -52,7 +53,7 @@ export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelMan setError(undefined) setAvailable(false) setInParameters([]) - chartRef.current?.reset() + setChartResetKey((k) => k + 1) channel.update( subscription, @@ -99,6 +100,7 @@ export function ScriptCandlesChannelManager({ channel }: ScriptCandlesChannelMan setError(chartError ?? undefined)} /> From a557fa441592f44f79cd597cc6dcaa5a22e0080a Mon Sep 17 00:00:00 2001 From: Antonio Pedro Date: Wed, 29 Apr 2026 14:57:41 +0100 Subject: [PATCH 4/5] feat(debug-console): bump dxcharts-lite and dxscript-editor to 1.0.1 release versions --- dxlink-javascript/dxlink-docs/package.json | 4 ++-- dxlink-javascript/package-lock.json | 28 ++++++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/dxlink-javascript/dxlink-docs/package.json b/dxlink-javascript/dxlink-docs/package.json index c076977..9032291 100644 --- a/dxlink-javascript/dxlink-docs/package.json +++ b/dxlink-javascript/dxlink-docs/package.json @@ -13,8 +13,8 @@ "@asyncapi/react-component": "2.5.0", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/dxlink-dxcharts-lite": "^1.0.0", - "@dxscript/dxlink-dxscript-editor": "^1.0.0", + "@dxscript/dxlink-dxcharts-lite": "^1.0.1", + "@dxscript/dxlink-dxscript-editor": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", diff --git a/dxlink-javascript/package-lock.json b/dxlink-javascript/package-lock.json index 25f4cc3..2117644 100644 --- a/dxlink-javascript/package-lock.json +++ b/dxlink-javascript/package-lock.json @@ -60,8 +60,8 @@ "@asyncapi/react-component": "2.5.0", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/dxlink-dxcharts-lite": "^1.0.0-SNAPSHOT", - "@dxscript/dxlink-dxscript-editor": "^1.0.0-SNAPSHOT", + "@dxscript/dxlink-dxcharts-lite": "^1.0.1", + "@dxscript/dxlink-dxscript-editor": "^1.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0", @@ -2637,9 +2637,9 @@ } }, "node_modules/@dxscript/dxlink-dxcharts-lite": { - "version": "1.0.0-SNAPSHOT", - "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxcharts-lite/-/dxlink-dxcharts-lite-1.0.0-SNAPSHOT.tgz", - "integrity": "sha512-Gb5gd4K0MLSLJHlVBbjAauGWIj1AOfOMvu+xoSMEzAD2OskBmzevTZjFIe5qhPBil6uKcADWpYMwd5hRhCqjkg==", + "version": "1.0.1", + "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxcharts-lite/-/dxlink-dxcharts-lite-1.0.1.tgz", + "integrity": "sha512-QxH4T5lo8cbREa3TuQ0bvt843iN3+0j0Berm7JeXVO9IZ/4ViP1LNQSMPC91R2StMUvl+mF/zo+UyUCh6llxag==", "dependencies": { "@devexperts/dxcharts-lite": "^2.7.25" }, @@ -2650,24 +2650,22 @@ } }, "node_modules/@dxscript/dxlink-dxscript-editor": { - "version": "1.0.0-SNAPSHOT", - "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxscript-editor/-/dxlink-dxscript-editor-1.0.0-SNAPSHOT.tgz", - "integrity": "sha512-0BTPO3AYDG7aANaZNB5k7rMpmXw2kUMQwPBx+rTcG5g2zaQ72e0Rq+FgFYPbSRvx7SlHBYlQ3BirkwEu3FqvZA==", + "version": "1.0.1", + "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/dxlink-dxscript-editor/-/dxlink-dxscript-editor-1.0.1.tgz", + "integrity": "sha512-D+8tILedMes4kIjF5ZCqyi+G9yulCn1c6yl1BVgpgweVl0pUwhk5XqJvVZsgOYOoRE5zi+wXIzUaXJBt0wFMfw==", "dependencies": { - "@dxscript/js-samples": "^1.5.0", + "@dxscript/js-samples": "1.10.0", "ace-builds": "1.35.4", "react-ace": "12.0.0" }, "peerDependencies": { - "@dxfeed/ui-kit": "*", - "react": ">=18", - "styled-components": ">=5" + "react": ">=18" } }, "node_modules/@dxscript/js-samples": { - "version": "1.5.0", - "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/js-samples/-/js-samples-1.5.0.tgz", - "integrity": "sha512-ndZ62AzZzMpYvgCIyS2JD5dTdWXlaWE8Y8F8RuDjc+9xp9Sge8fZf45E43LMeHK5Tzkd/WqnLKL4CThFQcyO5g==" + "version": "1.10.0", + "resolved": "https://nexus.in.devexperts.com/repository/dxlink-npm/@dxscript/js-samples/-/js-samples-1.10.0.tgz", + "integrity": "sha512-o0DjsNhrHRHIAKTjtCGKDnav6kkgllcH+iQ5ZQUSehMm2zImIFYHmZFjdsPaShuPy8Ja1xzvzHoPLKmZ+sABaQ==" }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.1", From 11d174525799bb59cabaa52a791f0c7a702dd420 Mon Sep 17 00:00:00 2001 From: Antonio Pedro Date: Mon, 18 May 2026 09:59:56 +0100 Subject: [PATCH 5/5] chore(indichart): bump dxScript devtools version to 1.11.2 --- dxlink-javascript/dxlink-docs/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dxlink-javascript/dxlink-docs/package.json b/dxlink-javascript/dxlink-docs/package.json index 9032291..211d71f 100644 --- a/dxlink-javascript/dxlink-docs/package.json +++ b/dxlink-javascript/dxlink-docs/package.json @@ -13,8 +13,8 @@ "@asyncapi/react-component": "2.5.0", "@dxfeed/dxlink-api": "*", "@dxfeed/ui-kit": "2.15.2", - "@dxscript/dxlink-dxcharts-lite": "^1.0.1", - "@dxscript/dxlink-dxscript-editor": "^1.0.1", + "@dxscript/dxlink-dxcharts-lite": "^1.11.2", + "@dxscript/dxlink-dxscript-editor": "^1.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-is": "^18.2.0",