Merge pull request #3577 from omnivore-app/fix/web-highlight-labels-ux

Web fixups: disable highlight label icon, add dropdown for settings nav in mobile, add open original hover card
This commit is contained in:
Jackson Harper
2024-02-26 15:41:35 +08:00
committed by GitHub
13 changed files with 154 additions and 51 deletions

View File

@ -4,7 +4,7 @@ import { LibraryItemNode } from '../../../lib/networking/queries/useGetLibraryIt
import { LinkedItemCardAction } from './CardTypes'
import { Button } from '../../elements/Button'
import { theme } from '../../tokens/stitches.config'
import { DotsThree } from 'phosphor-react'
import { DotsThree, Share } from 'phosphor-react'
import { CardMenu } from '../CardMenu'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { ArchiveIcon } from '../../elements/icons/ArchiveIcon'
@ -31,7 +31,7 @@ export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
overflow: 'clip',
height: '33px',
width: '162px',
width: '184px',
bg: '$thBackground',
display: 'flex',
@ -111,6 +111,17 @@ export const LibraryHoverActions = (props: LibraryHoverActionsProps) => {
>
<LabelIcon size={21} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<Button
title="Open original (o)"
style="hoverActionIcon"
onClick={(event) => {
props.handleAction('showOriginal')
event.preventDefault()
event.stopPropagation()
}}
>
<Share size={21} color={theme.colors.thNotebookSubtle.toString()} />
</Button>
<CardMenu
item={props.item}
viewer={props.viewer}

View File

@ -81,6 +81,9 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
'@media (min-width: 1600px)': {
width: '1200px',
},
'@media (max-width: 930px)': {
borderRadius: '0px',
},
}}
alignment="start"
distribution="start"

View File

