├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .lowcoderc ├── .npmrc ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── asset ├── activitybar.svg └── icon.png ├── esbuild.js ├── materials └── blocks │ └── react-mvp模块 │ ├── config │ ├── model.json │ ├── preview.json │ └── schema.json │ └── src │ ├── index.tsx.ejs │ ├── model.ts.ejs │ ├── presenter.tsx.ejs │ └── service.ts.ejs ├── package.json ├── src ├── commands │ ├── addSnippet.ts │ ├── chatGPT.ts │ ├── common.ts │ ├── createOrShowWebview.ts │ ├── generateCode.ts │ ├── openSnippet.ts │ ├── quickGenerateBlock.ts │ ├── registerCompletion.ts │ └── runSnippetScript.ts ├── context.ts ├── extension.ts ├── genCode │ ├── genCodeByJson.ts │ ├── genCodeByTypescript.ts │ └── genCodeByYapi.ts ├── lifecycle.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── config.test.ts │ │ ├── index.ts │ │ └── lib.test.ts ├── utils │ ├── clipboard.ts │ ├── config.ts │ ├── download.ts │ ├── editor.ts │ ├── ejs.ts │ ├── emitter.ts │ ├── env.ts │ ├── file.ts │ ├── generate.ts │ ├── json.ts │ ├── lib.ts │ ├── llm.ts │ ├── materials.ts │ ├── name.ts │ ├── openai.ts │ ├── outputChannel.ts │ ├── platform.ts │ ├── request.ts │ ├── scaffold.ts │ └── vscodeEnv.ts └── webview │ ├── callback.ts │ ├── controllers │ ├── alert.ts │ ├── block.ts │ ├── command.ts │ ├── config.ts │ ├── directory.ts │ ├── generate.ts │ ├── intelliSense.ts │ ├── json.ts │ ├── material.ts │ ├── openai.ts │ ├── request.ts │ ├── scaffold.ts │ ├── script.ts │ ├── snippet.ts │ ├── task.ts │ └── yapi.ts │ ├── index.ts │ ├── routes │ └── index.ts │ └── type.ts ├── tsconfig.json ├── webview-react ├── .editorconfig ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── README.md ├── mock │ └── .gitkeep ├── package.json ├── plugin.ts ├── src │ ├── app.less │ ├── app.tsx │ ├── components │ │ ├── AmisComponent │ │ │ ├── cxd.css │ │ │ ├── helper.css │ │ │ └── index.tsx │ │ ├── CodeMirror │ │ │ └── index.tsx │ │ ├── DownloadMaterials │ │ │ ├── api.ts │ │ │ └── index.tsx │ │ ├── Footer │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── FormilyComponent │ │ │ └── index.tsx │ │ ├── HeaderControl │ │ │ ├── components │ │ │ │ └── ConfigSyncFolder │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── model.ts │ │ │ │ │ ├── presenter.tsx │ │ │ │ │ └── service.ts │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ └── presenter.tsx │ │ ├── JsonToTs │ │ │ └── index.tsx │ │ ├── Marked │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── RouteWrapper │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── RunScript │ │ │ └── index.tsx │ │ ├── SelectDirectory │ │ │ └── index.tsx │ │ └── YapiModal │ │ │ └── index.tsx │ ├── hooks │ │ ├── useCheckVankeInternal.ts │ │ └── useImmer.ts │ ├── layout │ │ ├── index.less │ │ └── index.tsx │ ├── models │ │ ├── syncFolder.ts │ │ └── tab.ts │ ├── pages │ │ ├── blocks │ │ │ ├── Detail │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ ├── presenter.tsx │ │ │ │ └── service.ts │ │ │ └── List │ │ │ │ ├── components │ │ │ │ └── BlockItem │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ ├── presenter.tsx │ │ │ │ └── service.ts │ │ ├── chatGPT │ │ │ ├── components │ │ │ │ ├── ChatList │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ │ └── UpdateSeesionTitle │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ ├── service.ts │ │ │ └── store.ts │ │ ├── config │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ └── service.ts │ │ ├── downloadMaterials │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ └── service.ts │ │ ├── getClipboardImage │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── home │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── scaffold │ │ │ ├── DownloadModal │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ ├── presenter.tsx │ │ │ │ └── service.ts │ │ │ ├── FormModal │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ ├── presenter.tsx │ │ │ │ └── service.ts │ │ │ ├── List │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ ├── presenter.tsx │ │ │ │ └── service.ts │ │ │ └── LocalProjectModal │ │ │ │ ├── index.tsx │ │ │ │ ├── model.ts │ │ │ │ └── presenter.tsx │ │ └── snippets │ │ │ ├── AddSnippet │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ └── service.ts │ │ │ ├── Detail │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ └── service.ts │ │ │ └── List │ │ │ ├── components │ │ │ └── SnippetItem │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── model.ts │ │ │ ├── presenter.tsx │ │ │ └── service.ts │ ├── utils │ │ ├── clipboard.ts │ │ ├── emitter.ts │ │ └── schema.ts │ └── webview │ │ ├── handleTask.ts │ │ ├── index.ts │ │ ├── mock │ │ ├── addSnippets.ts │ │ ├── alert.ts │ │ ├── createProject.ts │ │ ├── downloadMaterials.ts │ │ ├── downloadScaffold.ts │ │ ├── genCodeByBlockMaterial.ts │ │ ├── genCodeBySnippetMaterial.ts │ │ ├── genTemplateModelByYapi.ts │ │ ├── getDirectoryTree.ts │ │ ├── getLocalMaterials.ts │ │ ├── getPluginConfig.ts │ │ ├── getScaffolds.ts │ │ ├── getYapiDomain.ts │ │ ├── getYapiProjects.ts │ │ ├── insertSnippet.ts │ │ ├── jsonToTs.ts │ │ ├── refreshIntelliSense.ts │ │ ├── savePluginConfig.ts │ │ └── selectDirectory.ts │ │ └── service.ts ├── tsconfig.json ├── typings.d.ts └── yarn.lock └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | ecmaVersion: 12, 6 | }, 7 | extends: ['airbnb-base', 'prettier'], 8 | plugins: ['@typescript-eslint', 'prettier'], 9 | rules: { 10 | 'prettier/prettier': [1, { printWidth: 80 }], 11 | '@typescript-eslint/semi': 'warn', 12 | curly: 'warn', 13 | eqeqeq: 'warn', 14 | 'no-throw-literal': 'warn', 15 | semi: 'off', 16 | 'import/no-unresolved': 'off', 17 | 'import/extensions': 'off', 18 | 'import/prefer-default-export': 'off', 19 | 'no-unused-vars': 'off', 20 | 'array-callback-return': 'off', 21 | 'import/no-extraneous-dependencies': 'off', 22 | 'consistent-return': 'off', 23 | 'no-use-before-define': 'off', 24 | 'no-empty': 'off', 25 | camelcase: 'off', 26 | 'no-undef': 'off', 27 | 'no-shadow': 'off', 28 | 'no-param-reassign': 'off', 29 | 'import/no-dynamic-require': 'off', 30 | 'global-require': 'off', 31 | 'no-new-func': 'off', 32 | 'prefer-destructuring': 'off', 33 | 'no-plusplus': 'off', 34 | 'func-names': 'off', 35 | 'no-fallthrough': 'off', 36 | 'import/no-webpack-loader-syntax': 'off', 37 | 'no-unused-expressions': 'off', 38 | 'max-len': 'off', 39 | 'class-methods-use-this': 'off', 40 | 'no-eval': 'off', 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=TypeScript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | build 3 | webview-dist 4 | node_modules 5 | .vscode-test/ 6 | *.vsix 7 | .lowcode 8 | -------------------------------------------------------------------------------- /.lowcoderc: -------------------------------------------------------------------------------- 1 | { 2 | "yapi": { 3 | "domain": "", 4 | "projects": [] 5 | }, 6 | "mock": { 7 | "mockNumber": "Random.natural(1000,1000)", 8 | "mockBoolean": "false", 9 | "mockString": "Random.cword(5, 7)", 10 | "mockKeyWordEqual": [], 11 | "mockKeyWordLike": [] 12 | }, 13 | "commonlyUsedBlock": [ 14 | "react-mvp模块" 15 | ] 16 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "arrowParens": "always", 6 | "eslintIntegration": true, 7 | "overrides": [ 8 | { 9 | "files": ".prettierrc", 10 | "options": { 11 | "parser": "json" 12 | } 13 | } 14 | ], 15 | "endOfLine": "auto" 16 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | 9 | { 10 | "name": "Run Extension", 11 | "type": "extensionHost", 12 | "request": "launch", 13 | "runtimeExecutable": "${execPath}", 14 | "args": [ 15 | "--extensionDevelopmentPath=${workspaceFolder}" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/build/**/*.js" 19 | ], 20 | "preLaunchTask": "${defaultBuildTask}" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "runtimeExecutable": "${execPath}", 27 | "args": [ 28 | "H:/Github/lowcode-materials-template", 29 | "--disable-extensions", 30 | "--extensionDevelopmentPath=${workspaceRoot}", 31 | "--extensionTestsPath=${workspaceFolder}/build/test/suite/index" 32 | ], 33 | "outFiles": [ 34 | "${workspaceFolder}/build/test/**/*.js" 35 | ], 36 | "preLaunchTask": "pretest" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit", 7 | "source.fixAll.stylelint": "explicit" 8 | }, 9 | "stylelint.validate": [ 10 | "css", 11 | "less", 12 | "scss", 13 | "vue" 14 | ], 15 | "[scss]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[vue]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | "[typescript]": { 22 | "editor.defaultFormatter": "esbenp.prettier-vscode" 23 | }, 24 | "files.exclude": { 25 | "build": false // set this to true to hide the "out" folder with the compiled JS files 26 | }, 27 | "search.exclude": { 28 | "build": true // set this to false to include "out" folder in search results 29 | }, 30 | "typescript.tsdk": "node_modules/typescript/lib", 31 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "compile:tsc", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "always" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "type": "npm", 21 | "script": "pretest", 22 | "problemMatcher": "$tsc-watch", 23 | "isBackground": false, 24 | "presentation": { 25 | "reveal": "never" 26 | }, 27 | "group": { 28 | "kind": "test", 29 | "isDefault": true 30 | }, 31 | "label": "pretest" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | materials/** 6 | webview-vue/** 7 | webview-react/** 8 | webview-angular/** 9 | webview-vue-webpack/** 10 | .gitignore 11 | vsc-extension-quickstart.md 12 | **/tsconfig.json 13 | **/.eslintrc.js 14 | **/*.map 15 | **/*.ts 16 | *.lock 17 | *.vsix 18 | .lowcoderc 19 | esbuild.js 20 | 21 | node_modules -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | https://github.com/lowcoding/lowcode-vscode/releases 4 | 5 | ## 0.1.0 6 | 7 | - 支持下载 npm 物料包 8 | - 支持通过可视化更新插件配置项 9 | 10 | ## 0.0.19 11 | 12 | - 支持快速添加代码片段 13 | 14 | ## 0.0.18 15 | 16 | - 支持可视化操作 17 | - 支持物料功能 18 | 19 | ## 0.0.17 20 | 21 | - 改用 `webpack` 打包,减小插件包大小 22 | 23 | ## 0.0.16 24 | 25 | - 支持直接在 `package.json` 中配置,优先级比 `settings.json` 高 26 | 27 | ## 0.0.15 28 | 29 | - 如果通过 vs 编辑器中选中的文本无法解析出 `typeName`,通过 `funcName` 拼凑出 `typeName`,比如 `funcName` 为 `fetch`,则 `typeName` 为 `IFetchResult`。 30 | - 输出变量 `rawSelectedText`,方便在模板中取到 vs 编辑器中选中的原始文本。 31 | - 输出变量 `rawClipboardText`,方便在模板中取到系统剪切板中的原始文本。 32 | - 编辑器右键菜单插件标题由 `YAPI-CODE->生成代码` 改为 `LOW-CODE->生成代码`。 33 | 34 | ## 0.0.12 35 | 36 | - 支持复制对象类型变量作为 json 数据,不需要标准 json 格式。 37 | - 生成的类型可选字段全部转为必选(替换 ?: 为 :) 38 | - 添加配置项,支持配置:根据 json key 关键字生成相应 mock 数据 39 | - 模板中可从 jsonData 取到 json 数据,jsonKeys 取到 json 数据 key 数组 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 关于 2 | 3 | 低代码工具,支持 ChatGPT 和其它 LLM 4 | 5 | [详细文档](https://lowcoding.github.io/) 6 | 7 | > 文档不经常更新,新功能使用方法可查看 [releases](https://github.com/lowcoding/lowcode-vscode/releases) 8 | 9 | [物料仓库](https://github.com/lowcode-scaffold/lowcode-materials) 10 | 11 | ## 下面是一些例子 12 | 13 | ### OCR + ChatGPT 翻译 14 | 15 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/c620a1fe-b7a8-4ffb-a6a9-8854fd836516) 16 | 17 | ### 生成指定格式 JSON 18 | 19 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/12345e45-ec10-4a39-902e-0c18e490c26e) 20 | 21 | ### 中文翻译成驼峰格式 22 | 23 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/ced740a2-2bcd-446b-919d-fd1a845df377) 24 | 25 | ### 当前目录翻译成英文 26 | 27 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/1126829d-92c4-4de6-9d33-d84792eff3c3) 28 | 29 | ### 快速创建代码模板 30 | 31 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/38a1f08a-c036-4800-81c8-2dd97c4e5bea) 32 | 33 | ### 生成 CURD 34 | 35 | 以管理后台一个列表页为例 36 | 37 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/e14ff6ed-fdc7-4a45-877a-88c0d2ab7e82) 38 | 39 | #### 选择对应的模板 40 | 41 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/36a581f3-cdd4-43be-ba1c-4f3e26197bf4) 42 | 43 | #### 截图查询区域,使用 OCR 初始化查询表单的配置 44 | 45 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/a3a8f693-a9bc-48fa-95d9-bfc7a00cf5ea) 46 | 47 | #### 截图表头,使用 OCR 初始化 table 的配置 48 | 49 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/c65f8532-68dd-4de4-a6fe-90e1a4cfd984) 50 | 51 | #### 使用 ChatGPT 翻译中文字段 52 | 53 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/dcb20ee4-561b-4539-b70e-f411e33b6aee) 54 | 55 | #### 生成代码 56 | 57 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/e858aaa6-0432-4e53-a58d-95727c0e5fce) 58 | 59 | #### 效果 60 | 61 | 目前我们没有写一行代码,就已经达到了如下的效果 62 | 63 | ![](https://github.com/lowcoding/lowcode-vscode/assets/9456046/c87225e8-39c4-4760-8bb4-2c8af4570657) 64 | 65 | #### 使用其它 LLM 66 | 67 | 具体查看 https://github.com/lowcode-scaffold/lowcode-materials 68 | 69 | ## 相关资料 70 | 71 | [我的 2023 年度关键词:ChatGPT、生产力工具](https://juejin.cn/post/7324889553508122664) 72 | 73 | [还在封装 xxxForm,xxxTable 残害你的同事?试试这个工具](https://juejin.cn/post/7315242945454735414) 74 | 75 | [TypeChat、JSONSchemaChat 实战 - 让 ChatGPT 更听你的话](https://juejin.cn/post/7309732396081020928) 76 | 77 | [我理想中的低代码开发工具的形态](https://juejin.cn/post/7248207744086638629) 78 | 79 | [曾经辛苦造的轮子,现在能否用 ChatGPT 替代呢?](https://juejin.cn/post/7246376735838502971) 80 | 81 | [我在 vscode 插件里接入了 ChatGPT,解决了代码变量命名的难题](https://juejin.cn/post/7243263236623450170) 82 | 83 | [vscode 插件可视化制作和管理脚手架及原理解析](https://juejin.cn/post/7080787567192309797) 84 | -------------------------------------------------------------------------------- /asset/activitybar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /asset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowcoding/lowcode-vscode/35a1ca64c3d717b6e600d75cf1e7900d9ae48b7e/asset/icon.png -------------------------------------------------------------------------------- /esbuild.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const { build } = require('esbuild'); 4 | 5 | fs.removeSync(path.join(__dirname, 'build')); 6 | const entryFile = path.join(__dirname, 'src', 'extension.ts'); 7 | build({ 8 | entryPoints: [entryFile], 9 | bundle: true, 10 | minify: true, 11 | // only needed if you have dependencies 12 | external: ['vscode'], 13 | platform: 'node', 14 | format: 'cjs', 15 | outfile: path.join(__dirname, 'build', 'extension.js'), 16 | write: true, 17 | logLevel: 'error', 18 | }); 19 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/config/model.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/config/preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "", 3 | "description": "", 4 | "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" 5 | } -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/config/schema.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/src/index.tsx.ejs: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { usePresenter } from './presenter'; 3 | 4 | const View = () => { 5 | const presenter = usePresenter(); 6 | const { model } = presenter; 7 | 8 | return
123
; 9 | }; 10 | 11 | export default View; 12 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/src/model.ts.ejs: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => {}; 4 | 5 | export type Model = ReturnType; 6 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/src/presenter.tsx.ejs: -------------------------------------------------------------------------------- 1 | import Service from './service'; 2 | import { useModel } from './model'; 3 | 4 | export const usePresenter = () => { 5 | const model = useModel(); 6 | const service = new Service(model); 7 | 8 | return { 9 | model, 10 | service, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /materials/blocks/react-mvp模块/src/service.ts.ejs: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/addSnippet.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { getClipboardText, getSelectedText } from '../utils/editor'; 3 | import { showWebView } from '../webview'; 4 | 5 | export const createOrShowWebview = (context: vscode.ExtensionContext) => { 6 | context.subscriptions.push( 7 | vscode.commands.registerTextEditorCommand( 8 | 'lowcode.addSnippet', 9 | async () => { 10 | const content = getSelectedText() || (await getClipboardText()); 11 | showWebView({ 12 | key: 'main', 13 | task: { task: 'addSnippets', data: { content } }, 14 | }); 15 | }, 16 | ), 17 | vscode.commands.registerCommand('lowcode.addPromptTemplate', () => { 18 | showWebView({ 19 | key: 'main', 20 | task: { task: 'addSnippets' }, 21 | }); 22 | }), 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/commands/chatGPT.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { commands } from '../utils/env'; 3 | import { hideChatGPTView, showChatGPTView } from '../webview'; 4 | import { getClipboardText, getSelectedText } from '../utils/editor'; 5 | import { getSnippets } from '../utils/materials'; 6 | import { compile as compileEjs, Model } from '../utils/ejs'; 7 | 8 | export const registerChatGPTCommand = (context: vscode.ExtensionContext) => { 9 | context.subscriptions.push( 10 | vscode.commands.registerCommand(commands.showChatGPTView, () => { 11 | showChatGPTView(); 12 | }), 13 | vscode.commands.registerCommand(commands.hideChatGPTView, () => { 14 | hideChatGPTView(); 15 | }), 16 | vscode.commands.registerCommand('lowcode.openSettingsChatGPT', () => { 17 | vscode.commands.executeCommand( 18 | 'workbench.action.openSettings', 19 | 'lowcode', 20 | ); 21 | }), 22 | vscode.commands.registerCommand('lowcode.askChatGPT', async () => { 23 | showChatGPTView({ 24 | task: { 25 | task: 'askChatGPT', 26 | data: getSelectedText() || (await getClipboardText()), 27 | }, 28 | }); 29 | }), 30 | vscode.commands.registerCommand( 31 | 'lowcode.askChatGPTWithTemplate', 32 | async () => { 33 | const templateList = getSnippets().filter( 34 | (s) => 35 | s.commandPrompt || 36 | (s.preview.chatGPT && s.preview.chatGPT.commandPrompt), 37 | ); 38 | if (templateList.length === 0) { 39 | vscode.window.showErrorMessage('请配置 Prompt 模板'); 40 | return; 41 | } 42 | const templateResult = await vscode.window.showQuickPick( 43 | templateList.map((s) => s.name), 44 | { placeHolder: '请选择 Prompt 模板' }, 45 | ); 46 | if (!templateResult) { 47 | return; 48 | } 49 | const template = templateList.find((s) => s.name === templateResult); 50 | const model = { 51 | rawSelectedText: getSelectedText() || '', 52 | rawClipboardText: await getClipboardText(), 53 | }; 54 | const code = compileEjs( 55 | template?.commandPrompt || template!.preview.chatGPT?.commandPrompt!, 56 | model as Model, 57 | ); 58 | showChatGPTView({ 59 | task: { task: 'askChatGPT', data: code }, 60 | }); 61 | }, 62 | ), 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /src/commands/common.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { commands } from '../utils/env'; 3 | import { hideChatGPTView, showChatGPTView, showWebView } from '../webview'; 4 | 5 | export const CommonCommands = (context: vscode.ExtensionContext) => { 6 | context.subscriptions.push( 7 | vscode.commands.registerCommand('lowcode.openFolderForceNewWindow', () => { 8 | vscode.commands.executeCommand('_files.pickFolderAndOpen', { 9 | forceNewWindow: true, 10 | }); 11 | }), 12 | vscode.commands.registerCommand('lowcode.openScaffold', () => { 13 | showWebView({ 14 | key: 'createApp', 15 | title: '创建应用', 16 | viewColumn: vscode.ViewColumn.One, 17 | task: { task: 'route', data: { path: '/scaffold' } }, 18 | }); 19 | }), 20 | vscode.commands.registerCommand('lowcode.openConfig', () => { 21 | showWebView({ 22 | key: 'main', 23 | task: { task: 'route', data: { path: '/config' } }, 24 | }); 25 | }), 26 | vscode.commands.registerCommand(commands.openDownloadMaterials, () => { 27 | showWebView({ 28 | key: 'downloadMaterials', 29 | title: '下载物料', 30 | viewColumn: vscode.ViewColumn.One, 31 | task: { task: 'route', data: { path: '/downloadMaterials' } }, 32 | }); 33 | }), 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/createOrShowWebview.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { formatPath } from '../utils/platform'; 3 | import { showWebView } from '../webview'; 4 | 5 | export const createOrShowWebview = (context: vscode.ExtensionContext) => { 6 | context.subscriptions.push( 7 | vscode.commands.registerCommand('lowcode.generateCodeByWebview', (args) => { 8 | const path = formatPath(args?.path); 9 | showWebView({ 10 | key: 'main', 11 | viewColumn: vscode.ViewColumn.Two, 12 | task: path 13 | ? { 14 | task: 'updateSelectedFolder', 15 | data: { selectedFolder: path }, 16 | } 17 | : undefined, 18 | }); 19 | }), 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/commands/generateCode.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { genCodeByYapi } from '../genCode/genCodeByYapi'; 3 | import { genCodeByJson } from '../genCode/genCodeByJson'; 4 | import { genCodeByTypescript } from '../genCode/genCodeByTypescript'; 5 | import { getClipboardText } from '../utils/editor'; 6 | import { isYapiId, jsonIsValid, jsonParse } from '../utils/json'; 7 | 8 | const { window } = vscode; 9 | 10 | export const generateCode = (context: vscode.ExtensionContext) => { 11 | context.subscriptions.push( 12 | vscode.commands.registerTextEditorCommand( 13 | 'lowcode.generateCode', 14 | async () => { 15 | const rawClipboardText = await getClipboardText(); 16 | let clipboardText = rawClipboardText.trim(); 17 | 18 | clipboardText = JSON.stringify(jsonParse(clipboardText)); 19 | 20 | const validYapiId = isYapiId(clipboardText); 21 | 22 | const validJson = jsonIsValid(clipboardText); 23 | 24 | const valid = validJson || validYapiId; 25 | if (valid) { 26 | if (validYapiId) { 27 | await genCodeByYapi(clipboardText, rawClipboardText); 28 | } else { 29 | await genCodeByJson(clipboardText, rawClipboardText); 30 | } 31 | return; 32 | } 33 | try { 34 | await genCodeByTypescript(rawClipboardText, rawClipboardText); 35 | } catch { 36 | window.showErrorMessage('请复制Yapi接口ID或JSON字符串或TS类型'); 37 | } 38 | }, 39 | ), 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/commands/openSnippet.ts: -------------------------------------------------------------------------------- 1 | import vscode, { TextEditor, TextEditorEdit } from 'vscode'; 2 | import { getClipboardText, pasteToEditor } from '../utils/editor'; 3 | import { compile } from '../utils/ejs'; 4 | import { jsonIsValid, jsonParse } from '../utils/json'; 5 | import { showWebView } from '../webview'; 6 | 7 | export const openSnippet = (context: vscode.ExtensionContext) => { 8 | context.subscriptions.push( 9 | vscode.commands.registerTextEditorCommand( 10 | 'lowcode.openSnippetByWebview', 11 | async (textEditor: TextEditor, edit: TextEditorEdit, ...args: any[]) => { 12 | const name = args[0]; 13 | const template = args[1]; 14 | 15 | const rawClipboardText = await getClipboardText(); 16 | let clipboardText = rawClipboardText.trim(); 17 | clipboardText = JSON.stringify(jsonParse(clipboardText)); 18 | 19 | const validJson = jsonIsValid(clipboardText); 20 | if (validJson) { 21 | try { 22 | const code = compile(template, JSON.parse(clipboardText)); 23 | pasteToEditor(code); 24 | } catch { 25 | showWebView({ 26 | key: 'main', 27 | task: { 28 | task: 'openSnippet', 29 | data: { name }, 30 | }, 31 | }); 32 | } 33 | } else { 34 | showWebView({ 35 | key: 'main', 36 | task: { 37 | task: 'openSnippet', 38 | data: { name }, 39 | }, 40 | }); 41 | } 42 | }, 43 | ), 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/commands/quickGenerateBlock.ts: -------------------------------------------------------------------------------- 1 | import { commands, ExtensionContext, window } from 'vscode'; 2 | import { getConfig } from '../utils/config'; 3 | import { genCodeByBlockWithDefaultModel } from '../utils/generate'; 4 | import { formatPath } from '../utils/platform'; 5 | 6 | export const registerQuickGenerateBlock = (context: ExtensionContext) => { 7 | context.subscriptions.push( 8 | commands.registerCommand('lowcode.quickGenerateBlock', async (args) => { 9 | const path = formatPath(args.path); 10 | const blocks = getConfig().commonlyUsedBlock || []; 11 | if (blocks.length < 1) { 12 | window.showWarningMessage('未配置常用区块', { 13 | modal: true, 14 | }); 15 | return; 16 | } 17 | let block: undefined | string = ''; 18 | if (blocks.length === 1) { 19 | block = blocks[0]; 20 | } else { 21 | block = await window.showQuickPick(blocks, { 22 | placeHolder: '请选择区块', 23 | }); 24 | if (!block) { 25 | return; 26 | } 27 | } 28 | try { 29 | await genCodeByBlockWithDefaultModel(path, block); 30 | } catch (ex: any) { 31 | window.showErrorMessage(ex.toString()); 32 | } 33 | }), 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/commands/registerCompletion.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { compile as compileEjs } from '../utils/ejs'; 3 | import { getSnippets } from '../utils/materials'; 4 | 5 | let provider: vscode.Disposable; 6 | 7 | export const registerCompletion = (context: vscode.ExtensionContext) => { 8 | if (!vscode.workspace.rootPath) { 9 | return; 10 | } 11 | if (provider) { 12 | provider.dispose(); 13 | } 14 | 15 | const snippets = getSnippets().filter( 16 | (s) => !s.preview.notShowInintellisense, 17 | ); 18 | provider = vscode.languages.registerCompletionItemProvider( 19 | { pattern: '**', scheme: 'file' }, 20 | { 21 | provideCompletionItems() { 22 | const completionItems: vscode.CompletionItem[] = []; 23 | snippets.map((s) => { 24 | const completionItem = new vscode.CompletionItem( 25 | s.name.replace('.ejs', ''), 26 | ); 27 | completionItem.kind = vscode.CompletionItemKind.Class; 28 | completionItem.documentation = s.template || 'lowcode'; 29 | try { 30 | const code = compileEjs(s.template, {} as any); 31 | // 支持 vscode 本身 Snippet 语法 32 | completionItem.insertText = new vscode.SnippetString(code); 33 | } catch { 34 | // 无法直接通过 ejs 编译,说明模板中需要额外的数据,触发命令打开 webview 35 | completionItem.insertText = ''; 36 | completionItem.command = { 37 | command: 'lowcode.openSnippetByWebview', 38 | title: '', 39 | arguments: [s.name, s.template], 40 | }; 41 | } 42 | completionItems.push(completionItem); 43 | }); 44 | return completionItems; 45 | }, 46 | }, 47 | ); 48 | context.subscriptions.push(provider); 49 | }; 50 | -------------------------------------------------------------------------------- /src/commands/runSnippetScript.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import path from 'path'; 3 | import fs from 'fs-extra'; 4 | import { getSnippets } from '../utils/materials'; 5 | import { getEnv, rootPath } from '../utils/vscodeEnv'; 6 | import { getInnerLibs } from '../utils/lib'; 7 | import { getOutputChannel } from '../utils/outputChannel'; 8 | import { createChatCompletionForScript } from '../utils/llm'; 9 | import { getClipboardImage } from '../utils/clipboard'; 10 | import { formatPath } from '../utils/platform'; 11 | 12 | const { window } = vscode; 13 | 14 | const handleRunSnippetScript = async (explorerSelectedPath?: string) => { 15 | let templateList = getSnippets().filter( 16 | (s) => s.preview.showInRunSnippetScript, 17 | ); 18 | if (explorerSelectedPath) { 19 | templateList = getSnippets().filter( 20 | (s) => s.preview.showInRunSnippetScriptOnExplorer, 21 | ); 22 | } 23 | if (templateList.length === 0) { 24 | window.showErrorMessage( 25 | `请配置模板(通过 ${ 26 | explorerSelectedPath 27 | ? 'showInRunSnippetScriptOnExplorer' 28 | : 'showInRunSnippetScript' 29 | } 字段开启)`, 30 | ); 31 | return; 32 | } 33 | const templateResult = await window.showQuickPick( 34 | templateList.map((s) => s.name), 35 | { placeHolder: '请选择模板' }, 36 | ); 37 | if (!templateResult) { 38 | return; 39 | } 40 | const template = templateList.find((s) => s.name === templateResult); 41 | const scriptFile = path.join(template!.path, 'script/index.js'); 42 | if (fs.existsSync(scriptFile)) { 43 | delete eval('require').cache[eval('require').resolve(scriptFile)]; 44 | const script = eval('require')(scriptFile); 45 | if (script.onSelect) { 46 | const context = { 47 | vscode, 48 | workspaceRootPath: rootPath, 49 | env: getEnv(), 50 | libs: getInnerLibs(), 51 | outputChannel: getOutputChannel(), 52 | log: getOutputChannel(), 53 | createChatCompletion: createChatCompletionForScript, 54 | materialPath: template!.path, 55 | getClipboardImage, 56 | code: '', 57 | explorerSelectedPath, 58 | }; 59 | try { 60 | await script.onSelect(context); 61 | } catch (ex: any) { 62 | window.showErrorMessage(ex.toString()); 63 | } 64 | } else { 65 | window.showErrorMessage('脚本中未实现 onSelect 方法'); 66 | } 67 | } else { 68 | window.showErrorMessage('当前模板中未添加脚本'); 69 | } 70 | }; 71 | 72 | export const registerRunSnippetScript = (context: vscode.ExtensionContext) => { 73 | context.subscriptions.push( 74 | vscode.commands.registerTextEditorCommand( 75 | 'lowcode.runSnippetScript', 76 | async () => { 77 | await handleRunSnippetScript(); 78 | }, 79 | ), 80 | ); 81 | context.subscriptions.push( 82 | vscode.commands.registerCommand( 83 | 'lowcode.runSnippetScriptOnExplorer', 84 | async (args) => { 85 | const explorerSelectedPath = formatPath(args.path); 86 | await handleRunSnippetScript(explorerSelectedPath); 87 | }, 88 | ), 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, window } from 'vscode'; 2 | 3 | const data: { 4 | extensionContext?: ExtensionContext; 5 | rootPath: string; 6 | extensionPath: string; 7 | activeTextEditorId: string; 8 | } = { 9 | extensionContext: undefined, // 插件 context 10 | rootPath: '', // 工作空间根目录 11 | extensionPath: '', // 插件安装目录 12 | activeTextEditorId: '', // 激活的 tab id 13 | }; 14 | export const setExtensionContext = (extensionContext: ExtensionContext) => { 15 | data.extensionContext = extensionContext; 16 | }; 17 | 18 | export const getExtensionContext = () => data.extensionContext; 19 | 20 | export const getRootPath = () => data.rootPath; 21 | 22 | export const setRootPath = (rootPath: string) => { 23 | data.rootPath = rootPath; 24 | }; 25 | 26 | export const setLastActiveTextEditorId = (activeTextEditorId: string) => { 27 | data.activeTextEditorId = activeTextEditorId; 28 | }; 29 | 30 | export const getLastActiveTextEditor = () => { 31 | const { visibleTextEditors } = window; 32 | const activeTextEditor = visibleTextEditors.find( 33 | (item: any) => item.id === data.activeTextEditorId, 34 | ); 35 | return window.activeTextEditor || activeTextEditor; 36 | }; 37 | 38 | export const getExtensionPath = () => data.extensionPath; 39 | 40 | export const init = (options: { 41 | extensionContext?: ExtensionContext; 42 | rootPath?: string; 43 | extensionPath?: string; 44 | }) => { 45 | if (options.rootPath) { 46 | data.rootPath = options.rootPath; 47 | } 48 | if (options.extensionPath) { 49 | data.extensionPath = options.extensionPath; 50 | } 51 | if (options.extensionContext) { 52 | data.extensionContext = options.extensionContext; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { generateCode } from './commands/generateCode'; 3 | import { createOrShowWebview } from './commands/createOrShowWebview'; 4 | import { createOrShowWebview as createOrShowAddSnippetWebview } from './commands/addSnippet'; 5 | import { registerCompletion } from './commands/registerCompletion'; 6 | import { openSnippet } from './commands/openSnippet'; 7 | import { CommonCommands } from './commands/common'; 8 | import { init, setLastActiveTextEditorId } from './context'; 9 | import { registerQuickGenerateBlock } from './commands/quickGenerateBlock'; 10 | import { registerChatGPTViewProvider } from './webview'; 11 | import { registerChatGPTCommand } from './commands/chatGPT'; 12 | import { registerRunSnippetScript } from './commands/runSnippetScript'; 13 | import { runActivate } from './lifecycle'; 14 | 15 | export function activate(context: vscode.ExtensionContext) { 16 | vscode.window.onDidChangeActiveTextEditor( 17 | (editor) => { 18 | if (editor) { 19 | const { id } = editor as any; 20 | setLastActiveTextEditorId(id); 21 | } 22 | }, 23 | null, 24 | context.subscriptions, 25 | ); 26 | 27 | runActivate(context); 28 | 29 | init({ extensionContext: context, extensionPath: context.extensionPath }); 30 | 31 | generateCode(context); 32 | 33 | registerRunSnippetScript(context); 34 | 35 | createOrShowWebview(context); 36 | 37 | createOrShowAddSnippetWebview(context); 38 | 39 | registerCompletion(context); 40 | 41 | openSnippet(context); 42 | 43 | registerQuickGenerateBlock(context); 44 | 45 | registerChatGPTViewProvider(context); 46 | 47 | const statusBarItem = vscode.window.createStatusBarItem( 48 | vscode.StatusBarAlignment.Left, 49 | -1, 50 | ); 51 | statusBarItem.command = 'lowcode.generateCodeByWebview'; 52 | statusBarItem.text = '$(octoface) Low Code'; 53 | statusBarItem.tooltip = '可视化生成代码'; 54 | statusBarItem.show(); 55 | 56 | CommonCommands(context); 57 | registerChatGPTCommand(context); 58 | } 59 | export function deactivate() {} 60 | -------------------------------------------------------------------------------- /src/genCode/genCodeByJson.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { compile } from 'json-schema-to-typescript'; 3 | import { compile as compileEjs, Model } from '../utils/ejs'; 4 | import { getSnippets } from '../utils/materials'; 5 | import { getFuncNameAndTypeName, pasteToEditor } from '../utils/editor'; 6 | import { mockFromSchema } from '../utils/json'; 7 | 8 | const GenerateSchema = require('generate-schema'); 9 | const strip = require('strip-comments'); 10 | 11 | export const genCodeByJson = async ( 12 | jsonString: string, 13 | rawClipboardText: string, 14 | ) => { 15 | const templateList = getSnippets().filter((s) => !s.preview.notShowInCommand); 16 | if (templateList.length === 0) { 17 | window.showErrorMessage('请配置模板'); 18 | return; 19 | } 20 | const selectInfo = getFuncNameAndTypeName(); 21 | 22 | const templateResult = await window.showQuickPick( 23 | templateList.map((s) => s.name), 24 | { placeHolder: '请选择JSON相关模板' }, 25 | ); 26 | if (!templateResult) { 27 | return; 28 | } 29 | const template = templateList.find((s) => s.name === templateResult); 30 | try { 31 | // const ts = await jsonToTs(selectInfo.typeName, jsonString); 32 | const json = JSON.parse(jsonString); 33 | const schema = GenerateSchema.json(selectInfo.typeName || 'Schema', json); 34 | let ts = await compile(schema, selectInfo.typeName, { 35 | bannerComment: undefined, 36 | }); 37 | ts = strip(ts.replace(/(\[k: string\]: unknown;)|\?/g, '')); 38 | const { mockCode, mockData } = mockFromSchema(schema); 39 | const model: Model = { 40 | type: ts, 41 | funcName: selectInfo.funcName, 42 | typeName: selectInfo.typeName, 43 | inputValues: selectInfo.inputValues, 44 | mockCode, 45 | mockData, 46 | jsonData: json, 47 | jsonKeys: Object.keys(json), 48 | rawSelectedText: selectInfo.rawSelectedText, 49 | rawClipboardText, 50 | }; 51 | const code = compileEjs(template!.template, model); 52 | pasteToEditor(code); 53 | } catch (e: any) { 54 | window.showErrorMessage(e.toString()); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/genCode/genCodeByTypescript.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { compile as compileEjs, Model } from '../utils/ejs'; 3 | import { getSnippets } from '../utils/materials'; 4 | import { getFuncNameAndTypeName, pasteToEditor } from '../utils/editor'; 5 | import { typescriptToJson } from '../utils/json'; 6 | 7 | export const genCodeByTypescript = async ( 8 | typeString: string, 9 | rawClipboardText: string, 10 | ) => { 11 | const templateList = getSnippets().filter((s) => !s.preview.notShowInCommand); 12 | if (templateList.length === 0) { 13 | window.showErrorMessage('请配置模板'); 14 | return; 15 | } 16 | const selectInfo = getFuncNameAndTypeName(); 17 | 18 | const templateResult = await window.showQuickPick( 19 | templateList.map((s) => s.name), 20 | { placeHolder: '请选择TS相关模板' }, 21 | ); 22 | if (!templateResult) { 23 | return; 24 | } 25 | const template = templateList.find((s) => s.name === templateResult); 26 | const { mockCode, mockData } = typescriptToJson(typeString); 27 | const model: Model = { 28 | type: '', 29 | funcName: selectInfo.funcName, 30 | typeName: selectInfo.typeName, 31 | inputValues: selectInfo.inputValues, 32 | mockCode, 33 | mockData, 34 | jsonData: '', 35 | rawSelectedText: selectInfo.rawSelectedText, 36 | rawClipboardText, 37 | }; 38 | const code = compileEjs(template!.template, model); 39 | pasteToEditor(code); 40 | }; 41 | -------------------------------------------------------------------------------- /src/lifecycle.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { window, ExtensionContext } from 'vscode'; 4 | import { getSnippets } from './utils/materials'; 5 | import { getEnv, rootPath } from './utils/vscodeEnv'; 6 | 7 | export const runActivate = (extensionContext: ExtensionContext) => { 8 | const templateList = getSnippets().filter((s) => s.preview.runActivate); 9 | templateList.forEach((template) => { 10 | const scriptFile = path.join(template!.path, 'script/index.js'); 11 | if (fs.existsSync(scriptFile)) { 12 | // delete eval('require').cache[eval('require').resolve(scriptFile)]; 13 | const script = eval('require')(scriptFile); 14 | if (script.onActivate) { 15 | const context = { 16 | workspaceRootPath: rootPath, 17 | env: { ...getEnv(), extensionContext }, 18 | materialPath: template!.path, 19 | code: '', 20 | }; 21 | try { 22 | script.onActivate(context); 23 | } catch (ex: any) { 24 | window.showErrorMessage(`${template.name}:${ex.toString()}`); 25 | } 26 | } 27 | } 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import os from 'os'; 3 | 4 | import { runTests } from 'vscode-test'; 5 | 6 | async function main() { 7 | try { 8 | // The folder containing the Extension Manifest package.json 9 | // Passed to `--extensionDevelopmentPath` 10 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 11 | 12 | // The path to test runner 13 | // Passed to --extensionTestsPath 14 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 15 | 16 | // Download VS Code, unzip it and run the integration test 17 | await runTests({ 18 | extensionDevelopmentPath, 19 | extensionTestsPath, 20 | launchArgs: [path.resolve(os.homedir()), '--disable-extensions'], // 以 cli 运行时传入打开的目录 21 | }); 22 | } catch (err) { 23 | console.error('Failed to run tests'); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /src/test/suite/config.test.ts: -------------------------------------------------------------------------------- 1 | import { getSnippets } from '../../utils/materials'; 2 | 3 | suite('Config Test Suite', () => { 4 | test('getSnippets', () => { 5 | getSnippets(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import Mocha from 'mocha'; 3 | import glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true, 10 | timeout: 100000, 11 | }); 12 | 13 | const testsRoot = path.resolve(__dirname, '..'); 14 | 15 | return new Promise((c, e) => { 16 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 17 | if (err) { 18 | return e(err); 19 | } 20 | 21 | // Add files to the test suite 22 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 23 | 24 | try { 25 | // Run the mocha test 26 | mocha.run((failures) => { 27 | if (failures > 0) { 28 | e(new Error(`${failures} tests failed.`)); 29 | } else { 30 | c(); 31 | } 32 | }); 33 | } catch (err) { 34 | console.error(err); 35 | e(err); 36 | } 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/test/suite/lib.test.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import os from 'os'; 3 | import { compileScaffold, downloadScaffoldFromGit } from '../../utils/scaffold'; 4 | import { selectDirectory } from '../../utils/editor'; 5 | 6 | suite('Lib Test Suite', () => { 7 | test('downloadScaffoldFromGit', async () => { 8 | try { 9 | downloadScaffoldFromGit( 10 | 'https://github.com/lowcode-scaffold/lowcode-mock.git', 11 | ); 12 | await compileScaffold( 13 | { 14 | name: '12121', 15 | emptyREADME: true, 16 | noREADME: false, 17 | }, 18 | path.join(os.homedir(), '.lowcode/scaffold.build'), 19 | ); 20 | } catch (ex) { 21 | console.log(ex); 22 | } 23 | }); 24 | test('selectDirectory', async () => { 25 | const dir = await selectDirectory(); 26 | console.log(dir); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/utils/clipboard.ts: -------------------------------------------------------------------------------- 1 | import { closeWebView, showWebView } from '../webview'; 2 | import { emitter } from './emitter'; 3 | 4 | export const getClipboardImage = () => 5 | new Promise((resolve) => { 6 | showWebView({ 7 | key: 'getClipboardImage', 8 | task: { task: 'getClipboardImage' }, 9 | }); 10 | emitter.on('clipboardImage', (data) => { 11 | emitter.off('clipboardImage'); 12 | setTimeout(() => { 13 | closeWebView('getClipboardImage'); 14 | }, 300); 15 | resolve(data); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/utils/download.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import path from 'path'; 3 | import execa from 'execa'; 4 | import fs from 'fs-extra'; 5 | import { tempDir } from './env'; 6 | 7 | const tar = require('tar'); 8 | 9 | export const download = (url: string, filePath: string, fileName: string) => 10 | new Promise((resolve, reject) => { 11 | fs.ensureDir(filePath) 12 | .then(() => { 13 | const file = fs.createWriteStream(path.join(filePath, fileName)); 14 | axios({ 15 | url, 16 | responseType: 'stream', 17 | }) 18 | .then((response) => { 19 | response.data 20 | .pipe(file) 21 | .on('finish', () => resolve(0)) 22 | .on('error', (err: any) => { 23 | fs.unlink(filePath, () => reject(err)); 24 | }); 25 | }) 26 | .catch((ex: any) => { 27 | reject(ex); 28 | }); 29 | }) 30 | .catch((ex: any) => { 31 | reject(ex); 32 | }); 33 | }); 34 | 35 | export const downloadMaterialsFromNpm = async (packageName: string) => { 36 | const result = execa.sync('npm', ['view', packageName, 'dist.tarball']); 37 | const tarball = result.stdout; 38 | fs.removeSync(tempDir.materials); 39 | await download(tarball, tempDir.temp, `temp.tgz`); 40 | if (!fs.existsSync(tempDir.materials)) { 41 | fs.mkdirSync(tempDir.materials); 42 | } 43 | await tar.x({ 44 | file: path.join(tempDir.temp, `temp.tgz`), 45 | C: tempDir.materials, 46 | strip: 1, 47 | }); 48 | }; 49 | 50 | export const downloadMaterialsFromGit = (remote: string) => { 51 | fs.removeSync(tempDir.materials); 52 | execa.sync('git', ['clone', ...remote.split(' '), tempDir.materials]); 53 | }; 54 | 55 | export const copyMaterialsFromTemp = ( 56 | from: { blocks: string[]; snippets: string[] }, 57 | to: string, 58 | ) => { 59 | from.blocks.map((s) => { 60 | fs.copySync( 61 | path.join(tempDir.blockMaterials, s), 62 | fs.existsSync(path.join(to, 'blocks', s)) 63 | ? path.join(to, 'blocks', `${s} copy`) 64 | : path.join(to, 'blocks', s), 65 | ); 66 | }); 67 | from.snippets.map((s) => { 68 | fs.copySync( 69 | path.join(tempDir.snippetMaterials, s), 70 | fs.existsSync(path.join(to, 'snippets', s)) 71 | ? path.join(to, 'snippets', `${s} copy`) 72 | : path.join(to, 'snippets', s), 73 | ); 74 | }); 75 | 76 | fs.removeSync(tempDir.materials); 77 | }; 78 | -------------------------------------------------------------------------------- /src/utils/editor.ts: -------------------------------------------------------------------------------- 1 | import { OpenDialogOptions, Range, SnippetString, window, env } from 'vscode'; 2 | import { getLastActiveTextEditor } from '../context'; 3 | 4 | export const getClipboardText = async () => { 5 | let text = ''; 6 | try { 7 | text = await env.clipboard.readText(); 8 | } catch (e) {} 9 | return text; 10 | }; 11 | 12 | export const getSelectedText = () => { 13 | const { selection, document } = window.activeTextEditor!; 14 | return document.getText(selection).trim(); 15 | }; 16 | 17 | export const pasteToEditor = (content: string, isInsertSnippet = true) => { 18 | // vscode 本身代码片段语法 19 | if (isInsertSnippet) { 20 | return insertSnippet(content); 21 | } 22 | const activeTextEditor = getLastActiveTextEditor(); 23 | if (activeTextEditor === undefined) { 24 | throw new Error('无打开文件'); 25 | } 26 | return activeTextEditor?.edit((editBuilder) => { 27 | // editBuilder.replace(activeTextEditor.selection, content); 28 | if (activeTextEditor.selection.isEmpty) { 29 | editBuilder.insert(activeTextEditor.selection.start, content); 30 | } else { 31 | editBuilder.replace( 32 | new Range( 33 | activeTextEditor.selection.start, 34 | activeTextEditor.selection.end, 35 | ), 36 | content, 37 | ); 38 | } 39 | }); 40 | }; 41 | 42 | export const insertSnippet = (content: string) => { 43 | const activeTextEditor = window.activeTextEditor || getLastActiveTextEditor(); 44 | if (activeTextEditor === undefined) { 45 | throw new Error('无打开文件'); 46 | } 47 | return activeTextEditor.insertSnippet(new SnippetString(content)); 48 | }; 49 | 50 | export const getFuncNameAndTypeName = () => { 51 | // 这部分代码可以写在模版里,暂时保留 52 | const selectedText = getSelectedText() || ''; 53 | let funcName = 'fetch'; 54 | let typeName = 'IFetchResult'; 55 | if (selectedText) { 56 | const splitValue = selectedText.split(' '); 57 | funcName = splitValue[0] || funcName; 58 | if (splitValue.length > 1 && splitValue[1]) { 59 | typeName = splitValue[1]; 60 | } else { 61 | typeName = `I${ 62 | funcName.charAt(0).toUpperCase() + funcName.slice(1) 63 | }Result`; 64 | } 65 | } 66 | return { 67 | funcName, 68 | typeName, 69 | inputValues: selectedText.split(' '), 70 | rawSelectedText: selectedText, 71 | }; 72 | }; 73 | 74 | export const selectDirectory = async () => { 75 | const options: OpenDialogOptions = { 76 | canSelectFolders: true, 77 | canSelectFiles: false, 78 | canSelectMany: false, 79 | openLabel: 'Open', 80 | }; 81 | const selectFolderUri = await window.showOpenDialog(options); 82 | if (selectFolderUri && selectFolderUri.length > 0) { 83 | return selectFolderUri[0].fsPath; 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /src/utils/ejs.ts: -------------------------------------------------------------------------------- 1 | import ejs from 'ejs'; 2 | import fse from 'fs-extra'; 3 | import path from 'path'; 4 | import glob from 'glob'; 5 | import prettier from 'prettier'; 6 | 7 | export type YapiInfo = { 8 | query_path: { path: string }; 9 | method: string; 10 | title: string; 11 | project_id: number; 12 | req_params: { 13 | name: string; 14 | desc: string; 15 | }[]; 16 | _id: number; 17 | req_query: { required: '0' | '1'; name: string }[]; 18 | res_body_type: 'raw' | 'json'; 19 | res_body: string; 20 | username: string; 21 | }; 22 | 23 | export type Model = { 24 | type: string; 25 | requestBodyType?: string; 26 | funcName: string; 27 | typeName: string; 28 | inputValues: string[]; 29 | api?: YapiInfo; 30 | mockCode: string; 31 | mockData: string; 32 | jsonData: any; 33 | jsonKeys?: string[]; 34 | rawSelectedText: string; // 编辑器中选中的原始文本 35 | rawClipboardText: string; // 系统剪切板中的原始文本 36 | activeTextEditorFilePath?: string; // 当前打开文件地址 37 | createBlockPath?: string; // 创建区块的目录 38 | }; 39 | 40 | export const compile = (templateString: string, model: Model) => 41 | ejs.render(templateString, model); 42 | 43 | export async function renderEjsTemplates( 44 | templateData: object, 45 | templateDir: string, 46 | exclude: string[] = [], 47 | ) { 48 | return new Promise((resolve, reject) => { 49 | glob( 50 | '**', 51 | { 52 | cwd: templateDir, 53 | ignore: ['node_modules/**'], 54 | nodir: true, 55 | dot: true, 56 | }, 57 | (err, files) => { 58 | if (err) { 59 | return reject(err); 60 | } 61 | const templateFiles = files.filter((s) => { 62 | let valid = true; 63 | if (s.indexOf('.ejs') < 0) { 64 | valid = false; 65 | } 66 | if (exclude && exclude.length > 0) { 67 | exclude.map((e) => { 68 | if (s.startsWith(e)) { 69 | valid = false; 70 | } 71 | }); 72 | } 73 | return valid; 74 | }); 75 | Promise.all( 76 | templateFiles.map((file) => { 77 | const filepath = path.join(templateDir, file); 78 | return renderFile(filepath, templateData); 79 | }), 80 | ) 81 | .then(() => resolve()) 82 | .catch(reject); 83 | }, 84 | ); 85 | }); 86 | } 87 | 88 | async function renderFile(templateFilepath: string, data: ejs.Data) { 89 | let content = await ejs.renderFile(templateFilepath, data); 90 | const targetFilePath = templateFilepath 91 | .replace(/\.ejs$/, '') 92 | .replace( 93 | /\$\{.+?\}/gi, 94 | (match) => data[match.replace(/\$|\{|\}/g, '')] || '', 95 | ); 96 | try { 97 | content = prettier.format(content, { 98 | singleQuote: true, 99 | filepath: targetFilePath, 100 | }); 101 | } catch {} 102 | await fse.rename(templateFilepath, targetFilePath); 103 | await fse.writeFile(targetFilePath, content); 104 | } 105 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import os from 'os'; 3 | 4 | export const commands = { 5 | openDownloadMaterials: 'lowcode.openDownloadMaterials', 6 | showChatGPTView: 'lowcode.showChatGPTView', 7 | hideChatGPTView: 'lowcode.hideChatGPTView', 8 | clearChatGPTViewContent: 'lowcode.clearChatGPTViewContent', 9 | }; 10 | 11 | export const materialsDir = 'materials'; 12 | 13 | export const tempDir = { 14 | temp: path.join(os.homedir(), '.lowcode'), 15 | materials: path.join(os.homedir(), '.lowcode', 'materials'), 16 | blockMaterials: path.join( 17 | os.homedir(), 18 | '.lowcode', 19 | 'materials', 20 | materialsDir, 21 | 'blocks', 22 | ), 23 | snippetMaterials: path.join( 24 | os.homedir(), 25 | '.lowcode', 26 | 'materials', 27 | materialsDir, 28 | 'snippets', 29 | ), 30 | scaffold: path.join(os.homedir(), '.lowcode', 'scaffold'), 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/file.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import { rootPath } from './vscodeEnv'; 4 | 5 | export const getFileContent = (filePath: string, fullPath = false) => { 6 | let fileContent = ''; 7 | const fileFullPath = fullPath ? filePath : path.join(rootPath, filePath); 8 | try { 9 | const fileBuffer = fs.readFileSync(fileFullPath); 10 | fileContent = fileBuffer.toString(); 11 | } catch (error) {} 12 | return fileContent; 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/lib.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import dirTree from 'directory-tree'; 3 | import ejs from 'ejs'; 4 | import fsExtra from 'fs-extra'; 5 | import execa from 'execa'; 6 | import glob from 'glob'; 7 | import prettier from 'prettier'; 8 | import jsonSchemaToTypescript from 'json-schema-to-typescript'; 9 | import typescriptJsonSchema from 'typescript-json-schema'; 10 | 11 | const stripComments = require('strip-comments'); 12 | const stripJsonComments = require('strip-json-comments'); 13 | const generateSchema = require('generate-schema'); 14 | const tar = require('tar'); 15 | 16 | export const getInnerLibs = () => ({ 17 | axios, 18 | dirTree, 19 | ejs, 20 | fsExtra, 21 | execa, 22 | glob, 23 | prettier, 24 | stripComments, 25 | stripJsonComments, 26 | generateSchema, 27 | jsonSchemaToTypescript, 28 | typescriptJsonSchema, 29 | tar, 30 | }); 31 | -------------------------------------------------------------------------------- /src/utils/llm.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { createChatCompletion as openaiCreateChatCompletion } from './openai'; 4 | import { emitter } from './emitter'; 5 | import { getSyncFolder } from './config'; 6 | import { showChatGPTView } from '../webview'; 7 | import { getEnv } from './vscodeEnv'; 8 | 9 | const LLMScript: { 10 | createChatCompletion?: (options: { 11 | messages: { role: 'system' | 'user' | 'assistant'; content: string }[]; 12 | handleChunk?: (data: { text?: string }) => void; 13 | lowcodeContext: object; 14 | }) => Promise; 15 | } = {}; 16 | 17 | const syncFolder = getSyncFolder(); 18 | 19 | if (syncFolder) { 20 | const scriptFile = path.join(syncFolder, 'llm/index.js'); 21 | if (fs.existsSync(scriptFile)) { 22 | delete eval('require').cache[eval('require').resolve(scriptFile)]; 23 | const script = eval('require')(scriptFile); 24 | if (script.createChatCompletion) { 25 | LLMScript.createChatCompletion = script.createChatCompletion; 26 | } 27 | } 28 | } 29 | 30 | export const createChatCompletion = async (options: { 31 | messages: { role: 'system' | 'user' | 'assistant'; content: string }[]; 32 | handleChunk?: (data: { text?: string }) => void; 33 | }) => { 34 | if (LLMScript.createChatCompletion) { 35 | const res = await LLMScript.createChatCompletion({ 36 | messages: options.messages, 37 | handleChunk: (data) => { 38 | if (options.handleChunk) { 39 | options.handleChunk(data); 40 | emitter.emit('chatGPTChunck', data); 41 | } 42 | }, 43 | lowcodeContext: { 44 | env: getEnv(), 45 | }, 46 | }); 47 | emitter.emit('chatGPTComplete', res); 48 | return res; 49 | } 50 | const res = await openaiCreateChatCompletion({ 51 | messages: options.messages, 52 | handleChunk: (data) => { 53 | if (options.handleChunk) { 54 | options.handleChunk(data); 55 | emitter.emit('chatGPTChunck', data); 56 | } 57 | }, 58 | }); 59 | emitter.emit('chatGPTComplete', res); 60 | return res; 61 | }; 62 | 63 | export const createChatCompletionForScript = (options: { 64 | messages: { role: 'system' | 'user' | 'assistant'; content: string }[]; 65 | handleChunk?: (data: { text?: string }) => void; 66 | showWebview?: boolean; 67 | }) => { 68 | if (!options.showWebview) { 69 | return createChatCompletion({ 70 | messages: options.messages, 71 | handleChunk: options.handleChunk, 72 | }); 73 | } 74 | // 打开 webview,使用 emitter 监听结果,把结果回传给 script 75 | showChatGPTView({ 76 | task: { 77 | task: 'askChatGPT', 78 | data: options.messages.map((m) => m.content).join('\n'), 79 | }, 80 | }); 81 | return new Promise((resolve) => { 82 | emitter.on('chatGPTChunck', (data) => { 83 | if (options.handleChunk) { 84 | options.handleChunk(data); 85 | } 86 | }); 87 | emitter.on('chatGPTComplete', (data) => { 88 | resolve(data); 89 | emitter.off('chatGPTChunck'); 90 | emitter.off('chatGPTComplete'); 91 | }); 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /src/utils/name.ts: -------------------------------------------------------------------------------- 1 | export const resetMaterialName = (name: string) => { 2 | const arrayStr = name.split('] '); 3 | return arrayStr[1] || name; 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/outputChannel.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | const channel = window.createOutputChannel('lowcode'); 4 | 5 | export const getOutputChannel = () => channel; 6 | -------------------------------------------------------------------------------- /src/utils/platform.ts: -------------------------------------------------------------------------------- 1 | export const formatPath = (path: string = '') => { 2 | if ( 3 | path.startsWith('/') && 4 | process.platform.toLowerCase().includes('win32') 5 | ) { 6 | path = path.substring(1); 7 | } 8 | return path; 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import common from 'mocha/lib/interfaces/common'; 3 | 4 | const https = require('https'); 5 | 6 | const agent = new https.Agent({ 7 | rejectUnauthorized: true, 8 | }); 9 | 10 | https.globalAgent.options.rejectUnauthorized = false; 11 | 12 | interface IApiDetailInfo { 13 | data: { 14 | query_path: { path: string }; 15 | path: string; 16 | method: string; 17 | title: string; 18 | project_id: number; 19 | req_params: { 20 | name: string; 21 | desc: string; 22 | }[]; 23 | _id: number; 24 | req_query: { required: '0' | '1'; name: string }[]; 25 | res_body_type: 'raw' | 'json'; 26 | res_body: string; 27 | req_body_other: string; 28 | username: string; 29 | }; 30 | } 31 | 32 | export const fetchApiDetailInfo = ( 33 | domain: string, 34 | id: string, 35 | token: string, 36 | ) => { 37 | const url = domain.endsWith('/') 38 | ? `${domain}api/interface/get?id=${id}&token=${token}` 39 | : `${domain}/api/interface/get?id=${id}&token=${token}`; 40 | return axios.get(url, { httpsAgent: agent }); 41 | }; 42 | 43 | export const fetchScaffolds = (url: string) => axios.get(url); 44 | 45 | export const checkVankeInternal = () => 46 | axios 47 | .get('https://npm.bu6.io') 48 | .then((res) => true) 49 | .catch(() => false); 50 | -------------------------------------------------------------------------------- /src/utils/scaffold.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import execa from 'execa'; 4 | import { renderEjsTemplates } from './ejs'; 5 | import { tempDir } from './env'; 6 | import { rootPath } from './vscodeEnv'; 7 | 8 | export const downloadScaffoldFromGit = (remote: string) => { 9 | fs.removeSync(tempDir.scaffold); 10 | execa.sync('git', ['clone', ...remote.split(' '), tempDir.scaffold]); 11 | fs.removeSync(path.join(tempDir.scaffold, '.git')); 12 | if ( 13 | fs.existsSync(path.join(tempDir.scaffold, 'lowcode.scaffold.config.json')) 14 | ) { 15 | return fs.readJSONSync( 16 | path.join(tempDir.scaffold, 'lowcode.scaffold.config.json'), 17 | ); 18 | } 19 | return {}; 20 | }; 21 | 22 | export const copyLocalScaffoldToTemp = (localScaffoldPath?: string) => { 23 | if (!localScaffoldPath) { 24 | localScaffoldPath = rootPath; 25 | } 26 | if (!localScaffoldPath) { 27 | throw new Error('当前没有打开项目,请选择本地项目'); 28 | } 29 | fs.removeSync(tempDir.scaffold); 30 | fs.copySync(localScaffoldPath, tempDir.scaffold, { 31 | filter: (src: string, dest: string) => { 32 | if (src.includes('.git') || src.includes('node_modules')) { 33 | return false; 34 | } 35 | return true; 36 | }, 37 | }); 38 | fs.removeSync(path.join(tempDir.scaffold, '.git')); 39 | if ( 40 | fs.existsSync(path.join(tempDir.scaffold, 'lowcode.scaffold.config.json')) 41 | ) { 42 | return fs.readJSONSync( 43 | path.join(tempDir.scaffold, 'lowcode.scaffold.config.json'), 44 | ); 45 | } 46 | return {}; 47 | }; 48 | 49 | export const compileScaffold = async (model: any, createDir: string) => { 50 | if ( 51 | fs.existsSync(path.join(tempDir.scaffold, 'lowcode.scaffold.config.json')) 52 | ) { 53 | const config = fs.readJSONSync( 54 | path.join(tempDir.scaffold, 'lowcode.scaffold.config.json'), 55 | ); 56 | const excludeCompile: string[] = config.excludeCompile || []; 57 | if (config.conditionFiles) { 58 | Object.keys(model).map((key) => { 59 | if ( 60 | config.conditionFiles[key] && 61 | config.conditionFiles[key].value === model[key] && 62 | Array.isArray(config.conditionFiles[key].exclude) 63 | ) { 64 | config.conditionFiles[key].exclude.map((exclude: string) => { 65 | fs.removeSync(path.join(tempDir.scaffold, exclude)); 66 | }); 67 | } 68 | }); 69 | } 70 | await renderEjsTemplates(model, tempDir.scaffold, excludeCompile); 71 | fs.removeSync(path.join(tempDir.scaffold, 'lowcode.scaffold.config.json')); 72 | } 73 | fs.copySync(tempDir.scaffold, createDir); 74 | }; 75 | -------------------------------------------------------------------------------- /src/utils/vscodeEnv.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { workspace } from 'vscode'; 3 | import { getSyncFolder } from './config'; 4 | import { getExtensionContext } from '../context'; 5 | 6 | export const rootPath = path.join(workspace.rootPath || ''); 7 | 8 | export const tempWorkPath = path.join(rootPath, '.lowcode'); 9 | 10 | export const materialsPath = path.join(rootPath, 'materials'); 11 | 12 | export const blockMaterialsPath = path.join(rootPath, 'materials', 'blocks'); 13 | 14 | export const snippetMaterialsPath = path.join( 15 | rootPath, 16 | 'materials', 17 | 'snippets', 18 | ); 19 | 20 | export const getPrivateBlockMaterialsPath = () => { 21 | const syncFolder = getSyncFolder(); 22 | if (!syncFolder) { 23 | return ''; 24 | } 25 | return path.join(syncFolder, 'materials', 'blocks'); 26 | }; 27 | 28 | export const getPrivateSnippetMaterialsPath = () => { 29 | const syncFolder = getSyncFolder(); 30 | if (!syncFolder) { 31 | return ''; 32 | } 33 | return path.join(syncFolder, 'materials', 'snippets'); 34 | }; 35 | 36 | export const getEnv = () => ({ 37 | rootPath, 38 | tempWorkPath, 39 | materialsPath, 40 | blockMaterialsPath, 41 | snippetMaterialsPath, 42 | privateMaterialsPath: getSyncFolder(), 43 | extensionContext: getExtensionContext(), 44 | }); 45 | 46 | export const checkRootPath = () => { 47 | if (!rootPath) { 48 | throw new Error('请打开工作目录'); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/webview/callback.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | 3 | export function invokeCallback( 4 | webview: vscode.Webview, 5 | cbid: string, 6 | res: T, 7 | ) { 8 | webview.postMessage({ 9 | cmd: 'vscodeCallback', 10 | cbid, 11 | data: res, 12 | code: 200, 13 | }); 14 | } 15 | 16 | export function invokeChatGPTChunkCallback( 17 | webview: vscode.Webview, 18 | cbid: string, 19 | res: T, 20 | ) { 21 | webview.postMessage({ 22 | cmd: 'vscodeChatGPTChunkCallback', 23 | task: 'handleChatGPTChunk', 24 | cbid, 25 | data: res, 26 | code: 200, 27 | }); 28 | } 29 | 30 | export function invokeErrorCallback( 31 | webview: vscode.Webview, 32 | cbid: string, 33 | res: any, 34 | ) { 35 | webview.postMessage({ 36 | cmd: 'vscodeCallback', 37 | cbid, 38 | data: res, 39 | code: 400, 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/webview/controllers/alert.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { IMessage } from '../type'; 3 | 4 | const alert = { 5 | alert: (message: IMessage) => { 6 | window.showErrorMessage(message.data); 7 | return '来自vscode的响应'; 8 | }, 9 | }; 10 | 11 | export default alert; 12 | -------------------------------------------------------------------------------- /src/webview/controllers/block.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { 4 | blockMaterialsPath, 5 | getPrivateBlockMaterialsPath, 6 | } from '../../utils/vscodeEnv'; 7 | import { IMessage } from '../type'; 8 | 9 | export const createBlock = ( 10 | message: IMessage<{ 11 | name: string; 12 | template: string; 13 | model: string; 14 | schema: string; 15 | preview: string; 16 | commandPrompt: string; 17 | viewPrompt: string; 18 | private?: boolean; 19 | }>, 20 | ) => { 21 | let blockPath = path.join(blockMaterialsPath, message.data.name); 22 | if (message.data.private) { 23 | blockPath = getPrivateBlockMaterialsPath(); 24 | if (!blockPath) { 25 | return { code: 404, msg: '私有目录未配置', result: false }; 26 | } 27 | blockPath = path.join(blockPath, message.data.name); 28 | } 29 | if (fs.existsSync(blockPath)) { 30 | throw new Error('区块名称已经存在'); 31 | } 32 | fs.outputFileSync( 33 | path.join(blockPath, 'src', 'README.md'), 34 | message.data.template, 35 | ); 36 | fs.outputFileSync( 37 | path.join(blockPath, 'config', 'model.json'), 38 | message.data.model, 39 | ); 40 | fs.outputFileSync( 41 | path.join(blockPath, 'config', 'schema.json'), 42 | message.data.schema, 43 | ); 44 | fs.outputFileSync( 45 | path.join(blockPath, 'config', 'preview.json'), 46 | message.data.preview, 47 | ); 48 | fs.outputFileSync( 49 | path.join(blockPath, 'config', 'commandPrompt.ejs'), 50 | message.data.commandPrompt, 51 | ); 52 | fs.outputFileSync( 53 | path.join(blockPath, 'config', 'viewPrompt.ejs'), 54 | message.data.viewPrompt, 55 | ); 56 | fs.outputFileSync( 57 | path.join(blockPath, 'script', 'index.js'), 58 | `const path = require("path"); 59 | module.exports = { 60 | beforeCompile: (context) => { 61 | context.outputChannel.appendLine("compile ${message.data.name} start"); 62 | }, 63 | afterCompile: (context) => { 64 | context.outputChannel.appendLine("compile ${message.data.name} end"); 65 | }, 66 | test: (context) => { 67 | context.outputChannel.appendLine(Object.keys(context)) 68 | context.outputChannel.appendLine(JSON.stringify(context.model)) 69 | context.outputChannel.appendLine(context.params) 70 | return { ...context.model, name: "测试一下", } 71 | }, 72 | };`, 73 | ); 74 | return { code: 200, msg: '', result: true }; 75 | }; 76 | -------------------------------------------------------------------------------- /src/webview/controllers/command.ts: -------------------------------------------------------------------------------- 1 | import { commands, Uri } from 'vscode'; 2 | import { IMessage } from '../type'; 3 | 4 | const command = { 5 | executeVscodeCommand: (message: IMessage<{ command: string }>) => { 6 | commands.executeCommand(message.data.command); 7 | }, 8 | openUri: (message: IMessage) => { 9 | commands.executeCommand('vscode.openFolder', Uri.file(message.data), true); 10 | }, 11 | }; 12 | 13 | export default command; 14 | -------------------------------------------------------------------------------- /src/webview/controllers/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs-extra'; 3 | import { 4 | Config, 5 | getConfig, 6 | getSyncFolder, 7 | saveSyncFolder, 8 | saveConfig, 9 | } from '../../utils/config'; 10 | import { IMessage } from '../type'; 11 | import { rootPath } from '../../utils/vscodeEnv'; 12 | 13 | export const getPluginConfig = () => getConfig(); 14 | 15 | export const savePluginConfig = (message: IMessage) => { 16 | // 处理旧的配置 17 | const packageObj = fs.readJsonSync(path.join(rootPath, 'package.json')); 18 | if ( 19 | packageObj['yapi-code.domain'] || 20 | packageObj['yapi-code.project'] || 21 | packageObj['yapi-code.mockNumber'] || 22 | packageObj['yapi-code.mockString'] || 23 | packageObj['yapi-code.mockBoolean'] || 24 | packageObj['yapi-code.mockKeyWordEqual'] || 25 | packageObj['yapi-code.mockKeyWordLike'] 26 | ) { 27 | delete packageObj['yapi-code.domain']; 28 | delete packageObj['yapi-code.project']; 29 | delete packageObj['yapi-code.mockNumber']; 30 | delete packageObj['yapi-code.mockString']; 31 | delete packageObj['yapi-code.mockBoolean']; 32 | delete packageObj['yapi-code.mockKeyWordEqual']; 33 | delete packageObj['yapi-code.mockKeyWordLike']; 34 | fs.writeFileSync( 35 | path.join(rootPath, 'package.json'), 36 | JSON.stringify(packageObj, null, 2), 37 | ); 38 | } 39 | 40 | saveConfig(message.data); 41 | return '保存成功'; 42 | }; 43 | 44 | export const getSyncFolderConfig = () => getSyncFolder(); 45 | 46 | export const saveSyncFolderConfig = (message: IMessage) => { 47 | saveSyncFolder(message.data); 48 | return true; 49 | }; 50 | -------------------------------------------------------------------------------- /src/webview/controllers/directory.ts: -------------------------------------------------------------------------------- 1 | import dirTree from 'directory-tree'; 2 | import { rootPath } from '../../utils/vscodeEnv'; 3 | 4 | export const getDirectoryTree = () => { 5 | const filteredTree = dirTree(rootPath, { 6 | exclude: /node_modules|\.umi|\.git/, 7 | }); 8 | return filteredTree; 9 | }; 10 | -------------------------------------------------------------------------------- /src/webview/controllers/generate.ts: -------------------------------------------------------------------------------- 1 | import { genTemplateModelByYapi as modelByYapi } from '../../genCode/genCodeByYapi'; 2 | import { genCodeByBlock, genCodeBySnippet } from '../../utils/generate'; 3 | import { IMessage } from '../type'; 4 | 5 | export const genTemplateModelByYapi = async ( 6 | message: IMessage<{ 7 | domain: string; 8 | id: string; 9 | token: string; 10 | typeName?: string; 11 | funName?: string; 12 | }>, 13 | ) => { 14 | const model = await modelByYapi( 15 | message.data.domain, 16 | message.data.id, 17 | message.data.token, 18 | message.data.typeName, 19 | message.data.funName, 20 | ); 21 | return model; 22 | }; 23 | 24 | export const genCodeByBlockMaterial = async ( 25 | message: IMessage<{ 26 | material: string; 27 | model: object; 28 | path: string; 29 | createPath: string[]; 30 | privateMaterials?: boolean; 31 | }>, 32 | ) => { 33 | await genCodeByBlock(message.data); 34 | return '生成成功'; 35 | }; 36 | 37 | export const genCodeBySnippetMaterial = async ( 38 | message: IMessage<{ 39 | model: any; 40 | template: string; 41 | name: string; 42 | privateMaterials?: boolean; 43 | }>, 44 | ) => { 45 | await genCodeBySnippet(message.data); 46 | return '生成成功'; 47 | }; 48 | -------------------------------------------------------------------------------- /src/webview/controllers/intelliSense.ts: -------------------------------------------------------------------------------- 1 | import { registerCompletion } from '../../commands/registerCompletion'; 2 | import { getExtensionContext } from '../../context'; 3 | 4 | export const refreshIntelliSense = () => { 5 | const context = getExtensionContext(); 6 | if (context) { 7 | registerCompletion(context); 8 | return '刷新成功'; 9 | } 10 | throw new Error('刷新失败'); 11 | }; 12 | -------------------------------------------------------------------------------- /src/webview/controllers/json.ts: -------------------------------------------------------------------------------- 1 | import { json2Ts } from '../../utils/json'; 2 | import { IMessage } from '../type'; 3 | 4 | export const jsonToTs = async ( 5 | message: IMessage<{ json: object; typeName: string }>, 6 | ) => { 7 | const type = await json2Ts(message.data.json, message.data.typeName); 8 | return type; 9 | }; 10 | -------------------------------------------------------------------------------- /src/webview/controllers/material.ts: -------------------------------------------------------------------------------- 1 | import { getSyncFolder } from '../../utils/config'; 2 | import { 3 | copyMaterialsFromTemp, 4 | downloadMaterialsFromGit, 5 | downloadMaterialsFromNpm, 6 | } from '../../utils/download'; 7 | import { tempDir } from '../../utils/env'; 8 | import { getLocalMaterials, getSnippets } from '../../utils/materials'; 9 | import { 10 | blockMaterialsPath, 11 | getPrivateBlockMaterialsPath, 12 | materialsPath, 13 | rootPath, 14 | } from '../../utils/vscodeEnv'; 15 | import { IMessage } from '../type'; 16 | 17 | const material = { 18 | getLocalMaterials: (message: IMessage<'blocks' | 'snippets'>) => { 19 | if (message.data === 'blocks') { 20 | let materials = getLocalMaterials('blocks', blockMaterialsPath); 21 | if (getPrivateBlockMaterialsPath() && getSyncFolder() !== rootPath) { 22 | const privateBlockMaterials = getLocalMaterials( 23 | 'blocks', 24 | getPrivateBlockMaterialsPath(), 25 | true, 26 | ); 27 | materials = materials.concat(privateBlockMaterials); 28 | } 29 | return materials; 30 | } 31 | const materials = getSnippets().filter( 32 | (s) => !s.preview.notShowInSnippetsList, // webview 获取列表,过滤掉不需要显示的 33 | ); 34 | return materials; 35 | }, 36 | 37 | downloadMaterials: async ( 38 | message: IMessage<{ type: 'git' | 'npm'; url: string }>, 39 | ) => { 40 | if (message.data.type === 'npm') { 41 | await downloadMaterialsFromNpm(message.data.url); 42 | } else { 43 | downloadMaterialsFromGit(message.data.url); 44 | } 45 | const materials = { 46 | blocks: getLocalMaterials('blocks', tempDir.blockMaterials), 47 | snippets: getLocalMaterials('snippets', tempDir.snippetMaterials), 48 | }; 49 | return materials; 50 | }, 51 | 52 | saveDownloadMaterials: async ( 53 | message: IMessage<{ blocks: string[]; snippets: string[] }>, 54 | ) => { 55 | copyMaterialsFromTemp(message.data, materialsPath); 56 | }, 57 | }; 58 | 59 | export default material; 60 | -------------------------------------------------------------------------------- /src/webview/controllers/openai.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { IMessage } from '../type'; 3 | import { createChatCompletion } from '../../utils/llm'; 4 | import { invokeChatGPTChunkCallback } from '../callback'; 5 | import { pasteToEditor } from '../../utils/editor'; 6 | import { compile as compileEjs, Model } from '../../utils/ejs'; 7 | import { showChatGPTView } from '..'; 8 | 9 | export const askChatGPT = async ( 10 | message: IMessage<{ 11 | sessionId: number; 12 | messageId: number; 13 | messages: { role: 'system' | 'user' | 'assistant'; content: string }[]; 14 | }>, 15 | context: { 16 | webview: vscode.Webview; 17 | }, 18 | ) => { 19 | const res = await createChatCompletion({ 20 | messages: message.data.messages, 21 | handleChunk: (data) => { 22 | invokeChatGPTChunkCallback(context.webview, message.cbid, { 23 | sessionId: message.data.sessionId, 24 | messageId: message.data.messageId, 25 | content: data.text, 26 | }); 27 | }, 28 | }); 29 | return { 30 | sessionId: message.data.sessionId, 31 | messageId: message.data.messageId, 32 | content: res, 33 | }; 34 | }; 35 | 36 | export const insertCode = async (message: IMessage) => { 37 | await pasteToEditor(message.data); 38 | return true; 39 | }; 40 | 41 | export const exportChatGPTContent = async (message: IMessage) => { 42 | const document = await vscode.workspace.openTextDocument({ 43 | language: 'markdown', 44 | }); 45 | const edit = new vscode.TextEdit(new vscode.Range(0, 0, 0, 0), message.data); 46 | const workspaceEdit = new vscode.WorkspaceEdit(); 47 | workspaceEdit.set(document.uri, [edit]); 48 | await vscode.workspace.applyEdit(workspaceEdit); 49 | await vscode.window.showTextDocument(document); 50 | return true; 51 | }; 52 | 53 | export const askChatGPTWithEjsTemplate = ( 54 | message: IMessage<{ template: string; model: object }>, 55 | ) => { 56 | const code = compileEjs(message.data.template, message.data.model as Model); 57 | showChatGPTView({ 58 | task: { task: 'askChatGPT', data: code }, 59 | }); 60 | return true; 61 | }; 62 | -------------------------------------------------------------------------------- /src/webview/controllers/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import { IMessage } from '../type'; 3 | 4 | export const axiosRequest = async ( 5 | message: IMessage<{ 6 | config: AxiosRequestConfig; 7 | }>, 8 | ) => { 9 | const res = await axios.request(message.data.config); 10 | return res.data; 11 | }; 12 | -------------------------------------------------------------------------------- /src/webview/controllers/scaffold.ts: -------------------------------------------------------------------------------- 1 | import { commands, Uri } from 'vscode'; 2 | import { selectDirectory as openSelectDirectory } from '../../utils/editor'; 3 | import { fetchScaffolds } from '../../utils/request'; 4 | import { 5 | compileScaffold, 6 | copyLocalScaffoldToTemp, 7 | downloadScaffoldFromGit, 8 | } from '../../utils/scaffold'; 9 | import { IMessage } from '../type'; 10 | 11 | export const getScaffolds = async (message: IMessage<{ url: string }>) => { 12 | const res = await fetchScaffolds(message.data.url); 13 | return res.data || []; 14 | }; 15 | 16 | export const downloadScaffold = ( 17 | message: IMessage<{ 18 | type: 'git' | 'npm'; 19 | repository: string; 20 | }>, 21 | ) => { 22 | if (message.data.type === 'git') { 23 | const config = downloadScaffoldFromGit(message.data.repository); 24 | return config; 25 | } 26 | }; 27 | 28 | export const selectDirectory = async () => { 29 | const dirs = await openSelectDirectory(); 30 | return dirs; 31 | }; 32 | 33 | export const createProject = async ( 34 | message: IMessage<{ 35 | model: any; 36 | createDir: string; 37 | immediateOpen: boolean; 38 | }>, 39 | ) => { 40 | await compileScaffold(message.data.model, message.data.createDir); 41 | if (message.data.immediateOpen) { 42 | commands.executeCommand( 43 | 'vscode.openFolder', 44 | Uri.file(message.data.createDir), 45 | true, 46 | ); 47 | } 48 | return '创建项目成功'; 49 | }; 50 | 51 | export const useLocalScaffold = ( 52 | message: IMessage<{ 53 | localPath?: string; 54 | }>, 55 | ) => { 56 | const config = copyLocalScaffoldToTemp(message.data.localPath); 57 | return config; 58 | }; 59 | -------------------------------------------------------------------------------- /src/webview/controllers/script.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-eval */ 2 | import vscode from 'vscode'; 3 | import path from 'path'; 4 | import fs from 'fs-extra'; 5 | import { IMessage } from '../type'; 6 | import { getEnv, rootPath } from '../../utils/vscodeEnv'; 7 | import { getInnerLibs } from '../../utils/lib'; 8 | import { getOutputChannel } from '../../utils/outputChannel'; 9 | import { createChatCompletionForScript } from '../../utils/llm'; 10 | import { getLastActiveTextEditor } from '../../context'; 11 | 12 | export const runScript = async ( 13 | message: IMessage<{ 14 | materialPath: string; 15 | createBlockPath?: string; 16 | script: string; 17 | params: string; 18 | clipboardImage?: string; 19 | model: object; 20 | }>, 21 | ) => { 22 | const scriptFile = path.join(message.data.materialPath, 'script/index.js'); 23 | if (fs.existsSync(scriptFile)) { 24 | delete eval('require').cache[eval('require').resolve(scriptFile)]; 25 | const script = eval('require')(scriptFile); 26 | if (script[message.data.script] || script.runScript) { 27 | const context = { 28 | model: message.data.model, 29 | method: message.data.script, 30 | script: message.data.script, 31 | params: message.data.params, 32 | clipboardImage: message.data.clipboardImage, 33 | vscode, 34 | workspaceRootPath: rootPath, 35 | env: getEnv(), 36 | libs: getInnerLibs(), 37 | outputChannel: getOutputChannel(), 38 | log: getOutputChannel(), 39 | createBlockPath: message.data.createBlockPath, 40 | createChatCompletion: createChatCompletionForScript, 41 | materialPath: message.data.materialPath, 42 | activeTextEditor: getLastActiveTextEditor(), 43 | }; 44 | const extendModel = await ( 45 | script[message.data.script] || script.runScript 46 | )(context); 47 | return extendModel; 48 | } 49 | throw new Error(`方法: ${message.data.script} 不存在`); 50 | } else { 51 | throw new Error(`脚本文件不存在`); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/webview/controllers/snippet.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | import path from 'path'; 3 | import { pasteToEditor } from '../../utils/editor'; 4 | import { 5 | getPrivateSnippetMaterialsPath, 6 | snippetMaterialsPath, 7 | } from '../../utils/vscodeEnv'; 8 | import { IMessage } from '../type'; 9 | 10 | export const insertSnippet = (message: IMessage<{ template: string }>) => { 11 | pasteToEditor(message.data.template); 12 | }; 13 | 14 | export const addSnippets = ( 15 | message: IMessage<{ 16 | name: string; 17 | template: string; 18 | model: string; 19 | schema: string; 20 | preview: string; 21 | commandPrompt: string; 22 | viewPrompt: string; 23 | private?: boolean; 24 | }>, 25 | ) => { 26 | let snippetPath = path.join(snippetMaterialsPath, message.data.name); 27 | if (message.data.private) { 28 | snippetPath = getPrivateSnippetMaterialsPath(); 29 | if (!snippetPath) { 30 | return { code: 404, msg: '私有目录未配置', result: false }; 31 | } 32 | snippetPath = path.join(snippetPath, message.data.name); 33 | } 34 | fs.outputFileSync( 35 | path.join(snippetPath, 'src', 'template.ejs'), 36 | message.data.template, 37 | ); 38 | fs.outputFileSync( 39 | path.join(snippetPath, 'config', 'model.json'), 40 | message.data.model, 41 | ); 42 | fs.outputFileSync( 43 | path.join(snippetPath, 'config', 'schema.json'), 44 | message.data.schema, 45 | ); 46 | fs.outputFileSync( 47 | path.join(snippetPath, 'config', 'preview.json'), 48 | message.data.preview, 49 | ); 50 | fs.outputFileSync( 51 | path.join(snippetPath, 'config', 'commandPrompt.ejs'), 52 | message.data.commandPrompt, 53 | ); 54 | fs.outputFileSync( 55 | path.join(snippetPath, 'config', 'viewPrompt.ejs'), 56 | message.data.viewPrompt, 57 | ); 58 | fs.outputFileSync( 59 | path.join(snippetPath, 'script', 'index.js'), 60 | `const path = require("path"); 61 | module.exports = { 62 | beforeCompile: (context) => { 63 | context.outputChannel.appendLine("compile ${message.data.name} start"); 64 | }, 65 | afterCompile: (context) => { 66 | context.outputChannel.appendLine("compile ${message.data.name} end"); 67 | }, 68 | test: (context) => { 69 | context.outputChannel.appendLine(Object.keys(context)) 70 | context.outputChannel.appendLine(JSON.stringify(context.model)) 71 | context.outputChannel.appendLine(context.params) 72 | return { ...context.model, name: "测试一下", } 73 | }, 74 | };`, 75 | ); 76 | return { code: 200, msg: '', result: true }; 77 | }; 78 | -------------------------------------------------------------------------------- /src/webview/controllers/task.ts: -------------------------------------------------------------------------------- 1 | import vscode from 'vscode'; 2 | import { IMessage } from '../type'; 3 | import { emitter } from '../../utils/emitter'; 4 | 5 | export const getTask = async ( 6 | message: IMessage, 7 | context: { 8 | webview: vscode.Webview; 9 | task: { task: string; data?: any }; 10 | }, 11 | ) => context.task; 12 | 13 | export const putClipboardImage = async ( 14 | message: IMessage, 15 | context: { 16 | webview: vscode.Webview; 17 | task: { task: string; data?: any }; 18 | }, 19 | ) => { 20 | emitter.emit('clipboardImage', message.data); 21 | return true; 22 | }; 23 | -------------------------------------------------------------------------------- /src/webview/controllers/yapi.ts: -------------------------------------------------------------------------------- 1 | import { getConfig } from '../../utils/config'; 2 | 3 | const config = getConfig(); 4 | 5 | export const getYapiDomain = () => { 6 | const domian = config.yapi?.domain; 7 | return domian; 8 | }; 9 | 10 | export const getYapiProjects = () => { 11 | const projects = config.yapi?.projects || []; 12 | return projects; 13 | }; 14 | -------------------------------------------------------------------------------- /src/webview/routes/index.ts: -------------------------------------------------------------------------------- 1 | import alert from '../controllers/alert'; 2 | import material from '../controllers/material'; 3 | import command from '../controllers/command'; 4 | import * as scaffold from '../controllers/scaffold'; 5 | import * as directory from '../controllers/directory'; 6 | import * as yapi from '../controllers/yapi'; 7 | import * as generate from '../controllers/generate'; 8 | import * as snippet from '../controllers/snippet'; 9 | import * as block from '../controllers/block'; 10 | import * as json from '../controllers/json'; 11 | import * as config from '../controllers/config'; 12 | import * as intelliSense from '../controllers/intelliSense'; 13 | import * as reqeust from '../controllers/request'; 14 | import * as openai from '../controllers/openai'; 15 | import * as task from '../controllers/task'; 16 | import * as script from '../controllers/script'; 17 | 18 | export const routes: Record = { 19 | alert: alert.alert, 20 | 21 | downloadMaterials: material.downloadMaterials, 22 | getLocalMaterials: material.getLocalMaterials, 23 | saveDownloadMaterials: material.saveDownloadMaterials, 24 | 25 | executeVscodeCommand: command.executeVscodeCommand, 26 | openUriByVscode: command.openUri, 27 | 28 | getScaffolds: scaffold.getScaffolds, 29 | downloadScaffold: scaffold.downloadScaffold, 30 | selectDirectory: scaffold.selectDirectory, 31 | createProject: scaffold.createProject, 32 | useLocalScaffold: scaffold.useLocalScaffold, 33 | 34 | getDirectoryTree: directory.getDirectoryTree, 35 | 36 | getYapiDomain: yapi.getYapiDomain, 37 | getYapiProjects: yapi.getYapiProjects, 38 | 39 | genTemplateModelByYapi: generate.genTemplateModelByYapi, 40 | genCodeByBlockMaterial: generate.genCodeByBlockMaterial, 41 | genCodeBySnippetMaterial: generate.genCodeBySnippetMaterial, 42 | 43 | insertSnippet: snippet.insertSnippet, 44 | addSnippets: snippet.addSnippets, 45 | 46 | createBlockTemplate: block.createBlock, 47 | 48 | jsonToTs: json.jsonToTs, 49 | 50 | getPluginConfig: config.getPluginConfig, 51 | savePluginConfig: config.savePluginConfig, 52 | getSyncFolder: config.getSyncFolderConfig, 53 | saveSyncFolder: config.saveSyncFolderConfig, 54 | 55 | refreshIntelliSense: intelliSense.refreshIntelliSense, 56 | request: reqeust.axiosRequest, 57 | 58 | askChatGPT: openai.askChatGPT, 59 | askChatGPTWithEjsTemplate: openai.askChatGPTWithEjsTemplate, 60 | insertCode: openai.insertCode, 61 | exportChatGPTContent: openai.exportChatGPTContent, 62 | 63 | getTask: task.getTask, 64 | 65 | runScript: script.runScript, 66 | 67 | putClipboardImage: task.putClipboardImage, 68 | }; 69 | -------------------------------------------------------------------------------- /src/webview/type.ts: -------------------------------------------------------------------------------- 1 | export interface IMessage { 2 | cmd: string; 3 | cbid: string; 4 | data: T; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "Node", 4 | "module": "commonjs", 5 | "target": "ES5", 6 | "outDir": "build", 7 | "allowSyntheticDefaultImports": true, 8 | "suppressImplicitAnyIndexErrors": true, 9 | "esModuleInterop": true, 10 | "lib": [ 11 | "ES5" 12 | ], 13 | "sourceMap": true, 14 | "rootDir": "src", 15 | "strict": true /* enable all strict type-checking options */ 16 | /* Additional Checks */ 17 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 18 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 19 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | ".vscode-test", 24 | "webview-vue", 25 | "webview-react", 26 | "webview-dist", 27 | "webview-vue-webpack", 28 | "test", 29 | "test.ts" 30 | ] 31 | } -------------------------------------------------------------------------------- /webview-react/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /webview-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /package-lock.json 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | 15 | # umi 16 | /src/.umi 17 | /src/.umi-production 18 | /src/.umi-test 19 | /.env.local 20 | -------------------------------------------------------------------------------- /webview-react/.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /webview-react/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /webview-react/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 85, 5 | "arrowParens": "always", 6 | "overrides": [ 7 | { 8 | "files": ".prettierrc", 9 | "options": { 10 | "parser": "json" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /webview-react/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | import path from 'path'; 3 | export default defineConfig({ 4 | nodeModulesTransform: { 5 | type: 'none', 6 | }, 7 | routes: [ 8 | { 9 | path: '/scaffold', 10 | component: '@/pages/scaffold/List', 11 | wrappers: [ 12 | '@/components/RouteWrapper', 13 | ], 14 | }, 15 | { 16 | path: '/downloadMaterials', 17 | component: '@/pages/downloadMaterials', 18 | wrappers: [ 19 | '@/components/RouteWrapper', 20 | ], 21 | }, 22 | { 23 | path: '/chatGPT', 24 | component: '@/pages/chatGPT', 25 | wrappers: [ 26 | '@/components/RouteWrapper', 27 | ], 28 | }, 29 | { 30 | path: '/getClipboardImage', 31 | component: '@/pages/getClipboardImage', 32 | }, 33 | { 34 | path: '/', 35 | component: '@/layout/index', 36 | wrappers: [ 37 | '@/components/RouteWrapper', 38 | ], 39 | routes: [ 40 | { 41 | path: '/config', 42 | component: '@/pages/config', 43 | }, 44 | { 45 | path: '/blocks', 46 | component: '@/pages/blocks/List', 47 | }, 48 | { 49 | path: '/blocks/detail/:name', 50 | component: '@/pages/blocks/Detail', 51 | }, 52 | { 53 | path: '/snippets', 54 | component: '@/pages/snippets/List', 55 | }, 56 | { 57 | path: '/snippets/add/:time', 58 | component: '@/pages/snippets/AddSnippet', 59 | }, 60 | { 61 | path: '/snippets/detail/:name', 62 | component: '@/pages/snippets/Detail', 63 | }, 64 | ], 65 | }, 66 | ], 67 | outputPath: '../webview-dist', 68 | //mpa: { path: '/', component: '@/pages/index' }, 69 | chunks: ['vendors', 'main'], 70 | chainWebpack: function(config, { webpack }) { 71 | config.merge({ 72 | optimization: { 73 | splitChunks: { 74 | cacheGroups: { 75 | vendors: { 76 | name: 'vendors', 77 | test: /[\\/]node_modules[\\/]/, 78 | chunks: 'all' 79 | } 80 | } 81 | }, 82 | }, 83 | }); 84 | }, 85 | styleLoader: {}, 86 | plugins: [path.join(__dirname, 'plugin')], 87 | antd: { 88 | dark: false, 89 | }, 90 | cssLoader: { 91 | localsConvention: 'camelCase', 92 | }, 93 | }); 94 | -------------------------------------------------------------------------------- /webview-react/README.md: -------------------------------------------------------------------------------- 1 | # umi project 2 | 3 | ## Getting Started 4 | 5 | Install dependencies, 6 | 7 | ```bash 8 | $ yarn 9 | ``` 10 | 11 | Start the dev server, 12 | 13 | ```bash 14 | $ yarn start 15 | ``` 16 | -------------------------------------------------------------------------------- /webview-react/mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lowcoding/lowcode-vscode/35a1ca64c3d717b6e600d75cf1e7900d9ae48b7e/webview-react/mock/.gitkeep -------------------------------------------------------------------------------- /webview-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "umi dev", 5 | "build": "umi build", 6 | "postinstall": "umi generate tmp", 7 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 8 | "test": "umi-test", 9 | "test:coverage": "umi-test --coverage" 10 | }, 11 | "gitHooks": { 12 | "pre-commit": "lint-staged" 13 | }, 14 | "lint-staged": { 15 | "*.{js,jsx,less,md,json}": [ 16 | "prettier --write" 17 | ], 18 | "*.ts?(x)": [ 19 | "prettier --parser=typescript --write" 20 | ] 21 | }, 22 | "dependencies": { 23 | "@ant-design/pro-layout": "^5.0.12", 24 | "@formily/antd": "^2.2.22", 25 | "@formily/core": "^2.2.22", 26 | "@formily/react": "^2.2.22", 27 | "@umijs/preset-react": "1.x", 28 | "@umijs/test": "^3.2.19", 29 | "add": "^2.0.6", 30 | "ahooks": "^2.9.4", 31 | "amis": "^2.9.0", 32 | "antd": "4.16.7", 33 | "codemirror": "^5.58.1", 34 | "form-render": "^1.9.2", 35 | "highlight.js": "^11.8.0", 36 | "immer": "^8.0.1", 37 | "jsonlint": "^1.6.3", 38 | "lint-staged": "^10.0.7", 39 | "marked": "^5.0.2", 40 | "marked-highlight": "^2.0.0", 41 | "mitt": "^3.0.0", 42 | "prettier": "^1.19.1", 43 | "react": "^16.12.0", 44 | "react-dom": "^16.12.0", 45 | "swiper": "^9.2.2", 46 | "umi": "^3.2.19", 47 | "use-immer": "^0.9.0", 48 | "viewerjs": "^1.11.3", 49 | "yarn": "^1.22.19", 50 | "yorkie": "^2.0.0", 51 | "zustand": "^4.3.8" 52 | }, 53 | "devDependencies": { 54 | "@types/codemirror": "^0.0.98", 55 | "@types/marked": "^5.0.0", 56 | "script-loader": "^0.7.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /webview-react/plugin.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { IApi } from 'umi'; 3 | 4 | export default function renamePlugin(api: IApi) { 5 | return api.chainWebpack(config => { 6 | config.entry('main').add(path.join(api.paths.absTmpPath!, 'umi.ts')); 7 | // 移除旧的 8 | config.entryPoints.delete('umi'); 9 | return config; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /webview-react/src/app.less: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | #root { 4 | height: 100%; 5 | } 6 | background: #f0f2f5 !important; 7 | } 8 | html, 9 | body { 10 | width: 100%; 11 | min-height: 100vh !important; 12 | height: auto !important; 13 | } 14 | -------------------------------------------------------------------------------- /webview-react/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { history } from 'umi'; 2 | import './app.less'; 3 | // export function render(oldRender: () => {}) { 4 | // history.push('/snippets'); 5 | // oldRender(); 6 | // } 7 | -------------------------------------------------------------------------------- /webview-react/src/components/CodeMirror/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import * as codemirror from 'codemirror'; 3 | 4 | require('script-loader!jsonlint'); 5 | require('codemirror/mode/javascript/javascript.js'); 6 | require('codemirror/addon/lint/lint.js'); 7 | // require('codemirror/addon/lint/javascript-lint.js'); 8 | require('codemirror/addon/lint/json-lint.js'); 9 | require('codemirror/lib/codemirror.css'); 10 | require('codemirror/theme/monokai.css'); 11 | require('codemirror/addon/lint/lint.css'); 12 | 13 | interface IProps { 14 | domId: string; 15 | onChange?: (value: string) => void; 16 | defaultValue?: string; 17 | value?: string; 18 | mode?: 'application/json' | 'javascript'; 19 | lint?: boolean; 20 | } 21 | 22 | const CodeMirror: React.FC = ({ 23 | domId, 24 | onChange, 25 | defaultValue, 26 | value = '', 27 | mode = 'application/json', 28 | lint = false, 29 | }) => { 30 | const codeMirrorInstant = useRef(); 31 | useEffect(() => { 32 | codeMirrorInstant.current = codemirror.fromTextArea( 33 | document.getElementById(domId) as any, 34 | { 35 | value: defaultValue || value, 36 | // lineNumbers: true, 37 | mode, 38 | // gutters: ['CodeMirror-lint-markers'], 39 | lint, 40 | theme: 'monokai', 41 | }, 42 | ); 43 | if (typeof onChange === 'function') { 44 | codeMirrorInstant.current.on('change', () => { 45 | const value = codeMirrorInstant.current!.getValue(); 46 | if (mode === 'application/json' && lint) { 47 | try { 48 | JSON.parse(value); 49 | onChange(value); 50 | } catch (ex) {} 51 | } else { 52 | onChange(value); 53 | } 54 | }); 55 | } 56 | }, []); 57 | useEffect(() => { 58 | if (value !== codeMirrorInstant.current?.getValue()) { 59 | codeMirrorInstant.current?.setValue(value); 60 | } 61 | }, [value]); 62 | return ; 63 | }; 64 | export default CodeMirror; 65 | -------------------------------------------------------------------------------- /webview-react/src/components/DownloadMaterials/api.ts: -------------------------------------------------------------------------------- 1 | import { request } from 'umi'; 2 | 3 | export interface IFetchMaterialRepositoryListResult { 4 | git: { 5 | title: string; 6 | repository: string; 7 | }[]; 8 | npm: { 9 | title: string; 10 | repository: string; 11 | }[]; 12 | } 13 | 14 | export function fetchMaterialRepositoryList() { 15 | return request( 16 | `https://fastly.jsdelivr.net/gh/lowcoding/material@latest/index.json`, 17 | { 18 | method: 'GET', 19 | skipErrorHandler: true, 20 | }, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /webview-react/src/components/Footer/index.less: -------------------------------------------------------------------------------- 1 | .footer { 2 | position: fixed; 3 | bottom: 0px; 4 | left: 0px; 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | height: 50px; 10 | background-color: #ffffff; 11 | padding: 10px; 12 | z-index: 999; 13 | :global { 14 | .ant-space-item { 15 | width: 100%; 16 | } 17 | } 18 | } 19 | .placeholder { 20 | width: 100%; 21 | height: 50px; 22 | } 23 | -------------------------------------------------------------------------------- /webview-react/src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import styles from './index.less'; 3 | 4 | const Footer: FC = (props) => ( 5 |
6 |
{props.children}
7 |
8 |
9 | ); 10 | 11 | export default Footer; 12 | -------------------------------------------------------------------------------- /webview-react/src/components/HeaderControl/components/ConfigSyncFolder/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Form, Input, Modal } from 'antd'; 3 | import { usePresenter } from './presenter'; 4 | 5 | const ConfigSyncFolder = () => { 6 | const presenter = usePresenter(); 7 | const { model } = presenter; 8 | 9 | return ( 10 | { 14 | presenter.syncFolderModal.setVisible(false); 15 | }} 16 | onOk={presenter.handleOk} 17 | okButtonProps={{ loading: model.loading, disabled: !model.syncFolder }} 18 | okText="确定" 19 | cancelText="取消" 20 | zIndex={999} 21 | > 22 |
23 | 24 |
25 | 31 | 38 |
39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default ConfigSyncFolder; 46 | -------------------------------------------------------------------------------- /webview-react/src/components/HeaderControl/components/ConfigSyncFolder/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [syncFolder, setSyncFolder] = useState(''); 5 | const [loading, setLoading] = useState(false); 6 | 7 | return { 8 | syncFolder, 9 | setSyncFolder, 10 | loading, 11 | setLoading, 12 | }; 13 | }; 14 | 15 | export type Model = ReturnType; 16 | -------------------------------------------------------------------------------- /webview-react/src/components/HeaderControl/components/ConfigSyncFolder/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { useModel as useUmiModel } from 'umi'; 2 | import { message } from 'antd'; 3 | import { useEffect } from 'react'; 4 | import Service from './service'; 5 | import { useModel } from './model'; 6 | import { 7 | selectDirectory, 8 | saveSyncFolder, 9 | getSyncFolder, 10 | openUriByVscode, 11 | } from '@/webview/service'; 12 | 13 | export const usePresenter = () => { 14 | const model = useModel(); 15 | const service = new Service(model); 16 | const syncFolderModal = useUmiModel('syncFolder'); 17 | 18 | useEffect(() => { 19 | if (syncFolderModal.visible) { 20 | getSyncFolder().then((res) => { 21 | model.setSyncFolder(res); 22 | }); 23 | } 24 | }, [syncFolderModal.visible]); 25 | 26 | const handleselectDirectory = () => { 27 | model.setLoading(true); 28 | selectDirectory() 29 | .then((res) => { 30 | if (res) { 31 | model.setSyncFolder(res); 32 | } 33 | }) 34 | .finally(() => { 35 | model.setLoading(false); 36 | }); 37 | }; 38 | 39 | const handleOpenUriByVscode = () => { 40 | openUriByVscode(model.syncFolder); 41 | }; 42 | 43 | const handleOk = () => { 44 | saveSyncFolder(model.syncFolder).then(() => { 45 | message.success('保存成功'); 46 | syncFolderModal.setVisible(false); 47 | }); 48 | }; 49 | 50 | return { 51 | model, 52 | syncFolderModal, 53 | handleselectDirectory, 54 | handleOk, 55 | handleOpenUriByVscode, 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /webview-react/src/components/HeaderControl/components/ConfigSyncFolder/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/components/HeaderControl/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [blockModal, setBlockModal] = useState({ 5 | visible: false, 6 | name: '', 7 | schemaType: 'amis', 8 | private: false, 9 | processing: false, 10 | }); 11 | 12 | return { 13 | blockModal, 14 | setBlockModal, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /webview-react/src/components/JsonToTs/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Modal, Form, Input } from 'antd'; 3 | import CodeMirror from '../CodeMirror'; 4 | import { callVscode } from '@/webview'; 5 | 6 | interface IProps { 7 | visible: boolean; 8 | json: object; 9 | onCancel: () => void; 10 | onOk: (tsType: string) => void; 11 | } 12 | 13 | const JsonToTs: React.FC = ({ visible, json, onCancel, onOk }) => { 14 | const [formData, setFormData] = useState<{ 15 | json: string; 16 | type: string; 17 | typeName: string; 18 | }>({ 19 | json: JSON.stringify(json, null, 2), 20 | type: '', 21 | typeName: '', 22 | }); 23 | 24 | useEffect(() => {}, []); 25 | 26 | useEffect(() => { 27 | if (visible) { 28 | setFormData((s) => ({ 29 | ...s, 30 | json: JSON.stringify(json, null, 2), 31 | })); 32 | } 33 | }, [visible]); 34 | 35 | useEffect(() => { 36 | if (formData.json && visible) { 37 | callVscode( 38 | { 39 | cmd: 'jsonToTs', 40 | data: { 41 | json: JSON.parse(formData.json), 42 | typeName: formData.typeName, 43 | }, 44 | }, 45 | (type: string) => { 46 | setFormData((s) => ({ 47 | ...s, 48 | type, 49 | })); 50 | }, 51 | () => {}, 52 | ); 53 | } 54 | }, [formData.json, formData.typeName]); 55 | 56 | return ( 57 | { 61 | onCancel(); 62 | }} 63 | onOk={() => { 64 | onOk(formData.type); 65 | }} 66 | okText="确定" 67 | cancelText="取消" 68 | > 69 |
70 | 71 | { 76 | setFormData((s) => ({ 77 | ...s, 78 | json: value, 79 | })); 80 | }} 81 | /> 82 | 83 | 84 | { 88 | const { value } = e.target; 89 | setFormData((s) => ({ 90 | ...s, 91 | typeName: value, 92 | })); 93 | }} 94 | /> 95 | 96 | 97 | 98 | { 102 | setFormData((s) => ({ 103 | ...s, 104 | type: value, 105 | })); 106 | }} 107 | /> 108 | 109 |
110 |
111 | ); 112 | }; 113 | 114 | export default JsonToTs; 115 | -------------------------------------------------------------------------------- /webview-react/src/components/Marked/index.less: -------------------------------------------------------------------------------- 1 | .marked { 2 | .header { 3 | height: 45px; 4 | } 5 | :global { 6 | pre { 7 | position: relative; 8 | } 9 | code { 10 | border-radius: 8px; 11 | } 12 | .div-copy { 13 | position: absolute; 14 | top: 0; 15 | right: 0; 16 | opacity: 0.5; 17 | } 18 | .div-copy:hover { 19 | opacity: 1; 20 | } 21 | 22 | .div-copy .icon-copy { 23 | opacity: 0; 24 | transition: opacity 0.3s; 25 | height: 15px; 26 | width: 15px; 27 | cursor: pointer; 28 | padding: 10px; 29 | box-sizing: content-box; 30 | } 31 | 32 | .div-copy.active .icon-copy { 33 | opacity: 1; 34 | } 35 | 36 | // insert 37 | .div-insert { 38 | position: absolute; 39 | top: 0; 40 | right: 35px; 41 | opacity: 0.5; 42 | } 43 | .div-insert:hover { 44 | opacity: 1; 45 | } 46 | 47 | .div-insert .icon-insert { 48 | opacity: 0; 49 | transition: opacity 0.3s; 50 | height: 23px; 51 | width: 23px; 52 | cursor: pointer; 53 | padding: 5px; 54 | box-sizing: content-box; 55 | } 56 | 57 | .div-insert.active .icon-insert { 58 | opacity: 1; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /webview-react/src/components/Marked/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { message } from 'antd'; 3 | import { marked } from 'marked'; 4 | import { markedHighlight } from 'marked-highlight'; 5 | import 'highlight.js/styles/agate.css'; 6 | import hljs from 'highlight.js'; 7 | import { useState } from '@/hooks/useImmer'; 8 | import styles from './index.less'; 9 | import { insertCode } from '@/webview/service'; 10 | 11 | marked.use({ 12 | mangle: false, 13 | headerIds: false, 14 | }); 15 | 16 | marked.use( 17 | markedHighlight({ 18 | langPrefix: 'hljs language-', 19 | highlight(code, lang) { 20 | const language = hljs.getLanguage(lang) ? lang : 'plaintext'; 21 | return hljs.highlight(code, { language }).value; 22 | }, 23 | }), 24 | ); 25 | 26 | interface IProps { 27 | text: string; 28 | complete?: boolean; 29 | } 30 | 31 | const Marked: React.FC = (props) => { 32 | const [parsedContent, setParsedContent] = useState(''); 33 | const containerRef = useRef(null); 34 | 35 | useEffect(() => { 36 | const inCodeBlock = 37 | props.text.includes('```') && props.text.split('```').length % 2 === 0; 38 | setParsedContent(marked.parse(props.text + (inCodeBlock ? '\n```' : ''))); 39 | if (props.complete) { 40 | setTimeout(() => { 41 | addCodeCopyBtn(); 42 | addCodeInsertBtn(); 43 | }, 200); 44 | } 45 | }, [props.text, props.complete]); 46 | 47 | const addCodeCopyBtn = () => { 48 | const div = document.createElement('div'); 49 | div.innerHTML = 50 | '
'; 51 | div.className = 'div-copy'; 52 | 53 | const allPres = containerRef.current?.querySelectorAll('pre'); 54 | allPres?.forEach((pre) => { 55 | const copy = div.cloneNode(true) as HTMLDivElement; 56 | pre.appendChild(copy); 57 | pre.addEventListener('mouseover', () => { 58 | copy.classList.add('active'); 59 | }); 60 | pre.addEventListener('mouseleave', () => { 61 | copy.classList.remove('active'); 62 | }); 63 | copy.onclick = function () { 64 | navigator.clipboard.writeText(pre.textContent || ''); 65 | message.success({ 66 | content: '内容已写入剪切板', 67 | }); 68 | }; 69 | }); 70 | }; 71 | 72 | const addCodeInsertBtn = () => { 73 | const div = document.createElement('div'); 74 | div.innerHTML = 75 | '
'; 76 | div.className = 'div-insert'; 77 | 78 | const allPres = containerRef.current?.querySelectorAll('pre'); 79 | allPres?.forEach((pre) => { 80 | const insert = div.cloneNode(true) as HTMLDivElement; 81 | pre.appendChild(insert); 82 | pre.addEventListener('mouseover', () => { 83 | insert.classList.add('active'); 84 | }); 85 | pre.addEventListener('mouseleave', () => { 86 | insert.classList.remove('active'); 87 | }); 88 | insert.onclick = function () { 89 | insertCode(pre.textContent || '').then(() => { 90 | message.success({ 91 | content: '内容已插入', 92 | }); 93 | }); 94 | }; 95 | }); 96 | }; 97 | 98 | return ( 99 |
100 | {/*
1212
*/} 101 |
102 |
103 | ); 104 | }; 105 | 106 | export default Marked; 107 | -------------------------------------------------------------------------------- /webview-react/src/components/RouteWrapper/index.less: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | height: 90vh; 3 | } 4 | -------------------------------------------------------------------------------- /webview-react/src/components/RouteWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useLocation, history } from 'umi'; 3 | import { Spin, message as antdMessage } from 'antd'; 4 | import styles from './index.less'; 5 | import { getTask } from '@/webview/service'; 6 | import { taskHandler } from '@/webview/handleTask'; 7 | 8 | const RouteWrapper: React.FC = (props) => { 9 | const location = useLocation(); 10 | 11 | useEffect(() => { 12 | if (location.pathname === '/index.html' || location.pathname === '/') { 13 | // 初始化,获取 task,进行路由跳转 14 | getTask().then((res) => { 15 | if (res && res.task) { 16 | if (taskHandler[res.task]) { 17 | taskHandler[res.task](res.data); 18 | } else { 19 | antdMessage.error(`未找到名为 ${res.task} 回调方法!`); 20 | } 21 | } else { 22 | history.push('/snippets'); 23 | } 24 | }); 25 | } 26 | }, []); 27 | 28 | if (location.pathname === '/index.html' || location.pathname === '/') { 29 | return ( 30 | 31 |
; 32 |
33 | ); 34 | } 35 | return <>{props.children}; 36 | }; 37 | 38 | export default RouteWrapper; 39 | -------------------------------------------------------------------------------- /webview-react/src/components/RunScript/index.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input, Modal, Select } from 'antd'; 2 | import React, { useEffect } from 'react'; 3 | import { useState } from '@/hooks/useImmer'; 4 | import { runScript } from '@/webview/service'; 5 | import { getClipboardImage } from '@/utils/clipboard'; 6 | 7 | interface IProps { 8 | visible: boolean; 9 | materialPath: string; 10 | model: object; 11 | scripts?: [{ method: string; remark: string; readClipboardImage?: boolean }]; 12 | privateMaterials?: boolean; 13 | onCancel: () => void; 14 | onOk: (result: { 15 | /** 立即更新 model */ 16 | updateModelImmediately: boolean; 17 | /** 仅更新参数 */ 18 | onlyUpdateParams: boolean; 19 | /** 要更新的参数 */ 20 | params?: string; 21 | model: object; 22 | }) => void; 23 | } 24 | 25 | const RunScript: React.FC = (props) => { 26 | const [script, setScript] = useState(''); 27 | const [params, setParams] = useState(''); 28 | const [loading, setLoading] = useState(false); 29 | 30 | useEffect(() => { 31 | if (props.visible) { 32 | setLoading(false); 33 | } 34 | }, [props.visible]); 35 | 36 | const handleOk = async () => { 37 | setLoading(true); 38 | const image = await getClipboardImage().catch((e) => console.log(e)); 39 | runScript({ 40 | script, 41 | params, 42 | clipboardImage: image || '', 43 | model: props.model, 44 | materialPath: props.materialPath, 45 | privateMaterials: props.privateMaterials, 46 | createBlockPath: localStorage.getItem('selectedFolder') || undefined, 47 | }) 48 | .then((result) => { 49 | if (result.model) { 50 | if (result.onlyUpdateParams) { 51 | setParams(result.params || ''); 52 | } else { 53 | props.onOk(result); 54 | } 55 | } else { 56 | props.onOk({ 57 | updateModelImmediately: false, 58 | onlyUpdateParams: false, 59 | model: result, // 旧版本只返回 model 60 | }); 61 | } 62 | }) 63 | .finally(() => { 64 | setLoading(false); 65 | }); 66 | }; 67 | 68 | return ( 69 | 79 |
80 | 81 | 94 | 95 | 96 | { 100 | const value = e.target.value; 101 | setParams(value); 102 | }} 103 | > 104 | 105 |
106 |
107 | ); 108 | }; 109 | 110 | export default RunScript; 111 | -------------------------------------------------------------------------------- /webview-react/src/components/SelectDirectory/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Modal, Form, TreeSelect, Select, message } from 'antd'; 3 | import { callVscode } from '@/webview'; 4 | 5 | interface Iprops { 6 | visible: boolean; 7 | loading?: boolean; 8 | onCancel: () => void; 9 | onOk: (path: string, createPath: string[]) => void; 10 | } 11 | 12 | type DirectoryTreeNode = { 13 | title: string; 14 | value: string; 15 | children?: DirectoryTreeNode[]; 16 | }; 17 | 18 | type OriDirectoryTreeNode = { 19 | path: string; 20 | name: string; 21 | size: number; 22 | extension: string; 23 | type: 'file' | 'directory'; 24 | children?: OriDirectoryTreeNode[]; 25 | }; 26 | 27 | const formatTreeData = (node: OriDirectoryTreeNode) => { 28 | const formatedNode: DirectoryTreeNode = { 29 | title: node.name, 30 | value: node.path, 31 | }; 32 | formatedNode.children = node.children 33 | ?.filter((s) => s.type === 'directory') 34 | .map((s) => formatTreeData(s)); 35 | return formatedNode; 36 | }; 37 | 38 | const SelectDirectory: React.FC = ({ 39 | visible, 40 | onOk, 41 | onCancel, 42 | loading, 43 | }) => { 44 | const [tree, setTree] = useState([]); 45 | const [formData, setFormData] = useState<{ 46 | path: string; 47 | createPath: string[]; 48 | }>({} as any); 49 | useEffect(() => { 50 | if (visible) { 51 | callVscode({ cmd: 'getDirectoryTree' }, (data: OriDirectoryTreeNode) => { 52 | const formatedTree = data.children 53 | ?.filter((s) => s.type === 'directory') 54 | .map((s) => formatTreeData(s)); 55 | setTree(formatedTree || []); 56 | const selectedFolder = localStorage.getItem('selectedFolder'); 57 | setFormData((s) => ({ 58 | ...s, 59 | path: selectedFolder as string, 60 | })); 61 | }); 62 | } 63 | setFormData({} as any); 64 | }, [visible]); 65 | 66 | return ( 67 | { 75 | if (loading) { 76 | return; 77 | } 78 | onCancel(); 79 | }} 80 | okButtonProps={{ loading }} 81 | onOk={() => { 82 | if (!formData.path) { 83 | message.error('未选择目录'); 84 | return; 85 | } 86 | onOk(formData.path, formData.createPath); 87 | }} 88 | > 89 |
90 | 91 | { 98 | setFormData((s) => ({ 99 | ...s, 100 | path: value as string, 101 | })); 102 | }} 103 | /> 104 | 105 | 106 | { 43 | const value = e.target.value; 44 | setTitle(value); 45 | }} 46 | > 47 | 48 | ); 49 | }; 50 | 51 | export default UpdateSeesionTitle; 52 | -------------------------------------------------------------------------------- /webview-react/src/pages/chatGPT/model.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | import { useState } from '@/hooks/useImmer'; 3 | 4 | export const useModel = () => { 5 | const [inputChatPrompt, setInputChatPrompt] = useState(''); 6 | 7 | const listRef = useRef(null); 8 | 9 | const [loading, setLoading] = useState(false); 10 | 11 | const [listVisible, setListVisible] = useState(false); 12 | 13 | const [updateTitleVisible, setUpdateTitleVisible] = useState(false); 14 | 15 | return { 16 | inputChatPrompt, 17 | setInputChatPrompt, 18 | listRef, 19 | loading, 20 | setLoading, 21 | listVisible, 22 | setListVisible, 23 | updateTitleVisible, 24 | setUpdateTitleVisible, 25 | }; 26 | }; 27 | 28 | export type Model = ReturnType; 29 | -------------------------------------------------------------------------------- /webview-react/src/pages/chatGPT/service.ts: -------------------------------------------------------------------------------- 1 | import { askChatGPT } from '@/webview/service'; 2 | import { Model } from './model'; 3 | import { ChatMessage, ChatStore } from './store'; 4 | 5 | export default class Service { 6 | private model: Model; 7 | 8 | private chatStore: ChatStore; 9 | 10 | constructor(model: Model, chatStore: ChatStore) { 11 | this.model = model; 12 | this.chatStore = chatStore; 13 | } 14 | 15 | startAsk(type: 'NewSessionWithPrompt' | 'NewMessage', prompt: string) { 16 | let sessionId = 0; 17 | let messageId = 0; 18 | let messages: Pick[] = []; 19 | if (type === 'NewSessionWithPrompt') { 20 | const session = this.chatStore.newSessionWithPrompt(prompt); 21 | sessionId = session.id; 22 | messageId = session.messages[0].id; 23 | messages = session.messages.filter((s) => s.content && s.asContext); 24 | } else if (type === 'NewMessage') { 25 | const session = this.chatStore.newMessage(prompt); 26 | sessionId = session.id; 27 | messageId = session.messages[session.messages.length - 1].id; 28 | messages = session.messages.filter((s) => s.content && s.asContext); 29 | } 30 | askChatGPT({ 31 | sessionId, 32 | messageId, 33 | messages: messages.map((s) => ({ 34 | content: s.content, 35 | role: s.role, 36 | })), 37 | }).finally(() => { 38 | this.chatStore.updateMessageLoading(sessionId, messageId); 39 | }); 40 | // this.model.listRef.current?.scrollTo( 41 | // 0, 42 | // this.model.listRef.current.scrollHeight, 43 | // ); 44 | // setTimeout(() => { 45 | // this.model.setCurrent((s) => { 46 | // s.prompt = prompt; 47 | // s.res = ''; 48 | // }); 49 | // globalPrompt = prompt; 50 | // globalRes = ''; 51 | // }, 50); 52 | // setTimeout(() => { 53 | // this.model.listRef.current?.scrollTo( 54 | // 0, 55 | // this.model.listRef.current.scrollHeight, 56 | // ); 57 | // }, 2000); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /webview-react/src/pages/config/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [formData, setFormDate] = useState({ 5 | yapi: { 6 | domain: '', 7 | projects: [ 8 | { 9 | name: '', 10 | token: '', 11 | domain: '', 12 | }, 13 | ], 14 | }, 15 | mock: { 16 | mockNumber: '', 17 | mockBoolean: '', 18 | mockString: '', 19 | mockKeyWordEqual: [ 20 | { 21 | key: '', 22 | value: '', 23 | }, 24 | ], 25 | mockKeyWordLike: [ 26 | { 27 | key: '', 28 | value: '', 29 | }, 30 | ], 31 | }, 32 | }); 33 | 34 | return { 35 | formData, 36 | setFormDate, 37 | }; 38 | }; 39 | 40 | export type Model = ReturnType; 41 | -------------------------------------------------------------------------------- /webview-react/src/pages/config/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { useForm } from 'form-render'; 2 | import React, { useEffect } from 'react'; 3 | import { getLocalMaterials, getPluginConfig } from '@/webview/service'; 4 | import Service from './service'; 5 | import { useModel } from './model'; 6 | 7 | export const usePresenter = () => { 8 | const model = useModel(); 9 | const service = new Service(model); 10 | const form = useForm(); 11 | 12 | useEffect(() => { 13 | getLocalMaterials('blocks').then((res) => { 14 | form.setSchemaByPath('commonlyUsedBlock', { 15 | enum: res.map((s) => s.name), 16 | enumNames: res.map((s) => s.name), 17 | }); 18 | }); 19 | getPluginConfig().then((data) => { 20 | model.setFormDate(data); 21 | form.setValues(data); 22 | }); 23 | }, []); 24 | 25 | const watch = { 26 | '#': (val: any) => { 27 | model.setFormDate(JSON.parse(JSON.stringify(val))); 28 | }, 29 | }; 30 | 31 | return { 32 | model, 33 | service, 34 | form, 35 | watch, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /webview-react/src/pages/config/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/downloadMaterials/index.less: -------------------------------------------------------------------------------- 1 | .page { 2 | .snippets-materials-item { 3 | position: relative; 4 | width: 100%; 5 | height: 200px; 6 | background-color: black; 7 | cursor: pointer; 8 | .snippets-materials-item-bg { 9 | position: absolute; 10 | width: 100%; 11 | height: 100%; 12 | top: 0; 13 | left: 0; 14 | z-index: 0; 15 | opacity: 0.4; 16 | } 17 | .badge { 18 | position: absolute; 19 | right: 0px; 20 | top: 0px; 21 | width: 0px; 22 | height: 0px; 23 | border-width: 0px 40px 40px 0px; 24 | border-style: solid; 25 | border-color: transparent #7ab7a7; 26 | .tick { 27 | position: absolute; 28 | color: white; 29 | top: -1px; 30 | right: -34px; 31 | font-size: 15px; 32 | font-weight: bold; 33 | } 34 | } 35 | .snippets-materials-item-wrapper { 36 | position: relative; 37 | display: flex; 38 | flex-direction: column; 39 | width: 100%; 40 | height: 100%; 41 | .scroll { 42 | display: flex; 43 | flex: 1; 44 | overflow: auto; 45 | .content { 46 | margin: auto; 47 | width: 100%; 48 | color: #ffffff; 49 | text-align: center; 50 | padding: 10px 20px; 51 | .title { 52 | font-weight: bold; 53 | word-break: break-all; 54 | } 55 | .remark { 56 | margin-top: 10px; 57 | word-break: break-all; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | .snippets-materials-item_ckecked { 64 | .snippets-materials-item-bg { 65 | opacity: 0.7; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /webview-react/src/pages/downloadMaterials/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | import { IDownloadMaterialsResult } from '@/webview/service'; 3 | 4 | export const useModel = () => { 5 | const [tab, setTab] = useState<'blocks' | 'snippets'>('snippets'); 6 | const [downloadMaterialsVisible, setDownloadMaterialsVisible] = 7 | useState(true); 8 | 9 | const [materials, setMaterials] = useState({ 10 | blocks: [], 11 | snippets: [], 12 | }); 13 | 14 | const [selectedMaterials, setSelectedMaterials] = useState<{ 15 | blocks: string[]; 16 | snippets: string[]; 17 | }>({ 18 | blocks: [], 19 | snippets: [], 20 | }); 21 | 22 | return { 23 | tab, 24 | setTab, 25 | downloadMaterialsVisible, 26 | setDownloadMaterialsVisible, 27 | materials, 28 | setMaterials, 29 | selectedMaterials, 30 | setSelectedMaterials, 31 | }; 32 | }; 33 | 34 | export type Model = ReturnType; 35 | -------------------------------------------------------------------------------- /webview-react/src/pages/downloadMaterials/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import { 3 | IDownloadMaterialsResult, 4 | saveDownloadMaterials, 5 | } from '@/webview/service'; 6 | import { useModel } from './model'; 7 | import Service from './service'; 8 | 9 | export const usePresenter = () => { 10 | const model = useModel(); 11 | const service = new Service(model); 12 | 13 | const handleDownloadMaterialsOk = (materials: IDownloadMaterialsResult) => { 14 | if (materials.blocks.length === 0 && materials.snippets.length === 0) { 15 | message.warn('未设置物料'); 16 | } 17 | model.setDownloadMaterialsVisible(false); 18 | model.setMaterials(materials); 19 | model.setSelectedMaterials({ 20 | blocks: [], 21 | snippets: [], 22 | }); 23 | }; 24 | 25 | const handleCheckItem = (key: string) => { 26 | if (model.tab === 'blocks') { 27 | if (model.selectedMaterials.blocks.includes(key)) { 28 | model.setSelectedMaterials((s) => { 29 | s.blocks = s.blocks.filter((b) => b !== key); 30 | }); 31 | } else { 32 | model.setSelectedMaterials((s) => { 33 | s.blocks = [...s.blocks, key]; 34 | }); 35 | } 36 | } else if (model.selectedMaterials.snippets.includes(key)) { 37 | model.setSelectedMaterials((s) => { 38 | s.snippets = s.snippets.filter((b) => b !== key); 39 | }); 40 | } else { 41 | model.setSelectedMaterials((s) => { 42 | s.snippets = [...s.snippets, key]; 43 | }); 44 | } 45 | }; 46 | 47 | const handleConfirm = () => { 48 | if ( 49 | model.selectedMaterials.blocks.length === 0 && 50 | model.selectedMaterials.snippets.length === 0 51 | ) { 52 | message.warn('请选择物料'); 53 | return; 54 | } 55 | saveDownloadMaterials(model.selectedMaterials).then(() => { 56 | message.success('保存成功'); 57 | }); 58 | }; 59 | 60 | return { 61 | model, 62 | service, 63 | handleDownloadMaterialsOk, 64 | handleCheckItem, 65 | handleConfirm, 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /webview-react/src/pages/downloadMaterials/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/getClipboardImage/index.less: -------------------------------------------------------------------------------- 1 | .getClipboardImage { 2 | height: 100vh; 3 | width: 100vw; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | -------------------------------------------------------------------------------- /webview-react/src/pages/getClipboardImage/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd'; 2 | import React from 'react'; 3 | import styles from './index.less'; 4 | import { getClipboardImage } from '@/utils/clipboard'; 5 | import { putClipboardImage } from '@/webview/service'; 6 | 7 | export default () => { 8 | const handleClick = async () => { 9 | const image = await getClipboardImage(); 10 | putClipboardImage(image); 11 | }; 12 | 13 | return ( 14 |
15 | 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /webview-react/src/pages/home/index.less: -------------------------------------------------------------------------------- 1 | .home { 2 | height: 100vh; 3 | } 4 | -------------------------------------------------------------------------------- /webview-react/src/pages/home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.less'; 3 | 4 | const Home = () =>
; 5 | 6 | export default Home; 7 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/DownloadModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, Checkbox, Select } from 'antd'; 3 | import { usePresenter } from './presenter'; 4 | import FormModal from '../FormModal'; 5 | 6 | interface IProps { 7 | visible: boolean; 8 | onClose: () => void; 9 | } 10 | 11 | const View: React.FC = ({ visible, onClose }) => { 12 | const presenter = usePresenter({ visible, onClose }); 13 | const { model } = presenter; 14 | 15 | return ( 16 | { 20 | if (model.processing) { 21 | return; 22 | } 23 | onClose(); 24 | }} 25 | onOk={() => { 26 | presenter.downloadScaffold(); 27 | }} 28 | okText="确定" 29 | cancelText="取消" 30 | okButtonProps={{ disabled: model.processing, loading: model.processing }} 31 | > 32 | 33 | 34 | 50 | 51 | {model.formData.type && ( 52 | 56 | { 62 | const { value } = e.target; 63 | model.setFormData((s) => ({ 64 | ...s, 65 | url: value, 66 | })); 67 | }} 68 | /> 69 | 70 | )} 71 | 72 | { 76 | model.setFormModal((s) => { 77 | s.visible = false; 78 | }); 79 | if (ok) { 80 | onClose(); 81 | } 82 | }} 83 | /> 84 | 85 | ); 86 | }; 87 | 88 | export default View; 89 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/DownloadModal/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [formData, setFormData] = useState<{ 5 | type: 'git' | 'npm'; 6 | url: string; 7 | }>({} as any); 8 | 9 | const [processing, setProcessing] = useState(false); 10 | 11 | const [formModal, setFormModal] = useState<{ visible: boolean; config: any }>( 12 | { 13 | visible: false, 14 | config: {}, 15 | }, 16 | ); 17 | 18 | return { 19 | formData, 20 | setFormData, 21 | processing, 22 | setProcessing, 23 | formModal, 24 | setFormModal, 25 | }; 26 | }; 27 | 28 | export type Model = ReturnType; 29 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/DownloadModal/presenter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { message } from 'antd'; 3 | import Service from './service'; 4 | import { downloadScaffoldByVsCode } from '@/webview/service'; 5 | import { useModel } from './model'; 6 | 7 | export const usePresenter = (props: { 8 | visible: boolean; 9 | onClose: () => void; 10 | }) => { 11 | const model = useModel(); 12 | const service = new Service(model); 13 | 14 | useEffect(() => { 15 | if (props.visible) { 16 | model.setFormData({} as any); 17 | } 18 | }, [props.visible]); 19 | 20 | const downloadScaffold = () => { 21 | if (!model.formData.type || !model.formData.url) { 22 | message.error('请完善信息'); 23 | return; 24 | } 25 | model.setProcessing(true); 26 | downloadScaffoldByVsCode({ 27 | type: model.formData.type, 28 | repository: model.formData.url, 29 | }) 30 | .then((res) => { 31 | model.setFormModal((s) => { 32 | s.config = res; 33 | s.visible = true; 34 | }); 35 | }) 36 | .finally(() => { 37 | model.setProcessing(false); 38 | }); 39 | }; 40 | 41 | return { 42 | model, 43 | service, 44 | downloadScaffold, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/DownloadModal/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/FormModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, Checkbox } from 'antd'; 3 | import FormRender from 'form-render'; 4 | import { usePresenter } from './presenter'; 5 | 6 | interface IProps { 7 | visible: boolean; 8 | config: { 9 | formSchema?: { schema?: object; formData?: object; [key: string]: any }; 10 | }; 11 | onClose: (ok?: boolean) => void; 12 | } 13 | 14 | const View: React.FC = ({ visible, config, onClose }) => { 15 | const presenter = usePresenter({ visible, config, onClose }); 16 | const { model } = presenter; 17 | 18 | return ( 19 | { 23 | onClose(); 24 | }} 25 | onOk={() => { 26 | presenter.createProjectByVsCode(); 27 | }} 28 | cancelText="取消" 29 | okText="确定" 30 | > 31 | {config.formSchema && ( 32 | 39 | )} 40 |
41 | 42 | { 45 | const { value } = e.target; 46 | model.setConfig((s) => { 47 | s.projectName = value; 48 | }); 49 | }} 50 | /> 51 | 52 | 53 | { 57 | presenter.selectDirectoryByVsCode(); 58 | }} 59 | /> 60 | 61 | 62 | { 65 | const { checked } = e.target; 66 | model.setConfig((s) => { 67 | s.immediateOpen = checked; 68 | }); 69 | }} 70 | /> 71 | 72 |
73 |
74 | ); 75 | }; 76 | 77 | export default View; 78 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/FormModal/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const defaultConfig = { 4 | projectName: '', 5 | createDir: '', 6 | immediateOpen: true, 7 | }; 8 | 9 | export const useModel = () => { 10 | const [formData, setFormData] = useState({}); 11 | 12 | const [config, setConfig] = useState<{ 13 | projectName: string; 14 | createDir: string; 15 | immediateOpen: boolean; 16 | }>(defaultConfig); 17 | 18 | return { 19 | formData, 20 | setFormData, 21 | config, 22 | setConfig, 23 | }; 24 | }; 25 | 26 | export type Model = ReturnType; 27 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/FormModal/presenter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { message } from 'antd'; 3 | import { useForm } from 'form-render'; 4 | import { createProject, selectDirectory } from '@/webview/service'; 5 | import { defaultConfig, useModel } from './model'; 6 | import Service from './service'; 7 | 8 | export const usePresenter = (props: { 9 | visible: boolean; 10 | config: { 11 | formSchema?: { schema?: object; formData?: object; [key: string]: any }; 12 | }; 13 | onClose: (ok?: boolean) => void; 14 | }) => { 15 | const model = useModel(); 16 | const service = new Service(model); 17 | const form = useForm(); 18 | 19 | useEffect(() => { 20 | if (props.visible) { 21 | model.setFormData(props.config.formSchema?.formData || {}); 22 | form.setValues(props.config.formSchema?.formData || {}); 23 | model.setConfig(defaultConfig); 24 | } 25 | }, [props.visible]); 26 | 27 | const watch = { 28 | '#': (val: any) => { 29 | model.setFormData(JSON.parse(JSON.stringify(val))); 30 | }, 31 | }; 32 | 33 | const selectDirectoryByVsCode = () => { 34 | selectDirectory().then((res) => { 35 | model.setConfig((s) => { 36 | s.createDir = res; 37 | }); 38 | }); 39 | }; 40 | 41 | const createProjectByVsCode = () => { 42 | if (!model.config.projectName) { 43 | message.error('请输入项目名称'); 44 | return; 45 | } 46 | if (!model.config.createDir) { 47 | message.error('请选择生成目录'); 48 | return; 49 | } 50 | createProject({ 51 | model: { 52 | ...model.formData, 53 | projectName: model.config.projectName, 54 | }, 55 | immediateOpen: model.config.immediateOpen, 56 | createDir: `${model.config.createDir}/${model.config.projectName}`, 57 | }).then(() => { 58 | message.success('创建成功'); 59 | props.onClose(true); 60 | }); 61 | }; 62 | 63 | return { 64 | model, 65 | service, 66 | selectDirectoryByVsCode, 67 | createProjectByVsCode, 68 | form, 69 | watch, 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/FormModal/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/List/index.less: -------------------------------------------------------------------------------- 1 | .scaffold { 2 | height: 100vh-300px; 3 | .header { 4 | height: 70px; 5 | line-height: 70px; 6 | text-align: left; 7 | margin-right: 50px; 8 | margin-left: 50px; 9 | font-weight: bold; 10 | font-size: 20px; 11 | .control { 12 | text-align: right; 13 | } 14 | } 15 | .content { 16 | padding: 0px 50px; 17 | flex-wrap: nowrap; 18 | .category { 19 | border-right: 2px solid #4494f9; 20 | min-width: 200px; 21 | min-height: 500px; 22 | max-height: 900px; 23 | overflow: auto; 24 | margin-right: 10px; 25 | padding-right: 20px; 26 | margin-bottom: 10px; 27 | .category-item { 28 | position: relative; 29 | display: flex; 30 | height: 36px; 31 | line-height: 36px; 32 | margin-top: 10px; 33 | border: 1px solid #474959; 34 | border-radius: 5px; 35 | padding: 5px 10px; 36 | box-sizing: content-box; 37 | cursor: pointer; 38 | img { 39 | height: 36px; 40 | width: 36px; 41 | } 42 | .title { 43 | font-weight: bold; 44 | margin-left: 5px; 45 | } 46 | .badge { 47 | position: absolute; 48 | right: 4px; 49 | top: 4px; 50 | width: 0px; 51 | height: 0px; 52 | border-width: 0px 28px 28px 0px; 53 | border-style: solid; 54 | border-color: transparent #4494f9; 55 | .tick { 56 | position: absolute; 57 | color: white; 58 | top: -9px; 59 | right: -25px; 60 | font-size: 15px; 61 | } 62 | } 63 | } 64 | .checked-item, 65 | .category-item:hover { 66 | border: 1px solid #4494f9; 67 | } 68 | } 69 | .scaffold { 70 | display: flex; 71 | flex-wrap: wrap; 72 | max-width: 1000px; 73 | margin-top: 10px; 74 | .scaffold-item { 75 | width: 210px; 76 | height: 250px; 77 | margin: 0px 10px 20px 10px; 78 | padding: 10px; 79 | border: 1px solid #474959; 80 | border-radius: 5px; 81 | cursor: pointer; 82 | overflow: auto; 83 | .screenshot { 84 | img { 85 | width: 100%; 86 | height: 120px; 87 | } 88 | } 89 | .title { 90 | font-weight: bold; 91 | line-height: 35px; 92 | font-size: 16px; 93 | } 94 | .description { 95 | font-weight: 14px; 96 | color: #474959; 97 | } 98 | } 99 | .scaffold-item:hover { 100 | border: 1px solid #4494f9; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/List/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [categories, setCategories] = useState< 5 | { name: string; icon: string; uuid: string }[] 6 | >([]); 7 | 8 | const [allScaffolds, setAllScaffolds] = useState< 9 | { 10 | category: string; 11 | title: string; 12 | description: string; 13 | screenshot: string; 14 | repository: string; 15 | repositoryType: 'git' | 'npm'; 16 | uuid: string; 17 | }[] 18 | >([]); 19 | 20 | const [scaffolds, setScaffolds] = useState< 21 | { 22 | category: string; 23 | title: string; 24 | description: string; 25 | screenshot: string; 26 | repository: string; 27 | repositoryType: 'git' | 'npm'; 28 | uuid: string; 29 | }[] 30 | >([]); 31 | 32 | const [currentCategory, setCurrentCategory] = useState(''); 33 | 34 | const [formModal, setFormModal] = useState<{ visible: boolean; config: any }>( 35 | { 36 | visible: false, 37 | config: {}, 38 | }, 39 | ); 40 | 41 | const [downloadVisible, setDownloadVisible] = useState(false); 42 | 43 | const [localProjectModalVisible, setLocalProjectModalVisible] = 44 | useState(false); 45 | 46 | const [loading, setLoading] = useState<{ fetch: boolean; download: boolean }>( 47 | { 48 | fetch: true, 49 | download: false, 50 | }, 51 | ); 52 | return { 53 | loading, 54 | setLoading, 55 | categories, 56 | setCategories, 57 | allScaffolds, 58 | setAllScaffolds, 59 | scaffolds, 60 | setScaffolds, 61 | currentCategory, 62 | setCurrentCategory, 63 | formModal, 64 | setFormModal, 65 | downloadVisible, 66 | setDownloadVisible, 67 | localProjectModalVisible, 68 | setLocalProjectModalVisible, 69 | }; 70 | }; 71 | 72 | export type Model = ReturnType; 73 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/List/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import Service from './service'; 3 | import { downloadScaffoldByVsCode, getScaffolds } from '@/webview/service'; 4 | import useCheckVankeInternal from '@/hooks/useCheckVankeInternal'; 5 | import { useModel } from './model'; 6 | 7 | export const usePresenter = () => { 8 | const model = useModel(); 9 | const service = new Service(model); 10 | const isVankeInternal = useCheckVankeInternal(); 11 | 12 | useEffect(() => { 13 | if (isVankeInternal !== undefined) { 14 | fetchScaffolds(); 15 | } 16 | }, [isVankeInternal]); 17 | 18 | useEffect(() => { 19 | if (model.currentCategory) { 20 | model.setScaffolds( 21 | model.allScaffolds.filter((s) => s.category === model.currentCategory), 22 | ); 23 | } 24 | }, [model.currentCategory]); 25 | 26 | const fetchScaffolds = () => { 27 | model.setLoading((s) => { 28 | s.fetch = true; 29 | }); 30 | const promises: ReturnType[] = [ 31 | getScaffolds( 32 | 'https://gitee.com/lowcoding/scaffold/raw/master/index.json', 33 | ), 34 | ]; 35 | if (isVankeInternal) { 36 | promises.push( 37 | getScaffolds( 38 | 'https://git.vankeservice.com/v0417672/lowcode-scaffolds/raw/master/index.json', 39 | ), 40 | ); 41 | } 42 | Promise.all(promises) 43 | .then((allRes) => { 44 | const res = allRes.flat(); 45 | model.setCategories( 46 | res.map((s) => ({ 47 | name: s.category, 48 | icon: s.icon, 49 | uuid: s.uuid, 50 | })), 51 | ); 52 | const scaffolds: typeof model.allScaffolds = []; 53 | res.map((r) => { 54 | r.scaffolds.map((s) => { 55 | scaffolds.push({ 56 | category: r.uuid, 57 | title: s.title, 58 | description: s.description, 59 | screenshot: s.screenshot, 60 | repository: s.repository, 61 | repositoryType: s.repositoryType, 62 | uuid: s.uuid, 63 | }); 64 | }); 65 | }); 66 | model.setAllScaffolds(scaffolds); 67 | if (res.length > 0) { 68 | model.setCurrentCategory(res[0].uuid); 69 | } 70 | }) 71 | .finally(() => { 72 | model.setLoading((s) => { 73 | s.fetch = false; 74 | }); 75 | }); 76 | }; 77 | 78 | const changeCategory = (uuid: string) => { 79 | if (uuid === model.currentCategory) { 80 | return; 81 | } 82 | model.setCurrentCategory(uuid); 83 | }; 84 | 85 | const downloadScaffold = (config: typeof model.scaffolds[0]) => { 86 | model.setLoading((s) => { 87 | s.download = true; 88 | }); 89 | downloadScaffoldByVsCode({ 90 | repository: config.repository, 91 | type: config.repositoryType, 92 | }) 93 | .then((res) => { 94 | model.setFormModal((s) => { 95 | s.visible = true; 96 | s.config = res; 97 | }); 98 | }) 99 | .finally(() => { 100 | model.setLoading((s) => { 101 | s.download = false; 102 | }); 103 | }); 104 | }; 105 | 106 | return { 107 | model, 108 | service, 109 | fetchScaffolds, 110 | changeCategory, 111 | downloadScaffold, 112 | }; 113 | }; 114 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/List/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/LocalProjectModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input } from 'antd'; 3 | import { usePresenter } from './presenter'; 4 | import FormModal from '../FormModal'; 5 | 6 | interface IProps { 7 | visible: boolean; 8 | onClose: () => void; 9 | } 10 | 11 | const View: React.FC = ({ visible, onClose }) => { 12 | const presenter = usePresenter(); 13 | const { model } = presenter; 14 | return ( 15 | { 19 | if (model.processing) { 20 | return; 21 | } 22 | onClose(); 23 | }} 24 | onOk={presenter.copyLocalScaffold} 25 | okText="确定" 26 | cancelText="取消" 27 | okButtonProps={{ disabled: model.processing, loading: model.processing }} 28 | > 29 |
30 | 31 | 37 | 38 |
39 | { 43 | model.setFormModal((s) => { 44 | s.visible = false; 45 | }); 46 | if (ok) { 47 | onClose(); 48 | } 49 | }} 50 | /> 51 |
52 | ); 53 | }; 54 | 55 | export default View; 56 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/LocalProjectModal/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [processing, setProcessing] = useState(false); 5 | 6 | const [formModal, setFormModal] = useState<{ visible: boolean; config: any }>( 7 | { 8 | visible: false, 9 | config: {}, 10 | }, 11 | ); 12 | 13 | const [openFolder, setOpenFolder] = useState(''); 14 | 15 | return { 16 | processing, 17 | setProcessing, 18 | formModal, 19 | setFormModal, 20 | openFolder, 21 | setOpenFolder, 22 | }; 23 | }; 24 | 25 | export type Model = ReturnType; 26 | -------------------------------------------------------------------------------- /webview-react/src/pages/scaffold/LocalProjectModal/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { selectDirectory, useLocalScaffold } from '@/webview/service'; 2 | import { useModel } from './model'; 3 | 4 | export const usePresenter = () => { 5 | const model = useModel(); 6 | 7 | const selectDirectoryByVsCode = () => { 8 | selectDirectory().then((res) => { 9 | model.setOpenFolder(res); 10 | }); 11 | }; 12 | 13 | const copyLocalScaffold = () => { 14 | model.setProcessing(true); 15 | useLocalScaffold({ localPath: model.openFolder }) 16 | .then((res) => { 17 | model.setFormModal((s) => { 18 | s.config = res; 19 | s.visible = true; 20 | }); 21 | }) 22 | .finally(() => { 23 | model.setProcessing(false); 24 | }); 25 | }; 26 | 27 | return { 28 | model, 29 | selectDirectoryByVsCode, 30 | copyLocalScaffold, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/AddSnippet/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useParams, useModel as useUmiModel } from 'umi'; 3 | import { message } from 'antd'; 4 | import { defaultSchema, useModel } from './model'; 5 | import Service from './service'; 6 | import { addSnippets } from '@/webview/service'; 7 | 8 | export const usePresenter = () => { 9 | const model = useModel(); 10 | const service = new Service(model); 11 | const syncFolderModal = useUmiModel('syncFolder'); 12 | 13 | const params = useParams<{ time: string }>(); 14 | 15 | useEffect(() => { 16 | model.setFormData((s) => ({ 17 | ...s, 18 | template: localStorage.getItem('addSnippets') || '<%= name %>', 19 | })); 20 | // localStorage.removeItem('addSnippets'); 21 | }, [params.time]); 22 | 23 | useEffect(() => { 24 | if (model.formData.schemaType === 'amis') { 25 | model.setFormData((s) => { 26 | s.model = defaultSchema.amis.model; 27 | s.schema = defaultSchema.amis.schema; 28 | s.preview = JSON.stringify( 29 | { ...JSON.parse(s.preview), schema: 'amis' }, 30 | null, 31 | 2, 32 | ); 33 | }); 34 | } else if (model.formData.schemaType === 'form-render') { 35 | model.setFormData((s) => { 36 | s.model = defaultSchema.formRender.model; 37 | s.schema = defaultSchema.formRender.schema; 38 | s.preview = JSON.stringify( 39 | { ...JSON.parse(s.preview), schema: 'form-render' }, 40 | null, 41 | 2, 42 | ); 43 | }); 44 | } else if (model.formData.schemaType === 'formily') { 45 | model.setFormData((s) => { 46 | s.model = defaultSchema.formily.model; 47 | s.schema = defaultSchema.formily.schema; 48 | s.preview = JSON.stringify( 49 | { ...JSON.parse(s.preview), schema: 'formily' }, 50 | null, 51 | 2, 52 | ); 53 | }); 54 | } 55 | }, [model.formData.schemaType]); 56 | 57 | const handleCreate = () => { 58 | if (!model.formData.name || !model.formData.template) { 59 | message.error('请完善必填信息'); 60 | return; 61 | } 62 | addSnippets(model.formData).then((res) => { 63 | if (res.code === 200) { 64 | message.success('添加成功'); 65 | } else if (res.code === 404) { 66 | message.error('请先配置私有目录'); 67 | syncFolderModal.setVisible(true); 68 | } 69 | }); 70 | }; 71 | 72 | return { 73 | model, 74 | service, 75 | handleCreate, 76 | }; 77 | }; 78 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/AddSnippet/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/Detail/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [selectedMaterial, setSelectedMaterial] = useState<{ 5 | path: string; 6 | name: string; 7 | model: any; 8 | schema: any; 9 | preview: { 10 | title: string; 11 | description: string; 12 | img?: string[] | string; 13 | schema?: 'form-render' | 'formily' | 'amis'; 14 | scripts?: [ 15 | { method: string; remark: string; readClipboardImage?: boolean }, 16 | ]; 17 | }; 18 | template: string; 19 | viewPrompt?: string; 20 | privateMaterials?: boolean; 21 | }>({ schema: {}, model: {} } as any); 22 | const [formData, setFormData] = useState({}); 23 | const [yapiModalVsible, setYapiModalVsible] = useState(false); 24 | const [templateModalVisble, setTemplateModalVisble] = useState(false); 25 | const [jsonToTsModalVisble, setJsonToTsModalVisble] = useState(false); 26 | const [scriptModalVisible, setScriptModalVisible] = useState(false); 27 | 28 | return { 29 | selectedMaterial, 30 | setSelectedMaterial, 31 | formData, 32 | setFormData, 33 | yapiModalVsible, 34 | setYapiModalVsible, 35 | jsonToTsModalVisble, 36 | setJsonToTsModalVisble, 37 | templateModalVisble, 38 | setTemplateModalVisble, 39 | scriptModalVisible, 40 | setScriptModalVisible, 41 | }; 42 | }; 43 | 44 | export type Model = ReturnType; 45 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/Detail/presenter.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useParams } from 'umi'; 3 | import { Menu, message } from 'antd'; 4 | import { useForm } from 'form-render'; 5 | import { 6 | askChatGPTWithEjsTemplate, 7 | getLocalMaterials, 8 | } from '@/webview/service'; 9 | import Service from './service'; 10 | import { useModel } from './model'; 11 | 12 | export const usePresenter = () => { 13 | const params = useParams<{ name: string }>(); 14 | const model = useModel(); 15 | const service = new Service(model); 16 | const form = useForm(); 17 | 18 | useEffect(() => { 19 | getLocalMaterials('snippets').then((data) => { 20 | if (data.length) { 21 | const selected = data.find((s) => s.name === params.name); 22 | if (selected && !selected.preview.schema) { 23 | selected.preview.schema = 'form-render'; 24 | } 25 | model.setFormData(selected?.model); 26 | model.setSelectedMaterial(selected!); 27 | form.setValues(selected?.model); 28 | } 29 | }); 30 | }, []); 31 | 32 | useEffect(() => { 33 | model.setSelectedMaterial((s) => ({ 34 | ...s, 35 | model: { ...s.model, ...model.formData }, 36 | })); 37 | }, [model.formData]); 38 | 39 | const watch = { 40 | '#': (val: any) => { 41 | model.setFormData(JSON.parse(JSON.stringify(val))); 42 | }, 43 | }; 44 | 45 | const handleRunScriptResult = (result: object) => { 46 | model.setSelectedMaterial((s) => ({ 47 | ...s, 48 | model: result, 49 | })); 50 | model.setFormData(result); 51 | form.setValues(result); 52 | model.setScriptModalVisible(false); 53 | }; 54 | 55 | const menu = ( 56 | 57 | { 59 | model.setJsonToTsModalVisble(true); 60 | }} 61 | > 62 | JSON TO TS 63 | 64 | { 66 | model.setYapiModalVsible(true); 67 | }} 68 | > 69 | 根据 YAPI 接口追加模板数据 70 | 71 | { 73 | model.setTemplateModalVisble(true); 74 | }} 75 | > 76 | 编辑模板 77 | 78 | 79 | ); 80 | 81 | const handleAskChatGPT = () => { 82 | if (!model.selectedMaterial.viewPrompt) { 83 | message.warn('未配置 viewPrompt,直接输入 model'); 84 | } 85 | askChatGPTWithEjsTemplate({ 86 | template: model.selectedMaterial.viewPrompt || '<%- model %>', 87 | model: { model: JSON.stringify(model.selectedMaterial.model, null, 2) }, 88 | }); 89 | }; 90 | 91 | return { 92 | model, 93 | service, 94 | menu, 95 | form, 96 | watch, 97 | handleAskChatGPT, 98 | handleRunScriptResult, 99 | }; 100 | }; 101 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/Detail/service.ts: -------------------------------------------------------------------------------- 1 | import { Model } from './model'; 2 | 3 | export default class Service { 4 | private model: Model; 5 | 6 | constructor(model: Model) { 7 | this.model = model; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/components/SnippetItem/index.less: -------------------------------------------------------------------------------- 1 | .snippets-materials-item { 2 | position: relative; 3 | width: 100%; 4 | height: 250px; 5 | background-color: black; 6 | .snippets-materials-item-img { 7 | position: absolute; 8 | width: 100%; 9 | height: 100%; 10 | top: 0; 11 | left: 0; 12 | z-index: 0; 13 | opacity: 0.6; 14 | .snippets-materials-item-bg, 15 | .swiper { 16 | width: 100%; 17 | height: 100%; 18 | } 19 | .img { 20 | display: none; 21 | } 22 | } 23 | .snippets-materials-item-wrapper { 24 | position: relative; 25 | display: flex; 26 | flex-direction: column; 27 | width: 100%; 28 | height: 100%; 29 | .scroll { 30 | display: flex; 31 | flex: 1; 32 | overflow: auto; 33 | .content { 34 | margin: auto; 35 | width: 100%; 36 | color: #ffffff; 37 | text-align: center; 38 | padding: 10px 20px; 39 | .title { 40 | font-weight: bold; 41 | word-break: break-all; 42 | } 43 | .remark { 44 | margin-top: 10px; 45 | word-break: break-all; 46 | } 47 | } 48 | } 49 | .control { 50 | :global { 51 | .ant-btn { 52 | height: 42px; 53 | border-radius: 0; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/index.less: -------------------------------------------------------------------------------- 1 | .snippets-materials { 2 | } 3 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { history } from 'umi'; 3 | import { Button, Empty, Input, Row, Select, Space } from 'antd'; 4 | import styles from './index.less'; 5 | import { usePresenter } from './presenter'; 6 | import SnippetItem from './components/SnippetItem'; 7 | import { executeVscodeCommand } from '@/webview/service'; 8 | 9 | const Search = Input.Search; 10 | 11 | export default () => { 12 | const presenter = usePresenter(); 13 | const { model } = presenter; 14 | 15 | return ( 16 |
17 |
18 | { 22 | const { value } = el.target; 23 | model.setSearchValue(value); 24 | presenter.handleSearch(); 25 | }} 26 | /> 27 |
28 | 46 |
47 |
48 | 49 | {model.materials?.map((s) => ( 50 | 51 | ))} 52 | 53 | {model.materials?.length === 0 && ( 54 | 暂无数据} 60 | > 61 | 62 | 70 | 80 | 81 | 82 | )} 83 |
84 | ); 85 | }; 86 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/model.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '@/hooks/useImmer'; 2 | 3 | export const useModel = () => { 4 | const [materials, setMaterials] = useState< 5 | | { 6 | path: string; 7 | name: string; 8 | model: object; 9 | schema: object; 10 | preview: { 11 | title?: string; 12 | description?: string; 13 | img?: string[]; 14 | category?: string[]; 15 | }; 16 | template: string; 17 | privateMaterials?: boolean; 18 | id: number; 19 | }[] 20 | | undefined 21 | >(undefined); 22 | const [oriMaterials, setOriMaterials] = useState([]); 23 | 24 | const [categoryList, setCategoryList] = useState([]); 25 | 26 | const [selectedCategory, setSelectedCategory] = useState([]); 27 | 28 | const [searchValue, setSearchValue] = useState(''); 29 | 30 | return { 31 | materials, 32 | setMaterials, 33 | oriMaterials, 34 | setOriMaterials, 35 | categoryList, 36 | setCategoryList, 37 | selectedCategory, 38 | setSelectedCategory, 39 | searchValue, 40 | setSearchValue, 41 | }; 42 | }; 43 | 44 | export type Model = ReturnType; 45 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/presenter.tsx: -------------------------------------------------------------------------------- 1 | import { useDebounceFn, useMount } from 'ahooks'; 2 | import { useEffect } from 'react'; 3 | import { useModel as umiModel } from 'umi'; 4 | import { useModel } from './model'; 5 | import Service from './service'; 6 | 7 | export const usePresenter = () => { 8 | const model = useModel(); 9 | const service = new Service(model); 10 | 11 | const { refresh, setRefresh } = umiModel('tab'); 12 | 13 | useMount(() => { 14 | service.getList().finally(() => { 15 | setRefresh(false); 16 | }); 17 | }); 18 | 19 | useEffect(() => { 20 | if (refresh) { 21 | service.getList().finally(() => { 22 | setRefresh(false); 23 | }); 24 | } 25 | }, [refresh]); 26 | 27 | const handleSearch = useDebounceFn( 28 | () => { 29 | service.search(); 30 | }, 31 | { wait: 300 }, 32 | ).run; 33 | 34 | return { 35 | model, 36 | handleSearch, 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /webview-react/src/pages/snippets/List/service.ts: -------------------------------------------------------------------------------- 1 | import { getLocalMaterials } from '@/webview/service'; 2 | import { Model } from './model'; 3 | 4 | export default class Service { 5 | private model: Model; 6 | 7 | constructor(model: Model) { 8 | this.model = model; 9 | } 10 | 11 | search() { 12 | const value = this.model.searchValue.toLowerCase().trim(); 13 | let filterList = [...this.model.oriMaterials]; 14 | if (value) { 15 | filterList = filterList.filter( 16 | (s) => 17 | s.name.toLowerCase().indexOf(value) > -1 || 18 | (s.preview.title && 19 | s.preview.title.toLowerCase().indexOf(value) > -1) || 20 | (s.preview.description && 21 | s.preview.description.toLowerCase().indexOf(value) > -1), 22 | ); 23 | } 24 | if (this.model.selectedCategory.length > 0) { 25 | filterList = filterList.filter((s) => { 26 | let flag = false; 27 | s.preview.category?.map((c) => { 28 | if (this.model.selectedCategory.includes(c)) { 29 | flag = true; 30 | } 31 | }); 32 | return flag; 33 | }); 34 | } 35 | this.model.setMaterials(filterList); 36 | } 37 | 38 | async getList() { 39 | const res = await getLocalMaterials('snippets'); 40 | const categoryList: string[] = []; 41 | res.map((s) => { 42 | s.preview.category?.map((c) => { 43 | if (!categoryList.includes(c)) { 44 | categoryList.push(c); 45 | } 46 | }); 47 | }); 48 | const list: Model['materials'] = res.map((s, index) => ({ 49 | ...s, 50 | id: index, 51 | preview: { 52 | ...s.preview, 53 | img: 54 | typeof s.preview.img === 'string' ? [s.preview.img] : s.preview.img, 55 | }, 56 | })); 57 | this.model.setMaterials(list); 58 | this.model.setOriMaterials(list); 59 | this.model.setSelectedCategory([]); 60 | this.model.setCategoryList(categoryList); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /webview-react/src/utils/clipboard.ts: -------------------------------------------------------------------------------- 1 | export function getClipboardImage() { 2 | // eslint-disable-next-line no-async-promise-executor 3 | return new Promise(async (resolve, reject) => { 4 | try { 5 | const clipboardContents = await navigator.clipboard.read(); 6 | // eslint-disable-next-line no-restricted-syntax 7 | for (const item of clipboardContents) { 8 | if (!item.types.includes('image/png')) { 9 | resolve(''); 10 | return; 11 | } 12 | // eslint-disable-next-line no-await-in-loop 13 | const blob = await item.getType('image/png'); 14 | const reader = new FileReader(); 15 | reader.readAsDataURL(blob); 16 | reader.onloadend = function () { 17 | const base64data = reader.result; 18 | resolve(base64data as string); 19 | }; 20 | } 21 | } catch (err) { 22 | console.log(err); 23 | resolve(''); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /webview-react/src/utils/emitter.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | type Events = { 4 | chatGPTChunk: { 5 | chunck: string; 6 | sessionId: number; 7 | messageId: number; 8 | }; 9 | askChatGPT: string; 10 | }; 11 | 12 | export const emitter = mitt(); 13 | -------------------------------------------------------------------------------- /webview-react/src/utils/schema.ts: -------------------------------------------------------------------------------- 1 | export const getSchemaWebUrl = ( 2 | schema: 'form-render' | 'amis' | 'formily' | string, 3 | ) => { 4 | if (schema === 'amis') { 5 | return 'https://aisuda.github.io/amis-editor-demo/#/edit/0'; 6 | } 7 | if (schema === 'form-render') { 8 | return 'https://1.xrender.fun/~demos/generator-demo'; 9 | } 10 | if (schema === 'formily') { 11 | return 'https://designable-antd.formilyjs.org/'; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /webview-react/src/webview/handleTask.ts: -------------------------------------------------------------------------------- 1 | import { history } from 'umi'; 2 | import { emitter } from '@/utils/emitter'; 3 | import { getClipboardImage } from '@/utils/clipboard'; 4 | import { putClipboardImage } from './service'; 5 | 6 | const toDefaultPage = () => { 7 | history.push('/snippets'); 8 | }; 9 | 10 | export const taskHandler: { 11 | [propName: string]: (data: any) => void; 12 | } = { 13 | addSnippets: (data?: { content?: string }) => { 14 | localStorage.setItem('addSnippets', data?.content || ''); 15 | history.push(`/snippets/add/${new Date().getTime()}`); 16 | }, 17 | openSnippet: (data: { name: string }) => { 18 | history.push(`/snippets/detail/${data.name}`); 19 | }, 20 | route: (data: { path: string }) => { 21 | history.push(data.path); 22 | }, 23 | updateSelectedFolder: (data: { selectedFolder: string }) => { 24 | localStorage.setItem('selectedFolder', data.selectedFolder || ''); 25 | toDefaultPage(); 26 | }, 27 | handleChatGPTChunk: (data: { 28 | sessionId: number; 29 | messageId: number; 30 | content: string; 31 | }) => { 32 | emitter.emit('chatGPTChunk', { 33 | sessionId: data.sessionId, 34 | messageId: data.messageId, 35 | chunck: data.content, 36 | }); 37 | }, 38 | askChatGPT: (data: string) => { 39 | if ( 40 | document.location.pathname === '/index.html' || 41 | document.location.pathname === '/' 42 | ) { 43 | localStorage.setItem('askChatGPT', data); 44 | history.push('/chatGPT'); 45 | } else { 46 | emitter.emit('askChatGPT', data); 47 | } 48 | }, 49 | async getClipboardImage() { 50 | history.push('getClipboardImage'); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /webview-react/src/webview/index.ts: -------------------------------------------------------------------------------- 1 | import { notification, message as antdMessage } from 'antd'; 2 | import { taskHandler } from './handleTask'; 3 | 4 | const callbacks: { [propName: string]: (data: any) => void } = {}; 5 | const errorCallbacks: { [propName: string]: (data: any) => void } = {}; 6 | if (process.env.NODE_ENV !== 'production') { 7 | (window as any).vscode = (window as any).vscode 8 | ? vscode 9 | : { 10 | postMessage: (message: { cmd: string; data: any; cbid: string }) => { 11 | setTimeout(() => { 12 | notification.success({ 13 | message: 'call vscode', 14 | description: `cmd: ${message.cmd}`, 15 | }); 16 | (callbacks[message.cbid] || function () {})( 17 | require(`./mock/${message.cmd}`).default, 18 | ); 19 | }, 1000); 20 | }, 21 | }; 22 | } 23 | export function callVscode( 24 | data: { cmd: string; data?: any; skipError?: boolean }, 25 | cb?: (data: any) => void, 26 | errorCb?: (data: any) => void, 27 | ) { 28 | if (cb) { 29 | const cbid = `${Date.now()}${Math.round(Math.random() * 100000)}`; 30 | callbacks[cbid] = cb; 31 | vscode.postMessage({ 32 | ...data, 33 | cbid, 34 | }); 35 | if (errorCb) { 36 | errorCallbacks[cbid] = errorCb; 37 | } 38 | } else { 39 | vscode.postMessage(data); 40 | } 41 | } 42 | 43 | export function callVscodePromise(cmd: string, data: any, skipError?: boolean) { 44 | return new Promise((resolve, reject) => { 45 | callVscode( 46 | { cmd, data, skipError }, 47 | (res) => { 48 | resolve(res); 49 | }, 50 | (error) => { 51 | reject(error); 52 | }, 53 | ); 54 | }); 55 | } 56 | 57 | export function request(params: { 58 | cmd: string; 59 | data?: any; 60 | skipError?: boolean; 61 | }) { 62 | return new Promise((resolve, reject) => { 63 | callVscode( 64 | { cmd: params.cmd, data: params.data, skipError: params.skipError }, 65 | (res) => { 66 | resolve(res); 67 | }, 68 | (error) => { 69 | reject(error); 70 | }, 71 | ); 72 | }); 73 | } 74 | 75 | window.addEventListener('message', (event) => { 76 | const message = event.data; 77 | switch (message.cmd) { 78 | // 来自vscode的回调 79 | case 'vscodeCallback': 80 | if (message.code === 200) { 81 | (callbacks[message.cbid] || function () {})(message.data); 82 | } else { 83 | (errorCallbacks[message.cbid] || function () {})(message.data); 84 | } 85 | delete callbacks[message.cbid]; 86 | delete errorCallbacks[message.cbid]; 87 | break; 88 | // 来自 chatgpt chunck 的回调 89 | case 'vscodeChatGPTChunkCallback': 90 | if (taskHandler[message.task]) { 91 | taskHandler[message.task](message.data); 92 | } else { 93 | antdMessage.error(`未找到名为 ${message.task} 回调方法!`); 94 | } 95 | break; 96 | // vscode 主动推送task 97 | case 'vscodePushTask': { 98 | if (taskHandler[message.task]) { 99 | taskHandler[message.task](message.data); 100 | } else { 101 | antdMessage.error(`未找到名为 ${message.task} 回调方法!`); 102 | } 103 | } 104 | default: 105 | break; 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/addSnippets.ts: -------------------------------------------------------------------------------- 1 | export default '添加成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/alert.ts: -------------------------------------------------------------------------------- 1 | export default '模拟 VsCode 响应'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/createProject.ts: -------------------------------------------------------------------------------- 1 | export default '创建成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/downloadMaterials.ts: -------------------------------------------------------------------------------- 1 | export default '下载成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/downloadScaffold.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | formSchema: { 3 | schema: { 4 | type: 'object', 5 | properties: { 6 | noREADME: { 7 | title: '移除README文件', 8 | type: 'boolean', 9 | 'ui:widget': 'switch', 10 | 'ui:width': '100%', 11 | 'ui:labelWidth': 0, 12 | }, 13 | emptyREADME: { 14 | title: '空README文件', 15 | type: 'boolean', 16 | 'ui:widget': 'switch', 17 | }, 18 | }, 19 | 'ui:displayType': 'row', 20 | 'ui:showDescIcon': true, 21 | }, 22 | displayType: 'row', 23 | showDescIcon: true, 24 | labelWidth: 158, 25 | formData: { 26 | noREADME: true, 27 | emptyREADME: false, 28 | }, 29 | }, 30 | excludeCompile: ['codeTemplate/', 'materials/'], 31 | conditionFiles: { 32 | noREADME: { 33 | value: true, 34 | exclude: ['README.md.ejs'], 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/genCodeByBlockMaterial.ts: -------------------------------------------------------------------------------- 1 | export default '生成成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/genCodeBySnippetMaterial.ts: -------------------------------------------------------------------------------- 1 | export default 'hhhhhhh'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/genTemplateModelByYapi.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | type: '\nexport interface IYapiRequestResult {\n message: string;\n code: number;\n result: {\n records: {\n id: number;\n code: string;\n name: string;\n icon: string;\n actived: number;\n \n }[];\n total: number;\n size: number;\n current: number;\n pages: number;\n \n };\n \n}\n', 3 | requestBodyType: '', 4 | funcName: 'fetch', 5 | typeName: 'IYapiRequestResult', 6 | api: { 7 | query_path: { 8 | path: '/api/bms/lebangmodule/all/page', 9 | params: [], 10 | }, 11 | edit_uid: 0, 12 | status: 'undone', 13 | type: 'static', 14 | req_body_is_json_schema: true, 15 | res_body_is_json_schema: true, 16 | api_opened: false, 17 | index: 0, 18 | tag: [], 19 | _id: 13897, 20 | method: 'GET', 21 | catid: 3198, 22 | title: '全部应用分页', 23 | path: '/api/bms/lebangmodule/all/page', 24 | project_id: 869, 25 | req_params: [], 26 | res_body_type: 'json', 27 | uid: 1339, 28 | add_time: 1597131826, 29 | up_time: 1597393768, 30 | req_query: [ 31 | { 32 | required: '0', 33 | _id: '5f364b681ba88c000862d778', 34 | name: 'name', 35 | example: '学习中心', 36 | desc: '应用名称', 37 | }, 38 | { 39 | required: '0', 40 | _id: '5f364b681ba88c000862d777', 41 | name: 'actived', 42 | example: '0', 43 | desc: '可用状态. 0: 不可用, 1: 可用 ', 44 | }, 45 | { 46 | required: '0', 47 | _id: '5f364b681ba88c000862d776', 48 | name: 'size', 49 | example: '10', 50 | desc: '每页条数', 51 | }, 52 | { 53 | required: '0', 54 | _id: '5f364b681ba88c000862d775', 55 | name: 'current', 56 | example: '1', 57 | desc: '当前页', 58 | }, 59 | { 60 | required: '0', 61 | _id: '5f364b681ba88c000862d774', 62 | name: 'type', 63 | example: 'MD1001', 64 | desc: '应用类型', 65 | }, 66 | ], 67 | req_headers: [], 68 | req_body_form: [], 69 | __v: 0, 70 | desc: '', 71 | markdown: '', 72 | res_body: 73 | '{"type":"object","title":"empty object","properties":{"message":{"type":"string","description":"返回日志信息"},"code":{"type":"number","description":"状态码"},"result":{"type":"object","properties":{"records":{"type":"array","items":{"type":"object","properties":{"id":{"type":"number","description":"主键id"},"code":{"type":"string","description":"应用编码"},"name":{"type":"string","description":"名称"},"icon":{"type":"string","description":"图标地址"},"actived":{"type":"number","description":"可用状态. 0: 不可用, 1: 可用"}}},"description":"分页对象"},"total":{"type":"number","description":"总条数"},"size":{"type":"number","description":"每页大小"},"current":{"type":"number","description":"当前页"},"pages":{"type":"number","description":"总页数"}},"required":["id","code","name","icon","sort","actived","records","total","size","current","pages"]}},"required":["id","code","name","icon","sort","actived","message","code","result"]}', 74 | username: 'ruoxie', 75 | }, 76 | inputValues: [], 77 | mockCode: 78 | '\n\t\t const list1=[];\n\t\t for(let i = 0; i < 10 ; i++){\n\t\t\tlist1.push(\n\t\t{id: Random.natural(1000,1000),code: Random.cword(5, 7),name: Random.cword(5, 7),icon: Random.cword(5, 7),actived: Random.natural(1000,1000),})}', 79 | mockData: 80 | '{message: Random.cword(5, 7),code: Random.natural(1000,1000),result: {records: list1,total: Random.natural(1000,1000),size: Random.natural(1000,1000),current: Random.natural(1000,1000),pages: Random.natural(1000,1000),},}', 81 | jsonData: {}, 82 | rawSelectedText: '', 83 | rawClipboardText: '', 84 | }; 85 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/getPluginConfig.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | yapi: { 3 | domain: 'https://www.google.com', 4 | projects: [ 5 | { 6 | name: '121212', 7 | token: '12121212', 8 | domain: '333333333', 9 | }, 10 | ], 11 | }, 12 | mock: { 13 | mockNumber: '1', 14 | mockBoolean: '2', 15 | mockString: '3', 16 | mockKeyWordEqual: [ 17 | { 18 | key: '1', 19 | value: '2', 20 | }, 21 | ], 22 | mockKeyWordLike: [ 23 | { 24 | key: '1', 25 | value: '2', 26 | }, 27 | ], 28 | }, 29 | saveOption: ['package'], 30 | }; 31 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/getScaffolds.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | category: 'PC Web', 4 | icon: 'https://s3.ax1x.com/2021/03/07/6M8rdO.png', 5 | uuid: '1', 6 | scaffolds: [ 7 | { 8 | title: 'vue-typescript-stater', 9 | description: 'vue-typescript-stater', 10 | screenshot: 11 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 12 | repository: '', 13 | uuid: '2', 14 | }, 15 | { 16 | title: 'vue-typescript-stater1', 17 | description: 'vue-typescript-stater', 18 | screenshot: 19 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 20 | repository: '', 21 | uuid: '3', 22 | }, 23 | { 24 | title: 'vue-typescript-stater2', 25 | description: 'vue-typescript-stater', 26 | screenshot: 27 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 28 | repository: '', 29 | uuid: '4', 30 | }, 31 | { 32 | title: 'vue-typescript-stater3', 33 | description: 'vue-typescript-stater', 34 | screenshot: 35 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 36 | repository: '', 37 | uuid: '5', 38 | }, 39 | { 40 | title: 'vue-typescript-stater4', 41 | description: 'vue-typescript-stater', 42 | screenshot: 43 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 44 | repository: '', 45 | uuid: '6', 46 | }, 47 | { 48 | title: 'vue-typescript-stater5', 49 | description: 'vue-typescript-stater', 50 | screenshot: 51 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 52 | repository: '', 53 | uuid: '7', 54 | }, 55 | ], 56 | }, 57 | { 58 | category: 'Mobile Web', 59 | icon: 'https://s3.ax1x.com/2021/03/07/6M8rdO.png', 60 | uuid: '8', 61 | scaffolds: [ 62 | { 63 | title: 'vue-typescript-stater', 64 | description: '', 65 | screenshot: 66 | 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg', 67 | repository: '', 68 | uuid: '9', 69 | }, 70 | ], 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/getYapiDomain.ts: -------------------------------------------------------------------------------- 1 | export default 'jkfhdjdfhddskdjskdjskjd'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/getYapiProjects.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: '价值一个亿的项目', 4 | token: 'baf6748bf45cd1b924a03d56b8a74e3fb13e744bd7dd49e222f3a976ca3c1edb', 5 | }, 6 | ]; 7 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/insertSnippet.ts: -------------------------------------------------------------------------------- 1 | export default '添加成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/jsonToTs.ts: -------------------------------------------------------------------------------- 1 | export default '121212121212'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/refreshIntelliSense.ts: -------------------------------------------------------------------------------- 1 | export default '刷新成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/savePluginConfig.ts: -------------------------------------------------------------------------------- 1 | export default '保存成功'; 2 | -------------------------------------------------------------------------------- /webview-react/src/webview/mock/selectDirectory.ts: -------------------------------------------------------------------------------- 1 | export default 'H:/.lowcode'; 2 | -------------------------------------------------------------------------------- /webview-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "baseUrl": "./", 11 | "strict": true, 12 | "paths": { 13 | "@/*": [ 14 | "src/*" 15 | ], 16 | "@@/*": [ 17 | "src/.umi/*" 18 | ] 19 | }, 20 | "allowSyntheticDefaultImports": true 21 | }, 22 | "include": [ 23 | "mock/**/*", 24 | "src/**/*", 25 | "config/**/*", 26 | ".umirc.ts", 27 | "typings.d.ts" 28 | ] 29 | } -------------------------------------------------------------------------------- /webview-react/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.png'; 4 | declare module '*.svg' { 5 | export function ReactComponent( 6 | props: React.SVGProps, 7 | ): React.ReactElement; 8 | const url: string; 9 | export default url; 10 | } 11 | 12 | interface IVscode { 13 | postMessage(message: any): void; 14 | } 15 | // declare function acquireVsCodeApi(): vscode; 16 | declare let vscode: IVscode; 17 | 18 | declare module 'form-render/lib/antd' { 19 | import React from 'react'; 20 | export interface FRProps { 21 | schema?: object; 22 | formData?: object; 23 | onChange?(data?: object): void; 24 | onMount?(data?: object): void; 25 | name?: string; 26 | column?: number; 27 | uiSchema?: object; 28 | widgets?: any; 29 | FieldUI?: any; 30 | fields?: any; 31 | mapping?: object; 32 | showDescIcon?: boolean; 33 | showValidate?: boolean; 34 | displayType?: string; 35 | onValidate?: any; 36 | readOnly?: boolean; 37 | labelWidth?: number | string; 38 | } 39 | class FormRender extends React.Component {} 40 | export default FormRender; 41 | } 42 | --------------------------------------------------------------------------------