From 4362d12f0f68f94ace10cf9282f80c77786463eb Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 9 Mar 2024 19:17:06 -0400 Subject: [PATCH] ongoing UI work; fixes; TV callback loop problems again/still --- package.json | 2 + src/charts/chart.js | 109 ++++---- src/charts/shape.js | 50 ++-- src/components/chart/BuilderFactory.vue | 5 +- src/components/chart/ChartOrder.vue | 61 +++-- src/components/chart/ColorBand.vue | 24 ++ src/components/chart/LimitBuilder.vue | 90 ++++--- src/components/chart/MarketBuilder.vue | 10 +- src/components/chart/RowBar.vue | 30 +++ src/main.js | 1 + src/misc.js | 11 +- src/orderbuild.js | 1 + src/plugins/vuetify.js | 3 - yarn.lock | 326 ++++++++++++------------ 14 files changed, 416 insertions(+), 307 deletions(-) create mode 100644 src/components/chart/ColorBand.vue create mode 100644 src/components/chart/RowBar.vue diff --git a/package.json b/package.json index 22c8943..4aa47cf 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { "name": "dexorder", "version": "0.0.0", + "license": "UNLICENSED", "private": true, "type": "module", "scripts": { "dev": "vite", + "clean": "vite clean", "build": "vite build", "preview": "vite preview", "lint": "eslint . --fix --ignore-path .gitignore" diff --git a/src/charts/chart.js b/src/charts/chart.js index 6dd724b..df29b84 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -42,7 +42,7 @@ export function initWidget(el) { autosize: true, // symbol: 'AAPL', symbol: 'UNIv3:WETH/USDC', // use this for ohlc - interval: '1D', + interval: '15', container: el, // datafeed: new Datafeeds.UDFCompatibleDatafeed("https://demo-feed-data.tradingview.com"), datafeed: datafeed, // use this for ohlc @@ -205,12 +205,15 @@ function handleCrosshairMovement(point) { let drawingEventQueue = [] +let eventLock = false // this is set to true before events are processed, in order to avoid any loops + function handleDrawingEvent(id, event) { + if (eventLock) return // it's important to decouple our actions from the TV thread. we must wait until the TV loop is completed // before interacting with the chart if (drawingEventQueue.length===0) - setTimeout(drawingEventWorker,0) + setTimeout(drawingEventWorker,1) drawingEventQueue.push([id,event]) } @@ -224,54 +227,60 @@ function drawingEventWorker() { function doHandleDrawingEvent(id, event) { // console.log('drawing event', arguments) - const shape = event === 'remove' ? null : chart.getShapeById(id); - if (event === 'create') { - const co = useChartOrderStore(); - const callbacks = co.drawingCallbacks - console.log('drawing callbacks', callbacks) - if (callbacks !== null) { - shapeCallbacks[id] = callbacks - co.drawing = false - const points = shape.getPoints() - const props = shape.getProperties() - invokeCallbacks(shapeCallbacks[id], 'onCreate', id, points, props) - invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points) - invokeCallbacks(shapeCallbacks[id], 'onProps', id, props) - } - } else if (event === 'points_changed') { - if (id in shapeCallbacks) { - const points = shape.getPoints() - // console.log('points', points) - invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points) - } - } else if (event === 'properties_changed') { - if (id in shapeCallbacks) { - const props = shape.getProperties() - // console.log('props', props) - invokeCallbacks(shapeCallbacks[id], 'onProps', id, props) - } - } else if (event === 'move') { - if (id in shapeCallbacks) { - invokeCallbacks(shapeCallbacks[id], 'onMove', id) - } - } else if (event === 'remove') { - if (id in shapeCallbacks) { - invokeCallbacks(shapeCallbacks[id], 'onDelete', id) - } - } else if (event === 'click') { - if (id in shapeCallbacks) { - invokeCallbacks(shapeCallbacks[id], 'onClick', id) - } - } else if (event === 'hide') { - if (id in shapeCallbacks) { - invokeCallbacks(shapeCallbacks[id], 'onHide', id) - } - } else if (event === 'show') { - if (id in shapeCallbacks) { - invokeCallbacks(shapeCallbacks[id], 'onShow', id) - } - } else - console.log('unknown drawing event', event) + eventLock = true + try { + const shape = event === 'remove' ? null : chart.getShapeById(id); + if (event === 'create') { + const co = useChartOrderStore(); + const callbacks = co.drawingCallbacks + console.log('drawing callbacks', callbacks) + if (callbacks !== null) { + shapeCallbacks[id] = callbacks + co.drawing = false + const points = shape.getPoints() + const props = shape.getProperties() + invokeCallbacks(shapeCallbacks[id], 'onCreate', id, points, props) + invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points) + invokeCallbacks(shapeCallbacks[id], 'onProps', id, props) + } + } else if (event === 'points_changed') { + if (id in shapeCallbacks) { + const points = shape.getPoints() + // console.log('points', points) + invokeCallbacks(shapeCallbacks[id], 'onPoints', id, points) + } + } else if (event === 'properties_changed') { + if (id in shapeCallbacks) { + const props = shape.getProperties() + // console.log('props', props) + invokeCallbacks(shapeCallbacks[id], 'onProps', id, props) + } + } else if (event === 'move') { + if (id in shapeCallbacks) { + invokeCallbacks(shapeCallbacks[id], 'onMove', id) + } + } else if (event === 'remove') { + if (id in shapeCallbacks) { + invokeCallbacks(shapeCallbacks[id], 'onDelete', id) + } + } else if (event === 'click') { + if (id in shapeCallbacks) { + invokeCallbacks(shapeCallbacks[id], 'onClick', id) + } + } else if (event === 'hide') { + if (id in shapeCallbacks) { + invokeCallbacks(shapeCallbacks[id], 'onHide', id) + } + } else if (event === 'show') { + if (id in shapeCallbacks) { + invokeCallbacks(shapeCallbacks[id], 'onShow', id) + } + } else + console.log('unknown drawing event', event) + } + finally { + eventLock = false + } } export function deleteShapeId(id) { diff --git a/src/charts/shape.js b/src/charts/shape.js index 07fdd6f..a300026 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -1,6 +1,6 @@ // noinspection JSPotentiallyInvalidUsageOfThis -import {invokeCallback} from "@/common.js"; +import {invokeCallback, mixin} from "@/common.js"; import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape} from "@/charts/chart.js"; @@ -11,19 +11,21 @@ import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape // shape.points // > [{time:17228394, price:42638.83}] // -// shape.model is a vue ref({}) which stores the shape-specific control points, as defined by the shape subclass -// use the shape.model.* fields in components to get reactive effects with the chart. +// shape.model stores the shape-specific control points, as defined by the shape subclass, and may be a vue ref. +// Use the shape.model.* fields in components to get reactive effects with the chart, or pass an onModel callback +// to the constructor // -/* -The `name` field must match the user-facing name in the TradingView toolbar UI -TradingView drawing tool codes: -https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library/#supportedlinetools -text anchored_text note anchored_note signpost double_curve arc icon emoji sticker arrow_up arrow_down arrow_left arrow_right price_label price_note arrow_marker flag vertical_line horizontal_line cross_line horizontal_ray trend_line info_line trend_angle arrow ray extended parallel_channel disjoint_angle flat_bottom anchored_vwap pitchfork schiff_pitchfork_modified schiff_pitchfork balloon comment inside_pitchfork pitchfan gannbox gannbox_square gannbox_fixed gannbox_fan fib_retracement fib_trend_ext fib_speed_resist_fan fib_timezone fib_trend_time fib_circles fib_spiral fib_speed_resist_arcs fib_channel xabcd_pattern cypher_pattern abcd_pattern callout triangle_pattern 3divers_pattern head_and_shoulders fib_wedge elliott_impulse_wave elliott_triangle_wave elliott_triple_combo elliott_correction elliott_double_combo cyclic_lines time_cycles sine_line long_position short_position forecast date_range price_range date_and_price_range bars_pattern ghost_feed projection rectangle rotated_rectangle circle ellipse triangle polyline path curve cursor dot arrow_cursor eraser measure zoom brush highlighter regression_trend fixed_range_volume_profile -*/ - export const ShapeType = { + // this "enum" is a record of the TradingView keystrings + + /* + The `name` field must match the user-facing name in the TradingView toolbar UI + TradingView drawing tool codes: + https://www.tradingview.com/charting-library-docs/latest/api/modules/Charting_Library/#supportedlinetools + text anchored_text note anchored_note signpost double_curve arc icon emoji sticker arrow_up arrow_down arrow_left arrow_right price_label price_note arrow_marker flag vertical_line horizontal_line cross_line horizontal_ray trend_line info_line trend_angle arrow ray extended parallel_channel disjoint_angle flat_bottom anchored_vwap pitchfork schiff_pitchfork_modified schiff_pitchfork balloon comment inside_pitchfork pitchfan gannbox gannbox_square gannbox_fixed gannbox_fan fib_retracement fib_trend_ext fib_speed_resist_fan fib_timezone fib_trend_time fib_circles fib_spiral fib_speed_resist_arcs fib_channel xabcd_pattern cypher_pattern abcd_pattern callout triangle_pattern 3divers_pattern head_and_shoulders fib_wedge elliott_impulse_wave elliott_triangle_wave elliott_triple_combo elliott_correction elliott_double_combo cyclic_lines time_cycles sine_line long_position short_position forecast date_range price_range date_and_price_range bars_pattern ghost_feed projection rectangle rotated_rectangle circle ellipse triangle polyline path curve cursor dot arrow_cursor eraser measure zoom brush highlighter regression_trend fixed_range_volume_profile + */ Segment: {name: 'Trend Line', code: 'trend_line'}, Ray: {name: 'Ray', code: 'ray'}, Line: {name: 'Extended Line', code: 'extended'}, @@ -47,8 +49,6 @@ class Shape { if (onDelete !== null ) this.onDelete = onDelete this.lock = 0 // used to prevent callbacks when we are the ones forcing the chart change - this.pointsLock = 0 - this.propsLock = 0 this.create() } @@ -93,11 +93,20 @@ class Shape { } - setModel(model) { + setModel(model, props=null) { for( const [k,v] of Object.entries(model)) this.model[k] = v this.setPoints(this.pointsFromModel()); - this.setPropsIfDirty(this.propsFromModel()) + let p + const mp = this.propsFromModel(); + if (props===null) { + p = mp + if (p===null) + return + } + else if (mp !== null) + p = mixin(props, mp) + this.setPropsIfDirty(p) } @@ -108,7 +117,6 @@ class Shape { else { const s = this.tvShape(); if (!dragging) { - this.pointsLock++ s.setPoints(points) } else { @@ -126,7 +134,6 @@ class Shape { setProps(props) { if(this.id) { - this.propsLock++ this.tvShape().setProperties(props) } } @@ -206,6 +213,8 @@ function dirtyProps(propsA, propsB) { class ShapeTVCallbacks { + // These methods are called by TradingView and provide some default handling before invoking our own Shape callbacks + constructor(shape) { this.shape = shape } @@ -220,21 +229,14 @@ class ShapeTVCallbacks { onPoints(shapeId, points) { this.shape.points = points - if( this.shape.pointsLock ) { - this.shape.pointsLock-- - return - } this.shape.onPoints(points) this.shape.pointsToModel() this.shape.onModel(this.shape.model) } onProps(shapeId, props) { + console.log('props', shapeId, props) this.shape.props = props - if( this.shape.propsLock ) { - this.shape.propsLock-- - return - } this.shape.onProps(props) this.shape.propsToModel() this.shape.onModel(this.shape.model) diff --git a/src/components/chart/BuilderFactory.vue b/src/components/chart/BuilderFactory.vue index 21a4ade..c0026cb 100644 --- a/src/components/chart/BuilderFactory.vue +++ b/src/components/chart/BuilderFactory.vue @@ -1,5 +1,5 @@ + + \ No newline at end of file diff --git a/src/components/chart/LimitBuilder.vue b/src/components/chart/LimitBuilder.vue index ae27127..3944fae 100644 --- a/src/components/chart/LimitBuilder.vue +++ b/src/components/chart/LimitBuilder.vue @@ -1,13 +1,15 @@