Add basic UI for editing labels

This commit is contained in:
Jackson Harper 2022-02-23 17:06:04 -08:00 committed by Hongbo Wu
parent c7841b8e8a
commit 8f68b60f18
5 changed files with 182 additions and 0 deletions

View file

@ -0,0 +1,34 @@
import { styled } from './../tokens/stitches.config'
import { Root, Image, Fallback } from '@radix-ui/react-avatar'
import { StyledText } from './StyledText'
type LabelProps = {
text: string
color: string // expected to be a RGB hex color string
}
export function Label(props: LabelProps): JSX.Element {
const hexToRgb = (hex: string) => {
var bigint = parseInt(hex.substring(1), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return [r,g,b];
}
const color = hexToRgb(props.color)
return (
<StyledText
css={{
margin: '4px',
borderRadius: '32px',
color: props.color,
padding: '4px 8px 4px 8px',
border: `1px solid ${props.color}`,
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.3)`,
}}
>
{props.text}
</StyledText>
)
}

View file

@ -17,6 +17,7 @@ import { ShareArticleModal } from './ShareArticleModal'
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
import { webBaseURL } from '../../../lib/appConfig'
import { updateThemeLocally } from '../../../lib/themeUpdater'
import { EditLabelsModal } from './EditLabelsModal'
type ArticleContainerProps = {
viewerUsername: string
@ -32,6 +33,7 @@ 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)
@ -56,6 +58,9 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
case 'decrementFontSize':
updateFontSize(Math.max(fontSize - 2, 10))
break
case 'editLabels':
setShowLabelsModal(true)
break
}
})
)
@ -234,6 +239,11 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
onOpenChange={(open: boolean) => setShowShareModal(open)}
/>
)}
{showLabelsModal && (
<EditLabelsModal article={props.article} labels={[ /* props.article.labels */]} onOpenChange={() => {
setShowLabelsModal(false)
}} />
)}
</>
)
}

View file

@ -0,0 +1,102 @@
import {
ModalRoot,
ModalContent,
ModalOverlay,
} from '../../elements/ModalPrimitives'
import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { CrossIcon } from '../../elements/images/CrossIcon'
import { theme } from '../../tokens/stitches.config'
import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
import { ChangeEvent, useCallback, useState } from 'react'
import { Label } from '../../elements/Label'
import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
type EditLabelsModalProps = {
labels: string[]
article: ArticleAttributes
onOpenChange: (open: boolean) => void
}
export function EditLabelsModal(
props: EditLabelsModalProps
): JSX.Element {
const [selectedLabels, setSelectedLabels] = useState(props.labels)
const { labels, revalidate, isValidating } = useGetLabelsQuery()
const saveAndExit = useCallback(async () => {
if (selectedLabels.length > 0) {
const result = await setLabelsMutation(
props.article.id,
selectedLabels,
)
console.log('result of setting labels', result)
}
props.onOpenChange(false)
}, [selectedLabels, props.onOpenChange])
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const label = event.target.value
if (event.target.checked) {
setSelectedLabels([...selectedLabels, label])
} else {
setSelectedLabels(selectedLabels.filter((l) => l !== label))
}
}, [selectedLabels, setSelectedLabels])
return (
<ModalRoot defaultOpen onOpenChange={saveAndExit}>
<ModalOverlay />
<ModalContent
onPointerDownOutside={(event) => {
event.preventDefault()
}}
css={{ overflow: 'auto', p: '0' }}
>
<VStack distribution="start" css={{ p: '0' }}>
<HStack
distribution="between"
alignment="center"
css={{ width: '100%' }}
>
<StyledText style="modalHeadline" css={{ p: '16px' }}>Edit Labels</StyledText>
<Button
css={{ pt: '16px', pr: '16px' }}
style="ghost"
onClick={() => {
props.onOpenChange(false)
}}
>
<CrossIcon
size={20}
strokeColor={theme.colors.grayText.toString()}
/>
</Button>
</HStack>
{labels && labels.map((label) => (
<HStack key={label.id} css={{ height: '50px', verticalAlign: 'middle' }} onClick={() => {
if (selectedLabels.includes(label.id)) {
setSelectedLabels(selectedLabels.filter((id) => id !== label.id))
} else {
setSelectedLabels([...selectedLabels, label.id])
}
}}>
<Label color={label.color} text={label.name} />
<input
type="checkbox"
value={label.id}
onChange={handleChange}
checked={selectedLabels.includes(label.id)}
/>
</HStack>
))}
<HStack css={{ width: '100%', mb: '16px' }} alignment="center">
<Button style="ctaDarkYellow" onClick={saveAndExit}>Save</Button>
</HStack>
</VStack>
</ModalContent>
</ModalRoot>
)
}

View file

@ -210,6 +210,7 @@ type ArticleKeyboardAction =
| 'openOriginalArticle'
| 'incrementFontSize'
| 'decrementFontSize'
| 'editLabels'
export function articleKeyboardCommands(
actionHandler: (action: ArticleKeyboardAction) => void
@ -233,5 +234,11 @@ export function articleKeyboardCommands(
shortcutKeyDescription: '-',
callback: () => actionHandler('decrementFontSize'),
},
{
shortcutKeys: ['l'],
actionDescription: 'Edit labels',
shortcutKeyDescription: 'l',
callback: () => actionHandler('editLabels'),
},
]
}

View file

@ -0,0 +1,29 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
export async function setLabelsMutation(
linkId: string,
labelIds: string[],
): Promise<unknown> {
const mutation = gql`
mutation SetLabels($input: SetLabelsInput!) {
setLabels(input: $input) {
... on SetLabelsSuccess {
labels {
id
}
}
... on SetLabelsError {
errorCodes
}
}
}`
try {
const data = await gqlFetcher(mutation, { input: { linkId, labelIds }})
return data
} catch (error) {
console.log('SetLabelsOutput error', error)
return undefined
}
}