Refresh labels when edited

This commit is contained in:
Jackson Harper 2022-04-10 20:25:17 -07:00
parent e2aeff0c6f
commit 0bb2c1d8c6
8 changed files with 98 additions and 76 deletions

View file

@ -1,3 +1,5 @@
import Link from 'next/link'
import { SpanBox } from './LayoutPrimitives'
import { StyledText } from './StyledText'
type LabelChipProps = {
@ -16,19 +18,24 @@ export function LabelChip(props: LabelChipProps): JSX.Element {
}
const color = hexToRgb(props.color)
return (
<StyledText
css={{
margin: '4px',
borderRadius: '32px',
color: props.color,
fontSize: '12px',
fontWeight: 'bold',
padding: '4px 8px 4px 8px',
border: `1px solid rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.7)`,
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.08)`,
}}
>
{props.text}
</StyledText>
<Link href={`/home?q=label:"${props.text}"`}>
<SpanBox
css={{
display: 'inline-table',
margin: '4px',
borderRadius: '32px',
color: props.color,
fontSize: '12px',
fontWeight: 'bold',
padding: '4px 8px 4px 8px',
whiteSpace: 'nowrap',
cursor: 'pointer',
border: `1px solid rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.7)`,
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.08)`,
}}
>
{props.text}
</SpanBox>
</Link>
)
}

View file

