Как условно отвести поток rx js? - PullRequest
5 голосов
/ 17 марта 2020

Я пытаюсь смоделировать функцию "bru sh", как в любом редакторе изображений.

У меня есть следующие потоки:

  • $pointerDown: указатель нажата
  • $pointerUp: нажат указатель вверх
  • $position: положение bru sh
  • $escape: нажата клавиша ESC

Что я хочу сделать

Когда пользователь перетаскивает мышь, делайте временные вычисления. Если мышь нажата, передайте эти изменения. Если клавиша ESC нажата, не вносите эти изменения.

То, что я сейчас обрабатываю, это первый случай:

$pointerDown.pipe(
   r.switchMap(() =>
       $position.pipe(
           r.throttleTime(150),
           r.map(getNodesUnderBrush),
           r.tap(prepareChanges),
           r.takeUntil($pointerUp),
           r.finalize(commitBrushStroke))
)).subscribe()

Как я могу завершить поток двумя различными способами? Что за идиоматизм c rx js для этого?

Спасибо

Ответы [ 5 ]

0 голосов
/ 20 марта 2020

Вот еще одно возможное решение, очень похожее на ваше оригинальное:

    const start$ = fromEvent(document, "mousedown").pipe(
      tap((event: MouseEvent) => this.start = `x: ${event.clientX}, y: ${event.clientY}`)
    );
    const drag$ = fromEvent(document, "mousemove").pipe(
      tap((event: MouseEvent) => (this.move = `x: ${event.clientX}, y: ${event.clientY}`)      )
    );
    const stop$ = fromEvent(document, "mouseup").pipe(
      tap((event: MouseEvent) => (this.stop = `x: ${event.clientX}, y: ${event.clientY}`))
    );
    const cancel$ = fromEvent(document, "keydown").pipe(
      filter((e: KeyboardEvent) => e.code === "Escape"),
      tap(() => this.stop = 'CANCELLED')
    );

    const source$ = start$
      .pipe(
        mergeMap(() =>
          drag$.pipe(
            takeUntil(merge(stop$, cancel$))
          )
        )
      )
      .subscribe();

Поток может завершиться двумя способами, объединяя оба условия takeUntil:

takeUntil(merge(stop$, cancel$))

Вот Stackblitz: https://stackblitz.com/edit/angular-gw7gyr

0 голосов
/ 17 марта 2020

Я думаю, что есть довольно простой способ сделать это с toArray() и takeUntil(). Я предполагаю, что когда вы сказали, что хотите «зафиксировать» изменения, вы хотите собрать все изменения и обработать их все сразу. В противном случае тот же подход будет работать и с buffer().

$pointerDown.pipe(
   switchMap(() => $position.pipe(
     throttleTime(150),
     map(getNodesUnderBrush),
     tap(prepareChanges), // ?
     takeUntil($pointerUp),
     toArray(),
     takeUntil($escape),
  )
).subscribe(changes => {
  ...
  commitBrushStroke(changes);
})

Таким образом, весь трюк заключается в том, завершите ли вы внутреннюю цепочку до или после toArray. Когда вы завершите его до toArray(), то toArray() выдаст один массив всех собранных им изменений. Если вы завершите его после toArray(), то цепочка будет уничтожена, а toArray() откажется от всего и просто откажется от подписки.

0 голосов
/ 17 марта 2020

Что касается вашего вопроса, я вижу, вам нужно какое-то время state. Здесь ваш state - это наблюдаемая pointerdown/move/dragging, которая должна быть accumulated или cleared и, наконец, emitted. Когда я вижу такой сценарий state, мне всегда нравится использовать оператор scan :

Pre

Ради Простой пример, я не использовал ваши предопределенные наблюдаемые. Если у вас возникли проблемы с адаптацией указанного вами сценария использования указателя c к этому очень похожему, я могу попытаться обновить его, чтобы он был ближе к вашему вопросу

1. Что может представлять мое состояние

Здесь я использую enum [status] для последующего реагирования на событие, которое произошло раньше, и накопление [acc] для точек во времени

interface State {
  acc: number[],
  status: Status
}

enum Status {
  init,
  move,
  finish,
  escape
}

const DEFAULT_STATE: State = {
  acc: [],
  status: Status.init
}

2. Напишите функции, которые изменяют состояние

Ваше требование можно разделить на: накопить [указатель $ + позиция $], фини sh [указатель $], escape [escape $]

const accumulate = (index: number) => (state: State): State =>
({status: Status.move, acc: [...state.acc, index]});

const finish = () => (state: State): State =>
({status: Status.finish, acc: state.acc})

const escape = () => (state: State): State =>
({status: Status.escape, acc: []})

3. Сопоставьте свои функции с вашими наблюдаемыми

merge(
  move$.pipe(map(accumulate)),
  finish$.pipe(map(finish)),
  escape$.pipe(map(escape))
)

4. Используйте функции сканирования, где ваше состояние с течением времени помещается

scan((acc: State, fn: (state: State) => State) => fn(acc), DEFAULT_STATE)

5. Обработка вашего мутированного состояния

Здесь мы хотим обрабатывать только если у нас есть конечное значение sh, поэтому мы фильтруем его

filter(state => state.status === Status.finish),

Пример внутреннего состояния

  1. move $ .next ('1') = Состояние: {status: move, a cc: ['1']}
  2. escape $ .next () = Состояние: {status: escape, cc: []}
  3. move $ .next ('2') = Состояние: {status: move, a cc: ['2']}
  4. finish $ .next () = Состояние: {status: fini sh, a cc: ['2']}

Выполняется stackblitz

К вашему сведению: довольно сложно прийти к такому решению проблем состояния с помощью rx js. Но как только вы поймете, что стоит за идеей, вы сможете использовать ее практически в каждом полном сценарии. Вы избежите побочных эффектов, придерживайтесь рабочего процесса rx js и сможете легко оптимизировать / отладить свой код.

0 голосов
/ 17 марта 2020

Очень интересная проблема!

Вот мой подход:

const down$ = fromEvent(div, 'mousedown');
const up$ = fromEvent(div, 'mouseup');
const calc$ = of('calculations');
const esc$ = fromEvent(document, 'keyup')
  .pipe(
    filter((e: KeyboardEvent) => e.code === 'Escape')
  );

down$ // The source of the stream - mousedown events
  .pipe(
    switchMapTo(calc$.pipe( // Do some calculations
      switchMapTo(
        merge( // `merge()` - want to stop making the calculations when either `up$` or `esc$` emit
          up$.pipe(mapTo({ shouldCommit: true })),
          esc$.pipe(mapTo({ shouldCommit: false })),
        ).pipe(first()) // `first()` - either of these 2 can stop the calculations;
      )
    ))
  )
  .subscribe(console.log)

StackBlitz .

0 голосов
/ 17 марта 2020

Если я правильно понимаю, когда пользователь нажимает управляющую клавишу , тогда весь поток должен быть отписан, чтобы, когда пользователь снова начинает перетаскивание с помощью указательной мыши вниз, поток снова начинает излучать.

Если это так, вы можете попробовать switchMap с $escape и merge, другими словами что-то вроде

const drag$ = $pointerDown.pipe(
   r.switchMap(() =>
       $position.pipe(
           r.throttleTime(150),
           r.map(getNodesUnderBrush),
           r.tap(prepareChanges),
           r.takeUntil($pointerUp),
           r.finalize(commitBrushStroke))
))
const brush$ = $escape.pipe(
   startWith({}),  // emit any value at start just to allow the stream to start
   switchMap(() => drag$)
);
const stopBrush$ = $escape.pipe(tap(() => // do stuff to cancel));
merge(brush$, stopBrush$).subscribe();

Идея заключается в том, что в любое время, когда $escape выходит, предыдущая подписка на drag$ отменяется, и начинается новая. В то же время может быть выполнен любой лог c для отмены того, что должно быть отменено.

Я не могу проверить эту вещь, поэтому я надеюсь, что я что-то не забыл.

...