CB preview debounce (#8909)

To make engine's life a bit easier, we added 200ms debounce to CB preview updates.
This commit is contained in:
Adam Obuchowicz 2024-02-01 18:37:00 +01:00 committed by GitHub
parent 9a2bb19a89
commit ed65af7005
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 28 deletions

View File

@ -52,10 +52,11 @@ test('Different ways of opening Component Browser', async ({ page }) => {
await page.mouse.click(40, 300)
await expectAndCancelBrowser('final.')
// Double-clicking port
// TODO[ao] Without timeout, even the first click would be treated as double due to previous
// event. Probably we need a better way to simulate double clicks.
await page.waitForTimeout(600)
await page.mouse.click(outputPortX, outputPortY)
await page.mouse.click(outputPortX, outputPortY)
// TODO[ao] the above click is already treated as double (due to previous event)
// But perhaps we should have more reliable method of simulating double clicks.
// await outputPortArea.dispatchEvent('pointerdown')
await expectAndCancelBrowser('final.')
})

View File

@ -21,6 +21,7 @@ import { tryGetIndex } from '@/util/data/array'
import type { Opt } from '@/util/data/opt'
import { allRanges } from '@/util/data/range'
import { Vec2 } from '@/util/data/vec2'
import { debouncedGetter } from '@/util/reactivity'
import type { SuggestionId } from 'shared/languageServerTypes/suggestions'
import { computed, onMounted, ref, watch, type ComputedRef, type Ref } from 'vue'
@ -158,29 +159,9 @@ useEvent(
{ capture: true },
)
// === Preview ===
const inputElement = ref<HTMLElement>()
const inputSize = useResizeObserver(inputElement, false)
const previewedExpression = computed(() => {
if (selectedSuggestion.value == null) return input.code.value
else return input.inputAfterApplyingSuggestion(selectedSuggestion.value).newCode
})
const previewDataSource: ComputedRef<VisualizationDataSource | undefined> = computed(() => {
if (!previewedExpression.value.trim()) return
if (!graphStore.methodAst) return
const body = graphStore.methodAst.body
if (!body) return
return {
type: 'expression',
expression: previewedExpression.value,
contextId: body.externalId,
}
})
// === Components List and Positions ===
const components = computed(() =>
@ -257,6 +238,26 @@ function selectWithoutScrolling(index: number) {
selected.value = index
}
// === Preview ===
const previewedExpression = debouncedGetter(() => {
if (selectedSuggestion.value == null) return input.code.value
else return input.inputAfterApplyingSuggestion(selectedSuggestion.value).newCode
}, 200)
const previewDataSource: ComputedRef<VisualizationDataSource | undefined> = computed(() => {
if (!previewedExpression.value.trim()) return
if (!graphStore.methodAst) return
const body = graphStore.methodAst.body
if (!body) return
return {
type: 'expression',
expression: previewedExpression.value,
contextId: body.externalId,
}
})
// === Scrolling ===
const scroller = ref<HTMLElement>()

View File

@ -119,16 +119,37 @@ export function cachedGetter<T>(
getter: () => T,
equalFn: (a: T, b: T) => boolean = defaultEquality,
): Ref<T> {
const valueRef = shallowRef<T>()
const valueRef = shallowRef<T>(getter())
watch(
getter,
(newValue) => {
const oldValue = valueRef.value
if (oldValue === undefined || !equalFn(oldValue, newValue)) valueRef.value = newValue
if (!equalFn(oldValue, newValue)) valueRef.value = newValue
},
{ immediate: true, flush: 'sync' },
{ flush: 'sync' },
)
// Since the watch is immediate, the value is guaranteed to be assigned at least once this point.
return valueRef as Ref<T>
return valueRef
}
/**
* Same as `cachedGetter`, except that any changes will be not applied immediately, but only after
* the timer set for `delayMs` milliseconds will expire. If any further update arrives in that
* time, the timer is restarted
*/
export function debouncedGetter<T>(
getter: () => T,
delayMs: number,
equalFn: (a: T, b: T) => boolean = defaultEquality,
): Ref<T> {
const valueRef = shallowRef<T>(getter())
let currentTimer: ReturnType<typeof setTimeout> | undefined
watch(getter, (newValue) => {
clearTimeout(currentTimer)
currentTimer = setTimeout(() => {
const oldValue = valueRef.value
if (!equalFn(oldValue, newValue)) valueRef.value = newValue
}, delayMs)
})
return valueRef
}