bbc / peaks.js

JavaScript UI component for interacting with audio waveforms
https://waveform.prototyping.bbc.co.uk
GNU Lesser General Public License v3.0
3.2k stars 279 forks source link

[Question] Can CustomPointer class events communicate with their parents? #431

Closed rowild closed 2 years ago

rowild commented 2 years ago

The app I am working on includes the CustomPointers pretty much like it is shown in the docs. The createCustomPointer methods looks like this:

createCustomPointMarker(opts) {
    let pm = null
    opts.view === 'zoomview'
        ? pm = new CustomPointMarker(opts)
        : pm = new SimplePointMarker(opts)
        }
}

and the marker is addedlike this:

addNewMarker(key) {
  if (this.getCues.length < this.maxCuesAllowed) {
    const pointOpts = { 
      time: this.peaks.player.getCurrentTime(),
      labelText: key,
      editable: true,
      markerDegree: parseInt(key),
      color: this.pointColors[this.experimentStep][parseInt(key)],
      pointerHeight: this.pointHeights[parseInt(key)],
    }
    this.peaks.points.add(pointOpts) 
    this.$store.commit('programme/addCue', pointOpts)
 }

In CustomPointer I can the assign listeners via bindEventHandler:

bindEventHandlers() {
    this._group.on('click', function () {
      console.log("click")

      console.log('this =', this);
    });
}

How can I "talk" from here with peaks, and even better, with Vue again? I didn't find anything on the console.log. Any ideas?

Bildschirmfoto 2021-12-15 um 00 24 16

chrisn commented 2 years ago

Would it help if the custom point marker options included a reference to the Peaks instance?

To getting access to the Vue objects, would it work to pass those in as additional attributes when you call points.add(). They should be passed through to your CustomPointer constructor.

Note that Peaks already has a points.click event, so do you need to add your own click handler in CustomPointer?

rowild commented 2 years ago

For me, it wouldn't help to add the peaks instance to the point object. I did, however, try to add the Vue Instance to the pointObj:

addNewMarker(key) {
        const pointOpts = {
            time: this.peaks.player.getCurrentTime(),
            labelText: key,
            editable: true,
            color: this.pointColors[this.experimentStep][parseInt(key)],
            markerDegree: parseInt(key),
            pointerHeight: this.pointHeights[parseInt(key)],
        }

        // Save to vuex store WITHOUT the vue instance (would cause a "Maximum call stack size exceeded")
        this.$store.commit('programme/addCue', pointOpts)

        // Add Vue instance now and add it to pointOpts (causes "Maximum..." every second call to "addMarker" ???)
        pointOpts.vueInstance = this
        this.peaks.points.add(pointOpts)
    }
}

The Vue instance is added later, because when adding it right away and then saving the point to the VueX store, a "Maximum call stack size exceeded" happens right away. This actually happens anyway, but written the way I have, it "only" happens every 2nd time.

I do not understand your last comment: Do I not have to add the "bindEvents" method in CustomPointer? I thought I have to, because eventually I will need "dblclick" to open a modal (which is on the Vue instance).

Any idea what I am doing wrong?

rowild commented 2 years ago

Hmm... $nuxt is a global object, which I can call directly from the click event of a point... maybe my thinking is completely wrong. (Even though I haven't found the function yet, that I need, when looking through console.log($nuxt)...)

chrisn commented 2 years ago

I do not understand your last comment: Do I not have to add the "bindEvents" method in CustomPointer? I thought I have to, because eventually I will need "dblclick" to open a modal (which is on the Vue instance).

You shouldn't need to bind events in the CustomPointer, because Peaks will automatically bind click and dblclick handlers to your CustomPointer object, so you should be able to use:

peaks.on('point.dblclick', (point) => {
  console.log(point);
});
rowild commented 2 years ago

Hmmm... there is a place in the Vue class, which is currently the only place where I can think of to place your code:

export default {
  [...]
  mounted() {
    this.peaks = null // non-reactive
  },
  methods: {
    onWaveformLoaded(error, peaks) {
      this.waveformIsLoading = false
      if (error) { throw new Error(error) }
      this.peaks = peaks

      // Here I place your code:
      // 1st option ("peaks" is still directly available)
      peaks.on('point.dblclick', (point) => {
        console.log(point);
      });

      // 2nd option
      this.peaks.on('point.dblclick', (point) => {
        console.log(point);
      });
    }
  }
}

But none works. (I also wouldn't know, how peaks would pick up newly added markers that way...)

This is my CustomPointer class. When I remove the biningEvents, all that is left i can do is dragging the markers...

// CustomPointer class

import { Label, Tag } from 'konva/lib/shapes/Label';
import { Line } from 'konva/lib/shapes/Line';
import { Text } from 'konva/lib/shapes/Text';

export default class CustomPointMarker {
  constructor(options) {
    this._options = options;
  }

  init(group) {
    this._group = group;
    this._label = new Label({ x: 0.5, y: 0.5 });
    const color = this._options.point.color;

    this._tag = new Tag({ ... });
    this._label.add(this._tag);

    const labelText = this._options.point.labelText
    this._text = new Text({ ... });
    this._label.add(this._text);

    this._line = new Line({ ... });

    group.add(this._label);
    group.add(this._line);

    this.fitToView();
    // this.bindEventHandlers();
  }

  fitToView() { ... }

  // bindEventHandlers() {
  //   this._group.on('dblclick', function () {
  //     console.log("double click")
  //   });
  // }
}

What am I doing wrong here?

(I know that you are not a Vue guy, so thank you very, very much for your patience and help - once again!)

chrisn commented 2 years ago

I'm sorry, it should be points.click and points.dblclick, not point.

rowild commented 2 years ago

I think this issue could be closed - what do you think, @chrisn ?

chrisn commented 2 years ago

Thanks, I agree.