Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: change path to JSON pointer #2168

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class ArrayLayoutRenderer
}
return {
schema: this.scopedSchema,
path: Paths.compose(this.propsPath, `${index}`),
path: Paths.compose(this.propsPath, `/${index}`),
uischema,
};
}
Expand Down
7 changes: 4 additions & 3 deletions packages/angular-material/src/other/master-detail/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const removeSchemaKeywords = (path: string) => {
<button
mat-icon-button
class="button hide"
(click)="onDeleteClick(i)"
(click)="onDeleteClick($event, i)"
[ngClass]="{ show: highlightedIdx == i }"
*ngIf="isEnabled()"
>
Expand Down Expand Up @@ -215,7 +215,7 @@ export class MasterListComponent
? d.toString()
: get(d, labelRefInstancePath ?? getFirstPrimitiveProp(schema)),
data: d,
path: `${path}.${index}`,
path: `${path}/${index}`,
schema,
uischema: detailUISchema,
};
Expand Down Expand Up @@ -266,7 +266,8 @@ export class MasterListComponent
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
}

onDeleteClick(item: number) {
onDeleteClick(e: any, item: number) {
e.stopPropagation();
this.removeItems(this.propsPath, [item])();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/angular-material/src/other/table.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export class TableRenderer extends JsonFormsArrayControl implements OnInit {
this.translations = props.translations;
}
getProps(index: number, props: OwnPropsOfRenderer): OwnPropsOfRenderer {
const rowPath = Paths.compose(props.path, `${index}`);
const rowPath = Paths.compose(props.path, `/${index}`);
return {
schema: props.schema,
uischema: props.uischema,
Expand Down
6 changes: 3 additions & 3 deletions packages/angular-material/test/master-detail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ describe('Master detail', () => {
// delete 1st item
spyOn(component, 'removeItems').and.callFake(() => () => {
getJsonFormsService(component).updateCore(
Actions.update('orders', () => moreData.orders.slice(1))
Actions.update('/orders', () => moreData.orders.slice(1))
);
fixture.detectChanges();
});
Expand Down Expand Up @@ -273,7 +273,7 @@ describe('Master detail', () => {
const copy = moreData.orders.slice();
copy.splice(1, 1);
getJsonFormsService(component).updateCore(
Actions.update('orders', () => copy)
Actions.update('/orders', () => copy)
);
fixture.detectChanges();
});
Expand Down Expand Up @@ -388,7 +388,7 @@ describe('Master detail', () => {
customer: { name: 'ACME' },
title: 'Carrots',
},
path: 'orders.0',
path: '/orders/0',
schema: schema.definitions.order,
uischema: {
type: 'VerticalLayout',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/i18n/i18nUtil.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ErrorObject } from 'ajv';
import { isInternationalized, Labelable, UISchemaElement } from '../models';
import { getControlPath } from '../reducers';
import { formatErrorMessage } from '../util';
import { formatErrorMessage, toLodashPath } from '../util';
import type { i18nJsonSchema, ErrorTranslator, Translator } from './i18nTypes';
import {
ArrayDefaultTranslation,
Expand All @@ -28,7 +28,7 @@ export const getI18nKeyPrefixBySchema = (
*/
export const transformPathToI18nPrefix = (path: string): string => {
return (
path
toLodashPath(path)
?.split('.')
.filter((segment) => !/^\d+$/.test(segment))
.join('.') || 'root'
Expand Down
27 changes: 13 additions & 14 deletions packages/core/src/reducers/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ import {
UPDATE_CORE,
UpdateCoreAction,
} from '../actions';
import { createAjv, decode, isOneOfEnumSchema, Reducer } from '../util';
import {
composePaths,
createAjv,
isOneOfEnumSchema,
Reducer,
toLodashSegments,
} from '../util';
import type { JsonSchema, UISchemaElement } from '../models';

export const validate = (
Expand Down Expand Up @@ -284,18 +290,19 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
errors,
};
} else {
const oldData: any = get(state.data, action.path);
const lodashDataPathSegments = toLodashSegments(action.path);
const oldData: any = get(state.data, lodashDataPathSegments);
const newData = action.updater(cloneDeep(oldData));
let newState: any;
if (newData !== undefined) {
newState = setFp(
action.path,
lodashDataPathSegments,
newData,
state.data === undefined ? {} : state.data
);
} else {
newState = unsetFp(
action.path,
lodashDataPathSegments,
state.data === undefined ? {} : state.data
);
}
Expand Down Expand Up @@ -369,19 +376,11 @@ export const getControlPath = (error: ErrorObject) => {
// With AJV v8 the property was renamed to 'instancePath'
let controlPath = (error as any).dataPath || error.instancePath || '';

// change '/' chars to '.'
controlPath = controlPath.replace(/\//g, '.');

const invalidProperty = getInvalidProperty(error);
if (invalidProperty !== undefined && !controlPath.endsWith(invalidProperty)) {
controlPath = `${controlPath}.${invalidProperty}`;
controlPath = composePaths(controlPath, invalidProperty);
}

// remove '.' chars at the beginning of paths
controlPath = controlPath.replace(/^./, '');

// decode JSON Pointer escape sequences
controlPath = decode(controlPath);
return controlPath;
};

Expand Down Expand Up @@ -479,5 +478,5 @@ export const errorAt = (instancePath: string, schema: JsonSchema) =>
getErrorsAt(instancePath, schema, (path) => path === instancePath);
export const subErrorsAt = (instancePath: string, schema: JsonSchema) =>
getErrorsAt(instancePath, schema, (path) =>
path.startsWith(instancePath + '.')
path.startsWith(instancePath + '/')
);
23 changes: 21 additions & 2 deletions packages/core/src/reducers/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@
import {
defaultErrorTranslator,
defaultTranslator,
ErrorTranslator,
JsonFormsI18nState,
Translator,
} from '../i18n';
import {
I18nActions,
SET_LOCALE,
SET_TRANSLATOR,
UPDATE_I18N,
} from '../actions';
import type { Reducer } from '../util';
import { Reducer, toLodashPath } from '../util';
import { ErrorObject } from 'ajv';
import { UISchemaElement } from '../models';

export const defaultJsonFormsI18nState: Required<JsonFormsI18nState> = {
locale: 'en',
Expand All @@ -53,7 +57,6 @@ export const i18nReducer: Reducer<JsonFormsI18nState, I18nActions> = (
action.translator ?? defaultJsonFormsI18nState.translate;
const translateError =
action.errorTranslator ?? defaultJsonFormsI18nState.translateError;

if (
locale !== state.locale ||
translate !== state.translate ||
Expand Down Expand Up @@ -84,6 +87,22 @@ export const i18nReducer: Reducer<JsonFormsI18nState, I18nActions> = (
}
};

export const wrapTranslateFunction = (translator: Translator): Translator => {
return (id: string, defaultMessage?: string | undefined, values?: any) => {
return translator(toLodashPath(id), defaultMessage, values);
};
};

export const wrapErrorTranslateFunction = (
translator: ErrorTranslator
): ErrorTranslator => {
return (
error: ErrorObject,
translate: Translator,
uischema?: UISchemaElement
) => translator(error, wrapTranslateFunction(translate), uischema);
};

export const fetchLocale = (state?: JsonFormsI18nState) => {
if (state === undefined) {
return undefined;
Expand Down
44 changes: 32 additions & 12 deletions packages/core/src/util/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,26 @@ import isEmpty from 'lodash/isEmpty';
import range from 'lodash/range';
import { isScoped, Scopable } from '../models';

export const compose = (path1: string, path2: string) => {
let p1 = path1;
if (!isEmpty(path1) && !isEmpty(path2) && !path2.startsWith('[')) {
p1 = path1 + '.';
/**
* Composes two JSON pointer. Pointer1 is appended to pointer2.
* JSON pointer is seperated and start with '/' e.g: /foo/0
*
* @param {string} pointer1 JSON pointer
* @param {string} pointer2 JSON pointer
* @returns {string} resulting JSON pointer
*/
export const compose = (pointer1: string, pointer2: string) => {
let p2 = pointer2;
if (!isEmpty(pointer2) && !pointer2.startsWith('/')) {
p2 = '/' + pointer2;
}

if (isEmpty(p1)) {
return path2;
} else if (isEmpty(path2)) {
return p1;
if (isEmpty(pointer1)) {
return p2;
} else if (isEmpty(pointer2)) {
return pointer1;
} else {
return `${p1}${path2}`;
return `${pointer1}${p2}`;
}
};

Expand Down Expand Up @@ -76,13 +84,13 @@ export const toDataPathSegments = (schemaPath: string): string[] => {
* Data paths can be used in field change event handlers like handleChange.
*
* @example
* toDataPath('#/properties/foo/properties/bar') === 'foo.bar')
* toDataPath('#/properties/foo/properties/bar') === '/foo/bar')
*
* @param {string} schemaPath the schema path to be converted
* @returns {string} the data path
*/
export const toDataPath = (schemaPath: string): string => {
return toDataPathSegments(schemaPath).join('.');
return '/' + toDataPathSegments(schemaPath).join('/');
};

export const composeWithUi = (scopableUi: Scopable, path: string): string => {
Expand All @@ -96,7 +104,19 @@ export const composeWithUi = (scopableUi: Scopable, path: string): string => {
return path ?? '';
}

return compose(path, segments.join('.'));
return compose(path, '/' + segments.join('/'));
};

export const toLodashSegments = (jsonPointer: string): string[] => {
let path = jsonPointer;
if (jsonPointer && jsonPointer.startsWith('/')) {
path = jsonPointer.substring(1);
}
return path ? path.split('/').map(decode) : [];
};

export const toLodashPath = (jsonPointer: string) => {
return toLodashSegments(jsonPointer).join('.');
};

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/util/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const resolveData = (instance: any, dataPath: string): any => {
if (isEmpty(dataPath)) {
return instance;
}
const dataPathSegments = dataPath.split('.');
const dataPathSegments = dataPath.split('/').slice(1);

return dataPathSegments.reduce((curInstance, decodedSegment) => {
if (
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ export const Resolve: {
};

// Paths --
const fromScoped = (scopable: Scoped): string =>
toDataPathSegments(scopable.scope).join('.');
const fromScoped = (scopable: Scoped): string => {
return '/' + toDataPathSegments(scopable.scope).join('/');
};

export const Paths = {
compose: composePaths,
Expand Down
20 changes: 10 additions & 10 deletions packages/core/test/i18n/i18nUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ test('transformPathToI18nPrefix returns root when empty', (t) => {
});

test('transformPathToI18nPrefix does not modify non-array paths', (t) => {
t.is(transformPathToI18nPrefix('foo'), 'foo');
t.is(transformPathToI18nPrefix('foo.bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('bar3.foo2'), 'bar3.foo2');
t.is(transformPathToI18nPrefix('/foo'), 'foo');
t.is(transformPathToI18nPrefix('/foo/bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('/bar3/foo2'), 'bar3.foo2');
});

test('transformPathToI18nPrefix removes array indices', (t) => {
t.is(transformPathToI18nPrefix('foo.2.bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo.234324234.bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo.0.bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo.0.bar.1.foobar'), 'foo.bar.foobar');
t.is(transformPathToI18nPrefix('3.foobar'), 'foobar');
t.is(transformPathToI18nPrefix('foobar.3'), 'foobar');
t.is(transformPathToI18nPrefix('foo1.23.b2ar3.1.5.foo'), 'foo1.b2ar3.foo');
t.is(transformPathToI18nPrefix('foo/2/bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo/234324234/bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo/0/bar'), 'foo.bar');
t.is(transformPathToI18nPrefix('foo/0/bar/1/foobar'), 'foo.bar.foobar');
t.is(transformPathToI18nPrefix('3/foobar'), 'foobar');
t.is(transformPathToI18nPrefix('foobar/3'), 'foobar');
t.is(transformPathToI18nPrefix('foo1/23/b2ar3/1/5/foo'), 'foo1.b2ar3.foo');
t.is(transformPathToI18nPrefix('3'), 'root');
});

Expand Down
Loading