Как создать контекстное меню Konva-React - PullRequest
0 голосов
/ 29 ноября 2018

Насколько мне известно, в Konva нет простого / сложного способа создания контекстного меню для щелчка правой кнопкой мыши по объектам.Я занят работой над проектом, который требует использования контекстных меню, поэтому я подумал, что просто создам свое собственное.

Нет нужды говорить, что я новичок в Konva, поэтому я надеялся, что кто-то из SO можету меня есть больше опыта, чтобы помочь мне преодолеть последние препятствия.

Я создал песочницу, расположенную ЗДЕСЬ

Требования:

  1. Объект должен быть перетаскиваемым.(Я скопировал рабочий пример из песочницы Konva.)
  2. Объект должен отображать контекстное меню при щелчке правой кнопкой мыши.
  3. Контекстное меню должно быть динамическим, поэтому допускается наличие нескольких элементов, каждыйвыполнение собственного обратного вызова при нажатии.
  4. После того, как выбор сделан, контекстное меню должно быть закрыто.

До сих пор я понял большую часть этого правильно, но всеЯ борюсь с:

  1. Я не могу понять, как навести курсор мыши на один элемент контекстного меню, выделить его, затем перейти к следующему, который должен быть выделен, а старый восстановлен в исходные настройки.
  2. Выход из контекстного меню перерисовывает весь объект.Я не понимаю, почему.
  3. Нажатие на один элемент запускает обратные вызовы обоих элементов.Зачем?Я нацелился на конкретный пункт меню, по которому щелкнули, но получил оба?
  4. Этот пункт не столько баг, но и больше, я не уверен, что делать дальше: как мне предотвратить создание нескольких контекстных меню?если пользователь щелкнет правой кнопкой мыши несколько раз на объекте?Концептуально я понимаю, что могу искать любые элементы в слое (?) С именем контекстного меню и закрывать его, однако я не знаю, как это сделать.

Я был бы признателен за любыеПомогите.Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 31 декабря 2018

Не уверен, что если я опоздаю, но я бы использовал Реактивные порталы , вот пример на странице реакции-конвы: https://konvajs.github.io/docs/react/DOM_Portal.html

Я раздвоил вашу песочницу с тем, какэто будет сделано: https://codesandbox.io/s/km0n1x8367

0 голосов
/ 30 ноября 2018

Не в реакции, а в простом JS Я боюсь, но он проливает свет на некоторые из ваших действий.

Нажмите на розовый кружок, затем выберите вариант 2 и нажмите подопцию 2.

Области, требующие дополнительной работы:

  • доставить данные конфигурации меню через JSON
  • сделать добавление обратных вызовов методом в классе
  • добавить тайм-аутскрыть, чтобы позволить дрожащим рукам мыши
  • как обрабатывать скрытые подменю, когда пользователь щелкает мышью или щелкает другой параметр
  • добавлять, отображать и скрывать анимации

// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});

// Add a layer some sample shapes
var layer = new Konva.Layer({draggable: false});
stage.add(layer);

// draw some shapes.
var circle = new Konva.Circle({ x: 80, y: 80, radius: 30, fill: 'Magenta'});
layer.add(circle);

var rect = new Konva.Rect({ x: 80, y: 80, width: 60, height: 40, fill: 'Cyan'});
layer.add(rect);

stage.draw();

// that is the boring bit over - now menu fun

// I decided to set up a plain JS object to define my menu structure - could easily receive from async in JSON format. [Homework #1]
var menuData = { options: [
  {key: 'opt1', text: 'Option 1', callBack: null},
  {key: 'opt2', text: 'Option 2', callBack: null, 
    options: [ 
      {key: 'opt2-1', text: 'Sub 1', callBack: null}, 
      {key: 'opt2-2', text: 'Sub 2', callBack: null} 
   ]
  },
  {key: 'opt3', text: 'Option 3', callBack: null},
  {key: 'opt4', text: 'Option 4', callBack: null}  
]};

