@adminforth/foreign-inline-list
Version:
AdminForth plugin for adding list of children items to the parent item show page
189 lines (164 loc) • 6.84 kB
text/typescript
import type {
AdminForthResource,
IAdminForth,
IHttpServer,
} from "adminforth";
import clone from 'clone';
import { AdminForthPlugin, AdminForthResourcePages, suggestIfTypo } from "adminforth";
import { PluginOptions } from "./types.js";
import { interpretResource, ActionCheckSource } from "adminforth";
export default class ForeignInlineListPlugin extends AdminForthPlugin {
foreignResource: AdminForthResource;
options: PluginOptions;
adminforth: IAdminForth;
constructor(options: PluginOptions) {
super(options, import.meta.url);
this.options = options;
}
instanceUniqueRepresentation(pluginOptions: any) : string {
return `${pluginOptions.foreignResourceId}`;
}
setupEndpoints(server: IHttpServer) {
process.env.HEAVY_DEBUG && console.log(`🪲 ForeignInlineListPlugin.setupEndpoints, registering: '/plugin/${this.pluginInstanceId}/get_resource'`);
server.endpoint({
method: 'POST',
path: `/plugin/${this.pluginInstanceId}/get_resource`,
handler: async ({ body, adminUser }) => {
const resource = this.adminforth.config.resources.find((res) => this.options.foreignResourceId === res.resourceId);
if (!resource) {
return { error: `Resource ${this.options.foreignResourceId} not found` };
}
// exclude "plugins" key
const resourceCopy = clone({ ...resource, plugins: undefined });
if (this.options.modifyTableResourceConfig) {
this.options.modifyTableResourceConfig(resourceCopy);
}
const { allowedActions } = await interpretResource(adminUser, resourceCopy, {}, ActionCheckSource.DisplayButtons, this.adminforth);
return {
resource: {
...resourceCopy,
options: {
...resourceCopy.options,
allowedActions,
},
}
};
}
});
server.endpoint({
method: 'POST',
path: `/plugin/${this.pluginInstanceId}/start_bulk_action`,
handler: async ({ body, adminUser, tr }) => {
const { resourceId, actionId, recordIds } = body;
const resource = this.adminforth.config.resources.find((res) => res.resourceId == resourceId);
if (!resource) {
return { error: await tr(`Resource {resourceId} not found`, 'errors', { resourceId }) };
}
const resourceCopy = JSON.parse(JSON.stringify({ ...resource, plugins: undefined }));
if (this.options.modifyTableResourceConfig) {
this.options.modifyTableResourceConfig(resourceCopy);
}
const { allowedActions } = await interpretResource(
adminUser,
resourceCopy,
{ requestBody: body },
ActionCheckSource.BulkActionRequest,
this.adminforth
);
const action = resourceCopy.options.bulkActions.find((act) => act.id == actionId);
if (!action) {
return { error: await tr(`Action {actionId} not found`, 'errors', { actionId }) };
}
if (action.allowed) {
const execAllowed = await action.allowed({ adminUser, resourceCopy, selectedIds: recordIds, allowedActions });
if (!execAllowed) {
return { error: await tr(`Action "{actionId}" not allowed`, 'errors', { actionId: action.label }) };
}
}
const response = await action.action({selectedIds: recordIds, adminUser, resourceCopy, tr});
return {
actionId,
recordIds,
resourceId,
...response
}
}
})
}
async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
super.modifyResourceConfig(adminforth, resourceConfig);
this.adminforth = adminforth;
// get resource with foreignResourceId
this.foreignResource = adminforth.config.resources.find((resource) => resource.resourceId === this.options.foreignResourceId);
if (!this.foreignResource) {
const similar = suggestIfTypo(adminforth.config.resources.map((res) => res.resourceId), this.options.foreignResourceId);
throw new Error(`ForeignInlineListPlugin: Resource with ID "${this.options.foreignResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
}
if (this.options.modifyTableResourceConfig) {
this.options.modifyTableResourceConfig(this.foreignResource);
}
const defaultSort = this.foreignResource.options?.defaultSort;
const newColumn = {
name: `foreignInlineList_${this.foreignResource.resourceId}`,
label: 'Foreign Inline List',
virtual: true,
showIn: {
show: true,
list: false,
edit: false,
create: false,
filter: false,
},
components: {
showRow: {
file: this.componentPath('InlineList.vue'),
meta: {
...this.options,
pluginInstanceId: this.pluginInstanceId,
...(defaultSort
? {
defaultSort: {
field: defaultSort.columnName,
direction: defaultSort.direction,
}
}
: {}
)
}
}
},
};
if (this.options.placeInGroup?.name) {
const fieldGroupTypes = [
'fieldGroups',
'createFieldGroups',
'editFieldGroups',
'showFieldGroups'
] as const;
let columnAdded = false;
for (const groupType of fieldGroupTypes) {
const targetGroup = resourceConfig.options?.[groupType]?.find(
group => group.groupName === this.options.placeInGroup?.name
);
if (targetGroup) {
if (this.options.placeInGroup.position < 0 || this.options.placeInGroup.position > targetGroup.columns.length) {
throw new Error(`ForeignInlineListPlugin: Invalid position ${this.options.placeInGroup?.position}. Must be between 0 and ${targetGroup.columns.length} for group "${this.options.placeInGroup?.name}"`);
}
// Only add the column to resourceConfig.columns once
if (!columnAdded) {
const beforeColumnName = targetGroup.columns[this.options.placeInGroup.position - 1];
const beforeColumnIndex = resourceConfig.columns.findIndex(
col => col.name === beforeColumnName
);
resourceConfig.columns.splice(beforeColumnIndex + 1, 0, newColumn);
columnAdded = true;
}
// Add the column name to the group's columns array
targetGroup.columns.splice(this.options.placeInGroup.position, 0, newColumn.name);
}
}
} else {
resourceConfig.columns.push(newColumn);
}
}
}