Skip to content

Commit

Permalink
[desk-tool] Begin basic array diff components
Browse files Browse the repository at this point in the history
  • Loading branch information
mariuslundgard authored and rexxars committed Oct 6, 2020
1 parent dcff35a commit 595294e
Show file tree
Hide file tree
Showing 16 changed files with 312 additions and 26 deletions.
15 changes: 15 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/ArrayDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'
import {DefaultArrayDiff} from './defaultArrayDiff'
import {isPTSchemaType} from './helpers'
import {PTDiff} from './ptDiff'
import {ArrayDiffProps} from './types'

export function ArrayDiff(props: ArrayDiffProps) {
// console.log('ArrayDiff', props.schemaType)

if (isPTSchemaType(props.schemaType)) {
return <PTDiff diff={props.diff} items={props.items} schemaType={props.schemaType} />
}

return <DefaultArrayDiff diff={props.diff} items={props.items} schemaType={props.schemaType} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.root {
/* border: 1px solid #ccc; */
}

.itemList {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-gap: 0.25em;
}

.diffItemContainer {
display: flex;
}

.diffItemIndexes {
background: #eee;
width: 1.25em;
font-size: 13px;
line-height: 16px;
font-weight: 600;
}

.diffItemBox {
flex: 1;
min-width: 0;
/* border-left: 1px solid #ccc; */
padding-left: 0.25em;
}

.item {
background: #f1f3f6;
margin: 0;
padding: 0.5em;
font-size: 14px;
border-radius: 4px;
overflow: auto;
}

.addedItem {
composes: item;
background: #d0f4dc;
}

.removedItem {
composes: item;
background: #fcd8d5;
}

.arrayDiffIndexes {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
104 changes: 104 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/defaultArrayDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {ItemDiff} from '@sanity/diff'
import React from 'react'
import {Annotation} from '../../panes/documentPane/history/types'
import {ArrayDiffProps} from './types'

import styles from './defaultArrayDiff.css'

export function DefaultArrayDiff(props: ArrayDiffProps) {
return (
<div className={styles.root}>
<div className={styles.itemList}>
{props.diff.items.map((diffItem, diffItemIndex) => (
<div className={styles.diffItemContainer} key={diffItemIndex}>
<div className={styles.diffItemIndexes}>
<ArrayDiffIndexes fromIndex={diffItem.fromIndex} toIndex={diffItem.toIndex} />
</div>
<div className={styles.diffItemBox}>
<DefaultArrayDiffItem
diff={diffItem}
metadata={props.items && props.items[diffItemIndex]}
/>
</div>
</div>
))}
</div>
</div>
)
}

// eslint-disable-next-line complexity
function ArrayDiffIndexes({fromIndex, toIndex}: {fromIndex?: number; toIndex?: number}) {
if (fromIndex === undefined && toIndex === undefined) {
return <span className={styles.arrayDiffIndexes} />
}

if (fromIndex !== undefined && toIndex === undefined) {
// `fromIndex` only (removed)
return (
<span className={styles.arrayDiffIndexes}>
<span>{fromIndex}</span>
</span>
)
}

if (fromIndex === undefined && toIndex !== undefined) {
// `toIndex` only
return (
<span className={styles.arrayDiffIndexes}>
<span>{toIndex}</span>
</span>
)
}

if (fromIndex !== undefined && toIndex !== undefined && fromIndex < toIndex) {
return (
<span className={styles.arrayDiffIndexes}>
<span>{fromIndex}</span>
<span>&darr;</span>
<span>{toIndex}</span>
</span>
)
}

return (
<span className={styles.arrayDiffIndexes}>
<span>{toIndex}</span>
<span>&uarr;</span>
<span>{fromIndex}</span>
</span>
)
}

function DefaultArrayDiffItem(props: {
diff: ItemDiff<Annotation>
metadata?: {fromType?: string; toType?: string}
}) {
const {diff} = props
const metadata = props.metadata || {fromType: undefined, toType: undefined}

if (diff.type === 'added') {
return (
<pre className={styles.addedItem}>
Added array item ({metadata.toType}): {JSON.stringify(diff, null, 2)}
</pre>
)
}

if (diff.type === 'removed') {
return (
<pre className={styles.removedItem}>
Removed array item ({metadata.fromType}): {JSON.stringify(diff, null, 2)}
</pre>
)
}

// @todo: render moved items?
return (
<pre className={styles.item}>
Unchanged item ({metadata.toType}): {JSON.stringify(diff, null, 2)}
</pre>
)

// return null
}
8 changes: 8 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function isPTSchemaType(schemaType: any) {
return (
schemaType.name === 'array' &&
schemaType.of &&
schemaType.of.length === 1 &&
schemaType.of.filter(t => t.name === 'block').length
)
}
1 change: 1 addition & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './arrayDiff'
Empty file.
6 changes: 6 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/ptDiff.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'
import {ArrayDiffProps} from './types'

export function PTDiff(props: ArrayDiffProps) {
return <div>Portable Text Diff</div>
}
8 changes: 8 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/arrayDiff/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {ArrayDiff} from '@sanity/diff'
import {Annotation} from '../../panes/documentPane/history/types'

export interface ArrayDiffProps {
diff: ArrayDiff<Annotation>
items?: {fromType?: string; toType?: string}[]
schemaType: any
}
2 changes: 2 additions & 0 deletions packages/@sanity/desk-tool/src/diffs/defaultComponents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {DiffComponent} from './types'
import {ArrayDiff} from './arrayDiff'
import {NumberFieldDiff} from './NumberFieldDiff'
import {StringFieldDiff} from './StringFieldDiff'
import {ReferenceFieldDiff} from './ReferenceFieldDiff'
Expand All @@ -7,6 +8,7 @@ import {BooleanFieldDiff} from './BooleanFieldDiff'
import {SlugFieldDiff} from './SlugFieldDiff'

export const defaultComponents: {[key: string]: DiffComponent<any>} = {
array: ArrayDiff,
string: StringFieldDiff,
number: NumberFieldDiff,
reference: ReferenceFieldDiff,
Expand Down
1 change: 1 addition & 0 deletions packages/@sanity/desk-tool/src/diffs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type DiffComponent<T extends Diff<Annotation> = Diff<Annotation>> = Compo
export type DiffProps<T extends Diff<Annotation> = Diff<Annotation>> = {
diff: T
schemaType: SchemaType<T>
items?: {fromType?: string; toType: string}[]
}

export interface ObjectField {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
.container {
display: flex;
flex: 1;
min-width: 0;
align-items: stretch;
overflow: hidden;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {Diff, NoDiff} from '@sanity/diff'
import {FallbackDiff} from '../../../diffs/FallbackDiff'
import {resolveDiffComponent} from '../../../diffs/resolveDiffComponent'
import {Annotation} from '../history/types'
import {SchemaType, ChangeNode, FieldChangeNode, GroupChangeNode} from '../types'
import {SchemaType} from '../types'
import {buildChangeList} from './buildChangeList'
import {OperationsAPI} from './types'
import {OperationsAPI, ChangeNode, FieldChangeNode, GroupChangeNode} from './types'
import {undoChange} from './undoChange'
import styles from './ChangesPanel.css'

Expand Down Expand Up @@ -51,6 +51,7 @@ export function ChangesPanel({diff, schemaType, documentId}: Props) {
}

function FieldChange({change, level = 0}: {change: FieldChangeNode; level: number}) {
// console.log('FieldChange', change)
const DiffComponent = resolveDiffComponent(change.schemaType) || FallbackDiff
const {documentId, schemaType} = useContext(DocumentContext)
const docOperations = useDocumentOperation(documentId, schemaType.name) as OperationsAPI
Expand All @@ -77,7 +78,11 @@ function FieldChange({change, level = 0}: {change: FieldChangeNode; level: numbe
</button>
</div>

<DiffComponent diff={change.diff} schemaType={change.schemaType} />
<DiffComponent
diff={change.diff}
schemaType={change.schemaType}
items={change.items as any}
/>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {toString as pathToString} from '@sanity/util/paths'
import {Diff, ObjectDiff, Path, FieldDiff} from '@sanity/diff'
import {resolveDiffComponent} from '../../../diffs/resolveDiffComponent'
import {Annotation} from '../history/types'
import {SchemaType, ChangeNode} from '../types'
import {SchemaType} from '../types'
import {resolveTypeName} from './schemaUtils/resolveType'
import {ChangeNode} from './types'

export function buildChangeList(
schemaType: SchemaType,
Expand All @@ -30,6 +32,7 @@ export function buildChangeList(
return list
}

// eslint-disable-next-line complexity
schemaType.fields.forEach(field => {
const fieldPath = path.concat([field.name])
const fieldTitlePath = titlePath.concat([field.type.title || field.name])
Expand All @@ -49,6 +52,24 @@ export function buildChangeList(
titlePath: fieldTitlePath.concat(objectChanges[0].titlePath)
})
}
} else if (field.type.jsonType === 'array') {
const fieldDiff = getDiffAtPath(diff, fieldPath)
if (fieldDiff && fieldDiff.isChanged) {
list.push({
type: 'field',
diff: fieldDiff,
key: fieldPath.join('.'),
path: fieldPath,
titlePath: fieldTitlePath,
schemaType: field.type,
items: ((fieldDiff as any) || []).items.map(diffItem => {
return {
fromType: diffItem.fromValue && resolveTypeName(diffItem.fromValue),
toType: diffItem.toValue && resolveTypeName(diffItem.toValue)
}
})
})
}
} else {
const fieldDiff = getDiffAtPath(diff, fieldPath)
if (fieldDiff && fieldDiff.isChanged) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const toString = Object.prototype.toString
// Copied from https://github.com/ForbesLindesay/type-of, but inlined to have fine grained control

// eslint-disable-next-line complexity
export function resolveJSType(val) {
switch (toString.call(val)) {
case '[object Function]':
return 'function'
case '[object Date]':
return 'date'
case '[object RegExp]':
return 'regexp'
case '[object Arguments]':
return 'arguments'
case '[object Array]':
return 'array'
case '[object String]':
return 'string'
default:
}

if (typeof val == 'object' && val && typeof val.length == 'number') {
try {
// eslint-disable-next-line max-depth
if (typeof val.callee == 'function') {
return 'arguments'
}
} catch (ex) {
// eslint-disable-next-line max-depth
if (ex instanceof TypeError) {
return 'arguments'
}
}
}

if (val === null) {
return 'null'
}

if (val === undefined) {
return 'undefined'
}

if (val && val.nodeType === 1) {
return 'element'
}

if (val === Object(val)) {
return 'object'
}

return typeof val
}

export function resolveTypeName(value) {
const jsType = resolveJSType(value)
return (jsType === 'object' && '_type' in value && value._type) || jsType
}

0 comments on commit 595294e

Please sign in to comment.