Как условно буферизовать ввод ключа на основе события в RxJs

Я новичок в RxJs и не читал решения этой проблемы. Более подробное объяснение находится в комментариях, но в основном я хочу обработать комбинацию клавиш (я думаю, что буфер сделает это) при нажатии определенной клавиши (например, нажатие «o» будет ждать короткое время для нажатия других клавиш) , но в противном случае немедленно обработайте ввод клавиши (что-либо, кроме «o», если «o» не была нажата или истек «тайм-аут» для «o»).

Observable.fromEvent(document, 'keydown')
  // Now I want to only continue processing the event if the user pressed "o99" in series,
  // as in pressed "o", followed by "9" and then another "9"
  // I think I can do it with buffer
  .buffer(() => Observable.timer(1000))
  .map((e) => 'option-99')
  // However I need to pass the keys through unbuffered if it's anything but an "o" (unless it is following an "o")
  // In other words, "o99" is buffered or something, but "9" is not, and processed immediately
  .map((e) => e.keyCode)

Спасибо


person Community    schedule 05.04.2017    source источник
comment
заняло у меня некоторое время, но см. мой ответ, который реализует логику с небольшим конечным автоматом   -  person user3743222    schedule 06.04.2017


Ответы (1)


У вас есть очень распространенный случай, с которым нужно иметь дело, это событие, связанное с которым действие зависит от некоторого состояния управления (т.е. у вас есть базовый конечный автомат). По сути, если учесть эти два состояния управления: SIMPLE_KEY, COMBO и эти два события: keyup и keyupTimer, то:

  • in the SIMPLE_KEY state
    • if the key pressed is o, then you switch to COMBO state
    • если не o, то остаёмся в том же состоянии и передаем ключ ниже по течению
    • если событие таймера, то мы передаем нулевое значение, которое будет отфильтровано ниже по течению
  • in the COMBO state
    • we accumulate key pressed
    • если события таймера, то мы возвращаемся к состоянию SIMPLE_KEY

Таким образом, чтобы сопоставить ваше событие с действием, вам нужно знать состояние управления, в котором вы находитесь. Оператор scan позволяет вам решать, как обрабатывать событие в зависимости от накопленного состояния.

Это может быть что-то вроде этого (https://jsfiddle.net/cbhmxaas/):

function getKeyFromEvent(ev) {return ev.key}
function isKeyPressedIsO(ev) {return getKeyFromEvent(ev) === 'o'}

const KEY = 'key';
const TIMER = 'timer';
const COMBO = 'combo';
const SIMPLE_KEY = 'whatever';
const timeSpanInMs = 1000;
const keyup$ = Rx.Observable.fromEvent(document, 'keyup')
  .map(ev => ({event: KEY, data: getKeyFromEvent(ev)}));
const keyupTimer$ = keyup$.flatMapLatest(eventStruct => 
  Rx.Observable.of(eventStruct)
//    .tap(console.warn.bind(console, 'timer event'))
    .delay(timeSpanInMs)
    .map(() => ({event : TIMER, data: null}))
    );

Rx.Observable.merge(keyup$, keyupTimer$)
//  .tap(console.warn.bind(console))
  .scan((acc, eventStruct) => {
    if (acc.control === SIMPLE_KEY) {
      if (eventStruct.event === KEY) {
        if (eventStruct.data === `o`) {
          return {control: COMBO, keyOrKeys : []}
        }
        else {
          return {control: SIMPLE_KEY, keyOrKeys : eventStruct.data}
        }
      }
      else {
      // TIMER event
        return {control: SIMPLE_KEY, keyOrKeys : null}
      }
    }
    else {
      // we only have two states, so it is COMBO state here
      if (eventStruct.event === KEY) {
        return {control: COMBO, keyOrKeys : acc.keyOrKeys.concat([eventStruct.data])}
      }
      else {
        // this is a TIMER event, we only have two events
        return {control: SIMPLE_KEY, keyOrKeys : acc.keyOrKeys}
      }
    }
  }, {control: SIMPLE_KEY, keyOrKeys : null})
//  .tap(console.warn.bind(console))
  .filter(state => state.keyOrKeys) // TIMER event in SIMPLE_KEY state
  .map (({control, keyOrKeys}) => {
  // Here you associate your action
  // if keyOrKeys is an array, then you have a combo
  // else you have a single key
  console.log('key(s) pressed', keyOrKeys)
  return keyOrKeys
})
  .subscribe (console.log.bind(console))
person user3743222    schedule 05.04.2017