UNPKG

adminjs

Version:
282 lines (264 loc) 8.97 kB
import ViewHelpers from '../../utils/view-helpers/view-helpers.js'; import { decorateActions, decorateProperties, getNavigation, flatSubProperties, getPropertyByKey } from './utils/index.js'; /** * Default maximum number of items which should be present in a list. * * @type {Number} * @private */ export const DEFAULT_MAX_COLUMNS_IN_LIST = 8; /** * Base decorator class which decorates the Resource. * * @category Decorators */ class ResourceDecorator { /** * Map of all root level properties. By root properties we mean property which is not nested * under other mixed property. * * Examples from PropertyOptions: * { * rootProperty: { type: mixed }, // root property * * // nested property - this should go be the subProperty of rootProperty * 'rootProperty.nested': { type: 'string' } * * // also root property because there is no another property of type mixed * 'another.property': { type: 'string' }, * } * * for a the reference {@see decorateProperties} */ /** * @param {object} options * @param {BaseResource} options.resource resource which is decorated * @param {AdminJS} options.admin current instance of AdminJS * @param {ResourceOptions} [options.options] */ constructor({ resource, admin, options = {} }) { this.getPropertyByKey = this.getPropertyByKey.bind(this); this._resource = resource; this._admin = admin; this.h = new ViewHelpers({ options: admin.options }); /** * Options passed along with a given resource * @type {ResourceOptions} */ this.options = options; this.options.properties = this.options.properties || {}; /** * List of all decorated root properties * @type {Array<PropertyDecorator>} */ this.properties = decorateProperties(resource, admin, this); /** * Actions for a resource * @type {Object<String, ActionDecorator>} */ this.actions = decorateActions(resource, admin, this); } /** * Returns the name for the resource. * @return {string} resource name */ getResourceName() { return this.id(); } /** * Returns the id for the resource. * @return {string} resource id */ id() { return this.options.id || this._resource.id(); } /** * Returns resource parent along with the icon. By default it is a * database type with its icon * @return {Parent} ResourceJSON['parent']} */ getNavigation() { return getNavigation(this.options, this._resource); } /** * Returns propertyDecorator by giving property path * * @param {String} propertyPath property path * * @return {PropertyDecorator} */ getPropertyByKey(propertyPath) { return getPropertyByKey(propertyPath, this.properties); } /** * Returns list of all properties which will be visible in given place (where) * * @param {Object} options * @param {String} options.where one of: 'list', 'show', 'edit', 'filter' * @param {String} [options.max] maximum number of properties returned where there are * no overrides in the options * * @return {Array<PropertyDecorator>} */ getProperties({ where, max = 0 }) { const whereProperties = `${where}Properties`; // like listProperties, viewProperties etc if (where && this.options[whereProperties] && this.options[whereProperties].length) { return this.options[whereProperties].map(propertyName => { const property = this.getPropertyByKey(propertyName); if (!property) { // eslint-disable-next-line no-console console.error([`[AdminJS]: There is no property of the name: "${propertyName}".`, `Check out the "${where}Properties" in the`, `resource: "${this._resource.id()}"`].join(' ')); } return property; }).filter(property => property); } const properties = Object.keys(this.properties).filter(key => !where || this.properties[key].isVisible(where)).sort((key1, key2) => this.properties[key1].position() > this.properties[key2].position() ? 1 : -1).map(key => this.properties[key]); if (max) { return properties.slice(0, max); } return properties; } /** * Returns all the properties with corresponding subProperties in one object. */ getFlattenProperties() { return Object.keys(this.properties).reduce((memo, propertyName) => { const property = this.properties[propertyName]; const subProperties = flatSubProperties(property); return Object.assign(memo, { [propertyName]: property }, subProperties); }, {}); } getListProperties() { return this.getProperties({ where: 'list', max: DEFAULT_MAX_COLUMNS_IN_LIST }); } /** * List of all actions which should be invoked for entire resource and not * for a particular record * * @param {CurrentAdmin} currentAdmin currently logged in admin user * @return {Array<ActionDecorator>} Actions assigned to resources */ resourceActions(currentAdmin) { return Object.values(this.actions).filter(action => action.isResourceType() && action.isVisible(currentAdmin) && action.isAccessible(currentAdmin)); } /** * List of all actions which should be invoked for entire resource and not * for a particular record * * @param {CurrentAdmin} currentAdmin currently logged in admin user * @return {Array<ActionDecorator>} Actions assigned to resources */ bulkActions(record, currentAdmin) { return Object.values(this.actions).filter(action => action.isBulkType() && action.isVisible(currentAdmin, record) && action.isAccessible(currentAdmin, record)); } /** * List of all actions which should be invoked for given record and not * for an entire resource * * @param {CurrentAdmin} [currentAdmin] currently logged in admin user * @return {Array<ActionDecorator>} Actions assigned to each record */ recordActions(record, currentAdmin) { return Object.values(this.actions).filter(action => action.isRecordType() && action.isVisible(currentAdmin, record) && action.isAccessible(currentAdmin, record)); } /** * Returns PropertyDecorator of a property which should be treated as a title property. * * @return {PropertyDecorator} PropertyDecorator of title property */ titleProperty() { let titleProperty; const properties = Object.values(this.properties); if (this.options.titleProperty) { titleProperty = this.getPropertyByKey(this.options.titleProperty); } else { titleProperty = properties.find(p => p.isTitle()); } return titleProperty || properties[0]; } /** * Returns title for given record. * * For example: If given record has `name` property and this property has `isTitle` flag set in * options or by the Adapter - value for this property will be shown * * @param {BaseRecord} record * * @return {String} title of given record */ titleOf(record) { return record.get(this.titleProperty().name()); } getHref(currentAdmin) { const { href } = this.options; if (href) { if (typeof href === 'function') { return href({ resource: this._resource, currentAdmin, h: this.h }); } return href; } if (this.resourceActions(currentAdmin).find(action => action.name === 'list')) { return this.h.resourceUrl({ resourceId: this.id() }); } return null; } /** * Returns JSON representation of a resource * * @param {CurrentAdmin} currentAdmin * @return {ResourceJSON} */ toJSON(currentAdmin) { const flattenProperties = this.getFlattenProperties(); const flattenPropertiesJSON = Object.keys(flattenProperties).reduce((memo, key) => ({ ...memo, [key]: flattenProperties[key].toJSON() }), {}); return { id: this.id(), name: this.getResourceName(), navigation: this.getNavigation(), href: this.getHref(currentAdmin), titleProperty: this.titleProperty().toJSON(), resourceActions: this.resourceActions(currentAdmin).map(ra => ra.toJSON(currentAdmin)), actions: Object.values(this.actions).map(action => action.toJSON(currentAdmin)), properties: flattenPropertiesJSON, listProperties: this.getProperties({ where: 'list', max: DEFAULT_MAX_COLUMNS_IN_LIST }).map(property => property.toJSON('list')), editProperties: this.getProperties({ where: 'edit' }).map(property => property.toJSON('edit')), showProperties: this.getProperties({ where: 'show' }).map(property => property.toJSON('show')), filterProperties: this.getProperties({ where: 'filter' }).map(property => property.toJSON('filter')) }; } } export default ResourceDecorator;