diff --git a/package.json b/package.json index 4aa47cf..9c5f5dd 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "socket.io-client": "^4.7.2", "vue": "^3.2.0", "vue-router": "^4.0.0", + "vue-scroll-picker": "^1.2.2", "vuetify": "^3.4.6", "webfontloader": "^1.0.0" }, diff --git a/src/charts/chart.js b/src/charts/chart.js index 9b325ae..f850013 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -216,7 +216,13 @@ function handleCrosshairMovement(point) { draggingShapeIds = chart.selection().allSources(); // console.log('dragging selected', draggingShapeIds) for (const shapeId of draggingShapeIds) { - const shape = chart.getShapeById(shapeId); + let shape + try { + shape = chart.getShapeById(shapeId); + } + catch (e) { + continue + } const points = shape.getPoints(); // console.log(`dragging ${shapeId}`, shape, points1, points2, points3) // invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, shape, points) diff --git a/src/charts/shape.js b/src/charts/shape.js index df7e97c..f433081 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -146,9 +146,10 @@ export class Shape { this.setProps(newProps) } - onModel(model, changedKeys) {} // model was changed by a TradingView user action + onModel(model, oldModel, changedKeys) {} // model was changed by a TradingView user action updateModel(changes) { + const oldModel = {...this.model} if (this.debug) console.log('updateModel', this.id, changes) const changedKeys = [] for (const k of Object.keys(changes)) { @@ -158,7 +159,7 @@ export class Shape { } } if (changedKeys.length) - this.onModel(this.model, changedKeys) + this.onModel(this.model, oldModel, changedKeys) } colorProps() { @@ -195,31 +196,37 @@ export class Shape { if (this.id !== null) return // programatically create the shape using the current this.points if( this.ourPoints && this.ourPoints.length ) { - this.doCreate() + this.doCreate(this.ourPoints, this.ourProps, this.creationOptions) } } - doCreate() { + doCreate(points, props, options={}) { // createShape(this.type, this.points, {overrides:this.props}, new ShapeTVCallbacks(this)) - const options = {...this.creationOptions} - options['overrides'] = this.ourProps - this.tvPoints = [...this.ourPoints] + options = {...options} + options['overrides'] = props + this.tvPoints = [...points] this.tvCallbacks = new ShapeTVCallbacks(this); - createShape(this.type, this.ourPoints, options, this.tvCallbacks) - if (this.debug) console.log('created', this.type.name, this.ourPoints, this.id) + createShape(this.type, points, options, this.tvCallbacks) + if (this.debug) console.log('created', this.type.name, points, this.id) } createOrDraw() { if(this.id) return if(this.ourPoints && this.ourPoints.length) - this.doCreate(this.ourPoints) + this.create() else this.draw() } tvShape() { - return this.id === null ? null : chart.getShapeById(this.id); + try { + return this.id === null ? null : chart.getShapeById(this.id); + } + catch (e) { + console.error(`Could not get chart shape ${this.id}`) + return null + } } @@ -233,19 +240,25 @@ export class Shape { this.delete() else { if (this.id === null) - this.doCreate() + this.create() else if (dirtyPoints(this.tvPoints, points)) { const s = this.tvShape(); let sbm = false if( draggingShapeIds.length ) { - const dragShape = chart.getShapeById(draggingShapeIds[0]) - const sbms = [...dragShape._model._sourcesBeingMoved] - // console.log('shape setPoints', this.id, dragShape, sbm) - sbm = !sbms.length - // if (!sbms.length) { - // console.log('shape setPoints SBM return') - // return - // } + let dragShape + try { + dragShape = chart.getShapeById(draggingShapeIds[0]) + const sbms = [...dragShape._model._sourcesBeingMoved] + // console.log('shape setPoints', this.id, dragShape, sbm) + sbm = !sbms.length + // if (!sbms.length) { + // console.log('shape setPoints SBM return') + // return + // } + } + catch (e) { + console.error(`could not get drag shape ${draggingShapeIds[0]}`) + } } if (this.debug) console.log('adjusting tv points', this.tvPoints, points, dragging, this.beingDragged()) // if ( dragging && !this.beingDragged()) @@ -328,7 +341,10 @@ export class Shape { onRedraw() {} // the mouse moved while in drawing mode onUndraw() {} // drawing was canceled by clicking on a different tool onAddPoint() {} // the user clicked a point while drawing (that point is added to the points list) - onCreate(points, props) {} // the user has finished creating all the control points. drawing mode is exited and the initial shape is created. + onCreate(points, props) { + this.tvPoints = points + this.tvProps = props + } // the user has finished creating all the control points. drawing mode is exited and the initial shape is created. onMove(points) {} // the shape was moved by dragging a drawing element not the control point onDrag(points) {} onHide(props) {} @@ -512,15 +528,6 @@ export class HLine extends Line { } -function timeAdjustmentTooSmall(orig, newValue) { - // TradingView adjusts our lines to be at the start of the intervals, so - // we ignore deltas smaller than one interval prior - return newValue === undefined || - orig !== null && orig !== undefined && newValue !== null && - newValue < orig && orig - newValue < useChartOrderStore().intervalSecs -} - - function nearestOhlcStart(time) { const period = useChartOrderStore().intervalSecs return Math.round(time/period) * period @@ -534,11 +541,22 @@ export class VLine extends Line { // Model this.model.time = null + // since VLines must be on the start of an OHLC boundry, we track that value separately + this.shapeTime = null + this.setModel(model) // call setModel at the end } + + doCreate(points, props, options = {}) { + this.shapeTime = nearestOhlcStart(points[0].time); + points = [{time: this.shapeTime, price:0}] + super.doCreate(points, props, options); + } + delete() { this.model.time = null + this.shapeTime = null super.delete() } @@ -546,28 +564,37 @@ export class VLine extends Line { setModel(model) { // console.log('VLine setModel', this.id, {...this.model}, model) super.setModel(model) - if (model.time === null) { - this.model.time = null + if (model.time === null) this.delete() - } else if (model.time !== this.model.time) { this.model.time = model.time - const time = nearestOhlcStart(model.time); - this.setPoints([{time, price:0}]) + this.setPoints([{time: model.time, price: 0}]) } } setPoints(points) { - // console.error('VLine setPoints', points) - super.setPoints(points); + if (this.debug) console.error('VLine setPoints...', points) + if (points !== null && points.length) { + // adjust shape to ohlc boundry + const time = nearestOhlcStart(points[0].time); + if (time !== this.shapeTime) { + this.shapeTime = time + super.setPoints([{time, price:0}]); + } + } + else { + this.shapeTime = null + super.setPoints(points); + } + if (this.debug) console.error('VLine setPoints', points) } onPoints(points) { - const orig = this.ourPoints && this.ourPoints.length ? this.ourPoints[0].time : null - super.onPoints(points); const time = points[0].time; - if (!timeAdjustmentTooSmall(orig, time)) + if ( time !== this.shapeTime ) { + super.onPoints(points); this.updateModel({time: time}) + } } } diff --git a/src/components/AbsoluteTimeEntry.vue b/src/components/AbsoluteTimeEntry.vue new file mode 100644 index 0000000..c66201b --- /dev/null +++ b/src/components/AbsoluteTimeEntry.vue @@ -0,0 +1,276 @@ + + + + + diff --git a/src/components/chart/Chart.vue b/src/components/chart/Chart.vue index 7587c92..7ee2710 100644 --- a/src/components/chart/Chart.vue +++ b/src/components/chart/Chart.vue @@ -3,8 +3,8 @@