mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Merge pull request #16455 from strapi/feature/custom-field-sizes
Let custom fields specify a custom input size
This commit is contained in:
commit
7c972f569b
@ -102,7 +102,9 @@ const ModalForm = ({ onMetaChange, onSizeChange }) => {
|
||||
);
|
||||
});
|
||||
|
||||
const { isResizable } = fieldSizes[attributes[selectedField].type];
|
||||
// Check for a custom input provided by a custom field, or use the default one for that type
|
||||
const { type, customField } = attributes[selectedField];
|
||||
const { isResizable } = fieldSizes[customField] ?? fieldSizes[type];
|
||||
|
||||
const sizeField = (
|
||||
<GridItem col={6} key="size">
|
||||
|
||||
@ -30,8 +30,14 @@ const reducer = (state = initialState, action) =>
|
||||
}
|
||||
case 'ON_ADD_FIELD': {
|
||||
const newState = cloneDeep(state);
|
||||
const type = get(newState, ['modifiedData', 'attributes', action.name, 'type'], '');
|
||||
const size = action.fieldSizes[type]?.default ?? DEFAULT_FIELD_SIZE;
|
||||
const attribute = get(newState, ['modifiedData', 'attributes', action.name], {});
|
||||
|
||||
// Get the default size, checking custom fields first, then the type and generic defaults
|
||||
const size =
|
||||
action.fieldSizes[attribute?.customField]?.default ??
|
||||
action.fieldSizes[attribute?.type]?.default ??
|
||||
DEFAULT_FIELD_SIZE;
|
||||
|
||||
const listSize = get(newState, layoutPathEdit, []).length;
|
||||
const actualRowContentPath = [...layoutPathEdit, listSize - 1, 'rowContent'];
|
||||
const rowContentToSet = get(newState, actualRowContentPath, []);
|
||||
|
||||
@ -6,4 +6,5 @@ module.exports = async () => {
|
||||
await getService('components').syncConfigurations();
|
||||
await getService('content-types').syncConfigurations();
|
||||
await getService('permission').registerPermissions();
|
||||
getService('field-sizes').setCustomFieldInputSizes();
|
||||
};
|
||||
|
||||
@ -1,10 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
const fieldSizesService = require('../field-sizes');
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
const createFieldSizesService = require('../field-sizes');
|
||||
|
||||
const strapi = {
|
||||
container: {
|
||||
// Mock container.get('custom-fields')
|
||||
get: jest.fn(() => ({
|
||||
// Mock container.get('custom-fields').getAll()
|
||||
getAll: jest.fn(() => ({
|
||||
'plugin::mycustomfields.color': {
|
||||
name: 'color',
|
||||
plugin: 'mycustomfields',
|
||||
type: 'string',
|
||||
},
|
||||
'plugin::mycustomfields.smallColor': {
|
||||
name: 'smallColor',
|
||||
plugin: 'mycustomfields',
|
||||
type: 'string',
|
||||
inputSize: {
|
||||
default: 4,
|
||||
isResizable: false,
|
||||
},
|
||||
},
|
||||
})),
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
describe('field sizes service', () => {
|
||||
it('should return the correct field sizes', () => {
|
||||
const { getAllFieldSizes } = fieldSizesService();
|
||||
const { getAllFieldSizes } = createFieldSizesService({ strapi });
|
||||
const fieldSizes = getAllFieldSizes();
|
||||
Object.values(fieldSizes).forEach((fieldSize) => {
|
||||
expect(typeof fieldSize.isResizable).toBe('boolean');
|
||||
@ -13,21 +39,42 @@ describe('field sizes service', () => {
|
||||
});
|
||||
|
||||
it('should return the correct field size for a given type', () => {
|
||||
const { getFieldSize } = fieldSizesService();
|
||||
const { getFieldSize } = createFieldSizesService({ strapi });
|
||||
const fieldSize = getFieldSize('string');
|
||||
expect(fieldSize.isResizable).toBe(true);
|
||||
expect(fieldSize.default).toBe(6);
|
||||
});
|
||||
|
||||
it('should throw an error if the type is not found', () => {
|
||||
const { getFieldSize } = fieldSizesService();
|
||||
expect(() => getFieldSize('not-found')).toThrowError(
|
||||
'Could not find field size for type not-found'
|
||||
);
|
||||
const { getFieldSize } = createFieldSizesService({ strapi });
|
||||
|
||||
try {
|
||||
getFieldSize('not-found');
|
||||
} catch (error) {
|
||||
expect(error instanceof ApplicationError).toBe(true);
|
||||
expect(error.message).toBe('Could not find field size for type not-found');
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an error if the type is not provided', () => {
|
||||
const { getFieldSize } = fieldSizesService();
|
||||
expect(() => getFieldSize()).toThrowError('The type is required');
|
||||
const { getFieldSize } = createFieldSizesService({ strapi });
|
||||
|
||||
try {
|
||||
getFieldSize();
|
||||
} catch (error) {
|
||||
expect(error instanceof ApplicationError).toBe(true);
|
||||
expect(error.message).toBe('The type is required');
|
||||
}
|
||||
});
|
||||
|
||||
it('should set the custom fields input sizes', () => {
|
||||
const { setCustomFieldInputSizes, getAllFieldSizes } = createFieldSizesService({ strapi });
|
||||
setCustomFieldInputSizes();
|
||||
const fieldSizes = getAllFieldSizes();
|
||||
console.log(fieldSizes);
|
||||
|
||||
expect(fieldSizes).not.toHaveProperty('plugin::mycustomfields.color');
|
||||
expect(fieldSizes['plugin::mycustomfields.smallColor'].default).toBe(4);
|
||||
expect(fieldSizes['plugin::mycustomfields.smallColor'].isResizable).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { ApplicationError } = require('@strapi/utils').errors;
|
||||
|
||||
const needsFullSize = {
|
||||
default: 12,
|
||||
isResizable: false,
|
||||
@ -44,20 +46,51 @@ const fieldSizes = {
|
||||
uid: defaultSize,
|
||||
};
|
||||
|
||||
module.exports = () => ({
|
||||
getAllFieldSizes() {
|
||||
return fieldSizes;
|
||||
},
|
||||
getFieldSize(type) {
|
||||
if (!type) {
|
||||
throw new Error('The type is required');
|
||||
}
|
||||
const createFieldSizesService = ({ strapi }) => {
|
||||
const fieldSizesService = {
|
||||
getAllFieldSizes() {
|
||||
return fieldSizes;
|
||||
},
|
||||
|
||||
const fieldSize = fieldSizes[type];
|
||||
if (!fieldSize) {
|
||||
throw new Error(`Could not find field size for type ${type}`);
|
||||
}
|
||||
getFieldSize(type) {
|
||||
if (!type) {
|
||||
throw new ApplicationError('The type is required');
|
||||
}
|
||||
|
||||
return fieldSize;
|
||||
},
|
||||
});
|
||||
const fieldSize = fieldSizes[type];
|
||||
if (!fieldSize) {
|
||||
throw new ApplicationError(`Could not find field size for type ${type}`);
|
||||
}
|
||||
|
||||
return fieldSize;
|
||||
},
|
||||
|
||||
setFieldSize(type, size) {
|
||||
if (!type) {
|
||||
throw new ApplicationError('The type is required');
|
||||
}
|
||||
|
||||
if (!size) {
|
||||
throw new ApplicationError('The size is required');
|
||||
}
|
||||
|
||||
fieldSizes[type] = size;
|
||||
},
|
||||
|
||||
setCustomFieldInputSizes() {
|
||||
// Find all custom fields already registered
|
||||
const customFields = strapi.container.get('custom-fields').getAll();
|
||||
|
||||
// If they have a custom field size, register it
|
||||
Object.entries(customFields).forEach(([uid, customField]) => {
|
||||
if (customField.inputSize) {
|
||||
fieldSizesService.setFieldSize(uid, customField.inputSize);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return fieldSizesService;
|
||||
};
|
||||
|
||||
module.exports = createFieldSizesService;
|
||||
|
||||
@ -20,9 +20,18 @@ const isAllowedFieldSize = (type, size) => {
|
||||
return size <= MAX_ROW_SIZE;
|
||||
};
|
||||
|
||||
const getDefaultFieldSize = (type) => {
|
||||
const getDefaultFieldSize = (attribute) => {
|
||||
// Check if it's a custom field with a custom size
|
||||
if (attribute.customField) {
|
||||
const customField = strapi.container.get('custom-fields').get(attribute.customField);
|
||||
if (customField.inputSize) {
|
||||
return customField.inputSize.default;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the default size for the field type
|
||||
const { getFieldSize } = getService('field-sizes');
|
||||
return getFieldSize(type).default;
|
||||
return getFieldSize(attribute.type).default;
|
||||
};
|
||||
|
||||
async function createDefaultLayouts(schema) {
|
||||
@ -127,7 +136,7 @@ const appendToEditLayout = (layout = [], keysToAppend, schema) => {
|
||||
for (const key of keysToAppend) {
|
||||
const attribute = schema.attributes[key];
|
||||
|
||||
const attributeSize = getDefaultFieldSize(attribute.type);
|
||||
const attributeSize = getDefaultFieldSize(attribute);
|
||||
const currenRowSize = rowSize(layout[currentRowIndex]);
|
||||
|
||||
if (currenRowSize + attributeSize > MAX_ROW_SIZE) {
|
||||
|
||||
@ -90,6 +90,28 @@ describe('Custom fields registry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('validates inputSize', () => {
|
||||
const mockCF = {
|
||||
name: 'test',
|
||||
type: 'text',
|
||||
};
|
||||
|
||||
const customFields = customFieldsRegistry(strapi);
|
||||
|
||||
expect(() => customFields.add({ ...mockCF, inputSize: 'small' })).toThrowError(
|
||||
`inputSize should be an object with 'default' and 'isResizable' keys`
|
||||
);
|
||||
expect(() => customFields.add({ ...mockCF, inputSize: ['array'] })).toThrowError(
|
||||
`inputSize should be an object with 'default' and 'isResizable' keys`
|
||||
);
|
||||
expect(() =>
|
||||
customFields.add({ ...mockCF, inputSize: { default: 99, isResizable: true } })
|
||||
).toThrowError('Custom fields require a valid default input size');
|
||||
expect(() =>
|
||||
customFields.add({ ...mockCF, inputSize: { default: 12, isResizable: 'true' } })
|
||||
).toThrowError('Custom fields should specify if their input is resizable');
|
||||
});
|
||||
|
||||
it('confirms the custom field does not already exist', () => {
|
||||
const mockCF = {
|
||||
name: 'test',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { has } = require('lodash/fp');
|
||||
const { has, isPlainObject } = require('lodash/fp');
|
||||
|
||||
const ALLOWED_TYPES = [
|
||||
'biginteger',
|
||||
@ -44,7 +44,7 @@ const customFieldsRegistry = (strapi) => {
|
||||
throw new Error(`Custom fields require a 'name' and 'type' key`);
|
||||
}
|
||||
|
||||
const { name, plugin, type } = cf;
|
||||
const { name, plugin, type, inputSize } = cf;
|
||||
if (!ALLOWED_TYPES.includes(type)) {
|
||||
throw new Error(
|
||||
`Custom field type: '${type}' is not a valid Strapi type or it can't be used with a Custom Field`
|
||||
@ -56,6 +56,23 @@ const customFieldsRegistry = (strapi) => {
|
||||
throw new Error(`Custom field name: '${name}' is not a valid object key`);
|
||||
}
|
||||
|
||||
// Validate inputSize when provided
|
||||
if (inputSize) {
|
||||
if (
|
||||
!isPlainObject(inputSize) ||
|
||||
!has('default', inputSize) ||
|
||||
!has('isResizable', inputSize)
|
||||
) {
|
||||
throw new Error(`inputSize should be an object with 'default' and 'isResizable' keys`);
|
||||
}
|
||||
if (![4, 6, 8, 12].includes(inputSize.default)) {
|
||||
throw new Error('Custom fields require a valid default input size');
|
||||
}
|
||||
if (typeof inputSize.isResizable !== 'boolean') {
|
||||
throw new Error('Custom fields should specify if their input is resizable');
|
||||
}
|
||||
}
|
||||
|
||||
// When no plugin is specified, or it isn't found in Strapi, default to global
|
||||
const uid = strapi.plugin(plugin) ? `plugin::${plugin}.${name}` : `global::${name}`;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user