Skip to content

Commit

Permalink
highlight duplicate orders, change click handling
Browse files Browse the repository at this point in the history
  • Loading branch information
SimeonGriggs committed Apr 26, 2022
1 parent c5cb91f commit da60052
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 105 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

Drag-and-drop Document Ordering without leaving the Editing surface.

![2021-09-28 16 33 51](https://user-images.githubusercontent.com/9684022/135118990-5e20ac68-d010-40c2-a722-f596089c631a.gif)

📹 [Installation Walkthrough Video](https://www.loom.com/share/309f21cce37e44739365a94d425e2e19) (including a bug that is now fixed 😅)
![2022-04-26 12 23 39](https://user-images.githubusercontent.com/9684022/165289621-dbd9d841-028e-40c7-be14-7398fcdb1210.gif)

This plugin aims to be OS-like in that you can select and move multiple documents by holding `shift` and clicking a second item, and toggling on/off selections by holding `command/control`.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sanity/orderable-document-list",
"version": "0.0.5",
"version": "0.0.6",
"description": "Drag-and-drop Document Ordering without leaving the Editing surface",
"main": "lib/index.js",
"scripts": {
Expand Down
50 changes: 2 additions & 48 deletions src/Document.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import PropTypes from 'prop-types'
// eslint-disable-next-line no-unused-vars
import React, {useCallback, useContext, useMemo} from 'react'
import {
DragHandleIcon,
ChevronUpIcon,
ChevronDownIcon,
ChevronRightIcon,
EditIcon,
} from '@sanity/icons'
import React, {useContext} from 'react'
import {DragHandleIcon, ChevronUpIcon, ChevronDownIcon} from '@sanity/icons'
import {Text, Flex, Box, Button} from '@sanity/ui'
import {usePaneRouter} from '@sanity/desk-tool'
import Preview from 'part:@sanity/base/preview'
import schema from 'part:@sanity/base/schema'

Expand Down Expand Up @@ -55,13 +48,6 @@ export default function Document({doc, increment, entities, handleSelect, index,
</Box>
</Flex>
</Button>
<Box paddingX={3} style={{flexShrink: 0}}>
<ChildEditLink id={doc._id}>
<Text fontSize={4}>
{doc._id.startsWith(`drafts.`) ? <EditIcon /> : <ChevronRightIcon />}
</Text>
</ChildEditLink>
</Box>
</Flex>
)
}
Expand All @@ -82,35 +68,3 @@ Document.propTypes = {
isFirst: PropTypes.bool.isRequired,
isLast: PropTypes.bool.isRequired,
}

const ChildEditLink = ({id, children}) => {
const router = usePaneRouter()
const {ChildLink, routerPanesState} = router

// Is this document currently being edited
const isOpen = useMemo(
() => routerPanesState.some((pane) => pane[0]?.id === id.replace(`drafts.`, ``)),
[id, routerPanesState]
)

const Link = useCallback(
(linkProps) => <ChildLink {...linkProps} childId={id.replace(`drafts.`, ``)} />,
[ChildLink, id]
)

return (
<Button
as={Link}
mode={isOpen ? `default` : `ghost`}
tone={isOpen ? `primary` : `transparent`}
padding={2}
>
{children}
</Button>
)
}

ChildEditLink.propTypes = {
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
}
11 changes: 8 additions & 3 deletions src/DocumentListQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const client = sanityClient.withConfig({

export default function DocumentListQuery({type}) {
const [isLoading, setIsLoading] = useState(true)
const [isUpdating, setIsUpdating] = useState(false)
const [listIsUpdating, setListIsUpdating] = useState(false)
const [data, setData] = useState([])

useEffect(() => {
Expand Down Expand Up @@ -57,7 +57,7 @@ export default function DocumentListQuery({type}) {
}

// Get data but only if a document isn't being patched or we don't yet have data
if (!isUpdating && !data.length) {
if (!listIsUpdating && !data.length) {
prepareData()
}

Expand Down Expand Up @@ -86,7 +86,12 @@ export default function DocumentListQuery({type}) {
</Feedback>
)}
<Box padding={1}>
<DraggableList data={data} isUpdating={isUpdating} setIsUpdating={setIsUpdating} />
<DraggableList
data={data}
type={type}
listIsUpdating={listIsUpdating}
setListIsUpdating={setListIsUpdating}
/>
</Box>
</Stack>
)
Expand Down
78 changes: 40 additions & 38 deletions src/DraggableList.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import PropTypes from 'prop-types'
import React, {useState, useEffect} from 'react'
import React, {useMemo, useState, useEffect} from 'react'
import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd'
import sanityClient from 'part:@sanity/base/client'
import {usePaneRouter} from '@sanity/desk-tool'
import {Box, Card, useToast} from '@sanity/ui'
import sanityClient from 'part:@sanity/base/client'

import Document from './Document'
import {reorderDocuments} from './helpers/reorderDocuments'
Expand All @@ -20,24 +21,27 @@ const getItemStyle = (draggableStyle, itemIsUpdating) => ({
...draggableStyle,
})

const cardTone = (isGhosting, isDragging, isSelected) => {
const cardTone = (settings) => {
const {isDuplicate, isGhosting, isDragging, isSelected} = settings

if (isGhosting) return `transparent`
if (isDragging || isSelected) {
return `primary`
}
if (isDragging || isSelected) return `primary`
if (isDuplicate) return `caution`

return undefined
}

export default function DraggableList({data, isUpdating, setIsUpdating}) {
export default function DraggableList({data, type, listIsUpdating, setListIsUpdating}) {
const toast = useToast()
const router = usePaneRouter()
const {navigateIntent} = router

// Maintains local state order before transaction completes
const [orderedData, setOrderedData] = useState(data)

// Update local state when documents change from an outside source
useEffect(() => {
if (!isUpdating) setOrderedData(data)
if (!listIsUpdating) setOrderedData(data)
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [data])

Expand All @@ -54,8 +58,11 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {

let updatedIds = []

// No modifiers, update selected to just this one
// No modifier keys pressed during click:
// - update selected to just this one
// - open document
if (!selectMultiple && !selectAdditional) {
navigateIntent('edit', {id: clickedId, type})
return setSelectedIds([clickedId])
}

Expand Down Expand Up @@ -94,27 +101,18 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {
.then((updated) => {
clearSelected()
setDraggingId(``)
setIsUpdating(false)
setListIsUpdating(false)
toast.push({
title: `${
updated.results.length === 1 ? `1 Document` : `${updated.results.length} Documents`
} Reordered`,
status: `success`,
description: message,
})

// Another "I didn't write tests but I'm going to console.log my way to victory" piece of programming
// fetch(
// `https://i2v7h052.api.sanity.io/v2021-03-25/data/query/production?query=*%5B_type%20%3D%3D%20%22category%22%5D%7Corder(orderRank)%7B%0A%20%20title%2C%20orderRank%0A%7D`
// )
// .then((res) => res.json())
// .then((res) => {
// console.table(res.result.map((item) => item?.title).join(', '))
// })
})
.catch(() => {
setDraggingId(``)
setIsUpdating(false)
setListIsUpdating(false)
toast.push({
title: `Reordering failed`,
status: `critical`,
Expand All @@ -140,7 +138,7 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {
if (!effectedIds?.length) return

// Update state to update styles + prevent data refetching
setIsUpdating(true)
setListIsUpdating(true)
setSelectedIds(effectedIds)

const {newOrder, patches, message} = reorderDocuments({
Expand Down Expand Up @@ -196,6 +194,15 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {
}
}, [])

// Find all items with duplicate order field
const duplicateOrders = useMemo(() => {
if (!orderedData.length) return []

const orderField = orderedData.map((item) => item[ORDER_FIELD_NAME])

return orderField.filter((item, index) => orderField.indexOf(item) !== index)
}, [orderedData])

return (
<DragDropContext
onDragStart={handleDragStart}
Expand All @@ -212,11 +219,13 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {
// onClick={(event) => handleDraggableClick(event, provided, snapshot)}
>
{(innerProvided, innerSnapshot) => {
const itemIsSelected = selectedIds.includes(item._id)
const itemIsDragging = innerSnapshot.isDragging
const isGhosting = !itemIsDragging && draggingId && itemIsSelected
const itemIsUpdating = isUpdating && itemIsSelected
const isDisabled = Boolean(!item?.[ORDER_FIELD_NAME])
const isSelected = selectedIds.includes(item._id)
const isDragging = innerSnapshot.isDragging
const isGhosting = Boolean(!isDragging && draggingId && isSelected)
const isUpdating = isUpdating && isSelected
const isDisabled = Boolean(!item[ORDER_FIELD_NAME])
const isDuplicate = duplicateOrders.includes(item[ORDER_FIELD_NAME])
const tone = cardTone({isDuplicate, isGhosting, isDragging, isSelected})

return (
<div
Expand All @@ -226,19 +235,11 @@ export default function DraggableList({data, isUpdating, setIsUpdating}) {
style={
isDisabled
? {opacity: 0.2, pointerEvents: `none`}
: getItemStyle(
innerProvided.draggableProps.style,
itemIsUpdating,
isGhosting
)
: getItemStyle(innerProvided.draggableProps.style, isUpdating, isGhosting)
}
>
<Box paddingBottom={1}>
<Card
tone={cardTone(isGhosting, itemIsDragging, itemIsSelected)}
shadow={itemIsDragging ? `2` : undefined}
radius={2}
>
<Card tone={tone} shadow={isDragging ? `2` : undefined} radius={2}>
<Document
doc={item}
entities={orderedData}
Expand Down Expand Up @@ -269,6 +270,7 @@ DraggableList.propTypes = {
_id: PropTypes.string,
}).isRequired
).isRequired,
isUpdating: PropTypes.bool.isRequired,
setIsUpdating: PropTypes.func.isRequired,
type: PropTypes.string.isRequired,
listIsUpdating: PropTypes.bool.isRequired,
setListIsUpdating: PropTypes.func.isRequired,
}
12 changes: 0 additions & 12 deletions src/helpers/reorderDocuments.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,6 @@ export const reorderDocuments = ({entities, selectedIds, source, destination, de
betweenRank = isMovingUp ? betweenRank.between(curRank) : betweenRank.between(nextRank)
}

// This data is not actually used by the plugin
// console.table(
// createManifest({
// entities,
// selectedItems,
// isMovingUp,
// curIndex,
// nextIndex,
// prevIndex,
// })
// )

return {
// The `all` array gets sorted by order field later anyway
// so that this probably isn't necessary ¯\_(ツ)_/¯
Expand Down

0 comments on commit da60052

Please sign in to comment.