// Define a menu 'class' object.
var menu = function(menuData) {

  var optHeight = 20;  // couple of dimension constants. 
  var optWidth = 100;
  var colors = ['white','gold'];
  
  this.options = {}; // prepare an associative list accessible by key - will put key into the shape as the name so we can can get from click event to this entry

  this.menuGroup = new Konva.Group({}); // prepare a canvas group to hold the option rects for this level. Make it accessible externally by this-prefix

  var _this = this;  // put a ref for this-this to overcome this-confusion later. 

  // recursive func to add a menu level and assign its option components.
  var addHost = function(menuData, hostGroup, level, pos){  // params are the data for the level, the parent group, the level counter, and an offset position counter
    var menuHost = new Konva.Group({ visible: false});  // make a canvas group to contain new options
    hostGroup.add(menuHost); // add to the parent group

    // for every option at this level
    for (var i = 0; i < menuData.options.length; i = i + 1 ){
      var option = menuData.options[i]; // get the option into a var for readability

      // Add a rect as the background for the visible option in the menu.
      option.optionRect = new Konva.Rect({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, fill: colors[0], stroke: 'silver', name: option.key });
      option.optionText = new Konva.Text({x: (level * optWidth), y: (pos + i) * optHeight, width: optWidth, height: optHeight, text: ' ' + option.text, listening: false, verticalAlign: 'middle'})
  console.log(option.optionText.height())
      option.optionRect
        .on('mouseover', function(){
          this.fill(colors[1])
          layer.draw();
          })
        .on('mouseleave', function(){
          this.fill(colors[0])
          layer.draw();
          })
      
      // click event listener for the menu option 
      option.optionRect.on('click', function(e){

        var key = this.name(); // get back the key we stashed in the rect so we can get the options object from the lookup list 

        if (_this.options[key] && (typeof _this.options[key].callback == 'function')){ // is we found an option and it has a real function as a callback then call it.
          _this.options[key].callback();
        } 
        else {
          console.log('No callback for ' + key)
        }
        
      })
      menuHost.add(option.optionRect); // better add the rect and text to the canvas or we will not see it
      menuHost.add(option.optionText);       
      
      _this.options[option.key] = option; // stash the option in the lookup list for later retrieval in click handlers.

      // pay attention Bond - if this menu level has a sub-level then we call into this function again.  
      if (option.options){
        
        var optionGroup = addHost(option, menuHost, level + 1, i)  // params 3 & 4 are menu depth and popout depth for positioning the rects. 

        // make an onclick listener to show the sub-options
        option.callback = function(e){
          optionGroup.visible(true);
          layer.draw();
        }        
      }
    }
    return menuHost; // return the konva group 
  } 

  // so - now we can call out addHost function for the top level of the menu and it will recurse as needed down the sub-options.
  var mainGroup = addHost(menuData, this.menuGroup, 0, 0);

  // lets be nice and make a show() method that takes a position x,y too.
  this.show = function(location){
    location.x = location.x - 10;  // little offset to get the group under the mouse
    location.y = location.y - 10;
    
    mainGroup.position(location);
    mainGroup.show(); // notice we do not draw the layer here - leave that to the caller to avoid too much redraw.
  }

  // and if we have a show we better have a hide.
  this.hide = function(){
    mainGroup.hide();
  }
  
  // and a top-level group listener for mouse-out to hide the menu. You might want to put a timer on this [Homework #3]
  mainGroup.on('mouseleave', function(){
    this.hide();
    layer.draw();
  })
  
   
  // end of the menu class object.
  return this;
}


// ok - now we can get our menu data turned into a menu
var theMenu = new menu(menuData);
layer.add(theMenu.menuGroup); // add the returned canvas group to the layer
layer.draw();  // and never forget to draw the layer when it is time!

//
// now we can add some arbitrary callbacks to some of the options.
//
// make a trivial function to pop a message when we click option 1
var helloFunc = function(){
  alert('hello')
}
// make this the callback for opt1 - you can move this inside the menu class object as a setCallback(name, function()) method if you prefer [homework #2] 
theMenu.options['opt1'].callback = helloFunc;

// put a function on sub2 just to show it works.
theMenu.options['opt2-2'].callback = function(){ alert('click on sub-2') };

// and the original reason for this - make it a context menu on a shape.
circle.on('click', function(e){
  theMenu.show({x: e.evt.offsetX, y: e.evt.offsetY});
    layer.draw(); 
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>
...