@ -11,6 +11,7 @@ import { PageMetaData } from '../patterns/PageMetaData'
import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer'
import { logout } from '../../lib/logout'
import { SettingsMenu } from './navMenu/SettingsMenu'
import { SettingsDropdown } from './navMenu/SettingsDropdown'
type SettingsLayoutProps = {
title?: string
@ -50,8 +51,23 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element {
<Box
css={{
height: DEFAULT_HEADER_HEIGHT,
'@mdDown': {
display: 'none',
},
}}
></Box>
<Box
css={{
p: '15px',
display: 'none',
height: DEFAULT_HEADER_HEIGHT,
'@mdDown': {
display: 'flex',
},
}}
>
<SettingsDropdown />
</Box>
<HStack css={{ width: '100%', height: '100%' }} distribution="start">
<SettingsMenu />
{props.children}

View File

@ -80,13 +80,15 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
const focusedHighlightMousePos = useRef({ pageX: 0, pageY: 0 })
const [currentHighlightIdx, setCurrentHighlightIdx] = useState(0)
const [focusedHighlight, setFocusedHighlight] =
useState<Highlight | undefined>(undefined)
const [focusedHighlight, setFocusedHighlight] = useState<
Highlight | undefined
>(undefined)
const [selectionData, setSelectionData] = useSelection(highlightLocations)
const [labelsTarget, setLabelsTarget] =
useState<Highlight | undefined>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const [
confirmDeleteHighlightWithNoteId,
@ -811,7 +813,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
<SetHighlightLabelsModalPresenter
highlight={labelsTarget}
highlightId={labelsTarget.id}
onOpenChange={() => setLabelsTarget(undefined)}
onUpdate={updateHighlightsCallback}
onOpenChange={() => {
setLabelsTarget(undefined)
}}
/>
)}
{confirmDeleteHighlightWithNoteId && (

View File

@ -57,8 +57,9 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
const [noteText, setNoteText] = useState<string>('')
const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
useState<undefined | string>(undefined)
const [labelsTarget, setLabelsTarget] =
useState<Highlight | undefined>(undefined)
const [labelsTarget, setLabelsTarget] = useState<Highlight | undefined>(
undefined
)
const noteState = useRef<NoteState>({
isCreating: false,
note: undefined,
@ -359,6 +360,10 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
<SetHighlightLabelsModalPresenter
highlight={labelsTarget}
highlightId={labelsTarget.id}
onUpdate={(highlight) => {
// Don't actually need to do something here
console.log('update highlight: ', highlight)
}}
onOpenChange={() => {
mutate()
setLabelsTarget(undefined)

View File

@ -3,6 +3,7 @@ import { useSetPageLabels } from '../../../lib/hooks/useSetPageLabels'
import { LabelsProvider } from './SetLabelsControl'
import { SetLabelsModal } from './SetLabelsModal'
import { useSetHighlightLabels } from '../../../lib/hooks/useSetHighlightLabels'
import { Highlight } from '../../../lib/networking/fragments/highlightFragment'
type SetPageLabelsModalPresenterProps = {
articleId: string
@ -15,13 +16,6 @@ export function SetPageLabelsModalPresenter(
): JSX.Element {
const [labels, dispatchLabels] = useSetPageLabels(props.articleId)
useEffect(() => {
dispatchLabels({
type: 'RESET',
labels: props.article.labels ?? [],
})
}, [props.article, dispatchLabels])
const onOpenChange = useCallback(() => {
if (props.article) {
props.article.labels = labels.labels
@ -29,6 +23,13 @@ export function SetPageLabelsModalPresenter(
props.onOpenChange(true)
}, [props, labels])
useEffect(() => {
dispatchLabels({
type: 'RESET',
labels: props.article.labels ?? [],
})
}, [props.article, dispatchLabels])
return (
<SetLabelsModal
provider={props.article}
@ -41,7 +42,9 @@ export function SetPageLabelsModalPresenter(
type SetHighlightLabelsModalPresenterProps = {
highlightId: string
highlight: LabelsProvider
highlight: Highlight
onUpdate: (updatedHighlight: Highlight) => void
onOpenChange: (open: boolean) => void
}
@ -57,12 +60,18 @@ export function SetHighlightLabelsModalPresenter(
})
}, [props.highlight, dispatchLabels])
const onOpenChange = useCallback(() => {
props.highlight.labels = labels.labels
props.onUpdate(props.highlight)
props.onOpenChange(true)
}, [props])
return (
<SetLabelsModal
provider={props.highlight}
selectedLabels={labels.labels}
dispatchLabels={dispatchLabels}
onOpenChange={props.onOpenChange}
onOpenChange={onOpenChange}
/>
)
}

View File

@ -115,8 +115,6 @@ export function HomeFeedContainer(): JSX.Element {
error: fetchItemsError,
} = useGetLibraryItemsQuery(queryInputs)
console.log('fetchItemsError fetchItemsError: ', fetchItemsError)
useEffect(() => {
const handleRevalidate = () => {
;(async () => {

View File

@ -0,0 +1,40 @@
import { DropdownMenu } from '@radix-ui/react-dropdown-menu'
import { SETTINGS_SECTION_1, SETTINGS_SECTION_2 } from './SettingsMenu'
import {
Dropdown,
DropdownOption,
DropdownSeparator,
} from '../../elements/DropdownElements'
import { useRouter } from 'next/router'
import { List } from 'phosphor-react'
export const SettingsDropdown = (): JSX.Element => {
const router = useRouter()
return (
<Dropdown triggerElement={<List size={30} />}>
<DropdownOption onSelect={() => router.push('/home')} title="Home" />
{SETTINGS_SECTION_1.map((item) => {
return (
<DropdownOption
key={`menu1-${item.name}`}
onSelect={() => router.push(item.destination)}
title={item.name}
/>
)
})}
<DropdownSeparator />
{SETTINGS_SECTION_2.map((item) => {
return (
<DropdownOption
key={`menu2-${item.name}`}
onSelect={() => router.push(item.destination)}
title={item.name}
/>
)
})}
</Dropdown>
)
}

View File

@ -9,6 +9,23 @@ import { ArrowSquareUpRight } from 'phosphor-react'
import { useRouter } from 'next/router'
import { NavMenuFooter } from './Footer'
export const SETTINGS_SECTION_1 = [
{ name: 'Account', destination: '/settings/account' },
{ name: 'API Keys', destination: '/settings/api' },
{ name: 'Emails', destination: '/settings/emails' },
{ name: 'Feeds', destination: '/settings/feeds' },
{ name: 'Subscriptions', destination: '/settings/subscriptions' },
{ name: 'Labels', destination: '/settings/labels' },
{ name: 'Shortcuts', destination: '/settings/shortcuts' },
{ name: 'Saved Searches', destination: '/settings/saved-searches' },
{ name: 'Pinned Searches', destination: '/settings/pinned-searches' },
]
export const SETTINGS_SECTION_2 = [
{ name: 'Integrations', destination: '/settings/integrations' },
{ name: 'Install', destination: '/settings/installation' },
]
const HorizontalDivider = styled(SpanBox, {
width: '100%',
height: '1px',
@ -81,22 +98,6 @@ function ExternalLink(props: ExternalLinkProps): JSX.Element {
}
export function SettingsMenu(): JSX.Element {
const section1 = [
{ name: 'Account', destination: '/settings/account' },
{ name: 'API Keys', destination: '/settings/api' },
{ name: 'Emails', destination: '/settings/emails' },
{ name: 'Feeds', destination: '/settings/feeds' },
{ name: 'Subscriptions', destination: '/settings/subscriptions' },
{ name: 'Labels', destination: '/settings/labels' },
{ name: 'Shortcuts', destination: '/settings/shortcuts' },
{ name: 'Saved Searches', destination: '/settings/saved-searches' },
{ name: 'Pinned Searches', destination: '/settings/pinned-searches' },
]
const section2 = [
{ name: 'Integrations', destination: '/settings/integrations' },
{ name: 'Install', destination: '/settings/installation' },
]
return (
<>
<Box
@ -124,7 +125,7 @@ export function SettingsMenu(): JSX.Element {
css={{
width: '100%',
px: '25px',
pb: '50px',
pb: '25px',
pt: '4.5px',
lineHeight: '1',
}}
@ -136,15 +137,17 @@ export function SettingsMenu(): JSX.Element {
css={{
gap: '10px',
width: '100%',
overflowY: 'scroll',
paddingBottom: '120px',
}}
distribution="start"
alignment="start"
>
{section1.map((item) => {
{SETTINGS_SECTION_1.map((item) => {
return <SettingsButton key={item.name} {...item} />
})}
<HorizontalDivider />
{section2.map((item) => {
{SETTINGS_SECTION_2.map((item) => {
return <SettingsButton key={item.name} {...item} />
})}
<HorizontalDivider />
@ -176,8 +179,8 @@ export function SettingsMenu(): JSX.Element {
destination="https://docs.omnivore.app"
title="Documentation"
/>
<NavMenuFooter />
</VStack>
<NavMenuFooter />
</Box>
{/* This spacer pushes library content to the right of
the fixed left side menu. */}

View File

@ -1,6 +1,7 @@
import { HighlightLocation } from './highlightGenerator'
import {
getHighlightElements,
getHighlightLabelButton,
getHighlightNoteButton,
} from './highlightHelpers'
@ -11,11 +12,16 @@ export function removeHighlights(
ids.forEach((id) => {
const elements = getHighlightElements(id)
const noteButtons = getHighlightNoteButton(id)
const labelButtons = getHighlightLabelButton(id)
noteButtons.forEach((button) => {
button.remove()
})
labelButtons.forEach((button) => {
button.remove()
})
elements.forEach((t: Element) => {
if (t.nodeName === 'IMG') {
t.classList.remove('highlight-image')

View File

@ -206,19 +206,19 @@ export function makeHighlightNodeAttributes(
lastElement.appendChild(ctr)
}
if (withLabels && lastElement) {
const svg = labelsImage(customColor)
svg.setAttribute(highlightLabelIdAttribute, id)
// if (withLabels && lastElement) {
// const svg = labelsImage(customColor)
// svg.setAttribute(highlightLabelIdAttribute, id)
const ctr = document.createElement('span')
ctr.className = 'highlight_label_button'
ctr.appendChild(svg)
ctr.setAttribute(highlightLabelIdAttribute, id)
ctr.setAttribute('width', '14px')
ctr.setAttribute('height', '14px')
// const ctr = document.createElement('span')
// ctr.className = 'highlight_label_button'
// ctr.appendChild(svg)
// ctr.setAttribute(highlightLabelIdAttribute, id)
// ctr.setAttribute('width', '14px')
// ctr.setAttribute('height', '14px')
lastElement.appendChild(ctr)
}
// lastElement.appendChild(ctr)
// }
return {
prefix,

View File

@ -28,6 +28,12 @@ export function getHighlightNoteButton(highlightId: string): Element[] {
)
}
export function getHighlightLabelButton(highlightId: string): Element[] {
return Array.from(
document.querySelectorAll(`[${highlightLabelIdAttribute}='${highlightId}']`)
)
}
export function noteImage(color: string | undefined): SVGSVGElement {
const svgURI = 'http://www.w3.org/2000/svg'
const svg = document.createElementNS(svgURI, 'svg')

View File

@ -3,6 +3,7 @@ import { Label } from '../networking/fragments/labelFragment'
import { showErrorToast } from '../toastHelpers'
import { setLabelsForHighlight } from '../networking/mutations/setLabelsForHighlight'
import { LabelsDispatcher } from './useSetPageLabels'
import { Highlight } from '../networking/fragments/highlightFragment'
export const useSetHighlightLabels = (
highlightId?: string