Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: de-couple linking controller from active item #2108

Merged
merged 5 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import ApplicationProvider from '../ApplicationProvider'
import CommandProvider from '../CommandProvider'
import PanesSystemComponent from '../Panes/PanesSystemComponent'
import DotOrgNotice from './DotOrgNotice'
import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider'

type Props = {
application: WebApplication
Expand Down Expand Up @@ -177,62 +178,61 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
application={application}
featuresController={viewControllerManager.featuresController}
>
<div className={platformString + ' main-ui-view sn-component h-full'}>
<FileDragNDropProvider
application={application}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
>
<PanesSystemComponent />
</FileDragNDropProvider>

<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
<LinkingControllerProvider controller={viewControllerManager.linkingController}>
<div className={platformString + ' main-ui-view sn-component h-full'}>
<FileDragNDropProvider
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>

{renderChallenges()}

<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
linkingController={viewControllerManager.linkingController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
<ConfirmDeleteAccountContainer
application={application}
viewControllerManager={viewControllerManager}
/>
</>
{application.routeService.isDotOrg && <DotOrgNotice />}
</div>
>
<PanesSystemComponent />
</FileDragNDropProvider>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>
{renderChallenges()}
<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
linkingController={viewControllerManager.linkingController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
<ConfirmDeleteAccountContainer
application={application}
viewControllerManager={viewControllerManager}
/>
</>
{application.routeService.isDotOrg && <DotOrgNotice />}
</div>
</LinkingControllerProvider>
</PremiumModalProvider>
</ResponsivePaneProvider>
</AndroidBackHandlerProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import { ElementIds } from '@/Constants/ElementIDs'
import Menu from '../Menu/Menu'
import { getLinkingSearchResults } from '@/Utils/Items/Search/getSearchResults'
import { useApplication } from '../ApplicationProvider'
import { DecryptedItem } from '@standardnotes/snjs'

type Props = {
linkingController: LinkingController
focusPreviousItem: () => void
focusedId: string | undefined
setFocusedId: (id: string) => void
hoverLabel?: string
item: DecryptedItem
}

const ItemLinkAutocompleteInput = ({
Expand All @@ -35,12 +37,16 @@ const ItemLinkAutocompleteInput = ({
focusedId,
setFocusedId,
hoverLabel,
item,
}: Props) => {
const application = useApplication()
const { tags, linkItemToSelectedItem, createAndAddNewTag, isEntitledToNoteLinking, activeItem } = linkingController

const { getLinkedTagsForItem, linkItems, createAndAddNewTag, isEntitledToNoteLinking } = linkingController

const tagsLinkedToItem = getLinkedTagsForItem(item) || []

const [searchQuery, setSearchQuery] = useState('')
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, activeItem)
const { unlinkedItems, shouldShowCreateTag } = getLinkingSearchResults(searchQuery, application, item)

const [dropdownVisible, setDropdownVisible] = useState(false)
const [dropdownMaxHeight, setDropdownMaxHeight] = useState<number | 'auto'>('auto')
Expand Down Expand Up @@ -122,7 +128,7 @@ const ItemLinkAutocompleteInput = ({
<input
ref={inputRef}
className={classNames(
`${tags.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
`${tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70'}`,
'bg-transparent text-sm text-text focus:border-b-2 focus:border-solid focus:border-info lg:text-xs',
'no-border h-7 focus:shadow-none focus:outline-none',
)}
Expand All @@ -141,7 +147,7 @@ const ItemLinkAutocompleteInput = ({
{areSearchResultsVisible && (
<DisclosurePanel
className={classNames(
tags.length > 0 ? 'w-80' : 'mr-10 w-70',
tagsLinkedToItem.length > 0 ? 'w-80' : 'mr-10 w-70',
'absolute z-dropdown-menu flex flex-col overflow-y-auto rounded bg-default py-2 shadow-main',
)}
style={{
Expand All @@ -159,7 +165,8 @@ const ItemLinkAutocompleteInput = ({
>
<LinkedItemSearchResults
createAndAddNewTag={createAndAddNewTag}
linkItemToSelectedItem={linkItemToSelectedItem}
linkItems={linkItems}
item={item}
results={unlinkedItems}
searchQuery={searchQuery}
shouldShowCreateTag={shouldShowCreateTag}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,30 @@ import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
import { useCommandService } from '../CommandProvider'
import { useApplication } from '../ApplicationProvider'
import { useItemLinks } from '@/Hooks/useItemLinks'

type Props = {
linkingController: LinkingController
}

const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
const application = useApplication()
const activeItem = application.itemControllerGroup.activeItemViewController?.item

const { toggleAppPane } = useResponsiveAppPane()

const commandService = useCommandService()

const {
allItemLinks,
notesLinkingToActiveItem,
filesLinkingToActiveItem,
unlinkItemFromSelectedItem: unlinkItem,
activateItem,
} = linkingController
const { unlinkItemFromSelectedItem: unlinkItem, activateItem } = linkingController

const { notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem, notesLinkingToItem, filesLinkingToItem } =
useItemLinks(activeItem)

const allItemsLinkedToItem: ItemLink[] = useMemo(
() => new Array<ItemLink>().concat(notesLinkedToItem, filesLinkedToItem, tagsLinkedToItem),
[filesLinkedToItem, notesLinkedToItem, tagsLinkedToItem],
)

useEffect(() => {
return commandService.addCommandHandler({
Expand All @@ -47,11 +54,11 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
)

const [focusedId, setFocusedId] = useState<string>()
const focusableIds = allItemLinks
const focusableIds = allItemsLinkedToItem
.map((link) => link.id)
.concat(
notesLinkingToActiveItem.map((link) => link.id),
filesLinkingToActiveItem.map((link) => link.id),
notesLinkingToItem.map((link) => link.id),
filesLinkingToItem.map((link) => link.id),
[ElementIds.ItemLinkAutocompleteInput],
)

Expand Down Expand Up @@ -84,26 +91,30 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
)

const isItemBidirectionallyLinked = (link: ItemLink) => {
const existsInAllItemLinks = !!allItemLinks.find((item) => link.item.uuid === item.item.uuid)
const existsInNotesLinkingToItem = !!notesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
const existsInFilesLinkingToItem = !!filesLinkingToActiveItem.find((item) => link.item.uuid === item.item.uuid)
const existsInAllItemLinks = !!allItemsLinkedToItem.find((item) => link.item.uuid === item.item.uuid)
const existsInNotesLinkingToItem = !!notesLinkingToItem.find((item) => link.item.uuid === item.item.uuid)
const existsInFilesLinkingToItem = !!filesLinkingToItem.find((item) => link.item.uuid === item.item.uuid)

return (
existsInAllItemLinks &&
(link.item.content_type === ContentType.Note ? existsInNotesLinkingToItem : existsInFilesLinkingToItem)
)
}

if (!activeItem) {
return null
}

return (
<div
className={classNames(
'note-view-linking-container hidden min-w-80 max-w-full flex-wrap items-center gap-2 bg-transparent md:-mr-2 md:flex',
allItemLinks.length || notesLinkingToActiveItem.length ? 'mt-1' : 'mt-0.5',
allItemsLinkedToItem.length || notesLinkingToItem.length ? 'mt-1' : 'mt-0.5',
)}
>
{allItemLinks
.concat(notesLinkingToActiveItem)
.concat(filesLinkingToActiveItem)
{allItemsLinkedToItem
.concat(notesLinkingToItem)
.concat(filesLinkingToItem)
.map((link) => (
<LinkedItemBubble
link={link}
Expand All @@ -123,6 +134,7 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
focusPreviousItem={focusPreviousItem}
setFocusedId={setFocusedId}
hoverLabel={`Focus input to add a link (${shortcut})`}
item={activeItem}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@ import { useCallback } from 'react'

type Props = {
createAndAddNewTag: LinkingController['createAndAddNewTag']
linkItemToSelectedItem: LinkingController['linkItemToSelectedItem']
linkItems: LinkingController['linkItems']
results: LinkableItem[]
searchQuery: string
shouldShowCreateTag: boolean
onClickCallback?: () => void
isEntitledToNoteLinking: boolean
item: LinkableItem
}

const LinkedItemSearchResults = ({
createAndAddNewTag,
linkItemToSelectedItem,
linkItems,
results,
searchQuery,
shouldShowCreateTag,
onClickCallback,
isEntitledToNoteLinking,
item,
}: Props) => {
const onClickAddNew = useCallback(
(searchQuery: string) => {
Expand All @@ -44,7 +46,7 @@ const LinkedItemSearchResults = ({
key={result.uuid}
className="flex w-full items-center justify-between gap-4 overflow-hidden py-2 px-3 hover:bg-contrast hover:text-foreground focus:bg-info-backdrop"
onClick={() => {
void linkItemToSelectedItem(result)
void linkItems(item, result)
onClickCallback?.()
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FilesController } from '@/Controllers/FilesController'
import { LinkingController } from '@/Controllers/LinkingController'
import { observer } from 'mobx-react-lite'
import { useRef, useCallback } from 'react'
import { useApplication } from '../ApplicationProvider'
import RoundIconButton from '../Button/RoundIconButton'
import Popover from '../Popover/Popover'
import StyledTooltip from '../StyledTooltip/StyledTooltip'
Expand All @@ -16,6 +17,9 @@ type Props = {
}

const LinkedItemsButton = ({ linkingController, filesController, onClickPreprocessing, featuresController }: Props) => {
const application = useApplication()
const activeItem = application.itemControllerGroup.activeItemViewController?.item

const { isLinkingPanelOpen, setIsLinkingPanelOpen } = linkingController
const buttonRef = useRef<HTMLButtonElement>(null)

Expand All @@ -27,13 +31,18 @@ const LinkedItemsButton = ({ linkingController, filesController, onClickPreproce
setIsLinkingPanelOpen(willMenuOpen)
}, [isLinkingPanelOpen, onClickPreprocessing, setIsLinkingPanelOpen])

if (!activeItem) {
return null
}

return (
<>
<StyledTooltip label="Linked items panel">
<RoundIconButton label="Linked items panel" onClick={toggleMenu} ref={buttonRef} icon="link" />
</StyledTooltip>
<Popover togglePopover={toggleMenu} anchorElement={buttonRef.current} open={isLinkingPanelOpen} className="pb-2">
<LinkedItemsPanel
item={activeItem}
isOpen={isLinkingPanelOpen}
linkingController={linkingController}
filesController={filesController}
Expand Down