@ -1,8 +1,7 @@
import { Separator } from "@radix-ui/react-separator"
import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from "phosphor-react"
import { useRef } from "react"
import { ArticleAttributes } from "../../../lib/networking/queries/useGetArticleQuery"
import { useGetUserPreferences, UserPreferences } from "../../../lib/networking/queries/useGetUserPreferences"
import { useGetUserPreferences } from "../../../lib/networking/queries/useGetUserPreferences"
import { Button } from "../../elements/Button"
import { Dropdown } from "../../elements/DropdownElements"
import { Box, SpanBox } from "../../elements/LayoutPrimitives"
@ -15,7 +14,7 @@ export type ArticleActionsMenuLayout = 'horizontal' | 'vertical'
type ArticleActionsMenuProps = {
article: ArticleAttributes
layout: ArticleActionsMenuLayout
articleActionHandler: (action: string, arg?: number) => void
articleActionHandler: (action: string, arg?: unknown) => void
}
type MenuSeparatorProps = {
@ -91,7 +90,10 @@ export function ArticleActionsMenu(props: ArticleActionsMenuProps): JSX.Element
</SpanBox>
}
>
<EditLabelsControl article={props.article} />
<EditLabelsControl
article={props.article}
articleActionHandler={props.articleActionHandler}
/>
</ActionDropdown>
{/*
<Button onClick={() => props.articleActionHandler('editLabels')} css={{

View file

@ -1,6 +1,6 @@
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { Article } from './../../../components/templates/article/Article'
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
import { StyledText } from './../../elements/StyledText'
import { ArticleSubtitle } from './../../patterns/ArticleSubtitle'
import { theme, ThemeId } from './../../tokens/stitches.config'
@ -14,9 +14,11 @@ import { userPersonalizationMutation } from '../../../lib/networking/mutations/u
import { updateThemeLocally } from '../../../lib/themeUpdater'
import { ArticleMutations } from '../../../lib/articleActions'
import { LabelChip } from '../../elements/LabelChip'
import { Label } from '../../../lib/networking/fragments/labelFragment'
type ArticleContainerProps = {
article: ArticleAttributes
labels: Label[]
articleMutations: ArticleMutations
scrollElementRef: MutableRefObject<HTMLDivElement | null>
isAppleAppEmbed: boolean
@ -29,14 +31,10 @@ type ArticleContainerProps = {
export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
const [showShareModal, setShowShareModal] = useState(false)
const [showLabelsModal, setShowLabelsModal] = useState(false)
const [showNotesSidebar, setShowNotesSidebar] = useState(false)
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
const [margin, setMargin] = useState(props.margin ?? 360)
const [labels, setLabels] = useState(
props.article.labels?.map((l) => l.id) || []
)
const updateFontSize = async (newFontSize: number) => {
if (fontSize !== newFontSize) {
@ -159,11 +157,13 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
author={props.article.author}
href={props.article.url}
/>
<HStack>
{props.article.labels?.map((label) =>
<LabelChip key={label.id} text={label.name} color={label.color} />
)}
</HStack>
{props.labels ? (
<SpanBox css={{ py: '16px', width: '100%' }}>
{props.labels?.map((label) =>
<LabelChip key={label.id} text={label.name} color={label.color} />
)}
</SpanBox>
) : null}
<ArticleHeaderToolbar
articleTitle={props.article.title}
articleShareURL={props.highlightsBaseURL}

View file

@ -11,12 +11,11 @@ import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticle
import { Check, Circle, PencilSimple, Plus } from 'phosphor-react'
import { isTouchScreenDevice } from '../../../lib/deviceType'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
type EditLabelsControlProps = {
// labels: Label[]
article: ArticleAttributes
// onOpenChange: (open: boolean) => void
// setLabels: (labels: Label[]) => void
articleActionHandler: (action: string, arg?: unknown) => void
}
type HeaderProps = {
@ -170,7 +169,6 @@ export function EditLabelsControl(props: EditLabelsControlProps): JSX.Element {
const parentRef = useRef<HTMLDivElement>(null)
const [filterText, setFilterText] = useState('')
const { labels } = useGetLabelsQuery()
const [selectedLabels, setSelectedLabels] = useState<Label[]>(props.article.labels || [])
const isSelected = useCallback((label: Label): boolean => {
@ -179,14 +177,25 @@ export function EditLabelsControl(props: EditLabelsControlProps): JSX.Element {
})
}, [selectedLabels])
const toggleLabel = useCallback((label: Label) => {
const toggleLabel = useCallback(async (label: Label) => {
let newSelectedLabels = [...selectedLabels]
if (isSelected(label)) {
setSelectedLabels(selectedLabels.filter((other) => {
newSelectedLabels = selectedLabels.filter((other) => {
return other.id !== label.id
}))
})
} else {
setSelectedLabels([...selectedLabels, label])
newSelectedLabels = [...selectedLabels, label]
}
setSelectedLabels(newSelectedLabels)
const result = await setLabelsMutation(
props.article.linkId,
newSelectedLabels.map((label) => label.id)
)
props.article.labels = result
props.articleActionHandler('refreshLabels', result)
console.log('refreshing article with labels', props.article.labels)
}, [isSelected, selectedLabels])
const filteredLabels = useMemo(() => {

View file

@ -1,16 +1,26 @@
import { gql } from 'graphql-request'
import { Label, labelFragment } from '../fragments/labelFragment'
import { gqlFetcher } from '../networkHelpers'
type SetLabelsResult = {
setLabels: SetLabels
errorCodes?: unknown[]
}
type SetLabels = {
labels: Label[]
}
export async function setLabelsMutation(
pageId: string,
linkId: string,
labelIds: string[]
): Promise<any | undefined> {
): Promise<Label[] | undefined> {
const mutation = gql`
mutation SetLabels($input: SetLabelsInput!) {
setLabels(input: $input) {
... on SetLabelsSuccess {
labels {
id
...LabelFields
}
}
... on SetLabelsError {
@ -18,12 +28,12 @@ export async function setLabelsMutation(
}
}
}
${labelFragment}
`
try {
const data = await gqlFetcher(mutation, { input: { pageId, labelIds } })
console.log(data)
return data
const data = await gqlFetcher(mutation, { input: { linkId, labelIds } }) as SetLabelsResult
return data.errorCodes ? undefined : data.setLabels.labels
} catch (error) {
console.log('SetLabelsOutput error', error)
return undefined

View file

@ -1,9 +1,10 @@
import { gql } from 'graphql-request'
import useSWRImmutable from 'swr'
import useSWR from 'swr'
import { makeGqlFetcher, RequestContext, ssrFetcher } from '../networkHelpers'
import { articleFragment, ContentReader } from '../fragments/articleFragment'
import { Highlight, highlightFragment } from '../fragments/highlightFragment'
import { ScopedMutator } from 'swr/dist/types'
import { KeyedMutator, ScopedMutator } from 'swr/dist/types'
import { Label, labelFragment } from '../fragments/labelFragment'
type ArticleQueryInput = {
@ -16,6 +17,7 @@ type ArticleQueryOutput = {
articleData?: ArticleData
articleFetchError: unknown
isLoading: boolean
mutate: KeyedMutator<unknown>
}
type ArticleData = {
@ -99,7 +101,7 @@ export function useGetArticleQuery({
includeFriendsHighlights,
}
const { data, error } = useSWRImmutable(
const { data, error, mutate } = useSWR(
slug ? [query, username, slug, includeFriendsHighlights] : null,
makeGqlFetcher(variables)
)
@ -110,12 +112,14 @@ export function useGetArticleQuery({
// it will be nested in the data pages, if there is one error,
// we invalidate the data and return the error. We also zero out
// the response in the case of an error.
console.log('result data', resultData)
if (!error && resultData && resultData.article.errorCodes) {
resultError = resultData.article.errorCodes
resultData = undefined
}
return {
mutate,
articleData: resultData,
articleFetchError: resultError as unknown,
isLoading: !error && !data,

View file

@ -51,7 +51,7 @@ export function useGetNewsletterEmailsQuery(): NewsletterEmailsQueryResponse {
isValidating,
emailAddresses,
revalidate: () => {
mutate()
mutate(undefined, true)
}
}
}

View file

@ -6,7 +6,7 @@ import { useRouter } from 'next/router'
import { VStack } from './../../../components/elements/LayoutPrimitives'
import { ArticleContainer } from './../../../components/templates/article/ArticleContainer'
import { PdfArticleContainerProps } from './../../../components/templates/article/PdfArticleContainer'
import { useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { articleKeyboardCommands, navigationCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import dynamic from 'next/dynamic'
@ -20,26 +20,12 @@ import { articleReadingProgressMutation } from '../../../lib/networking/mutation
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
import Script from 'next/script'
import { EditLabelsControl } from '../../../components/templates/article/EditLabelsControl'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { isVipUser } from '../../../lib/featureFlag'
import { styled, theme } from '../../../components/tokens/stitches.config'
import { Button } from '../../../components/elements/Button'
import { ArchiveBox, DotsThree, HighlighterCircle, TagSimple, TextAa } from 'phosphor-react'
import { Separator } from '@radix-ui/react-separator'
import { Article } from '../../../components/templates/article/Article'
import { theme } from '../../../components/tokens/stitches.config'
import { ArticleActionsMenu } from '../../../components/templates/article/ArticleActionsMenu'
import { HighlightsModal } from '../../../components/templates/article/HighlightsModal'
import { setLinkArchivedMutation } from '../../../lib/networking/mutations/setLinkArchivedMutation'
import { EditLabelsModal } from '../../../components/templates/article/EditLabelsModal'
import { Label } from '../../../lib/networking/fragments/labelFragment'
const MenuSeparator = styled(Separator, {
width: '100%',
margin: 0,
backgroundColor: 'red',
borderBottom: `1px solid ${theme.colors.grayLine.toString()}`,
my: '8px',
})
const PdfArticleContainerNoSSR = dynamic<PdfArticleContainerProps>(
() => import('./../../../components/templates/article/PdfArticleContainer'),
@ -50,20 +36,28 @@ export default function Home(): JSX.Element {
const router = useRouter()
const scrollRef = useRef<HTMLDivElement | null>(null)
const { slug } = router.query
const [showLabelsModal, setShowLabelsModal] = useState(false)
const [showHighlightsModal, setShowHighlightsModal] = useState(false)
// Populate data cache
const { viewerData } = useGetViewerQuery()
const { articleData } = useGetArticleQuery({
const { preferencesData } = useGetUserPreferences()
const [fontSize, setFontSize] = useState(preferencesData?.fontSize ?? 20)
const [marginWidth, setMarginWidth] = useState(preferencesData?.margin ?? 360)
const { articleData, mutate } = useGetArticleQuery({
username: router.query.username as string,
slug: router.query.slug as string,
includeFriendsHighlights: false,
})
const { preferencesData } = useGetUserPreferences()
const article = articleData?.article.article
const [fontSize, setFontSize] = useState(preferencesData?.fontSize ?? 20)
const [marginWidth, setMarginWidth] = useState(preferencesData?.margin ?? 360)
const [labels, setLabels] = useState<Label[]>([])
useEffect(() => {
if (article?.labels) {
setLabels(article.labels)
}
}, [article])
useKeyboardShortcuts(navigationCommands(router))
@ -73,11 +67,10 @@ export default function Home(): JSX.Element {
}
const updateMarginWidth = async (newMargin: number) => {
console.log('margin', newMargin)
setMarginWidth(newMargin)
}
const actionHandler = async (action: string, arg?: number) => {
const actionHandler = async (action: string, arg?: unknown) => {
switch (action) {
case 'archive':
if (article) {
@ -96,6 +89,9 @@ export default function Home(): JSX.Element {
window.open(url, '_blank')
}
break
case 'refreshLabels':
setLabels(arg as Label[])
break
case 'showHighlights':
setShowHighlightsModal(true)
break
@ -111,10 +107,6 @@ export default function Home(): JSX.Element {
case 'decrementMarginWidth':
updateMarginWidth(Math.max(marginWidth - 50, 200))
break
case 'editLabels':
setShowLabelsModal(true)
console.log('showing labels modal')
break
}
};
@ -194,6 +186,7 @@ export default function Home(): JSX.Element {
highlightsBaseURL={`${webBaseURL}/${viewerData.me?.profile?.username}/${slug}/highlights`}
fontSize={fontSize}
margin={marginWidth}
labels={labels}
articleMutations={{
createHighlightMutation,
deleteHighlightMutation,
@ -213,9 +206,6 @@ export default function Home(): JSX.Element {
}}
/>
)}
{showLabelsModal && (
<EditLabelsModal />
)}
</PrimaryLayout>
)
}