Настраиваемая всплывающая подсказка с Rxjs - PullRequest
0 голосов
/ 28 мая 2020

Я пытаюсь реализовать свою настраиваемую подсказку на диаграмме, используя vis-network библиотеку и RX JS.

Основной принцип:

  • Один наблюдаемый diagramElementHover$ для отображения всплывающей подсказки
  • Один наблюдаемый hideTooltipObs$, чтобы скрыть всплывающую подсказку (выдаваемое значение null)
  • Я хочу, чтобы всплывающая подсказка ждала 1000 мс, чтобы проверить, остается ли указатель мыши под отображаемым элементом (очистите всплывающую подсказку, если указатель ушел до этой задержки)
  • Отображение всплывающей подсказки использует директиву *ngIf с tooltip значением

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

В глобальном масштабе мой реализация выглядит так:

this.sub = merge(
    diagramElementHover$.pipe(
        mergeMap(event => of(event).pipe(delay(1000))),
        repeat()
    ),
        hideTooltipObs$
    ).pipe(
        tap(tooltip => this.tooltip = tooltip)
    )

Я пытался использовать оператор takeUntil(), но безуспешно ... Можете ли вы помочь мне в этом?

Вот пример stackblitz .

Ответы [ 3 ]

0 голосов
/ 28 мая 2020

Вы можете использовать switchMap для объединенных объектов.

Тогда delay должен находиться внутри switchMap.

this.sub = merge<any>(
    diagramElementHover$.pipe(
      map(event => event),
      repeat()
    ),
    hideTooltipObs$
).pipe(
    switchMap(tooltip => {
      return of(tooltip).pipe(
        delay(tooltip ? 1000 : 0),
        tap(() =>this.tooltip = tooltip ))
    })
).subscribe()

Здесь мой stackblitz

0 голосов
/ 28 мая 2020

Здесь может быть подходом, при котором вам не нужно вручную подписываться:

app.component. html

<div *ngIf="tooltip$ | async as tooltip"
     class="diagram-tooltip"
     [style.top.px]="tooltip.y"
     [style.left.px]="tooltip.x">
     <p>{{ tooltip.text }}</p>
</div>

app.component .ts

const diagramElementHover$ = merge(
    fromEventPattern(handler => this.network.on('hoverNode', handler))
      .pipe(
        map<any, any>(params => ({ event: params.event, element: this.nodes.find(n => n.id === params.node) }))
      ),

    fromEventPattern(handler => this.network.on('hoverEdge', handler))
      .pipe(
        map<any, any>(params => ({ event: params.event, element: this.edges.find(e => e.id === params.edge) }))
      ),
    ).pipe(
        map(obj => ({
          text: obj.element.label,
          x: obj.event.offsetX + 10,
          y: obj.event.offsetY + 10
        })
      )
    );

  const hideTooltipObs$ = merge(
      fromEventPattern(handler => this.network.on('selectNode', handler)),
      fromEventPattern(handler => this.network.on('blurEdge', handler)),
      fromEventPattern(handler => this.network.on('blurNode', handler)),
  ).pipe(
    mapTo(null), 
    tap(() => console.log('hide tooltip')),
    publish(),
    refCount(),
  );

  const showTooltip$ = diagramElementHover$.pipe(
    switchMap(
      obj => 
        timer(1000).pipe(
          takeUntil(hideTooltipObs$),
          isEmpty(),
          map(isEmpty => isEmpty ? null : obj)
        ),
    )
  );

  const hideToolTip$ = hideTooltipObs$;

  this.tooltip$ = merge(showTooltip$, hideToolTip$);

Давайте go через каждую соответствующую часть.

fromEventPattern просто предоставляет способ преобразования API в Observable.

В случаях выше, он создает Observable, который будет генерировать каждый раз, когда вызывается функция-обработчик .

Подробнее об этом здесь .

const hideTooltipObs$ = merge(
    /* ... */
).pipe(
  mapTo(null), 
  tap(() => console.log('hide tooltip')),
  publish(),
  refCount(),
);

publish() + refCount() будет выполнять многоадресную рассылку результатов, помещая экземпляр Subject между источником данных (hideTooltipObs$) и потребителями данных (например, hideTooltipObs$.pipe().subscribe()). Это необходимо, так как hideTooltipObs$ подписан в нескольких местах.

const showTooltip$ = diagramElementHover$.pipe(
  switchMap(
    obj => 
      timer(1000).pipe(
        takeUntil(hideTooltipObs$),
        isEmpty(),
        map(isEmpty => isEmpty ? null : obj)
      ),
  )
);

Каждый раз, когда выдает diagramElementHover$, запускайте таймер (timer(1000)), который завершится, когда пройдёт либо 1s, либо hideTooltipObs$ излучает (из-за takeUntil). isEmpty() сгенерирует true, если источник завершил без выдачи каких-либо значений и false в противном случае.

Если isEmpty === true, это означает, что hideTooltipObs$ сгенерирован до 1s прошло.

0 голосов
/ 28 мая 2020
  1. Вы можете установить ссылку на всплывающую подсказку как null в наблюдаемой скрытой всплывающей подсказке, чтобы убедиться, что она удаляет всплывающую подсказку при скрытии:
const hideTooltipObs$ = merge(
    new Observable<null>(obs => this.network.on('selectNode', () => obs.next(null))),
    new Observable<null>(obs => this.network.on('blurEdge', () => obs.next(null))),
    new Observable<null>(obs => this.network.on('blurNode', () => obs.next(null)))
).pipe(tap(() => {
  console.log('hide tooltip');
  this.tooltip = null;
}))
Затем вы должны добавить в задержку mergeMap оператор takeUntil. Идея здесь состоит в том, чтобы сохранить ссылку на задержку для отображения всплывающей подсказки до тех пор, пока не сработает скрытие.
 mergeMap(event => of(event).pipe(
     delay(1000),
     takeUntil(hideTooltipObs$),
  )
),

Взгляните на этот stackblitz: https://stackblitz.com/edit/angular-ivy-gfj1gg

...