Skip to content

Commit

Permalink
Add editable item component for all views
Browse files Browse the repository at this point in the history
Add editable item to Kanban, List, and Table
  • Loading branch information
Komediruzecki authored and Davy-c committed Feb 7, 2022
1 parent b641971 commit 18d96e8
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 82 deletions.
128 changes: 128 additions & 0 deletions src/cloud/components/Views/EditableItemContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { lngKeys } from '../../lib/i18n/types'
import { mdiDotsVertical, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'
import React, { useCallback, useState } from 'react'
import { SerializedDocWithSupplemental } from '../../interfaces/db/doc'
import { useCloudApi } from '../../lib/hooks/useCloudApi'
import { useI18n } from '../../lib/hooks/useI18n'
import {
MenuItem,
MenuTypes,
useContextMenu,
} from '../../../design/lib/stores/contextMenu'
import Icon from '../../../design/components/atoms/Icon'
import styled from '../../../design/lib/styled'
import EditableInput from '../../../design/components/atoms/EditableInput'

interface ItemProps {
doc: SerializedDocWithSupplemental
teamId?: string
children?: React.ReactNode
}

const EditableItemContainer = ({ doc, teamId, children }: ItemProps) => {
const [editingItemTitle, setEditingItemTitle] = useState<boolean>(false)
const [showingContextMenuActions, setShowingContextMenuActions] = useState<
boolean
>(false)

const { updateDoc, deleteDocApi } = useCloudApi()
const { translate } = useI18n()
const { popup } = useContextMenu()

const updateDocTitle = useCallback(
async (doc, newTitle) => {
await updateDoc(doc, {
workspaceId: doc.workspaceId,
title: newTitle,
})
setEditingItemTitle(false)
},
[updateDoc]
)

const onDocRemove = useCallback(
(doc) => {
if (teamId == null) {
if (doc.teamId != null && doc.teamId != '') {
deleteDocApi({ id: doc.id, teamId: doc.teamId })
}
return
}
deleteDocApi({ id: doc.id, teamId: teamId })
},
[deleteDocApi, teamId]
)

const openActionMenu: (
event: React.MouseEvent<HTMLDivElement>,
doc: SerializedDocWithSupplemental
) => void = useCallback(
(
event: React.MouseEvent<HTMLDivElement>,
doc: SerializedDocWithSupplemental
) => {
const editTitleAction: MenuItem = {
icon: <Icon path={mdiPencilOutline} />,
type: MenuTypes.Normal,
label: translate(lngKeys.GeneralEditTitle),
onClick: () => setEditingItemTitle(true),
}
const deleteDocAction: MenuItem = {
icon: <Icon path={mdiTrashCanOutline} />,
type: MenuTypes.Normal,
label: translate(lngKeys.GeneralDelete),
onClick: () => onDocRemove(doc),
}
const actions: MenuItem[] = [editTitleAction, deleteDocAction]

event.preventDefault()
event.stopPropagation()
popup(event, actions)
},
[onDocRemove, popup, translate]
)

return (
<ItemContainer
onMouseEnter={() => setShowingContextMenuActions(true)}
onMouseLeave={() => setShowingContextMenuActions(false)}
>
{editingItemTitle && (
<EditableInput
editOnStart={true}
placeholder={'Title...'}
text={doc.title}
onTextChange={(newText) => updateDocTitle(doc, newText)}
/>
)}

{!editingItemTitle && <>{children}</>}

{showingContextMenuActions && (
<div className={'item__container__item__actions'}>
<div
onClick={(event) => openActionMenu(event, doc)}
className='doc__action'
>
<Icon size={20} path={mdiDotsVertical} />
</div>
</div>
)}
</ItemContainer>
)
}

const ItemContainer = styled.div`
position: relative;
.item__container__item__actions {
position: absolute;
right: 5px;
z-index: 1;
margin: 0;
top: 50%;
transform: translate(-50%, -50%);
}
`

export default EditableItemContainer
84 changes: 45 additions & 39 deletions src/cloud/components/Views/Kanban/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SerializedDocWithSupplemental } from '../../../interfaces/db/doc'
import { getDocTitle } from '../../../lib/utils/patterns'
import { isKanbanStaticProp, KanbanViewProp } from '../../../lib/views/kanban'
import PropPicker from '../../Props/PropPicker'
import EditableItemContainer from '../EditableItemContainer'

