Throttle set label calls

This commit is contained in:
Jackson Harper 2023-06-21 19:59:32 +08:00
parent 5dd0e2183f
commit e540c57da4
5 changed files with 82 additions and 49 deletions

View file

@ -2,7 +2,6 @@ import { Button } from './Button'
import { SpanBox, HStack } from './LayoutPrimitives'
import { Circle, X } from 'phosphor-react'
import { isDarkTheme } from '../../lib/themeUpdater'
import { theme } from '../tokens/stitches.config'
type EditLabelChipProps = {
text: string
@ -11,7 +10,7 @@ type EditLabelChipProps = {
xAction: () => void
}
export function EditLabelLabelChip(props: EditLabelChipProps): JSX.Element {
export function EditLabelChip(props: EditLabelChipProps): JSX.Element {
const isDark = isDarkTheme()
const selectedBorder = isDark ? '#FFEA9F' : '#D9D9D9'

View file

@ -1,11 +1,14 @@
import AutosizeInput from 'react-input-autosize'
import { Box, SpanBox } from './LayoutPrimitives'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Label } from '../../lib/networking/fragments/labelFragment'
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
import { isTouchScreenDevice } from '../../lib/deviceType'
import { EditLabelLabelChip } from './EditLabelChip'
import { EditLabelChip } from './EditLabelChip'
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
import { EditLabelChipStack } from './EditLabelChipStack'
const MaxUnstackedLabels = 4
type LabelsPickerProps = {
selectedLabels: Label[]
@ -33,6 +36,7 @@ type LabelsPickerProps = {
export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
const inputRef = useRef<HTMLInputElement | null>()
const availableLabels = useGetLabelsQuery()
const [isStackExpanded, setIsStackExpanded] = useState(false)
const {
focused,
inputValue,
@ -100,6 +104,10 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
return selectedLabels.length === 0 && inputValue.length === 0
}, [inputValue, selectedLabels])
const isStacked = useMemo(() => {
return selectedLabels.length > MaxUnstackedLabels && !isStackExpanded
}, [selectedLabels.length, isStackExpanded])
return (
<Box
css={{
@ -150,27 +158,39 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
event.preventDefault()
}}
>
{props.selectedLabels.map((label, idx) => (
<EditLabelLabelChip
key={label.id}
text={label.name}
color={label.color}
isSelected={
props.highlightLastLabel && idx == props.selectedLabels.length - 1
}
xAction={() => {
const idx = props.selectedLabels.findIndex((l) => l.id == label.id)
if (idx !== -1) {
const _selectedLabels = props.selectedLabels
_selectedLabels.splice(idx, 1)
props.dispatchLabels({
type: 'SAVE',
labels: [..._selectedLabels],
})
}
{isStacked ? (
<EditLabelChipStack
labels={selectedLabels}
setExpanded={(expanded: boolean) => {
setIsStackExpanded(true)
}}
isSelected={props.highlightLastLabel}
/>
))}
) : (
props.selectedLabels.map((label, idx) => (
<EditLabelChip
key={label.id}
text={label.name}
color={label.color}
isSelected={
props.highlightLastLabel && idx == props.selectedLabels.length - 1
}
xAction={() => {
const idx = props.selectedLabels.findIndex(
(l) => l.id == label.id
)
if (idx !== -1) {
const _selectedLabels = props.selectedLabels
_selectedLabels.splice(idx, 1)
props.dispatchLabels({
type: 'SAVE',
labels: [..._selectedLabels],
})
}
}}
/>
))
)}
<SpanBox
css={{
display: 'inline-flex',
@ -208,7 +228,12 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
props.clearInputState()
break
case 'Enter':
props.selectOrCreateLabel(props.inputValue)
if (isStacked && props.highlightLastLabel) {
setIsStackExpanded(true)
props.setHighlightLastLabel(false)
} else {
props.selectOrCreateLabel(props.inputValue)
}
event.preventDefault()
break
}
@ -223,7 +248,12 @@ export const LabelsPicker = (props: LabelsPickerProps): JSX.Element => {
case 'Backspace':
clearTabState()
if (props.inputValue.length === 0) {
props.deleteLastLabel()
if (isStacked && props.highlightLastLabel) {
setIsStackExpanded(true)
props.setHighlightLastLabel(false)
} else {
props.deleteLastLabel()
}
event.preventDefault()
}
break

View file

@ -52,11 +52,6 @@ import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
import { NotebookPresenter } from '../article/NotebookPresenter'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import {
LabelAction,
LabelsDispatcher,
} from '../../../lib/hooks/useSetPageLabels'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export type LibraryMode = 'reads' | 'highlights'

View file

@ -1,8 +1,8 @@
import { useEffect, useReducer } from 'react'
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react'
import { setLabelsMutation } from '../networking/mutations/setLabelsMutation'
import { Label } from '../networking/fragments/labelFragment'
import { showErrorToast } from '../toastHelpers'
import { LabelsProvider } from '../../components/templates/article/SetLabelsControl'
import throttle from 'lodash/throttle'
export type LabelAction = 'RESET' | 'TEMP' | 'SAVE'
export type LabelsDispatcher = (action: {
@ -13,9 +13,24 @@ export type LabelsDispatcher = (action: {
export const useSetPageLabels = (
articleId?: string
): [{ labels: Label[] }, LabelsDispatcher] => {
const saveLabels = (labels: Label[]) => {
;(async () => {
const labelIds = labels.map((l) => l.id)
if (articleId) {
const result = await setLabelsMutation(articleId, labelIds)
if (!result) {
showErrorToast('Error saving labels', {
position: 'bottom-right',
})
}
}
})()
}
const labelsReducer = (
state: {
labels: Label[]
throttledSave: (labels: Label[]) => void
},
action: {
type: string
@ -25,37 +40,26 @@ export const useSetPageLabels = (
switch (action.type) {
case 'RESET': {
return {
...state,
labels: action.labels,
}
}
case 'TEMP': {
return {
...state,
labels: action.labels,
}
}
case 'SAVE': {
const labelIds = action.labels.map((l) => l.id)
if (articleId) {
;(async () => {
const result = await setLabelsMutation(articleId, labelIds)
if (result) {
dispatchLabels({
type: 'RESET',
// Use the original labels value here so we dont re-order
labels: action.labels ?? [],
})
} else {
showErrorToast('Error saving labels', {
position: 'bottom-right',
})
}
})()
state.throttledSave(action.labels)
} else {
showErrorToast('Unable to update labels', {
position: 'bottom-right',
})
}
return {
...state,
labels: action.labels,
}
}
@ -64,8 +68,13 @@ export const useSetPageLabels = (
}
}
const debouncedSave = useCallback(
throttle((labels: Label[]) => saveLabels(labels), 2000),
[]
)
const [labels, dispatchLabels] = useReducer(labelsReducer, {
labels: [],
throttledSave: debouncedSave,
})
return [labels, dispatchLabels]

View file

@ -4,11 +4,11 @@ import { gqlFetcher } from '../networkHelpers'
type SetLabelsResult = {
setLabels: SetLabels
errorCodes?: unknown[]
}
type SetLabels = {
labels: Label[]
errorCodes?: unknown[]
}
export async function setLabelsMutation(
@ -35,7 +35,7 @@ export async function setLabelsMutation(
const data = (await gqlFetcher(mutation, {
input: { pageId, labelIds },
})) as SetLabelsResult
return data.errorCodes ? undefined : data.setLabels.labels
return data.setLabels.errorCodes ? undefined : data.setLabels.labels
} catch (error) {
console.log(' -- SetLabelsOutput error', error)
return undefined