Skip to content

Commit

Permalink
change path to JSON pointer
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasBoll committed Aug 22, 2023
1 parent e80a873 commit caf7383
Show file tree
Hide file tree
Showing 42 changed files with 458 additions and 382 deletions.
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

0 comments on commit caf7383

Please sign in to comment.