Skip to content

Commit

Permalink
[form-builder] Asset Source Plugin support (#1591)
Browse files Browse the repository at this point in the history
* [form-builder] Remove upload from image input

* [form-builder] Image input: Browse sources defined by part 'form-builder/input/asset-source'

* [form-builder] Let asset sources be configured on type or via global part

* [form-builder] Remove asset source browser and use button dropdown instead

* [form-builder] Adjust default source to show in dialog

* [form-builder] Add @sanity/uuid

* Rename parts

* [form-builder] Add default config for form builder package

* [form-builder] Rename parts

* [form-builder] Tweak default asset source to fit plugin conventions

* [form-builder] Image field utils

* [form-builder] Re-introduce uploads in image field and deal with assets given from source
plugins

* [form-builder] Organize and Image input code better

* [example-studio] Add default formbuilder config file

* [test-studio] Add default formbuilder config file

* [form-builder] Only use upload props when upload component

* [form-builder] Array input must respect direct uploads settings

* [form-builder] Remove console.log

* [form-builder] Tweak icon alignments

* [form-builder] Change button copy + title of default asset source

* [form-builder] Uploads should support label

* [form-builder] Make image input pass through label from asset source plugin

* [form-builder] Rename option: originalFilename > filename

* [client] Support source and sourceId in asset client

* [form-builder] Image asset to support source and sourceId

* [base] Update image asset schema to support source and sourceId

* [base] Update base schema to know about assetSourceData

* [client] Asset client can  take title, description, and source

* [form-builder] Update upload props for sanityImageAsset

* [base] Rename assetSource > assetSourceData
  • Loading branch information
skogsmaskin committed Nov 21, 2019
1 parent 78c9b12 commit 4c7caff
Show file tree
Hide file tree
Showing 22 changed files with 583 additions and 192 deletions.
2 changes: 2 additions & 0 deletions packages/@sanity/base/src/schema/createSchema.js
Expand Up @@ -7,6 +7,7 @@ import slug from './types/slug'
import geopoint from './types/geopoint'
import imageCrop from './types/imageCrop'
import imageHotspot from './types/imageHotspot'
import assetSourceData from './types/assetSourceData'
import imageAsset from './types/imageAsset'
import imagePalette from './types/imagePalette'
import imagePaletteSwatch from './types/imagePaletteSwatch'
Expand All @@ -26,6 +27,7 @@ module.exports = schemaDef => {
if (!hasErrors) {
types = [
...schemaDef.types,
assetSourceData,
slug,
geopoint,
legacyRichDate,
Expand Down
26 changes: 26 additions & 0 deletions packages/@sanity/base/src/schema/types/assetSourceData.js
@@ -0,0 +1,26 @@
export default {
name: 'sanity.assetSourceData',
title: 'Asset Source Data',
type: 'object',
fields: [
{
name: 'name',
title: 'Source name',
description: 'A canonical name for the source this asset is originating from',
type: 'string'
},
{
name: 'id',
title: 'Asset Source ID',
description:
'The unique ID for the asset within the originating source so you can programatically find back to it',
type: 'string'
},
{
name: 'url',
title: 'Asset information URL',
description: 'A URL to find more information about this asset in the originating source',
type: 'string'
}
]
}
17 changes: 17 additions & 0 deletions packages/@sanity/base/src/schema/types/fileAsset.js
Expand Up @@ -21,6 +21,16 @@ export default {
type: 'string',
title: 'Label'
},
{
name: 'title',
type: 'string',
title: 'Title'
},
{
name: 'description',
type: 'string',
title: 'Description'
},
{
name: 'sha1hash',
type: 'string',
Expand Down Expand Up @@ -69,6 +79,13 @@ export default {
title: 'Url',
readOnly: true,
fieldset: 'system'
},
{
name: 'source',
type: 'sanity.assetSourceData',
title: 'Source',
readOnly: true,
fieldset: 'system'
}
],
preview: {
Expand Down
17 changes: 17 additions & 0 deletions packages/@sanity/base/src/schema/types/imageAsset.js
Expand Up @@ -21,6 +21,16 @@ export default {
type: 'string',
title: 'Label'
},
{
name: 'title',
type: 'string',
title: 'Title'
},
{
name: 'description',
type: 'string',
title: 'Description'
},
{
name: 'sha1hash',
type: 'string',
Expand Down Expand Up @@ -74,6 +84,13 @@ export default {
name: 'metadata',
type: 'sanity.imageMetadata',
title: 'Metadata'
},
{
name: 'source',
type: 'sanity.assetSourceData',
title: 'Source',
readOnly: true,
fieldset: 'system'
}
],
preview: {
Expand Down
28 changes: 25 additions & 3 deletions packages/@sanity/client/src/assets/assetsClient.js
Expand Up @@ -51,6 +51,17 @@ assign(AssetsClient.prototype, {
* @param {String} opts.contentType Mime type of the file
* @param {Array} opts.extract Array of metadata parts to extract from image.
* Possible values: `location`, `exif`, `image`, `palette`
* @param {String} opts.label Label
* @param {String} opts.title Title
* @param {String} opts.description Description
* @param {String} opts.creditLine The credit to person(s) and/or organisation(s) required by the supplier of the image to be used when published
* @param {Object} opts.source Source data (when the asset is from an external service)
* @param {String} opts.source.id The (u)id of the asset within the source, i.e. 'i-f323r1E'
* Required if source is defined
* @param {String} opts.source.name The name of the source, i.e. 'unsplash'
* Required if source is defined
* @param {String} opts.source.url A url to where to find the asset, or get more info about it in the source
* Optional
* @return {Promise} Resolves with the created asset document
*/
upload(assetType, body, opts = {}) {
Expand All @@ -65,9 +76,20 @@ assign(AssetsClient.prototype, {
const dataset = validators.hasDataset(this.client.clientConfig)
const assetEndpoint = assetType === 'image' ? 'images' : 'files'
const options = optionsFromFile(opts, body)
const {label, filename} = options
const query = {label, filename, meta}

const {label, title, description, creditLine, filename, source} = options
const query = {
label,
title,
description,
filename,
meta,
creditLine
}
if (source) {
query.sourceId = source.id
query.sourceName = source.name
query.sourceUrl = source.url
}
const observable = this.client._requestObservable({
method: 'POST',
timeout: options.timeout || 0,
Expand Down
5 changes: 5 additions & 0 deletions packages/@sanity/form-builder/config.dist.json
@@ -0,0 +1,5 @@
{
"images": {
"directUploads": true
}
}
1 change: 1 addition & 0 deletions packages/@sanity/form-builder/package.json
Expand Up @@ -30,6 +30,7 @@
"@sanity/mutator": "0.145.0",
"@sanity/schema": "0.145.0",
"@sanity/util": "0.145.0",
"@sanity/uuid": "0.145.0",
"attr-accept": "^1.1.0",
"classnames": "^2.2.5",
"date-fns": "^1.29.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/@sanity/form-builder/sanity.json
Expand Up @@ -122,6 +122,11 @@
"name": "part:@sanity/form-builder/input/block-editor/block-markers-custom-default",
"implements": "part:@sanity/form-builder/input/block-editor/block-markers-custom",
"path": "inputs/BlockEditor/nodes/CustomMarkers.js"
},
{
"name": "part:@sanity/form-builder/input/image/asset-source-default",
"implements": "part:@sanity/form-builder/input/image/asset-source",
"path": "inputs/ImageInput/DefaultSource.js"
}
]
}
4 changes: 4 additions & 0 deletions packages/@sanity/form-builder/src/@types/parts.d.ts
Expand Up @@ -33,6 +33,10 @@ declare module 'part:@sanity/components/textareas/*' {
export default SanityTextareaComponent
}


declare module 'config:@sanity/form-builder'
declare module 'all:part:@sanity/form-builder/input/image/asset-source'

declare module 'part:@sanity/components/utilities/portal'
declare module 'part:@sanity/components/lists/*'
declare module 'part:@sanity/*'
@@ -1,7 +1,7 @@
import React from 'react'
import ArrayFunctions from 'part:@sanity/form-builder/input/array/functions'
import {map} from 'rxjs/operators'
import {isPlainObject} from 'lodash'
import {isPlainObject, get} from 'lodash'
import {ResolvedUploader, Uploader} from '../../sanity/uploads/typedefs'
import {Marker, Type} from '../../typedefs'
import {Path} from '../../typedefs/path'
Expand All @@ -18,6 +18,9 @@ import randomKey from './randomKey'
import Button from 'part:@sanity/components/buttons/default'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import Details from '../common/Details'
import formBuilderConfig from 'config:@sanity/form-builder'

const SUPPORT_DIRECT_UPLOADS = get(formBuilderConfig, 'images.directUploads')

function createProtoValue(type: Type): ItemValue {
if (type.jsonType !== 'object') {
Expand Down Expand Up @@ -317,19 +320,20 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
</Fieldset>
)
}
const FieldSetComponent = SUPPORT_DIRECT_UPLOADS ? UploadTargetFieldset : Fieldset
const uploadProps = SUPPORT_DIRECT_UPLOADS ? {getUploadOptions: this.getUploadOptions, onUpload: this.handleUpload} : {}
return (
<UploadTargetFieldset
<FieldSetComponent
markers={markers}
tabIndex={0}
legend={type.title}
description={type.description}
level={level}
className={styles.root}
onUpload={this.handleUpload}
onFocus={this.handleFocus}
type={type}
getUploadOptions={this.getUploadOptions}
ref={this.setElement}
{...uploadProps}
>
{value && value.length > 0 && this.renderList()}
<ArrayFunctions
Expand All @@ -342,7 +346,7 @@ export default class ArrayInput extends React.Component<Props, ArrayInputState>
onCreateValue={createProtoValue}
onChange={onChange}
/>
</UploadTargetFieldset>
</FieldSetComponent>
)
}
}
7 changes: 4 additions & 3 deletions packages/@sanity/form-builder/src/inputs/ImageInput/Asset.tsx
Expand Up @@ -27,6 +27,7 @@ type AssetProps = {
referenceCount?: number
url?: string
}
isSelected: boolean
onClick?: (...args: any[]) => any
onKeyPress?: (...args: any[]) => any
onDeleteFinished: (...args: any[]) => any
Expand Down Expand Up @@ -97,7 +98,7 @@ export default class Asset extends React.PureComponent<AssetProps, State> {
return [DIALOG_CLOSE_ACTION]
}
render() {
const {asset, onClick, onKeyPress} = this.props
const {asset, onClick, onKeyPress, isSelected} = this.props
const {isDeleting, dialogType} = this.state
const size = 75
const dpi =
Expand All @@ -122,7 +123,7 @@ export default class Asset extends React.PureComponent<AssetProps, State> {
]
return (
<a
className={styles.item}
className={`${styles.item}${isSelected ? ` ${styles.selected}` : ''}`}
tabIndex={0}
data-id={asset._id}
onKeyPress={onKeyPress}
Expand All @@ -148,7 +149,7 @@ export default class Asset extends React.PureComponent<AssetProps, State> {
<DropDownButton
placement="bottom-end"
showArrow={false}
items={menuItems}
items={isSelected ? menuItems.filter(item => item.name !== 'delete') : menuItems}
renderItem={this.renderMenuItem}
onAction={this.handleMenuAction}
>
Expand Down
@@ -1,15 +1,22 @@
import React from 'react'
import ImageIcon from 'react-icons/lib/md/image'
import client from 'part:@sanity/base/client'
import Button from 'part:@sanity/components/buttons/default'
import Dialog from 'part:@sanity/components/dialogs/fullscreen'
import styles from './styles/SelectAsset.css'
import AssetWidget from './Asset'
import {AssetFromSource} from './ImageInput'

const PER_PAGE = 200
type Asset = {
_id: string
url: string
}
type Props = {
onSelect: (arg0: Asset) => void
onSelect: (arg0: AssetFromSource[]) => void
onClose: () => void
selectedAssets: Asset[]
selectionType: boolean
}
function createQuery(start = 0, end = PER_PAGE) {
return `
Expand All @@ -26,7 +33,7 @@ type State = {
isLoading: boolean
}

export default class SelectAsset extends React.Component<Props, State> {
class DefaultSource extends React.Component<Props, State> {
state = {
assets: [],
isLastPage: false,
Expand Down Expand Up @@ -56,7 +63,7 @@ export default class SelectAsset extends React.Component<Props, State> {
select(id) {
const selected = this.state.assets.find(doc => doc._id === id)
if (selected) {
this.props.onSelect(selected)
this.props.onSelect([{kind: 'assetDocumentId', value: id}])
}
}
handleItemClick = (event: React.SyntheticEvent<any>) => {
Expand All @@ -69,35 +76,51 @@ export default class SelectAsset extends React.Component<Props, State> {
this.select(event.currentTarget.getAttribute('data-id'))
}
}
handleClose = () => {
if (this.props.onClose) {
this.props.onClose()
}
}
handleFetchNextPage = () => {
this.fetchPage(++this.pageNo)
}
render() {
const {selectedAssets} = this.props
const {assets, isLastPage, isLoading} = this.state
return (
<div className={styles.root}>
<div className={styles.imageList}>
{assets.map(asset => (
<AssetWidget
key={asset._id}
asset={asset}
onClick={this.handleItemClick}
onKeyPress={this.handleItemKeyPress}
onDeleteFinished={this.handleDeleteFinished}
/>
))}
</div>
{!isLoading && assets.length === 0 && (
<div className={styles.noAssets}>No images found</div>
)}
<div className={styles.loadMore}>
{!isLastPage && (
<Button onClick={this.handleFetchNextPage} loading={isLoading}>
Load more
</Button>
<Dialog title="Select image" onClose={this.handleClose} isOpen>
<div className={styles.root}>
<div className={styles.imageList}>
{assets.map(asset => (
<AssetWidget
key={asset._id}
asset={asset}
isSelected={selectedAssets.some(selected => selected._id === asset._id)}
onClick={this.handleItemClick}
onKeyPress={this.handleItemKeyPress}
onDeleteFinished={this.handleDeleteFinished}
/>
))}
</div>
{!isLoading && assets.length === 0 && (
<div className={styles.noAssets}>No images found</div>
)}
<div className={styles.loadMore}>
{!isLastPage && (
<Button onClick={this.handleFetchNextPage} loading={isLoading}>
Load more
</Button>
)}
</div>
</div>
</div>
</Dialog>
)
}
}

export default {
name: 'sanity-default',
title: 'Uploaded images',
component: DefaultSource,
icon: ImageIcon
}

0 comments on commit 4c7caff

Please sign in to comment.