Skip to content

Commit

Permalink
feat: Trigger support. #1601
Browse files Browse the repository at this point in the history
  • Loading branch information
mturoci committed Nov 15, 2022
1 parent 05dead0 commit 7f5ba02
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 45 deletions.
36 changes: 18 additions & 18 deletions py/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6732,46 +6732,46 @@ class AudioAnnotatorItem:
"""
def __init__(
self,
range_from: float,
range_to: float,
from: float,
to: float,
tag: str,
):
_guard_scalar('AudioAnnotatorItem.range_from', range_from, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.range_to', range_to, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.from', from, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.to', to, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.tag', tag, (str,), False, False, False)
self.range_from = range_from
self.from = from
"""The start of the audio annotation in seconds."""
self.range_to = range_to
self.to = to
"""The end of the audio annotation in seconds."""
self.tag = tag
"""The `name` of the audio annotator tag to refer to for the `label` and `color` of this item."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
_guard_scalar('AudioAnnotatorItem.range_from', self.range_from, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.range_to', self.range_to, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.from', self.from, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.to', self.to, (float, int,), False, False, False)
_guard_scalar('AudioAnnotatorItem.tag', self.tag, (str,), False, False, False)
return _dump(
range_from=self.range_from,
range_to=self.range_to,
from=self.from,
to=self.to,
tag=self.tag,
)

@staticmethod
def load(__d: Dict) -> 'AudioAnnotatorItem':
"""Creates an instance of this class using the contents of a dict."""
__d_range_from: Any = __d.get('range_from')
_guard_scalar('AudioAnnotatorItem.range_from', __d_range_from, (float, int,), False, False, False)
__d_range_to: Any = __d.get('range_to')
_guard_scalar('AudioAnnotatorItem.range_to', __d_range_to, (float, int,), False, False, False)
__d_from: Any = __d.get('from')
_guard_scalar('AudioAnnotatorItem.from', __d_from, (float, int,), False, False, False)
__d_to: Any = __d.get('to')
_guard_scalar('AudioAnnotatorItem.to', __d_to, (float, int,), False, False, False)
__d_tag: Any = __d.get('tag')
_guard_scalar('AudioAnnotatorItem.tag', __d_tag, (str,), False, False, False)
range_from: float = __d_range_from
range_to: float = __d_range_to
from: float = __d_from
to: float = __d_to
tag: str = __d_tag
return AudioAnnotatorItem(
range_from,
range_to,
from,
to,
tag,
)

Expand Down
12 changes: 6 additions & 6 deletions py/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2506,22 +2506,22 @@ def audio_annotator_tag(


def audio_annotator_item(
range_from: float,
range_to: float,
from: float,
to: float,
tag: str,
) -> AudioAnnotatorItem:
"""Create an annotator item with initial selected tags or no tags.
Args:
range_from: The start of the audio annotation in seconds.
range_to: The end of the audio annotation in seconds.
from: The start of the audio annotation in seconds.
to: The end of the audio annotation in seconds.
tag: The `name` of the audio annotator tag to refer to for the `label` and `color` of this item.
Returns:
A `h2o_wave.types.AudioAnnotatorItem` instance.
"""
return AudioAnnotatorItem(
range_from,
range_to,
from,
to,
tag,
)

Expand Down
16 changes: 8 additions & 8 deletions r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -2911,21 +2911,21 @@ ui_audio_annotator_tag <- function(

#' Create an annotator item with initial selected tags or no tags.
#'
#' @param range_from The start of the audio annotation in seconds.
#' @param range_to The end of the audio annotation in seconds.
#' @param from The start of the audio annotation in seconds.
#' @param to The end of the audio annotation in seconds.
#' @param tag The `name` of the audio annotator tag to refer to for the `label` and `color` of this item.
#' @return A AudioAnnotatorItem instance.
#' @export
ui_audio_annotator_item <- function(
range_from,
range_to,
from,
to,
tag) {
.guard_scalar("range_from", "numeric", range_from)
.guard_scalar("range_to", "numeric", range_to)
.guard_scalar("from", "numeric", from)
.guard_scalar("to", "numeric", to)
.guard_scalar("tag", "character", tag)
.o <- list(
range_from=range_from,
range_to=range_to,
from=from,
to=to,
tag=tag)
class(.o) <- append(class(.o), c(.wave_obj, "WaveAudioAnnotatorItem"))
return(.o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
<option name="Python" value="true"/>
</context>
</template>
<template name="w_audio_annotator_item" value="ui.audio_annotator_item(range_from=$range_from$,range_to=$range_to$,tag='$tag$'),$END$" description="Create a minimal Wave AudioAnnotatorItem." toReformat="true" toShortenFQNames="true">
<variable name="range_from" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="range_to" expression="" defaultValue="" alwaysStopAt="true"/>
<template name="w_audio_annotator_item" value="ui.audio_annotator_item(from=$from$,to=$to$,tag='$tag$'),$END$" description="Create a minimal Wave AudioAnnotatorItem." toReformat="true" toShortenFQNames="true">
<variable name="from" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="to" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="tag" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
<option name="Python" value="true"/>
Expand Down Expand Up @@ -994,9 +994,9 @@
<option name="Python" value="true"/>
</context>
</template>
<template name="w_full_audio_annotator_item" value="ui.audio_annotator_item(range_from=$range_from$,range_to=$range_to$,tag='$tag$'),$END$" description="Create Wave AudioAnnotatorItem with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="range_from" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="range_to" expression="" defaultValue="" alwaysStopAt="true"/>
<template name="w_full_audio_annotator_item" value="ui.audio_annotator_item(from=$from$,to=$to$,tag='$tag$'),$END$" description="Create Wave AudioAnnotatorItem with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="from" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="to" expression="" defaultValue="" alwaysStopAt="true"/>
<variable name="tag" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
<option name="Python" value="true"/>
Expand Down
4 changes: 2 additions & 2 deletions tools/vscode-extension/component-snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"Wave AudioAnnotatorItem": {
"prefix": "w_audio_annotator_item",
"body": [
"ui.audio_annotator_item(range_from=$1, range_to=$2, tag='$3'),$0"
"ui.audio_annotator_item(from=$1, to=$2, tag='$3'),$0"
],
"description": "Create a minimal Wave AudioAnnotatorItem."
},
Expand Down Expand Up @@ -961,7 +961,7 @@
"Wave Full AudioAnnotatorItem": {
"prefix": "w_full_audio_annotator_item",
"body": [
"ui.audio_annotator_item(range_from=$1, range_to=$2, tag='$3'),$0"
"ui.audio_annotator_item(from=$1, to=$2, tag='$3'),$0"
],
"description": "Create a full Wave AudioAnnotatorItem."
},
Expand Down
95 changes: 95 additions & 0 deletions ui/src/audio_annotator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,99 @@ describe('AudioAnnotator.tsx', () => {
})

})
describe('Wave trigger', () => {
const pushMock = jest.fn()

beforeAll(() => wave.push = pushMock)
beforeEach(() => pushMock.mockReset())

it('Calls push after drawing the annotation', async () => {
const { container } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
const canvasEl = container.querySelector('canvas')!
fireEvent.mouseDown(canvasEl, { clientX: 30, clientY: 10, buttons: 1 })
fireEvent.mouseMove(canvasEl, { clientX: 40, clientY: 20, buttons: 1 })
fireEvent.click(canvasEl, { clientX: 40, clientY: 20, buttons: 1 })

expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Does not call push after drawing the annottaion', async () => {
const { container } = render(<XAudioAnnotator model={model} />)
await waitForComponentLoad()
const canvasEl = container.querySelector('canvas')!
fireEvent.mouseDown(canvasEl, { clientX: 30, clientY: 10, buttons: 1 })
fireEvent.mouseMove(canvasEl, { clientX: 40, clientY: 20, buttons: 1 })
fireEvent.click(canvasEl, { clientX: 40, clientY: 20, buttons: 1 })

expect(pushMock).toHaveBeenCalledTimes(0)
})

it('Calls push after moving', async () => {
const { container } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
expect(wave.args[name]).toMatchObject(items)
const canvasEl = container.querySelector('canvas')!
const moveOffset = 5
fireEvent.click(canvasEl, { clientX: 10, clientY: 50 })
fireEvent.mouseDown(canvasEl, { clientX: 10, clientY: 50, buttons: 1 })
fireEvent.mouseMove(canvasEl, { clientX: 10 + moveOffset, clientY: 60, buttons: 1 })
fireEvent.click(canvasEl, { clientX: 10 + moveOffset, clientY: 60 })

expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Calls push after resizing annotation from', async () => {
const { container } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
expect(wave.args[name]).toMatchObject(items)
const canvasEl = container.querySelector('canvas')!
const moveOffset = 5
const { from } = items[1]
fireEvent.click(canvasEl, { clientX: 70, clientY: 50 })
fireEvent.mouseDown(canvasEl, { clientX: from, clientY: 50, buttons: 1 })
fireEvent.mouseMove(canvasEl, { clientX: from - moveOffset, clientY: 60, buttons: 1 })
fireEvent.click(canvasEl, { clientX: from - moveOffset, clientY: 60 })

expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Calls push after resizing annotation to', async () => {
const { container } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
expect(wave.args[name]).toMatchObject(items)
const canvasEl = container.querySelector('canvas')!
const moveOffset = 5
const { to } = items[0]
fireEvent.click(canvasEl, { clientX: 10, clientY: 50 })
fireEvent.mouseDown(canvasEl, { clientX: to, clientY: 50, buttons: 1 })
fireEvent.mouseMove(canvasEl, { clientX: to + moveOffset, clientY: 60, buttons: 1 })
fireEvent.click(canvasEl, { clientX: to + moveOffset, clientY: 60 })

expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Calls push after removing all annotations', async () => {
const { getByTitle } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
expect(wave.args[name]).toMatchObject(items)
fireEvent.click(getByTitle('reset audio annotator'))
expect(pushMock).toHaveBeenCalledTimes(1)
})

it('Calls push after removing annotation', async () => {
const { container, getByTitle } = render(<XAudioAnnotator model={{ ...model, trigger: true }} />)
await waitForComponentLoad()
const canvasEl = container.querySelector('canvas')!
expect(wave.args[name]).toMatchObject(items)

const removeBtn = getByTitle('remove audio annotation')!
expect(removeBtn).toHaveAttribute('aria-disabled', 'true')
fireEvent.click(canvasEl, { clientX: 3, clientY: 3 })
expect(removeBtn).not.toHaveAttribute('aria-disabled')
fireEvent.click(removeBtn)

expect(pushMock).toHaveBeenCalledTimes(1)
})
})
})
13 changes: 8 additions & 5 deletions ui/src/audio_annotator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ const
: Math.abs(canvasY - (verticalIntersections[j]?.canvasY || WAVEFORM_HEIGHT))
return { canvasY, canvasHeight }
},
fromDrawnToAnnotatorItem = ({ from, to, tag }: DrawnAudioAnnotatorItem) => ({ from, to, tag }),
RangeAnnotator = ({ onAnnotate, activeTag, tags, percentPlayed, skipToTime, annotations, focusAnnotation, duration }: RangeAnnotator) => {
const
canvasRef = React.useRef<HTMLCanvasElement>(null),
Expand Down Expand Up @@ -418,11 +417,15 @@ export const XAudioAnnotator = ({ model }: { model: AudioAnnotator }) => {
gainNodeRef = React.useRef<GainNode>(),
fetchedAudioUrlRef = React.useRef<S>(),
audioPositionIntervalRef = React.useRef<U>(),
setWaveArgs = (annotations: DrawnAudioAnnotatorItem[]) => {
wave.args[model.name] = annotations.map(({ from, to, tag }) => ({ from, to, tag })) as unknown as Rec[]
if (model.trigger) wave.push()
},
activateTag = (tagName: S) => () => {
setActiveTag(tagName)
setAnnotations(prev => {
const newAnnotations = prev.map(a => { if (a.isFocused) a.tag = tagName; return a })
wave.args[model.name] = newAnnotations.map(fromDrawnToAnnotatorItem) as unknown as Rec[]
setWaveArgs(newAnnotations)
return newAnnotations
})
},
Expand Down Expand Up @@ -500,18 +503,18 @@ export const XAudioAnnotator = ({ model }: { model: AudioAnnotator }) => {
setAnnotations(prev => {
const newAnnotations = newAnnotation ? [...prev, newAnnotation] : prev
newAnnotations.sort((a, b) => a.from - b.from)
wave.args[model.name] = newAnnotations.map(fromDrawnToAnnotatorItem) as unknown as Rec[]
setWaveArgs(newAnnotations)
return newAnnotations
})
},
reset = () => {
setAnnotations([])
wave.args[model.name] = []
setWaveArgs([])
},
removeAnnotation = () => {
setAnnotations(prev => {
const newAnnotations = prev.filter(a => !a.isFocused)
wave.args[model.name] = newAnnotations.map(fromDrawnToAnnotatorItem) as unknown as Rec[]
setWaveArgs(newAnnotations)
return newAnnotations
})
},
Expand Down

0 comments on commit 7f5ba02

Please sign in to comment.