mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
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:
commit
9854f4652f
8 changed files with 147 additions and 81 deletions
|
|
@ -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)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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({})
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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[]) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue