├── .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 | 
16 |
17 | ### 生成指定格式 JSON
18 |
19 | 
20 |
21 | ### 中文翻译成驼峰格式
22 |
23 | 
24 |
25 | ### 当前目录翻译成英文
26 |
27 | 
28 |
29 | ### 快速创建代码模板
30 |
31 | 
32 |
33 | ### 生成 CURD
34 |
35 | 以管理后台一个列表页为例
36 |
37 | 
38 |
39 | #### 选择对应的模板
40 |
41 | 
42 |
43 | #### 截图查询区域,使用 OCR 初始化查询表单的配置
44 |
45 | 
46 |
47 | #### 截图表头,使用 OCR 初始化 table 的配置
48 |
49 | 
50 |
51 | #### 使用 ChatGPT 翻译中文字段
52 |
53 | 
54 |
55 | #### 生成代码
56 |
57 | 
58 |
59 | #### 效果
60 |
61 | 目前我们没有写一行代码,就已经达到了如下的效果
62 |
63 | 
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 |
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 |
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 |
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 |
91 | {
98 | setFormData((s) => ({
99 | ...s,
100 | path: value as string,
101 | }));
102 | }}
103 | />
104 |
105 |
106 |
120 |
121 |
122 | );
123 | };
124 |
125 | export default SelectDirectory;
126 |
--------------------------------------------------------------------------------
/webview-react/src/hooks/useCheckVankeInternal.ts:
--------------------------------------------------------------------------------
1 | import { useMount } from 'ahooks';
2 | import { nodeRequest } from '@/webview/service';
3 | import { useState } from './useImmer';
4 |
5 | const useCheckVankeInternal = () => {
6 | const [isVankeInternal, setIsVankeInternal] = useState(
7 | undefined,
8 | );
9 |
10 | useMount(() => {
11 | nodeRequest({ url: 'https://yapi.onewo.com' })
12 | .then(() => {
13 | setIsVankeInternal(true);
14 | })
15 | .catch(() => {
16 | setIsVankeInternal(false);
17 | });
18 | });
19 |
20 | return isVankeInternal;
21 | };
22 |
23 | export default useCheckVankeInternal;
24 |
--------------------------------------------------------------------------------
/webview-react/src/hooks/useImmer.ts:
--------------------------------------------------------------------------------
1 | import { useImmer } from 'use-immer';
2 |
3 | export const useState = useImmer;
4 |
--------------------------------------------------------------------------------
/webview-react/src/layout/index.less:
--------------------------------------------------------------------------------
1 | .ant-layout {
2 | min-height: none;
3 | }
4 | .base-layout {
5 | height: 100%;
6 | .ant-menu-inline .ant-menu-item {
7 | margin-top: 0px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/webview-react/src/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Layout } from 'antd';
3 | import { history } from 'umi';
4 | import './index.less';
5 | import { useMount } from 'ahooks';
6 | import HeaderControl from '@/components/HeaderControl';
7 |
8 | const { Content } = Layout;
9 |
10 | const LayoutC: React.FC = ({ children }) => (
11 |
12 |
13 |
14 |
21 | {children}
22 |
23 |
24 |
25 | );
26 | export default LayoutC;
27 |
--------------------------------------------------------------------------------
/webview-react/src/models/syncFolder.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useSyncFolderModal = () => {
4 | const [visible, setVisible] = useState(false);
5 |
6 | return {
7 | visible,
8 | setVisible,
9 | };
10 | };
11 |
12 | export default useSyncFolderModal;
13 |
--------------------------------------------------------------------------------
/webview-react/src/models/tab.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useTab = () => {
4 | const [tab, setTab] = useState('/snippets');
5 | const [refresh, setRefresh] = useState(false);
6 | return {
7 | tab,
8 | setTab,
9 | refresh,
10 | setRefresh,
11 | };
12 | };
13 | export default useTab;
14 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/Detail/model.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import { useState } from '@/hooks/useImmer';
3 |
4 | export const useModel = () => {
5 | const [selectedMaterial, setSelectedMaterial] = useState<{
6 | path: string;
7 | name: string;
8 | model: object;
9 | schema: any;
10 | preview: {
11 | title?: string;
12 | description?: string;
13 | img?: string | string[];
14 | schema?: 'form-render' | 'formily' | 'amis';
15 | scripts?: [
16 | { method: string; remark: string; readClipboardImage?: boolean },
17 | ];
18 | };
19 | template: string;
20 | viewPrompt?: string;
21 | privateMaterials?: boolean;
22 | }>({ schema: {}, model: {} } as any);
23 | const [materials, setMaterials] = useState([]);
24 | const [directoryModalVsible, setDirectoryModalVsible] = useState(false);
25 | const [scriptModalVisible, setScriptModalVisible] = useState(false);
26 |
27 | const amisComponent = useRef<{
28 | getValues: () => object;
29 | setValues: (values: object) => void;
30 | } | null>(null);
31 |
32 | const formilyComponent = useRef<{
33 | getValues: () => object;
34 | setValues: (values: object) => void;
35 | } | null>(null);
36 |
37 | const [loading, setLoding] = useState(false);
38 |
39 | const [tempFormDataModal, setTempFormDataModal] = useState<{
40 | visible: boolean;
41 | formData: object;
42 | }>({ visible: false, formData: {} });
43 |
44 | return {
45 | selectedMaterial,
46 | setSelectedMaterial,
47 | materials,
48 | setMaterials,
49 | directoryModalVsible,
50 | setDirectoryModalVsible,
51 | scriptModalVisible,
52 | setScriptModalVisible,
53 | amisComponent,
54 | formilyComponent,
55 | loading,
56 | setLoding,
57 | tempFormDataModal,
58 | setTempFormDataModal,
59 | };
60 | };
61 |
62 | export type Model = ReturnType;
63 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/Detail/presenter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useParams } from 'umi';
3 | import { useForm } from 'form-render';
4 | import { message } from 'antd';
5 | import Service from './service';
6 | import { getLocalMaterials } from '@/webview/service';
7 | import { useModel } from './model';
8 |
9 | export const usePresenter = () => {
10 | const model = useModel();
11 | const service = new Service(model);
12 | const params = useParams<{ name: string }>();
13 | const form = useForm();
14 |
15 | useEffect(() => {
16 | getLocalMaterials('blocks').then((data) => {
17 | model.setMaterials(data);
18 | if (data.length) {
19 | const selected = data.find((s: any) => s.name === params.name);
20 | if (selected && !selected.preview.schema) {
21 | selected.preview.schema = 'form-render';
22 | }
23 | model.setSelectedMaterial(selected!);
24 | form.setValues(selected?.model);
25 | }
26 | });
27 | }, []);
28 |
29 | const watch = {
30 | '#': (val: any) => {
31 | model.setSelectedMaterial((s) => ({
32 | ...s,
33 | model: { ...s.model, ...JSON.parse(JSON.stringify(val)) },
34 | }));
35 | },
36 | };
37 |
38 | const handleRunScriptResult = (result: {
39 | /** 立即更新 model */
40 | updateModelImmediately: boolean;
41 | model: object;
42 | }) => {
43 | if (result.updateModelImmediately) {
44 | model.setSelectedMaterial((s) => ({
45 | ...s,
46 | model: result.model,
47 | }));
48 | form.setValues(result.model);
49 | if (
50 | model.selectedMaterial.preview.schema === 'amis' &&
51 | model.amisComponent.current
52 | ) {
53 | model.amisComponent.current!.setValues(result.model);
54 | }
55 | model.setScriptModalVisible(false);
56 | message.success('执行成功');
57 | } else {
58 | message.success('执行成功');
59 | model.setTempFormDataModal((s) => {
60 | s.visible = true;
61 | s.formData = result.model;
62 | });
63 | }
64 | };
65 |
66 | const handleUpdateModelOpen = () => {
67 | if (
68 | model.selectedMaterial.preview.schema === 'amis' &&
69 | model.amisComponent.current
70 | ) {
71 | model.setSelectedMaterial((s) => {
72 | s.model = model.amisComponent.current!.getValues();
73 | });
74 | model.setTempFormDataModal((s) => {
75 | s.visible = true;
76 | s.formData = model.amisComponent.current!.getValues();
77 | });
78 | return;
79 | }
80 | model.setTempFormDataModal((s) => {
81 | s.visible = true;
82 | s.formData = JSON.parse(JSON.stringify(model.selectedMaterial.model));
83 | });
84 | };
85 |
86 | const handleUpdateModelOk = () => {
87 | model.setSelectedMaterial((s) => ({
88 | ...s,
89 | model: JSON.parse(JSON.stringify(model.tempFormDataModal.formData)),
90 | }));
91 | form.setValues(
92 | JSON.parse(JSON.stringify(model.tempFormDataModal.formData)),
93 | );
94 | if (
95 | model.selectedMaterial.preview.schema === 'amis' &&
96 | model.amisComponent.current
97 | ) {
98 | model.amisComponent.current!.setValues(
99 | JSON.parse(JSON.stringify(model.tempFormDataModal.formData)),
100 | );
101 | }
102 | model.setTempFormDataModal((s) => {
103 | s.visible = false;
104 | });
105 | model.setScriptModalVisible(false);
106 | };
107 |
108 | const handleUpdateModelCancel = () => {
109 | model.setTempFormDataModal((s) => {
110 | s.visible = false;
111 | });
112 | };
113 |
114 | return {
115 | model,
116 | service,
117 | form,
118 | watch,
119 | handleRunScriptResult,
120 | handleUpdateModelOpen,
121 | handleUpdateModelOk,
122 | handleUpdateModelCancel,
123 | };
124 | };
125 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/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/blocks/List/components/BlockItem/index.less:
--------------------------------------------------------------------------------
1 | .item {
2 | position: relative;
3 | width: 100%;
4 | height: 200px;
5 | background-color: black;
6 | .item-image {
7 | position: absolute;
8 | width: 100%;
9 | height: 100%;
10 | top: 0;
11 | left: 0;
12 | z-index: 0;
13 | opacity: 0.6;
14 | .item-bg,
15 | .swiper {
16 | width: 100%;
17 | height: 100%;
18 | }
19 | .img {
20 | display: none;
21 | }
22 | }
23 | .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/blocks/List/components/BlockItem/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { Col, Button, message } from 'antd';
3 | import { EyeOutlined } from '@ant-design/icons';
4 | import { Swiper, SwiperSlide } from 'swiper/react';
5 | import Viewer from 'viewerjs';
6 | import { history } from 'umi';
7 | import { Autoplay } from 'swiper';
8 | import { Model } from '../../model';
9 | import styles from './index.less';
10 | import 'swiper/swiper.css';
11 | import 'viewerjs/dist/viewer.css';
12 |
13 | export default (props: {
14 | blockItem: NonNullable[0];
15 | onDefaultClick: () => void;
16 | }) => {
17 | const viewer = useRef();
18 |
19 | useEffect(() => {
20 | const v = new Viewer(
21 | document.getElementById(`images_${props.blockItem.name}`)!,
22 | {
23 | hidden() {
24 | viewer.current?.destroy();
25 | },
26 | title: false,
27 | },
28 | );
29 | viewer.current = v;
30 | }, [props.blockItem]);
31 |
32 | useEffect(
33 | () => () => {
34 | if (viewer.current) {
35 | viewer.current.destroy();
36 | }
37 | },
38 | [],
39 | );
40 |
41 | const showViewer = () => {
42 | viewer.current?.show();
43 | };
44 | return (
45 |
46 |
47 |
48 | {props.blockItem.preview.img && (
49 |
59 | {props.blockItem.preview.img?.map((img) => (
60 |
61 |
68 |

69 |
70 |
71 | ))}
72 |
73 | )}
74 |
75 |
76 |
77 |
78 |
79 | {props.blockItem.preview.title || props.blockItem.name}
80 |
81 | {props.blockItem.preview.description && (
82 |
83 | {props.blockItem.preview.description}
84 |
85 | )}
86 |
87 |
88 |
89 |
98 |
107 | }
111 | onClick={showViewer}
112 | >
113 |
114 |
115 |
116 |
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/List/index.less:
--------------------------------------------------------------------------------
1 | .materials {
2 | }
3 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/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 | NonNullable
24 | >([]);
25 |
26 | const [selectedMaterial, setSelectedMaterial] = useState<
27 | NonNullable[0]
28 | >({ schema: {}, model: {} } as any);
29 |
30 | const [directoryModalVsible, setDirectoryModalVsible] = useState(false);
31 |
32 | const [projectList, setProjectList] = useState([]);
33 |
34 | const [categoryList, setCategoryList] = useState([]);
35 |
36 | const [selectedCategory, setSelectedCategory] = useState([]);
37 |
38 | const [selectProject, setSelectProject] = useState('');
39 |
40 | const [searchValue, setSearchValue] = useState('');
41 |
42 | return {
43 | materials,
44 | setMaterials,
45 | oriMaterials,
46 | setOriMaterials,
47 | selectedMaterial,
48 | setSelectedMaterial,
49 | directoryModalVsible,
50 | setDirectoryModalVsible,
51 | projectList,
52 | setProjectList,
53 | categoryList,
54 | setCategoryList,
55 | selectedCategory,
56 | setSelectedCategory,
57 | selectProject,
58 | setSelectProject,
59 | searchValue,
60 | setSearchValue,
61 | };
62 | };
63 |
64 | export type Model = ReturnType;
65 |
--------------------------------------------------------------------------------
/webview-react/src/pages/blocks/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/blocks/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 | if (this.model.selectProject) {
36 | filterList = filterList.filter((s) =>
37 | s.name.includes(this.model.selectProject),
38 | );
39 | }
40 | this.model.setMaterials(filterList);
41 | }
42 |
43 | async getList() {
44 | const res = await getLocalMaterials('blocks');
45 | const categoryList: string[] = [];
46 | const projectList: string[] = [];
47 | res.map((s) => {
48 | s.preview.category?.map((c) => {
49 | if (!categoryList.includes(c)) {
50 | categoryList.push(c);
51 | }
52 | });
53 | const project = s.name.split(' ')[0];
54 | if (!projectList.includes(project)) {
55 | projectList.push(project);
56 | }
57 | });
58 | const list: Model['materials'] = res.map((s, index) => ({
59 | ...s,
60 | id: index,
61 | preview: {
62 | ...s.preview,
63 | img:
64 | typeof s.preview.img === 'string' ? [s.preview.img] : s.preview.img,
65 | },
66 | }));
67 |
68 | this.model.setSelectedCategory([]);
69 | this.model.setSelectProject('');
70 | this.model.setOriMaterials(list);
71 | this.model.setCategoryList(categoryList);
72 | this.model.setProjectList(projectList);
73 | this.model.setMaterials(list);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/webview-react/src/pages/chatGPT/components/ChatList/index.less:
--------------------------------------------------------------------------------
1 | .drawer {
2 | :global {
3 | .ant-drawer-content {
4 | padding: 0px;
5 | background-color: #343541;
6 | }
7 | .ant-drawer-close {
8 | color: #ffffff;
9 | opacity: 0.8;
10 | }
11 | .ant-list-item {
12 | color: rgba(255, 255, 255, 0.85);
13 | }
14 | .ant-list-item-meta {
15 | align-items: center;
16 | width: 100%;
17 | }
18 | .ant-list-item-meta-content {
19 | color: rgba(255, 255, 255, 0.85);
20 | }
21 | .ant-list-item-meta-title {
22 | color: rgba(255, 255, 255, 0.85);
23 | }
24 | .ant-list-item-meta-description {
25 | color: rgba(255, 255, 255, 0.45);
26 | }
27 | .ant-list-item-meta-avatar {
28 | width: 35px;
29 | height: 35px;
30 | border-radius: 35px;
31 | background-color: #10a37f;
32 | }
33 | a {
34 | color: rgba(255, 255, 255, 0.85);
35 | &:hover {
36 | color: rgba(255, 255, 255, 1);
37 | }
38 | }
39 | @media screen and (max-width: 400px) {
40 | .ant-list-item {
41 | flex-direction: column;
42 | align-items: start;
43 | }
44 | .ant-list-item-action {
45 | margin-left: 48px;
46 | }
47 | }
48 | }
49 | }
50 | .drawerShow {
51 | :global {
52 | .ant-drawer-content {
53 | padding: 20px 0px;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/webview-react/src/pages/chatGPT/components/ChatList/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Drawer, List, Space, message } from 'antd';
2 | import React from 'react';
3 | import dayjs from 'dayjs';
4 | import styles from './index.less';
5 | import { ChatSession, exportSession, useChatStore } from '../../store';
6 | import { useState } from '@/hooks/useImmer';
7 | import UpdateSeesionTitle from '../UpdateSeesionTitle';
8 |
9 | interface IProps {
10 | visible: boolean;
11 | onClose: () => void;
12 | }
13 |
14 | const ChatList: React.FC = (props) => {
15 | const [updateTitleVisible, setUpdateTitleVisible] = useState(false);
16 | const [edit, setEdit] = useState({ sessionId: 0, title: '' });
17 |
18 | const chatStore = useChatStore();
19 |
20 | const handleEdit = (sesstion: ChatSession) => {
21 | setEdit((s) => {
22 | s.sessionId = sesstion.id;
23 | s.title = sesstion.topic;
24 | });
25 | setUpdateTitleVisible(true);
26 | };
27 |
28 | const handleEditOk = (title: string) => {
29 | chatStore.updateSessionTopic(title, edit.sessionId);
30 | setUpdateTitleVisible(false);
31 | message.success('修改成功');
32 | };
33 |
34 | return (
35 |
44 | {
49 | const actions = [];
50 | if (
51 | chatStore.currentSessionIndex !== item.id &&
52 | chatStore.sessions.length > 1
53 | ) {
54 | actions.push(
55 | {
58 | chatStore.changeSession(item.id);
59 | }}
60 | >
61 | 切换
62 | ,
63 | );
64 | }
65 | actions.push(
66 | {
69 | exportSession(item).then(() => {
70 | message.success('导出成功');
71 | });
72 | }}
73 | >
74 | 导出
75 | ,
76 | );
77 | actions.push(
78 | {
81 | handleEdit(item);
82 | }}
83 | >
84 | 编辑
85 | ,
86 | );
87 | actions.push(
88 | {
91 | chatStore.delSeesion(item.id);
92 | message.success('删除成功');
93 | }}
94 | >
95 | 删除
96 | ,
97 | );
98 | return (
99 |
100 | }
102 | title={item.topic}
103 | description={
104 |
105 | {item.messages.length}条对话
106 |
107 | {dayjs(item.id).format('YYYY-MM-DD HH:mm:ss')}
108 |
109 |
110 | }
111 | />
112 |
113 | );
114 | }}
115 | />
116 | {
120 | setUpdateTitleVisible(false);
121 | }}
122 | onOk={handleEditOk}
123 | >
124 |
125 | );
126 | };
127 |
128 | export default ChatList;
129 |
--------------------------------------------------------------------------------
/webview-react/src/pages/chatGPT/components/UpdateSeesionTitle/index.less:
--------------------------------------------------------------------------------
1 | .updateSessionTitle {
2 | :global {
3 | .ant-modal-content {
4 | background-color: #5d5d67;
5 | }
6 | .ant-modal-footer {
7 | border-top: none;
8 | }
9 | .ant-btn {
10 | &:hover {
11 | border-color: #10a37f;
12 | color: #10a37f;
13 | }
14 | }
15 | .ant-btn-primary {
16 | background-color: #10a37f;
17 | border-color: #10a37f;
18 | &:hover {
19 | background-color: #10a37f;
20 | border-color: #10a37f;
21 | color: #ffffff;
22 | }
23 | }
24 | .ant-input {
25 | background: #494a53;
26 | color: #ffffff;
27 | &:hover {
28 | background: #494a53;
29 | }
30 | &:focus {
31 | background: #494a53;
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/webview-react/src/pages/chatGPT/components/UpdateSeesionTitle/index.tsx:
--------------------------------------------------------------------------------
1 | import { Checkbox, Input, Modal } from 'antd';
2 | import React, { FC, useEffect } from 'react';
3 | import { useState } from '@/hooks/useImmer';
4 | import styles from './index.less';
5 |
6 | interface IProps {
7 | visible: boolean;
8 | title?: string;
9 | onCancel: () => void;
10 | onOk: (title: string) => void;
11 | }
12 |
13 | const UpdateSeesionTitle: FC = (props) => {
14 | const [title, setTitle] = useState('');
15 |
16 | useEffect(() => {
17 | if (props.visible) {
18 | setTitle(props.title || '');
19 | }
20 | }, [props.visible]);
21 |
22 | const handleOk = () => {
23 | if (title) {
24 | props.onOk(title);
25 | }
26 | };
27 |
28 | return (
29 |
38 | {
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------