mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Throttle set label calls
This commit is contained in:
parent
5dd0e2183f
commit
e540c57da4
5 changed files with 82 additions and 49 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue