diff --git a/packages/@sanity/form-builder/src/sanity/focusManagers/HashFocusManager.js b/packages/@sanity/form-builder/src/sanity/focusManagers/HashFocusManager.js index 8703062fe15..5e459d9e34c 100644 --- a/packages/@sanity/form-builder/src/sanity/focusManagers/HashFocusManager.js +++ b/packages/@sanity/form-builder/src/sanity/focusManagers/HashFocusManager.js @@ -1,48 +1,31 @@ /** * An example of how to sync focus path through document.location.hash * -*/ + */ // @flow import React from 'react' -import {arrayToJSONMatchPath} from '../../../../mutator/lib/index' -import type {Path, PathSegment} from '../../typedefs/path' +import type {Path} from '../../typedefs/path' +import {toFormBuilder, toGradient} from '../utils/convertPath' + +type ChildArgs = { + onFocus: (path: Path) => void, + onBlur: () => void, + focusPath: Path +} type Props = { focusPath: ?any, onFocus: () => {}, onBlur: () => {}, - children: () => any + children: ( + ChildArgs + ) => any } type State = { focusPath: Array<*> } -const IS_NUMERIC = /^\d+$/ - -function unquote(str) { - return str.replace(/^['"]/, '').replace(/['"]$/, '') -} - -function splitAttr(segment) { - const [attr, key] = segment.split('==') - return {[attr]: unquote(key)} -} - -function coerce(segment: string): PathSegment { - return IS_NUMERIC.test(segment) ? Number(segment) : segment -} - -function parseSimplePath(focusPathStr): Path { - return focusPathStr - .split(/[[.\]]/g) - .filter(Boolean) - .map(seg => (seg.includes('==') ? splitAttr(seg) : coerce(seg))) -} - -function formatPath(focusPath) { - return arrayToJSONMatchPath(focusPath) -} function getHash() { return decodeURIComponent(document.location.hash.substring(1)) @@ -50,7 +33,7 @@ function getHash() { function getPathFromHash() { const hash = getHash() - return hash ? parseSimplePath(hash) : [] + return hash ? toFormBuilder(hash) : [] } export default class HashFocusManager extends React.Component { @@ -71,7 +54,7 @@ export default class HashFocusManager extends React.Component { } handleFocus = (focusPath: Path) => { - document.location.hash = formatPath(focusPath) + document.location.hash = toGradient(focusPath) } handleBlur = () => { diff --git a/packages/@sanity/form-builder/src/sanity/formBuilderValueStore.js b/packages/@sanity/form-builder/src/sanity/formBuilderValueStore.js index 81c2df38788..bd7763a6d3c 100644 --- a/packages/@sanity/form-builder/src/sanity/formBuilderValueStore.js +++ b/packages/@sanity/form-builder/src/sanity/formBuilderValueStore.js @@ -1,6 +1,7 @@ // @flow weak import documentStore from 'part:@sanity/base/datastore/document' -import gradientPatchAdapter from './utils/gradientPatchAdapter' +import * as gradientPatchAdapter from './utils/gradientPatchAdapter' +import type {Patch} from '../typedefs/patch' export function checkout(documentId) { @@ -35,7 +36,7 @@ export function checkout(documentId) { ...document, events: events$, patch(patches: Array) { - document.patch(gradientPatchAdapter.fromFormBuilder(patches)) + document.patch(gradientPatchAdapter.toGradient(patches)) } } } diff --git a/packages/@sanity/form-builder/src/sanity/utils/convertPath.js b/packages/@sanity/form-builder/src/sanity/utils/convertPath.js new file mode 100644 index 00000000000..ff6d0d3c45b --- /dev/null +++ b/packages/@sanity/form-builder/src/sanity/utils/convertPath.js @@ -0,0 +1,33 @@ +// @flow +import {arrayToJSONMatchPath} from '@sanity/mutator' +import type {Path, PathSegment} from '../../typedefs/path' + +const IS_NUMERIC = /^\d+$/ + +function unquote(str) { + return str.replace(/^['"]/, '').replace(/['"]$/, '') +} + +function splitAttr(segment) { + const [attr, key] = segment.split('==') + return {[attr]: unquote(key)} +} + +function coerce(segment: string): PathSegment { + return IS_NUMERIC.test(segment) ? Number(segment) : segment +} + +function parseGradientPath(focusPathStr): Path { + return focusPathStr + .split(/[[.\]]/g) + .filter(Boolean) + .map(seg => (seg.includes('==') ? splitAttr(seg) : coerce(seg))) +} + +export function toGradient(formBuilderPath: Path): string { + return arrayToJSONMatchPath(formBuilderPath) +} + +export function toFormBuilder(gradientPath: string) { + return parseGradientPath(gradientPath) +} diff --git a/packages/@sanity/form-builder/src/sanity/utils/gradientPatchAdapter.js b/packages/@sanity/form-builder/src/sanity/utils/gradientPatchAdapter.js index a649e1f7c99..e4a6dab15aa 100644 --- a/packages/@sanity/form-builder/src/sanity/utils/gradientPatchAdapter.js +++ b/packages/@sanity/form-builder/src/sanity/utils/gradientPatchAdapter.js @@ -1,79 +1,79 @@ // @flow - import {arrayToJSONMatchPath} from '@sanity/mutator' import assert from 'assert' import {flatten} from 'lodash' -import type {Patch} from '../../utils/patches' +import type {Origin, Patch} from '../../typedefs/patch' +import * as convertPath from './convertPath' + type GradientPatch = Object -type Adapter = { - fromFormBuilder: (patches: Array) => Array, - toFormBuilder: (origin: string, patches: Array) => Array +export function toGradient(patches: Patch[]): GradientPatch[] { + return patches.map(toGradientPatch) } -const adapter: Adapter = { - fromFormBuilder(patches) { - return patches.map(fromFormBuilder) - }, - toFormBuilder +export function toFormBuilder(origin: Origin, patches: GradientPatch[]): Patch[] { + return flatten(patches.map(patch => toFormBuilderPatch(origin, patch))) } -export default adapter - - -/** - * - * *** WARNING *** - * - * This function is *EXPERIMENTAL* and very likely to have bugs. It is not in real use yet, and needs - * to be revised. - */ - -function toFormBuilder(origin, patches: Array): Array { - return flatten(patches.map(patch => { - return flatten(Object.keys(patch) +function toFormBuilderPatch(origin: Origin, patch: GradientPatch): Patch { + return flatten( + Object.keys(patch) .filter(key => key !== 'id') - .map((type): Array => { + .map(type => { if (type === 'unset') { return patch.unset.map(path => { return { type: 'unset', - path: path.split('.'), + path: convertPath.toFormBuilder(path), origin } }) } - return Object.keys(patch[type]).map(path => { - if (type === 'insert') { - const position = 'before' in patch.insert ? 'before' : 'after' - return { - type: 'insert', - position: position, - path: path.split('.'), - items: patch[type][path], - origin + return Object.keys(patch[type]) + .map(gradientPath => { + if (type === 'insert') { + const position = 'before' in patch.insert ? 'before' : 'after' + return { + type: 'insert', + position: position, + path: convertPath.toFormBuilder(patch.insert[position]), + items: patch.insert.items, + origin + } } - } - if (type === 'set') { - return { - type: 'set', - path: path.split('.'), - value: patch[type][path], - origin + if (type === 'set') { + return { + type: 'set', + path: convertPath.toFormBuilder(gradientPath), + value: patch[type][gradientPath], + origin + } } - } - return { - type, - path: path.split('.'), - value: patch[type][path], - origin - } - }) - })) - })) + if (type === 'setIfMissing') { + return { + type: 'setIfMissing', + path: convertPath.toFormBuilder(gradientPath), + value: patch[type][gradientPath], + origin + } + } + if (type === 'diffMatchPatch') { + return { + type: 'diffMatchPatch', + path: convertPath.toFormBuilder(gradientPath), + value: patch[type][gradientPath], + origin + } + } + console.warn(new Error(`Unsupported patch type: ${type}`)) + return null + }) + .filter(Boolean) + }) + ) } -function fromFormBuilder(patch: Patch): GradientPatch { +function toGradientPatch(patch: Patch): GradientPatch { const matchPath = arrayToJSONMatchPath(patch.path || []) if (patch.type === 'insert') { const {position, items} = patch diff --git a/packages/@sanity/form-builder/src/typedefs/patch.js b/packages/@sanity/form-builder/src/typedefs/patch.js new file mode 100644 index 00000000000..8b8facb5a00 --- /dev/null +++ b/packages/@sanity/form-builder/src/typedefs/patch.js @@ -0,0 +1,42 @@ +// @flow +type KeyedSegment = { + _key: string +} + +type JSONValue = number | string | boolean | {[string]: JSONValue} | JSONValue[] + +export type PathSegment = string | number | KeyedSegment + +export type Path = Array + +export type Origin = 'remote' | 'local' + +type SetPatch = { + path: Path, + type: 'set', + origin: Origin, + value: JSONValue +} + +type SetIfMissingPatch = { + path: Path, + origin: Origin, + type: 'setIfMissing', + value: JSONValue +} + +type UnsetPatch = { + path: Path, + origin: Origin, + type: 'unset' +} + +type InsertPatch = { + path: Path, + origin: Origin, + type: 'insert', + position: 'before' | 'after', + items: JSONValue[] +} + +export type Patch = SetPatch | SetIfMissingPatch | UnsetPatch | InsertPatch diff --git a/packages/@sanity/form-builder/src/typedefs/path.js b/packages/@sanity/form-builder/src/typedefs/path.js deleted file mode 100644 index 41a7305daf7..00000000000 --- a/packages/@sanity/form-builder/src/typedefs/path.js +++ /dev/null @@ -1,8 +0,0 @@ - -type KeyedSegment = { - _key: string -} - -export type PathSegment = string | number | KeyedSegment - -export type Path = Array diff --git a/packages/@sanity/form-builder/src/utils/patches.js b/packages/@sanity/form-builder/src/utils/patches.js index 748875e3c48..f46cc0c375c 100644 --- a/packages/@sanity/form-builder/src/utils/patches.js +++ b/packages/@sanity/form-builder/src/utils/patches.js @@ -1,35 +1,5 @@ // @flow -import type {Path, PathSegment} from '../typedefs/path' - -type HasPath = { - path: Path -} -type HasOrigin = { - origin?: 'remote' | 'local' -} -type SetPatch = HasPath & HasOrigin & { - type: 'set', - value: any -} - -type SetIfMissingPatch = HasPath & HasOrigin & { - type: 'setIfMissing', - value: any -} - -type UnsetPatch = HasPath & HasOrigin & { - type: 'unset', -} - -type InsertPosition = 'before' | 'after' -type InsertPatch = HasPath & HasOrigin & { - type: 'insert', - position: InsertPosition, - items: any[] -} - -export type Patch = SetPatch | SetIfMissingPatch | UnsetPatch | InsertPatch export function setIfMissing(value : any, path : Path = []) : SetIfMissingPatch { return {