diff --git a/packages/web/components/elements/Label.tsx b/packages/web/components/elements/Label.tsx new file mode 100644 index 000000000..2a666e0b0 --- /dev/null +++ b/packages/web/components/elements/Label.tsx @@ -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 ( + + {props.text} + + ) +} diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx index 0611b2c09..4058b32d5 100644 --- a/packages/web/components/templates/article/ArticleContainer.tsx +++ b/packages/web/components/templates/article/ArticleContainer.tsx @@ -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 && ( + { + setShowLabelsModal(false) + }} /> + )} ) } diff --git a/packages/web/components/templates/article/EditLabelsModal.tsx b/packages/web/components/templates/article/EditLabelsModal.tsx new file mode 100644 index 000000000..b266e05c3 --- /dev/null +++ b/packages/web/components/templates/article/EditLabelsModal.tsx @@ -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) => { + const label = event.target.value + if (event.target.checked) { + setSelectedLabels([...selectedLabels, label]) + } else { + setSelectedLabels(selectedLabels.filter((l) => l !== label)) + } + }, [selectedLabels, setSelectedLabels]) + + return ( + + + { + event.preventDefault() + }} + css={{ overflow: 'auto', p: '0' }} + > + + + Edit Labels + + + {labels && labels.map((label) => ( + { + if (selectedLabels.includes(label.id)) { + setSelectedLabels(selectedLabels.filter((id) => id !== label.id)) + } else { + setSelectedLabels([...selectedLabels, label.id]) + } + }}> + + ))} + + + + + + + ) +} diff --git a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts index 3bf61288f..29c5eff7e 100644 --- a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts +++ b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts @@ -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'), + }, ] } diff --git a/packages/web/lib/networking/mutations/setLabelsMutation.ts b/packages/web/lib/networking/mutations/setLabelsMutation.ts new file mode 100644 index 000000000..3136daa23 --- /dev/null +++ b/packages/web/lib/networking/mutations/setLabelsMutation.ts @@ -0,0 +1,29 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' + +export async function setLabelsMutation( + linkId: string, + labelIds: string[], +): Promise { + 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 + } +}