Skip to content

Commit 55c3229

Browse files
committedJul 12, 2021
refactor(field): migrate image and file diffs to @sanity/ui
1 parent f84f0bf commit 55c3229

File tree

9 files changed

+179
-227
lines changed

9 files changed

+179
-227
lines changed
 

‎packages/@sanity/field/src/diff/components/FieldChange.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const DiffBorder = styled.div`
3030
--diff-inspect-padding-small: ${({theme}) => rem(theme.sanity.space[2])};
3131
3232
position: relative;
33-
padding: var(--diff-inspect-padding-xsmall) var(--diff-inspect-padding-small);
33+
padding: var(--diff-inspect-padding-xsmall) 0 var(--diff-inspect-padding-xsmall)
34+
var(--diff-inspect-padding-small);
3435
3536
&::before {
3637
content: '';

‎packages/@sanity/field/src/diff/components/FromTo.tsx

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {forwardRef, useMemo} from 'react'
2-
import {Flex, Grid} from '@sanity/ui'
2+
import {Flex, Grid, rem, useTheme} from '@sanity/ui'
33
import {FromToArrow} from './FromToArrow'
44

55
export type FromToProps = {
@@ -9,40 +9,46 @@ export type FromToProps = {
99
to?: React.ReactNode
1010
} & Omit<React.HTMLProps<HTMLDivElement>, 'children' | 'as' | 'height' | 'wrap'>
1111

12+
const INLINE_COLUMN_STYLES = {flexShrink: 0}
13+
const BLOCK_COLUMN_STYLES = {alignItems: 'stretch'}
14+
15+
const FLEX_ALIGN: Record<string, 'flex-start' | 'center' | 'flex-end'> = {
16+
top: 'flex-start',
17+
center: 'center',
18+
bottom: 'flex-end',
19+
default: 'flex-start',
20+
}
21+
1222
export const FromTo = forwardRef<HTMLDivElement, FromToProps>(
1323
({align = 'top', layout = 'inline', from, to, style, ...restProps}, ref) => {
14-
const flexAlign: Record<string, 'flex-start' | 'center' | 'flex-end'> = {
15-
top: 'flex-start',
16-
center: 'center',
17-
bottom: 'flex-end',
18-
default: 'flex-start',
19-
}
24+
const theme = useTheme()
25+
2026
const Layout = layout === 'inline' ? Flex : Grid
2127
const layoutStyles = useMemo(
2228
() => ({
2329
...style,
2430
...(layout === 'inline'
2531
? {maxWidth: '100%', display: 'inline-flex'}
26-
: {gridTemplateColumns: `minmax(0, 1fr) 26px minmax(0, 1fr)`}),
32+
: {gridTemplateColumns: `minmax(0, 1fr) ${rem(theme.sanity.space[5])} minmax(0, 1fr)`}),
2733
}),
28-
[layout, style]
34+
[layout, style, theme]
2935
)
3036

31-
const columnStyles = useMemo(() => (layout === 'inline' ? {flexShrink: 0} : {}), [layout])
37+
const columnStyles = layout === 'inline' ? INLINE_COLUMN_STYLES : BLOCK_COLUMN_STYLES
3238

3339
return (
3440
<Layout {...restProps} ref={ref} style={layoutStyles} data-from-to-layout>
3541
{from && (
3642
<>
37-
<Flex align={flexAlign[align]} style={columnStyles}>
43+
<Flex align={FLEX_ALIGN[align]} style={columnStyles}>
3844
{from}
3945
</Flex>
4046
<Flex align="center" justify="center" padding={2}>
4147
<FromToArrow />
4248
</Flex>
4349
</>
4450
)}
45-
<Flex align={flexAlign[align]} style={columnStyles}>
51+
<Flex align={FLEX_ALIGN[align]} style={columnStyles}>
4652
{to}
4753
</Flex>
4854
</Layout>

‎packages/@sanity/field/src/diff/components/MetaInfo.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function MetaInfo({
3030
</MetaText>
3131
</Box>
3232
)}
33-
<Stack space={2} paddingLeft={4}>
33+
<Stack space={2} paddingLeft={2}>
3434
<MetaText
3535
size={1}
3636
weight="semibold"

‎packages/@sanity/field/src/types/file/diff/FileFieldDiff.css

-26
This file was deleted.

‎packages/@sanity/field/src/types/file/diff/FileFieldDiff.tsx

+48-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import FileIcon from 'part:@sanity/base/file-icon'
2-
import React from 'react'
2+
import React, {useMemo} from 'react'
3+
import {Box, Card, Flex, Text} from '@sanity/ui'
4+
import styled from 'styled-components'
35
import {
46
DiffComponent,
57
DiffCard,
@@ -13,7 +15,23 @@ import {useRefValue} from '../../../diff/hooks'
1315
import {getSizeDiff} from './helpers'
1416
import {File, FileAsset} from './types'
1517

16-
import styles from './FileFieldDiff.css'
18+
const SizeDiff = styled.div`
19+
${({theme}) => `
20+
--size-diff-positive: ${theme.sanity.color.solid.positive.enabled.bg};
21+
--size-diff-negative: ${theme.sanity.color.solid.critical.enabled.bg};
22+
`}
23+
&:not([hidden]) {
24+
display: inline-block;
25+
}
26+
27+
[data-number='positive'] {
28+
color: var(--size-diff-positive);
29+
}
30+
31+
[data-number='negative'] {
32+
color: var(--size-diff-negative);
33+
}
34+
`
1735

1836
export const FileFieldDiff: DiffComponent<ObjectDiff<File>> = ({diff, schemaType}) => {
1937
const {fromValue, toValue, fields} = diff
@@ -26,12 +44,12 @@ export const FileFieldDiff: DiffComponent<ObjectDiff<File>> = ({diff, schemaType
2644
(name) => fields[name].isChanged && name !== '_type'
2745
)
2846

47+
const didAssetChange = changedFields.includes('asset')
48+
2949
const nestedFields = schemaType.fields
3050
.filter((field) => field.name !== 'asset' && changedFields.includes(field.name))
3151
.map((field) => field.name)
3252

33-
const didAssetChange = changedFields.includes('asset')
34-
3553
// Sizes in MB TODO: improve. Apple uses 1000^2
3654
const prevSize = prev?.size && prev.size / 1000 / 1000
3755
const nextSize = next?.size && next.size / 1000 / 1000
@@ -40,30 +58,36 @@ export const FileFieldDiff: DiffComponent<ObjectDiff<File>> = ({diff, schemaType
4058
const roundedPrevSize = prevSize ? prevSize.toFixed(2) : undefined
4159
const roundedNextSize = nextSize ? nextSize.toFixed(2) : undefined
4260

61+
const cardStyles = useMemo(() => ({display: 'block', flex: 1}), [])
62+
4363
const from = prev && (
44-
<DiffCard as="del" className={styles.card} diff={diff} path="asset._ref">
64+
<DiffCard as="del" diff={diff} path="asset._ref" style={cardStyles}>
4565
<MetaInfo title={prev.originalFilename || 'Untitled'} icon={FileIcon}>
46-
<span>{`${roundedPrevSize}MB`}</span>
66+
<Text size={0} style={{color: 'inherit'}}>{`${roundedPrevSize}MB`}</Text>
4767
</MetaInfo>
4868
</DiffCard>
4969
)
5070

5171
const to = next && (
52-
<DiffCard as="ins" className={styles.card} diff={diff} path="asset._ref">
72+
<DiffCard as="ins" diff={diff} path="asset._ref" style={cardStyles}>
5373
<MetaInfo title={next.originalFilename || 'Untitled'} icon={FileIcon}>
54-
<span>{`${roundedNextSize}MB`}</span>
55-
{pctDiff !== 0 && (
56-
<div className={styles.sizeDiff} data-number={pctDiff > 0 ? 'positive' : 'negative'}>
57-
{pctDiff > 0 && '+'}
58-
{pctDiff}%
59-
</div>
60-
)}
74+
<Flex align="center">
75+
<Text size={0} style={{color: 'inherit'}}>{`${roundedNextSize}MB`}</Text>
76+
{pctDiff !== 0 && (
77+
<Card radius={2} padding={1} as={SizeDiff} marginLeft={2}>
78+
<Text size={0} data-number={pctDiff > 0 ? 'positive' : 'negative'}>
79+
{pctDiff > 0 && '+'}
80+
{pctDiff}%
81+
</Text>
82+
</Card>
83+
)}
84+
</Flex>
6185
</MetaInfo>
6286
</DiffCard>
6387
)
6488

65-
return (
66-
<div className={styles.root}>
89+
const FileAssetChange = (
90+
<>
6791
{/* Removed only */}
6892
{from && !to && (
6993
<DiffTooltip diff={diff} path="asset._ref" description="Removed">
@@ -84,12 +108,17 @@ export const FileFieldDiff: DiffComponent<ObjectDiff<File>> = ({diff, schemaType
84108
{to}
85109
</DiffTooltip>
86110
)}
111+
</>
112+
)
87113

114+
return (
115+
<>
116+
{didAssetChange && FileAssetChange}
88117
{nestedFields.length > 0 && (
89-
<div className={styles.nestedFields}>
118+
<Box marginTop={didAssetChange ? 4 : 3}>
90119
<ChangeList diff={diff} schemaType={schemaType} fields={nestedFields} />
91-
</div>
120+
</Box>
92121
)}
93-
</div>
122+
</>
94123
)
95124
}

‎packages/@sanity/field/src/types/image/diff/ImageFieldDiff.css

-38
This file was deleted.

‎packages/@sanity/field/src/types/image/diff/ImageFieldDiff.tsx

+17-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Image} from '@sanity/types'
22
import * as React from 'react'
3+
import {Box, Card, Text} from '@sanity/ui'
34
import {
45
DiffCard,
56
DiffComponent,
@@ -10,11 +11,14 @@ import {
1011
} from '../../../diff'
1112
import {FromTo} from '../../../diff/components'
1213
import {ImagePreview, NoImagePreview} from './ImagePreview'
13-
import styles from './ImageFieldDiff.css'
1414

1515
const IMAGE_META_FIELDS = ['crop', 'hotspot']
1616
const BASE_IMAGE_FIELDS = ['asset', ...IMAGE_META_FIELDS]
1717

18+
const CARD_STYLES = {
19+
flex: 1,
20+
}
21+
1822
export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaType}) => {
1923
const {fromValue, toValue, fields, isChanged} = diff
2024
const fromRef = fromValue?.asset?._ref
@@ -48,7 +52,7 @@ export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaTy
4852

4953
const from =
5054
fromValue && fromRef ? (
51-
<DiffCard className={styles.annotation} annotation={assetAnnotation}>
55+
<DiffCard annotation={assetAnnotation} style={CARD_STYLES}>
5256
<ImagePreview
5357
is="from"
5458
id={fromRef}
@@ -64,7 +68,7 @@ export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaTy
6468

6569
const to =
6670
toValue && toRef ? (
67-
<DiffCard className={styles.annotation} annotation={assetAnnotation}>
71+
<DiffCard annotation={assetAnnotation} style={CARD_STYLES}>
6872
<ImagePreview
6973
is="to"
7074
id={toRef}
@@ -79,17 +83,17 @@ export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaTy
7983

8084
if (!from && !to) {
8185
return (
82-
<div className={styles.root}>
83-
<div className={styles.emptyMessage}>
84-
<span>Image not set</span>
85-
</div>
86-
</div>
86+
<Card padding={4} radius={2} tone="transparent">
87+
<Text muted size={1} align="center">
88+
Image not set
89+
</Text>
90+
</Card>
8791
)
8892
}
8993

9094
if (!isChanged) {
9195
return toRef ? (
92-
<DiffCard className={styles.annotation} annotation={assetAnnotation}>
96+
<DiffCard annotation={assetAnnotation} style={CARD_STYLES}>
9397
<ImagePreview id={toRef} is="to" diff={diff} />
9498
</DiffCard>
9599
) : null
@@ -98,7 +102,7 @@ export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaTy
98102
const imageDiff = <FromTo align="center" from={from} layout="grid" to={to} />
99103

100104
return (
101-
<div className={styles.root}>
105+
<>
102106
{showImageDiff &&
103107
(didAssetChange ? (
104108
<DiffTooltip
@@ -110,12 +114,11 @@ export const ImageFieldDiff: DiffComponent<ObjectDiff<Image>> = ({diff, schemaTy
110114
) : (
111115
imageDiff
112116
))}
113-
114117
{nestedFields.length > 0 && (
115-
<div className={styles.nestedFields}>
118+
<Box marginTop={showImageDiff ? 4 : 3}>
116119
<ChangeList diff={diff} schemaType={schemaType} fields={nestedFields} />
117-
</div>
120+
</Box>
118121
)}
119-
</div>
122+
</>
120123
)
121124
}

‎packages/@sanity/field/src/types/image/diff/ImagePreview.css

-89
This file was deleted.

‎packages/@sanity/field/src/types/image/diff/ImagePreview.tsx

+93-27
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import {useDocumentValues} from '@sanity/base/hooks'
33
import {getImageDimensions, isDefaultCrop, isDefaultHotspot} from '@sanity/asset-utils'
44
import imageUrlBuilder from '@sanity/image-url'
55
import ImageIcon from 'part:@sanity/base/image-icon'
6+
import {Box, Card, Flex, Text} from '@sanity/ui'
7+
import styled from 'styled-components'
8+
import {hues} from '@sanity/color'
69
import {versionedClient} from '../../../versionedClient'
710
import {MetaInfo} from '../../../diff'
811
import {getDeviceDpr, simpleHash} from './helpers'
912
import {HotspotCropSVG} from './HotspotCropSVG'
1013
import {ImagePreviewProps, MinimalAsset} from './types'
1114

12-
import styles from './ImagePreview.css'
13-
1415
const ASSET_FIELDS = ['originalFilename']
1516
const imageBuilder = imageUrlBuilder(versionedClient)
1617

@@ -21,11 +22,70 @@ const imageBuilder = imageUrlBuilder(versionedClient)
2122
// eg: 'image-1217bc35db5030739b7be571c79d3c401551911d-300x200-png'
2223

2324
export const NoImagePreview = () => (
24-
<div className={styles.noImage}>
25-
<div>(no image)</div>
26-
</div>
25+
<Card flex={1} tone="transparent" padding={4} radius={2} height="stretch">
26+
<Flex align="center" justify="center" height="fill">
27+
<Text size={1} muted>
28+
(no image)
29+
</Text>
30+
</Flex>
31+
</Card>
2732
)
2833

34+
const ImageWrapper = styled.div`
35+
height: 100%;
36+
max-height: 190px;
37+
position: relative;
38+
39+
/* Ideally the checkerboard component currently in the form builder should be made available and used here */
40+
background-color: ${hues.gray[100].hex};
41+
background-image: linear-gradient(45deg, ${hues.gray[50].hex} 25%, transparent 25%),
42+
linear-gradient(-45deg, ${hues.gray[50].hex} 25%, transparent 25%),
43+
linear-gradient(45deg, transparent 75%, ${hues.gray[50].hex} 75%),
44+
linear-gradient(-45deg, transparent 75%, ${hues.gray[50].hex} 75%);
45+
background-size: 16px 16px;
46+
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
47+
48+
&::after {
49+
content: '';
50+
display: block;
51+
position: absolute;
52+
top: 0;
53+
left: 0;
54+
right: 0;
55+
bottom: 0;
56+
box-shadow: inset 0 0 0 1px var(--card-border-color);
57+
pointer-events: none;
58+
}
59+
60+
&[data-changed] {
61+
opacity: 0.45;
62+
}
63+
`
64+
65+
const Image = styled.img`
66+
display: block;
67+
flex: 1;
68+
min-height: 0;
69+
object-fit: contain;
70+
width: 100%;
71+
height: 100%;
72+
73+
&[data-action='removed'] {
74+
opacity: 0.45;
75+
}
76+
`
77+
78+
const HotspotDiff = styled.div`
79+
svg {
80+
display: block;
81+
position: absolute;
82+
top: 0;
83+
left: 0;
84+
width: 100%;
85+
height: 100%;
86+
}
87+
`
88+
2989
export function ImagePreview(props: ImagePreviewProps): React.ReactElement {
3090
const {id, action, diff, hotspot, crop, is} = props
3191
const [imageError, setImageError] = React.useState<SyntheticEvent<HTMLImageElement, Event>>()
@@ -43,17 +103,20 @@ export function ImagePreview(props: ImagePreviewProps): React.ReactElement {
43103
.fit('max')
44104

45105
const assetChanged = diff.fromValue?.asset?._ref !== diff.toValue?.asset?._ref
46-
const imageWrapperClassName =
47-
is === 'from' && assetChanged ? styles.imageWrapperChanged : styles.imageWrapper
106+
48107
const metaAction = action === 'changed' ? undefined : action
49108

50109
return (
51-
<div className={styles.root}>
52-
<div className={styles.header}>
53-
<div className={imageWrapperClassName}>
110+
<Flex direction="column" height="fill" flex={1}>
111+
<Box flex={1} padding={2} paddingBottom={0}>
112+
<Flex
113+
as={ImageWrapper}
114+
direction="column"
115+
data-changed={is === 'from' && assetChanged ? '' : undefined}
116+
data-error={imageError ? '' : undefined}
117+
>
54118
{!assetIsDeleted && !imageError && (
55-
<img
56-
className={styles.image}
119+
<Image
57120
src={imageSource.toString() || ''}
58121
alt={title}
59122
data-action={metaAction}
@@ -64,22 +127,25 @@ export function ImagePreview(props: ImagePreviewProps): React.ReactElement {
64127
)}
65128

66129
{(assetIsDeleted || imageError) && (
67-
<div className={styles.error}>
68-
{assetIsDeleted ? 'Image is deleted' : 'Error loading image'}
69-
</div>
130+
<Box paddingY={5}>
131+
<Text size={1} muted align="center">
132+
{assetIsDeleted ? 'Image is deleted' : 'Error loading image'}
133+
</Text>
134+
</Box>
70135
)}
71136

72-
<HotspotCropSVG
73-
className={styles.hotspotCrop}
74-
crop={crop && !isDefaultCrop(crop) ? crop : undefined}
75-
diff={diff}
76-
hash={simpleHash(`${imageSource.toString() || ''}-${is}`)}
77-
hotspot={hotspot && !isDefaultHotspot(hotspot) ? hotspot : undefined}
78-
width={dimensions.width}
79-
height={dimensions.height}
80-
/>
81-
</div>
82-
</div>
137+
<HotspotDiff>
138+
<HotspotCropSVG
139+
crop={crop && !isDefaultCrop(crop) ? crop : undefined}
140+
diff={diff}
141+
hash={simpleHash(`${imageSource.toString() || ''}-${is}`)}
142+
hotspot={hotspot && !isDefaultHotspot(hotspot) ? hotspot : undefined}
143+
width={dimensions.width}
144+
height={dimensions.height}
145+
/>
146+
</HotspotDiff>
147+
</Flex>
148+
</Box>
83149

84150
<MetaInfo title={title} icon={ImageIcon} markRemoved={assetChanged && is === 'from'}>
85151
{metaAction ? (
@@ -90,6 +156,6 @@ export function ImagePreview(props: ImagePreviewProps): React.ReactElement {
90156
</div>
91157
)}
92158
</MetaInfo>
93-
</div>
159+
</Flex>
94160
)
95161
}

2 commit comments

Comments
 (2)

vercel[bot] commented on Jul 12, 2021

@vercel[bot]

Successfully deployed to the following URLs:

test-studio – ./

test-studio.sanity.build
test-studio-git-next.sanity.build

vercel[bot] commented on Jul 12, 2021

@vercel[bot]

Successfully deployed to the following URLs:

perf-studio – ./

perf-studio-git-next.sanity.build
perf-studio.sanity.build

Please sign in to comment.