Skip to content

Commit

Permalink
refactor: de-couple linking controller from active item (#2108)
Browse files Browse the repository at this point in the history
  • Loading branch information
amanharwara committed Dec 19, 2022
1 parent 2b84c24 commit 31bb039
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 200 deletions.
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

0 comments on commit 31bb039

Please sign in to comment.