/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { BlockModel } from '@nocobase/client'; import { APIResource } from '@nocobase/flow-engine'; import { Card, Spin } from 'antd'; import React, { createRef } from 'react'; import { NAMESPACE } from './locale'; export class LowcodeBlockModel extends BlockModel { ref = createRef(); declare resource: APIResource; render() { const { loading, error } = this.props; if (error) { return (
Error loading lowcode component: {error}
); } return (
); } } LowcodeBlockModel.define({ title: 'Lowcode', group: 'Content', icon: 'CloudOutlined', defaultOptions: { use: 'LowcodeBlockModel', stepParams: { default: { executionStep: { code: ` // Welcome to the lowcode block // Create powerful interactive components with JavaScript ctx.element.innerHTML = \`

🚀 \${ctx.i18n.t('Welcome to Lowcode Block', { ns: '` + NAMESPACE + `' })}

\${ctx.i18n.t('Build interactive components with JavaScript and external libraries', { ns: '` + NAMESPACE + `' })}

✨ \${ctx.i18n.t('Key Features', { ns: '` + NAMESPACE + `' })}

  • 🎨 \${ctx.i18n.t('Custom JavaScript execution', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Full programming capabilities', { ns: '` + NAMESPACE + `' })}
  • 📚 \${ctx.i18n.t('External library support', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Load any npm package or CDN library', { ns: '` + NAMESPACE + `' })}
  • 🔗 \${ctx.i18n.t('NocoBase API integration', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Access your data and collections', { ns: '` + NAMESPACE + `' })}
  • 💡 \${ctx.i18n.t('Async/await support', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Handle asynchronous operations', { ns: '` + NAMESPACE + `' })}
  • 🎯 \${ctx.i18n.t('Direct DOM manipulation', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Full control over rendering', { ns: '` + NAMESPACE + `' })}

💡 \${ctx.i18n.t('Ready to start?', { ns: '` + NAMESPACE + `' })} \${ctx.i18n.t('Replace this code with your custom JavaScript to build amazing components!', { ns: '` + NAMESPACE + `' })}

\`; `.trim(), }, }, }, }, }); LowcodeBlockModel.registerFlow({ key: 'default', title: 'Configuration', auto: true, steps: { setMainResource: { handler(ctx) { if (ctx.model.resource) { return; } ctx.model.resource = new APIResource(); ctx.model.resource.setAPIClient(ctx.globals.api); }, }, executionStep: { title: 'Code', uiSchema: { code: { type: 'string', title: 'Execution Code', 'x-component': 'CodeEditor', 'x-component-props': { minHeight: '400px', theme: 'light', enableLinter: true, }, }, }, defaultParams: { code: ` // Welcome to the lowcode block // Create powerful interactive components with JavaScript ctx.element.innerHTML = \`

🚀 \${ctx.i18n.t('Welcome to Lowcode Block', { ns: '` + NAMESPACE + `' })}

\${ctx.i18n.t('Build interactive components with JavaScript and external libraries', { ns: '` + NAMESPACE + `' })}

✨ \${ctx.i18n.t('Key Features', { ns: '` + NAMESPACE + `' })}

  • 🎨 \${ctx.i18n.t('Custom JavaScript execution', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Full programming capabilities', { ns: '` + NAMESPACE + `' })}
  • 📚 \${ctx.i18n.t('External library support', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Load any npm package or CDN library', { ns: '` + NAMESPACE + `' })}
  • 🔗 \${ctx.i18n.t('NocoBase API integration', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Access your data and collections', { ns: '` + NAMESPACE + `' })}
  • 💡 \${ctx.i18n.t('Async/await support', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Handle asynchronous operations', { ns: '` + NAMESPACE + `' })}
  • 🎯 \${ctx.i18n.t('Direct DOM manipulation', { ns: '` + NAMESPACE + `' })} - \${ctx.i18n.t('Full control over rendering', { ns: '` + NAMESPACE + `' })}

💡 \${ctx.i18n.t('Ready to start?', { ns: '` + NAMESPACE + `' })} \${ctx.i18n.t('Replace this code with your custom JavaScript to build amazing components!', { ns: '` + NAMESPACE + `' })}

\`; `.trim(), }, settingMode: 'drawer', async handler(flowContext, params: any) { flowContext.model.setProps('loading', true); flowContext.model.setProps('error', null); flowContext.reactView.onRefReady(flowContext.model.ref, async (element: HTMLElement) => { try { // Get requirejs from app context const requirejs = flowContext.app?.requirejs?.requirejs; // Helper function to load CSS const loadCSS = (url: string): Promise => { return new Promise((resolve, reject) => { // Check if CSS is already loaded const existingLink = document.querySelector(`link[href="${url}"]`); if (existingLink) { resolve(); return; } const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; link.onload = () => resolve(); link.onerror = () => reject(new Error(`Failed to load CSS: ${url}`)); document.head.appendChild(link); }); }; // Helper function for async requirejs const requireAsync = (modules: string | string[]): Promise => { return new Promise((resolve, reject) => { if (!requirejs) { reject(new Error('requirejs is not available')); return; } const moduleList = Array.isArray(modules) ? modules : [modules]; requirejs( moduleList, (...args: any[]) => { // If single module, return the module directly // If multiple modules, return array resolve(moduleList.length === 1 ? args[0] : args); }, reject, ); }); }; const getModelById = (uid: string) => { return flowContext.globals.flowEngine.getModel(uid); }; const request = flowContext.globals.api.request.bind(flowContext.globals.api); // Create a safe execution context for the code (as async function) // Wrap user code in an async function const wrappedCode = ` return (async function(ctx) { ${params.code} }).apply(this, arguments); `; const executionFunction = new Function(wrappedCode); const ctx = { element, model: flowContext.model, resource: flowContext.model.resource, requirejs, requireAsync, loadCSS, getModelById, request, i18n: flowContext.app.i18n, }; // Execute the code await executionFunction(ctx); flowContext.model.setProps('loading', false); } catch (error: any) { console.error('Lowcode component execution error:', error); flowContext.model.setProps('error', error.message); flowContext.model.setProps('loading', false); } }); }, }, }, });