Merge pull request #1586 from omnivore-app/fix/set-labels-on-highlights

Improve the modal for setting highlight labels, always use SetLabelsControl from within the modal
This commit is contained in:
Jackson Harper 2022-12-30 12:23:14 +08:00 committed by GitHub
commit 9854f4652f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 81 deletions

View file

@ -20,6 +20,7 @@ import { useReaderSettings } from '../../../lib/hooks/useReaderSettings'
import { useRef } from 'react'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { SetLabelsModal } from './SetLabelsModal'
export type ArticleActionsMenuLayout = 'top' | 'side'
@ -102,12 +103,7 @@ export function ArticleActionsMenu(
tooltipContent="Adjust Display Settings"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<SpanBox ref={displaySettingsButtonRef}>
<TextAa
size={24}
color={theme.colors.readerFont.toString()}
/>
</SpanBox>
<TextAa size={24} color={theme.colors.readerFont.toString()} />
</TooltipWrapped>
</Button>
<MenuSeparator layout={props.layout} />
@ -122,37 +118,56 @@ export function ArticleActionsMenu(
}}
>
{props.article ? (
<ActionDropdown
layout={props.layout}
triggerElement={
<>
<Button
style="articleActionIcon"
onClick={() => readerSettings.setShowSetLabelsModal(true)}
>
<TooltipWrapped
tooltipContent="Edit labels"
tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
>
<TagSimple
size={24}
color={theme.colors.readerFont.toString()}
/>
<SpanBox ref={displaySettingsButtonRef}>
<TagSimple
size={24}
color={theme.colors.readerFont.toString()}
/>
</SpanBox>
</TooltipWrapped>
}
>
<SetLabelsControl
provider={props.article}
save={(labels: Label[]) => {
if (props.article?.id) {
return setLabelsMutation(
props.article?.id,
labels.map((label) => label.id)
)
}
return Promise.resolve(undefined)
}}
onLabelsChanged={(labels) => {
props.articleActionHandler('refreshLabels', labels)
}}
/>
</ActionDropdown>
</Button>
<MenuSeparator layout={props.layout} />
</>
) : (
// <ActionDropdown
// layout={props.layout}
// triggerElement={
// <TooltipWrapped
// tooltipContent="Edit labels"
// tooltipSide={props.layout == 'side' ? 'right' : 'bottom'}
// >
// <TagSimple
// size={24}
// color={theme.colors.readerFont.toString()}
// />
// </TooltipWrapped>
// }
// >
// <SetLabelsControl
// provider={props.article}
// save={(labels: Label[]) => {
// if (props.article?.id) {
// return setLabelsMutation(
// props.article?.id,
// labels.map((label) => label.id)
// )
// }
// return Promise.resolve(undefined)
// }}
// onLabelsChanged={(labels) => {
// props.articleActionHandler('refreshLabels', labels)
// }}
// />
// </ActionDropdown>
<Button
style="articleActionIcon"
css={{
@ -254,6 +269,29 @@ export function ArticleActionsMenu(
}
/>
)}
{props.article && readerSettings.showSetLabelsModal && (
<SetLabelsModal
provider={props.article}
onOpenChange={(open: boolean) => {
readerSettings.setShowSetLabelsModal(false)
}}
onLabelsUpdated={(labels: Label[]) => {
props.articleActionHandler('refreshLabels', labels)
}}
save={(labels: Label[]) => {
if (props.article?.id) {
return (
setLabelsMutation(
props.article?.id,
labels.map((l) => l.id)
) ?? []
)
}
return Promise.resolve(labels)
}}
/>
)}
</>
)
}

View file

