mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Add basic UI for editing labels
This commit is contained in:
parent
c7841b8e8a
commit
8f68b60f18
5 changed files with 182 additions and 0 deletions
34
packages/web/components/elements/Label.tsx
Normal file
34
packages/web/components/elements/Label.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
102
packages/web/components/templates/article/EditLabelsModal.tsx
Normal file
102
packages/web/components/templates/article/EditLabelsModal.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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'),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
|||
29
packages/web/lib/networking/mutations/setLabelsMutation.ts
Normal file
29
packages/web/lib/networking/mutations/setLabelsMutation.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue