Skip to content

Commit

Permalink
View Hierarchy (#405)
Browse files Browse the repository at this point in the history
#405

* View hierarchy added

* View Header changes

* Patch changetset added

* Use getView hook

* Use getPartialTree in search
  • Loading branch information
dineshsingh1 authored Apr 4, 2023
1 parent a3ddfd1 commit 295c9ba
Show file tree
Hide file tree
Showing 54 changed files with 1,794 additions and 401 deletions.
6 changes: 6 additions & 0 deletions .changeset/twelve-clouds-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'mexit': patch
'mexit-webapp': patch
---

View hierarchy
1 change: 0 additions & 1 deletion apps/extension/src/Hooks/useSnippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export const useSnippets = () => {

chrome.runtime.sendMessage(request, (response) => {
const { message, error } = response

if (error) {
console.error('An Error Occured. Please try again.')
} else {
Expand Down
3 changes: 3 additions & 0 deletions apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"dependencies": {
"@asseinfo/react-kanban": "^2.2.0",
"@atlaskit/tree": "^8.6.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@floating-ui/react": "^0.18.0",
"@floating-ui/react-dom-interactions": "^0.9.3",
Expand Down
4 changes: 2 additions & 2 deletions apps/webapp/src/Components/Sidebar/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useSingleton } from '@tippyjs/react'

import { NavTooltip, TitleWithShortcut } from '@workduck-io/mex-components'

import { useDataStore, useEditorStore, useHelpStore,useLayoutStore } from '@mexit/core'
import { useDataStore, useEditorStore, useHelpStore, useLayoutStore } from '@mexit/core'
import {
ComingSoon,
Count,
Expand Down Expand Up @@ -155,7 +155,7 @@ const Nav = () => {
<NavTooltip key={shortcuts.showHome.title} singleton={target} content={<TitleWithShortcut title="Home" />}>
<NavLogoWrapper>
<NavLink to={ROUTE_PATHS.home}>
<WDLogo height={'56'} width={'56'} />
<WDLogo height={'40'} width={'40'} />
</NavLink>
</NavLogoWrapper>
</NavTooltip>
Expand Down
6 changes: 2 additions & 4 deletions apps/webapp/src/Components/Sidebar/Sidebar.tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useTheme } from 'styled-components'

import { tinykeys } from '@workduck-io/tinykeys'

import { defaultContent, fuzzySearch, ILink, useEditorStore, useLayoutStore, usePublicNodeStore } from '@mexit/core'
import { defaultContent, fuzzySearch, ILink, useLayoutStore } from '@mexit/core'
import { Input, isOnEditableElement, MexIcon, SidebarListFilter } from '@mexit/shared'

import { useCreateNewNote } from '../../Hooks/useCreateNewNote'
Expand Down Expand Up @@ -40,9 +40,7 @@ export const MexTree = ({ items, filterText, spaceId, publicILink, readOnly }: S
* - MultiSelect
* - Drop to Different space
*/
const editorNode = useEditorStore((store) => store.node)
const publicNode = usePublicNodeStore((store) => store.currentNode)
const node = publicILink ? publicNode : editorNode

const [search, setSearch] = useState('')
const [selected, setSelected] = useState<number>(-1)
const { goTo } = useRouting()
Expand Down
200 changes: 200 additions & 0 deletions apps/webapp/src/Components/Sidebar/Sidebar.views.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { ChangeEventHandler, useEffect, useMemo, useRef, useState } from 'react'

import searchLine from '@iconify/icons-ri/search-line'
import { Icon } from '@iconify/react'
import Tippy, { useSingleton } from '@tippyjs/react'
import { debounce } from 'lodash'

import { tinykeys } from '@workduck-io/tinykeys'

import { fuzzySearch, getParentEntity, useLayoutStore, useTreeStore } from '@mexit/core'
import {
EmptyMessage,
FilteredItemsWrapper,
IconDisplay,
Input,
isOnEditableElement,
ItemContent,
ItemTitle,
SidebarListFilter,
SidebarListWrapper,
StyledTreeItem
} from '@mexit/shared'

import { useViewStore } from '../../Stores/useViewStore'
import { SortableTree } from '../Tree'
import { FlattenedItem } from '../Tree/types'
import { buildPartialTree, buildTree, flattenTree } from '../Tree/utilities'

const SidebarViewTree = ({ defaultItems, onClick, onContextMenu }) => {
const [source, target] = useSingleton()
const [tree, setTree] = useState([])
const [filtered, setFiltered] = useState<{ filteredTree: any; matchedFlatItems: any }>({
filteredTree: undefined,
matchedFlatItems: []
})

const views = useViewStore((store) => store.views)
const inputRef = useRef<HTMLInputElement>(null)
const activeId = useViewStore((s) => s.currentView?.id)
const [search, setSearch] = useState('')
const expandSidebar = useLayoutStore((store) => store.expandSidebar)
const [selected, setSelected] = useState<number>(-1)
const onSearchChange: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearch(e.target.value)
}

useEffect(() => {
const items = views.reduce((acc, view) => {
const parent = getParentEntity(view.path)?.parent
const expanded = useTreeStore.getState().expanded

return [
...acc,
{
id: view.id,
parentId: parent,
properties: {
label: view.title,
path: view.path
},
depth: 0,
index: 0,
collapsed: !expanded.includes(view.id),
children: []
}
]
}, [] as FlattenedItem[])

setTree(buildTree(items))
}, [views])

useEffect(() => {
if (search !== '') {
const items = flattenTree(tree)
const matchedFlatItems = fuzzySearch(items, search, (item) => item.properties.label)
const filteredTree = buildPartialTree(matchedFlatItems, items)

setFiltered({ filteredTree, matchedFlatItems })
} else {
setFiltered({ filteredTree: undefined, matchedFlatItems: [] })
}
}, [search])

const reset = () => {
setSearch('')
setSelected(-1)
const inpEl = inputRef.current
if (inpEl) inpEl.value = ''
}

const selectedItem = useMemo(() => {
if (selected >= 0 && filtered.matchedFlatItems.length > 0 && filtered.matchedFlatItems.length > selected) {
return filtered.matchedFlatItems[selected]
} else return undefined
}, [selected, filtered.matchedFlatItems])

useEffect(() => {
if (inputRef.current) {
const unsubscribe = tinykeys(inputRef.current, {
Escape: (event) => {
// event.stopPropagation()
reset()
},

Enter: (event) => {
// event.stopPropagation()
if (selected >= 0) {
if (selectedItem) {
onClick(selectedItem.id)
}
}
},
ArrowDown: (event) => {
event.stopPropagation()
event.preventDefault()
// Circular increment
const newSelected = (selected + 1) % filtered.matchedFlatItems.length
if (newSelected >= 0) {
setSelected(newSelected)
}
},
ArrowUp: (event) => {
event.preventDefault()

event.stopPropagation()
// Circular decrement with no negative
const newSelected = (selected - 1 + filtered.matchedFlatItems.length) % filtered.matchedFlatItems.length
if (newSelected >= 0) {
setSelected(newSelected)
}
}
})

return () => {
unsubscribe()
}
}
}, [filtered.matchedFlatItems, selected])

useEffect(() => {
if (!inputRef.current?.isContentEditable) {
const unsubscribe = tinykeys(window, {
Slash: (event) => {
if (!isOnEditableElement(event)) {
event.preventDefault()
expandSidebar()

if (inputRef) inputRef.current.focus()
}
}
})

return () => unsubscribe()
}
}, [])

return (
<SidebarListWrapper>
<Tippy theme="mex" placement="right" singleton={source} />

<div>
{defaultItems &&
defaultItems.map((defaultItem) => (
<StyledTreeItem selected={activeId === defaultItem.id} key={defaultItem.id} noSwitcher>
<ItemContent onClick={() => onClick(defaultItem.id)}>
<ItemTitle>
<IconDisplay icon={defaultItem.icon} />
<span>{defaultItem.label}</span>
</ItemTitle>
</ItemContent>
</StyledTreeItem>
))}
</div>

{tree.length > 0 && (
<SidebarListFilter>
<Icon icon={searchLine} />
<Input placeholder={'Find Views...'} onChange={debounce((e) => onSearchChange(e), 250)} ref={inputRef} />
</SidebarListFilter>
)}

<FilteredItemsWrapper hasDefault={!!defaultItems}>
<SortableTree
items={filtered.filteredTree ?? tree}
setItems={setTree}
onClick={onClick}
activeId={activeId}
highlightedId={filtered.matchedFlatItems[selected]?.id}
onContextMenu={onContextMenu}
collapsible
indicator
indentationWidth={16}
/>
{tree.length === 0 && search !== '' && <EmptyMessage>{'No Items Found'}</EmptyMessage>}
</FilteredItemsWrapper>
</SidebarListWrapper>
)
}

export default SidebarViewTree
24 changes: 13 additions & 11 deletions apps/webapp/src/Components/Sidebar/SidebarList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,17 +175,19 @@ const SidebarList = ({
<SidebarListWrapper noMargin={noMargin}>
<Tippy theme="mex" placement="right" singleton={source} />

{defaultItems &&
defaultItems.map((defaultItem) => (
<StyledTreeItem key={defaultItem.id} noSwitcher selected={selectedItemId === defaultItem.id}>
<ItemContent onClick={() => onSelectItem(defaultItem.id)}>
<ItemTitle>
<IconDisplay icon={defaultItem.icon} />
<span>{defaultItem.label}</span>
</ItemTitle>
</ItemContent>
</StyledTreeItem>
))}
<div>
{defaultItems &&
defaultItems.map((defaultItem) => (
<StyledTreeItem key={defaultItem.id} noSwitcher selected={selectedItemId === defaultItem.id}>
<ItemContent onClick={() => onSelectItem(defaultItem.id)}>
<ItemTitle>
<IconDisplay icon={defaultItem.icon} />
<span>{defaultItem.label}</span>
</ItemTitle>
</ItemContent>
</StyledTreeItem>
))}
</div>

{showSearch && items.length > 0 && (
<SidebarListFilter noMargin={noMargin}>
Expand Down
Loading

0 comments on commit 295c9ba

Please sign in to comment.