@ -584,8 +584,6 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
onOpenChange={function (open: boolean): void {
setLabelsTarget(undefined)
}}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onLabelsChanged={function (labels: Label[]): void {}}
save={function (labels: Label[]): Promise<Label[] | undefined> {
const result = setLabelsForHighlight(
labelsTarget.id,

View file

@ -105,7 +105,7 @@ export function HighlightsModal(props: HighlightsModalProps): JSX.Element {
onOpenChange={function (open: boolean): void {
setLabelsTarget(undefined)
}}
onLabelsChanged={function (labels: Label[]): void {
onLabelsUpdated={function (labels: Label[]): void {
updateState({})
}}
save={function (labels: Label[]): Promise<Label[] | undefined> {

View file

@ -20,8 +20,11 @@ export interface LabelsProvider {
type SetLabelsControlProps = {
provider: LabelsProvider
onLabelsChanged: (labels: Label[]) => void
save: (labels: Label[]) => Promise<Label[] | undefined>
selectedLabels: Label[]
setSelectedLabels: (labels: Label[]) => void
onLabelsUpdated?: (labels: Label[]) => void
}
type HeaderProps = {
@ -239,9 +242,6 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const router = useRouter()
const [filterText, setFilterText] = useState('')
const { labels, revalidate } = useGetLabelsQuery()
const [selectedLabels, setSelectedLabels] = useState<Label[]>(
props.provider.labels ?? []
)
useEffect(() => {
setFocusedIndex(undefined)
@ -249,36 +249,33 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
const isSelected = useCallback(
(label: Label): boolean => {
return selectedLabels.some((other) => {
return props.selectedLabels.some((other) => {
return other.id === label.id
})
},
[selectedLabels]
[props.selectedLabels]
)
const toggleLabel = useCallback(
async (label: Label) => {
let newSelectedLabels = [...selectedLabels]
let newSelectedLabels = [...props.selectedLabels]
if (isSelected(label)) {
newSelectedLabels = selectedLabels.filter((other) => {
newSelectedLabels = props.selectedLabels.filter((other) => {
return other.id !== label.id
})
} else {
newSelectedLabels = [...selectedLabels, label]
newSelectedLabels = [...props.selectedLabels, label]
}
setSelectedLabels(newSelectedLabels)
props.setSelectedLabels(newSelectedLabels)
props.provider.labels = newSelectedLabels
const result = await props.save(newSelectedLabels)
if (result) {
props.provider.labels = result
if (props.onLabelsChanged) {
props.onLabelsChanged(result)
}
if (props.onLabelsUpdated) {
props.onLabelsUpdated(newSelectedLabels)
}
revalidate()
},
[isSelected, selectedLabels, setSelectedLabels]
[isSelected, props]
)
const filteredLabels = useMemo(() => {
@ -349,14 +346,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
}
}
},
[
filterText,
filteredLabels,
focusedIndex,
isSelected,
selectedLabels,
setSelectedLabels,
]
[filterText, filteredLabels, focusedIndex, isSelected, props]
)
const createLabelFromFilterText = useCallback(async () => {
@ -373,7 +363,7 @@ export function SetLabelsControl(props: SetLabelsControlProps): JSX.Element {
} else {
showErrorToast('Failed to create label', { position: 'bottom-right' })
}
}, [filterText, selectedLabels, setSelectedLabels, toggleLabel])
}, [filterText, props, toggleLabel])
return (
<VStack

View file

@ -1,6 +1,6 @@
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { showErrorToast } from '../../../lib/toastHelpers'
import { SpanBox, VStack } from '../../elements/LayoutPrimitives'
import {
ModalRoot,
@ -12,35 +12,67 @@ import { LabelsProvider, SetLabelsControl } from './SetLabelsControl'
type SetLabelsModalProps = {
provider: LabelsProvider
onLabelsUpdated?: (labels: Label[]) => void
onOpenChange: (open: boolean) => void
onLabelsChanged: (labels: Label[]) => void
save: (labels: Label[]) => Promise<Label[] | undefined>
}
export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
const [selectedLabels, setSelectedLabels] = useState(props.provider.labels)
const [previousSelectedLabels, setPreviousSelectedLabels] = useState(
props.provider.labels ?? []
)
const [selectedLabels, setSelectedLabels] = useState(
props.provider.labels ?? []
)
const labelsEqual = (left: Label[], right: Label[]) => {
if (left.length !== right.length) {
return false
}
for (const label of left) {
if (!right.find((r) => label.id == r.id)) {
return false
}
}
return true
}
const onOpenChange = useCallback(
async (open: boolean) => {
if (selectedLabels) {
// Only make API call if the labels have been modified
if (!labelsEqual(selectedLabels, previousSelectedLabels)) {
const result = await props.save(selectedLabels)
if (result) {
props.onLabelsChanged(result)
} else {
if (props.onLabelsUpdated) {
props.onLabelsUpdated(selectedLabels)
}
if (!result) {
showErrorToast('Error updating labels')
}
}
props.onOpenChange(open)
},
[props.onOpenChange]
[props, selectedLabels, previousSelectedLabels, setSelectedLabels]
)
const handleSave = useCallback(
(labels: Label[]) => {
setSelectedLabels(labels)
return Promise.resolve(labels)
},
[selectedLabels, setSelectedLabels]
)
useEffect(() => {
if (labelsEqual(selectedLabels, previousSelectedLabels)) {
return
}
props
.save(selectedLabels)
.then((result) => {
setPreviousSelectedLabels(result ?? [])
})
.catch((err) => {
console.log('error saving labels: ', err)
})
}, [selectedLabels, setPreviousSelectedLabels])
return (
<ModalRoot defaultOpen onOpenChange={onOpenChange}>
@ -58,8 +90,9 @@ export function SetLabelsModal(props: SetLabelsModalProps): JSX.Element {
</SpanBox>
<SetLabelsControl
provider={props.provider}
save={handleSave}
onLabelsChanged={props.onLabelsChanged}
selectedLabels={selectedLabels}
setSelectedLabels={setSelectedLabels}
onLabelsUpdated={props.onLabelsUpdated}
/>
</VStack>
</ModalContent>

View file

@ -1205,7 +1205,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
{props.labelsTarget?.node.id && (
<SetLabelsModal
provider={props.labelsTarget.node}
onLabelsChanged={(labels: Label[]) => {
onLabelsUpdated={(labels: Label[]) => {
if (props.labelsTarget) {
props.labelsTarget.node.labels = labels
updateState({})

View file

@ -31,6 +31,13 @@ export async function setLabelsForHighlight(
${labelFragment}
`
console.log(
'setting label for highlight id: ',
highlightId,
'labelIds',
labelIds
)
try {
const data = (await gqlFetcher(mutation, {
input: { highlightId, labelIds },

View file

@ -368,7 +368,7 @@ export default function Home(): JSX.Element {
{article && readerSettings.showSetLabelsModal && (
<SetLabelsModal
provider={article}
onLabelsChanged={(labels: Label[]) => {
onLabelsUpdated={(labels: Label[]) => {
actionHandler('refreshLabels', labels)
}}
save={(labels: Label[]) => {