interface ItemProps {
doc: SerializedDocWithSupplemental
Expand All @@ -19,48 +20,53 @@ interface ItemProps {

const Item = ({ doc, displayedProps, onClick }: ItemProps) => {
return (
<Container
labelClick={() => onClick && onClick(doc)}
label={
<Flexbox direction='column'>
<Flexbox className='kanban__label__wrapper'>
<div className='kanban__label__icon'>
{doc.emoji != null ? (
<Emoji emoji={doc.emoji} set='apple' size={16} />
) : (
<Icon path={mdiFileDocumentOutline} size={16} />
)}
</div>
<span className='kanban__label'>
{getDocTitle(doc, 'Untitled')}
</span>
</Flexbox>
{Object.values(displayedProps).map((prop, i) => {
const docProp = doc.props[prop.name]
<EditableItemContainer doc={doc}>
<Container
labelClick={() => onClick && onClick(doc)}
label={
<Flexbox direction='column'>
<Flexbox className='kanban__label__wrapper'>
<div className='kanban__label__icon'>
{doc.emoji != null ? (
<Emoji emoji={doc.emoji} set='apple' size={16} />
) : (
<Icon path={mdiFileDocumentOutline} size={16} />
)}
</div>
<span className='kanban__label'>
{getDocTitle(doc, 'Untitled')}
</span>
</Flexbox>
{Object.values(displayedProps).map((prop, i) => {
const docProp = doc.props[prop.name]

if (isKanbanStaticProp(prop)) {
return
}
if (isKanbanStaticProp(prop)) {
return
}

if (docProp == null || docProp.data == null) {
return null
}
if (docProp == null || docProp.data == null) {
return null
}

return (
<div key={`kanban-${doc.id}-prop-${i}`} className='kanban__prop'>
<PropPicker
parent={{ type: 'doc', target: doc }}
propName={prop.name}
propData={docProp}
readOnly={true}
showIcon={true}
/>
</div>
)
})}
</Flexbox>
}
/>
return (
<div
key={`kanban-${doc.id}-prop-${i}`}
className='kanban__prop'
>
<PropPicker
parent={{ type: 'doc', target: doc }}
propName={prop.name}
propData={docProp}
readOnly={true}
showIcon={true}
/>
</div>
)
})}
</Flexbox>
}
/>
</EditableItemContainer>
)
}

Expand Down
48 changes: 25 additions & 23 deletions src/cloud/components/Views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { getDocLinkHref } from '../../Link/DocLink'
import ListViewPropertiesContext from './ListViewPropertiesContext'
import { useListView } from '../../../lib/hooks/views/listView'
import ListDocProperties from './ListDocProperties'
import EditableItemContainer from '../EditableItemContainer'

type ListViewProps = {
view: SerializedView<ViewListData>
Expand Down Expand Up @@ -229,29 +230,30 @@ const ListView = ({
const { id } = doc
const href = getDocLinkHref(doc, team, 'index')
return (
<ListViewItem
key={id}
id={id}
checked={hasDocInSelection(doc.id)}
onSelect={() => toggleDocInSelection(doc.id)}
showCheckbox={currentUserIsCoreMember}
label={doc.title}
defaultIcon={mdiFileDocumentOutline}
emoji={doc.emoji}
labelHref={href}
labelOnclick={() => push(href)}
onDragStart={(event: any) => saveDocTransferData(event, doc)}
onDragEnd={(event: any) => clearDragTransferData(event)}
onDrop={(event: any) => onDropDoc(event, doc)}
hideOrderingHandle={true}
>
<ListDocProperties
doc={doc}
props={orderedViewProps}
team={team}
currentUserIsCoreMember={currentUserIsCoreMember}
/>
</ListViewItem>
<EditableItemContainer key={id} doc={doc} teamId={team.id}>
<ListViewItem
id={id}
checked={hasDocInSelection(doc.id)}
onSelect={() => toggleDocInSelection(doc.id)}
showCheckbox={currentUserIsCoreMember}
label={doc.title}
defaultIcon={mdiFileDocumentOutline}
emoji={doc.emoji}
labelHref={href}
labelOnclick={() => push(href)}
onDragStart={(event: any) => saveDocTransferData(event, doc)}
onDragEnd={(event: any) => clearDragTransferData(event)}
onDrop={(event: any) => onDropDoc(event, doc)}
hideOrderingHandle={true}
>
<ListDocProperties
doc={doc}
props={orderedViewProps}
team={team}
currentUserIsCoreMember={currentUserIsCoreMember}
/>
</ListViewItem>
</EditableItemContainer>
)
})}
{currentWorkspaceId != null && (
Expand Down
30 changes: 17 additions & 13 deletions src/cloud/components/Views/Table/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import { DraggedTo } from '../../../../design/lib/dnd'
import { StyledContentManagerList } from '../../ContentManager/styled'
import Flexbox from '../../../../design/components/atoms/Flexbox'
import ColumnSettingsContext from './ColSettingsContext'
import { getDocLinkHref } from '../../Link/DocLink'
import NavigationItem from '../../../../design/components/molecules/Navigation/NavigationItem'
import { mdiFileDocumentOutline, mdiPlus } from '@mdi/js'
import DocTagsList from '../../DocPage/DocTagsList'
import { getFormattedBoosthubDateTime } from '../../../lib/date'
Expand All @@ -41,6 +39,9 @@ import Button from '../../../../design/components/atoms/Button'
import TableViewPropertiesContext from './TableViewPropertiesContext'
import TitleColumnSettingsContext from './TitleColumnSettingsContext'
import { usePage } from '../../../lib/stores/pageStore'
import EditableItemContainer from '../EditableItemContainer'
import NavigationItem from '../../../../design/components/molecules/Navigation/NavigationItem'
import { getDocLinkHref } from '../../Link/DocLink'
import { useCloudResourceModals } from '../../../lib/hooks/useCloudResourceModals'

type TableViewProps = {
Expand Down Expand Up @@ -72,14 +73,15 @@ const TableView = ({
toggleDocInSelection,
resetDocsInSelection,
}: TableViewProps) => {
const { goToDocPreview } = useCloudResourceModals()

const currentStateRef = useRef(view.data)
const [state, setState] = useState<ViewTableData>(
Object.assign({}, view.data as ViewTableData)
)
const { translate } = useI18n()
const { createDoc } = useCloudApi()
const { openContextModal, closeLastModal } = useModal()
const { goToDocPreview } = useCloudResourceModals()
const { permissions = [] } = usePage()

const {
Expand Down Expand Up @@ -268,16 +270,18 @@ const TableView = ({
cells: [
{
children: (
<NavigationItem
labelHref={docLink}
labelClick={() => goToDocPreview(doc)}
label={getDocTitle(doc, 'Untitled')}
icon={
doc.emoji != null
? { type: 'emoji', path: doc.emoji }
: { type: 'icon', path: mdiFileDocumentOutline }
}
/>
<EditableItemContainer doc={doc} teamId={team.id}>
<NavigationItem
labelHref={docLink}
labelClick={() => goToDocPreview(doc)}
label={getDocTitle(doc, 'Untitled')}
icon={
doc.emoji != null
? { type: 'emoji', path: doc.emoji }
: { type: 'icon', path: mdiFileDocumentOutline }
}
/>
</EditableItemContainer>
),
},
...orderedColumns.map((col) => {
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ const enTranslation: TranslationSource = {
[lngKeys.GeneralContinueVerb]: 'Continue',
[lngKeys.GeneralShared]: 'Shared',
[lngKeys.GeneralRenameVerb]: 'Rename',
[lngKeys.GeneralEditTitle]: 'Edit title',
[lngKeys.GeneralEditVerb]: 'Settings',
[lngKeys.GeneralBookmarks]: 'Bookmarks',
[lngKeys.GeneralUnbookmarkVerb]: 'Remove from Bookmarks',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export enum lngKeys {
GeneralLabels = 'general.Labels',
GeneralMore = 'general.More',
GeneralStatus = 'general.Status',
GeneralEditTitle = 'general.EditTitle',
GeneralRenameVerb = 'general.Renameverb',
GeneralEditVerb = 'general.Editverb',
GeneralSettings = 'general.Settings',
Expand Down
Loading

0 comments on commit 18d96e8

Please sign in to comment.