Skip to content

Commit

Permalink
chore(drag-and-drop): enable re-ordering of primitive array items (#2055
Browse files Browse the repository at this point in the history
)
  • Loading branch information
georgedoescode authored Oct 28, 2024
1 parent af5a881 commit 9dde3f9
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 126 deletions.
17 changes: 16 additions & 1 deletion apps/page-builder-demo/src/app/dnd/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ export default async function Page() {
key={child._key}
>
<p>{child.title}</p>
<div>
{child.childrenStrings &&
child.childrenStrings.map((s: string, i: number) => (
<span
data-sanity={createDataAttribute({
id: data._id,
type: 'dndTestPage',
path: `children[_key=="${child._key}"].childrenStrings[${i}]`,
}).toString()}
key={i}
>
{s}
</span>
))}
</div>
</a>
))}
</div>
Expand Down Expand Up @@ -209,7 +224,7 @@ export default async function Page() {
</section>
{/* Inline */}
<section className="mt-6">
<h2>Vertical (Flow Auto Calculated)</h2>
<h2>Inline</h2>
<div className="mt-4">
{data.children.map((child) => (
<a
Expand Down
230 changes: 116 additions & 114 deletions apps/page-builder-demo/src/sanity.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ export type DndTestPage = {
title?: string
children?: Array<{
title?: string
childrenStrings?: Array<string>
children?: Array<{
title?: string
children?: Array<{
Expand Down Expand Up @@ -701,6 +702,7 @@ export type DndPageQueryResult = {
title: string | null
children: Array<{
title?: string
childrenStrings?: Array<string>
children?: Array<{
title?: string
children?: Array<{
Expand All @@ -716,6 +718,19 @@ export type DndPageQueryResult = {
}> | null
} | null

// Source: ./src/app/projects/page.tsx
// Variable: projectsPageQuery
// Query: *[_type == "project" && defined(slug.current)]
export type ProjectsPageQueryResult = Array<{
_id: string
_type: 'project'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
slug?: Slug
}>

// Source: ./src/app/products/page.tsx
// Variable: productsPageQuery
// Query: *[_type == "product" && defined(slug.current)]{ _id, title, description, slug, "media": media[0] }
Expand Down Expand Up @@ -756,115 +771,22 @@ export type ProductsPageQueryResult = Array<{
} | null
}>

// Source: ./src/app/projects/page.tsx
// Variable: projectsPageQuery
// Query: *[_type == "project" && defined(slug.current)]
export type ProjectsPageQueryResult = Array<{
_id: string
_type: 'project'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
slug?: Slug
}>

// Source: ./src/app/product/[slug]/page.tsx
// Variable: productSlugsQuery
// Query: *[_type == "product" && defined(slug.current)]{"slug": slug.current}
export type ProductSlugsQueryResult = Array<{
// Source: ./src/app/project/[slug]/page.tsx
// Variable: projectSlugsQuery
// Query: *[_type == "project" && defined(slug.current)]{"slug": slug.current}
export type ProjectSlugsQueryResult = Array<{
slug: string | null
}>
// Variable: productPageQuery
// Query: *[_type == "product" && slug.current == $slug][0]
export type ProductPageQueryResult = {
// Variable: projectPageQuery
// Query: *[_type == "project" && slug.current == $slug][0]
export type ProjectPageQueryResult = {
_id: string
_type: 'product'
_type: 'project'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
slug?: Slug
media?: Array<{
asset?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
}
hotspot?: SanityImageHotspot
crop?: SanityImageCrop
alt?: string
_type: 'image'
_key: string
}>
description?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
brandReference?: unknown
details?: {
materials?: string
collectionNotes?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
performance?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
ledLifespan?: string
certifications?: Array<string>
}
variants?: Array<{
title?: string
price?: string
sku?: string
_type: 'variant'
_key: string
}>
} | null

// Source: ./src/app/pages/[slug]/page.tsx
Expand Down Expand Up @@ -1015,36 +937,116 @@ export type PageSlugsResult = Array<{
slug: string | null
}>

// Source: ./src/app/project/[slug]/page.tsx
// Variable: projectSlugsQuery
// Query: *[_type == "project" && defined(slug.current)]{"slug": slug.current}
export type ProjectSlugsQueryResult = Array<{
// Source: ./src/app/product/[slug]/page.tsx
// Variable: productSlugsQuery
// Query: *[_type == "product" && defined(slug.current)]{"slug": slug.current}
export type ProductSlugsQueryResult = Array<{
slug: string | null
}>
// Variable: projectPageQuery
// Query: *[_type == "project" && slug.current == $slug][0]
export type ProjectPageQueryResult = {
// Variable: productPageQuery
// Query: *[_type == "product" && slug.current == $slug][0]
export type ProductPageQueryResult = {
_id: string
_type: 'project'
_type: 'product'
_createdAt: string
_updatedAt: string
_rev: string
title?: string
slug?: Slug
media?: Array<{
asset?: {
_ref: string
_type: 'reference'
_weak?: boolean
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
}
hotspot?: SanityImageHotspot
crop?: SanityImageCrop
alt?: string
_type: 'image'
_key: string
}>
description?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
brandReference?: unknown
details?: {
materials?: string
collectionNotes?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
performance?: Array<{
children?: Array<{
marks?: Array<string>
text?: string
_type: 'span'
_key: string
}>
style?: 'blockquote' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'normal'
listItem?: 'bullet' | 'number'
markDefs?: Array<{
href?: string
_type: 'link'
_key: string
}>
level?: number
_type: 'block'
_key: string
}>
ledLifespan?: string
certifications?: Array<string>
}
variants?: Array<{
title?: string
price?: string
sku?: string
_type: 'variant'
_key: string
}>
} | null

declare module '@sanity/client' {
interface SanityQueries {
'\n *[_id == "siteSettings"][0]{\n title,\n description,\n copyrightText\n}': LayoutQueryResult
"\n *[_id == \"siteSettings\"][0]{\n frontPage->{\n _type,\n _id,\n title,\n sections[]{\n ...,\n symbol->{_type},\n 'headline': coalesce(headline, symbol->headline),\n 'tagline': coalesce(tagline, symbol->tagline),\n 'subline': coalesce(subline, symbol->subline),\n 'image': coalesce(image, symbol->image),\n product->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n },\n products[]{\n _key,\n ...(@->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n })\n }\n },\n style\n }\n }.frontPage\n": FrontPageQueryResult
'\n *[_type == "dndTestPage"]{\n _id,\n title,\n children\n }[0]\n': DndPageQueryResult
'\n *[_type == "product" && defined(slug.current)]{\n _id,\n title,\n description,\n slug,\n "media": media[0]\n }\n': ProductsPageQueryResult
'*[_type == "project" && defined(slug.current)]': ProjectsPageQueryResult
'*[_type == "product" && defined(slug.current)]{"slug": slug.current}': ProductSlugsQueryResult
'*[_type == "product" && slug.current == $slug][0]': ProductPageQueryResult
"\n *[_type == \"page\" && slug.current == $slug][0]{\n _type,\n _id,\n title,\n sections[]{\n ...,\n symbol->{_type},\n 'headline': coalesce(headline, symbol->headline),\n 'tagline': coalesce(tagline, symbol->tagline),\n 'subline': coalesce(subline, symbol->subline),\n 'image': coalesce(image, symbol->image),\n product->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n },\n products[]{\n _key,\n ...(@->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n })\n }\n },\n style\n }\n": PageQueryResult
'*[_type == "page" && defined(slug.current)]{"slug": slug.current}': PageSlugsResult
'\n *[_type == "product" && defined(slug.current)]{\n _id,\n title,\n description,\n slug,\n "media": media[0]\n }\n': ProductsPageQueryResult
'*[_type == "project" && defined(slug.current)]{"slug": slug.current}': ProjectSlugsQueryResult
'*[_type == "project" && slug.current == $slug][0]': ProjectPageQueryResult
"\n *[_type == \"page\" && slug.current == $slug][0]{\n _type,\n _id,\n title,\n sections[]{\n ...,\n symbol->{_type},\n 'headline': coalesce(headline, symbol->headline),\n 'tagline': coalesce(tagline, symbol->tagline),\n 'subline': coalesce(subline, symbol->subline),\n 'image': coalesce(image, symbol->image),\n product->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n },\n products[]{\n _key,\n ...(@->{\n _type,\n _id,\n title,\n slug,\n \"media\": media[0]\n })\n }\n },\n style\n }\n": PageQueryResult
'*[_type == "page" && defined(slug.current)]{"slug": slug.current}': PageSlugsResult
'*[_type == "product" && defined(slug.current)]{"slug": slug.current}': ProductSlugsQueryResult
'*[_type == "product" && slug.current == $slug][0]': ProductPageQueryResult
}
}
6 changes: 6 additions & 0 deletions packages/@repo/sanity-schema/src/page-builder-demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,12 @@ const dndTestItemRoot = defineArrayMember({
name: 'title',
title: 'Title',
}),
defineField({
type: 'array',
name: 'childrenStrings',
title: 'Children Strings',
of: [{type: 'string'}],
}),
defineField({
type: 'array',
name: 'children',
Expand Down
42 changes: 38 additions & 4 deletions packages/visual-editing/src/util/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,45 @@ import type {SanityNode} from '@repo/visual-editing-helpers'
export function getArrayItemKeyAndParentPath(pathOrNode: string | SanityNode): {
path: string
key: string
hasExplicitKey: boolean
} {
const elementPath = typeof pathOrNode === 'string' ? pathOrNode : pathOrNode.path
const result = elementPath.match(/^(.+)\[_key=="(.+)"]$/)
if (!result) throw new Error('Invalid path')
const [, path, key] = result

const lastDotIndex = elementPath.lastIndexOf('.')
const lastPathItem = elementPath.substring(lastDotIndex + 1, elementPath.length)

if (!lastPathItem.indexOf('[')) throw new Error('Invalid path: not an array')

const lastArrayIndex = elementPath.lastIndexOf('[')
const path = elementPath.substring(0, lastArrayIndex)

let key
let hasExplicitKey

if (lastPathItem.includes('_key')) {
// explicit [_key="..."]

const startIndex = lastPathItem.indexOf('"') + 1
const endIndex = lastPathItem.indexOf('"', startIndex)

key = lastPathItem.substring(startIndex, endIndex)

hasExplicitKey = true
} else {
// indexes [int]
const startIndex = lastPathItem.indexOf('[') + 1
const endIndex = lastPathItem.indexOf(']', startIndex)

key = lastPathItem.substring(startIndex, endIndex)

hasExplicitKey = false
}

if (!path || !key) throw new Error('Invalid path')
return {path, key}

return {
path,
key,
hasExplicitKey,
}
}
Loading

0 comments on commit 9dde3f9

Please sign in to comment.