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:
@ -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}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -115,8 +115,6 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
error: fetchItemsError,
|
||||
} = useGetLibraryItemsQuery(queryInputs)
|
||||
|
||||
console.log('fetchItemsError fetchItemsError: ', fetchItemsError)
|
||||
|
||||
useEffect(() => {
|
||||
const handleRevalidate = () => {
|
||||
;(async () => {
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
@ -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. */}
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user