├── .editorconfig
├── .gitignore
├── .npmrc
├── .vscode
├── launch.json
├── settings.json
├── tasks.json
└── xmouse.json
├── .vscodeignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── client
├── components
│ ├── Accordion
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── ContextMenu
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── Dialog
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── Layout
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── SegmentGroup
│ │ ├── index.ts
│ │ └── segment-group.scss
│ ├── Select
│ │ ├── index.tsx
│ │ └── select.scss
│ ├── Switch
│ │ └── index.scss
│ ├── Tab
│ │ ├── index.tsx
│ │ └── tab.scss
│ ├── Toast
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── TreeSelect
│ │ ├── index.less
│ │ └── index.tsx
│ ├── TreeView
│ │ ├── index.css
│ │ └── index.tsx
│ ├── combobox
│ │ ├── index.scss
│ │ └── index.tsx
│ └── index.tsx
├── index.scss
├── index.tsx
├── lib
│ └── font-awesome
│ │ ├── css
│ │ ├── font-awesome.css
│ │ └── font-awesome.min.css
│ │ └── fonts
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
├── pages
│ ├── FileRelation.tsx
│ ├── LegoEditor
│ │ ├── LayoutEditor.tsx
│ │ └── LegoEditor.tsx
│ └── LegoList
│ │ ├── LegoList.tsx
│ │ ├── ViewForCustom
│ │ ├── CustomView.tsx
│ │ ├── CustomViewItem.tsx
│ │ ├── CustomViewPlugin.tsx
│ │ ├── DialogForAddLego.tsx
│ │ ├── DialogForDelete.tsx
│ │ ├── DialogForExport.tsx
│ │ ├── DialogForImport.tsx
│ │ ├── DialogForMultiDelete.tsx
│ │ ├── index.tsx
│ │ └── lego.svg
│ │ ├── ViewForFiles
│ │ └── index.tsx
│ │ └── ViewForPackages
│ │ └── index.tsx
├── toolkit.d.ts
└── utilities
│ └── vscode.ts
├── eslint.config.mjs
├── index.html
├── package.json
├── pnpm-lock.yaml
├── resources
├── add.dark.svg
├── add.light.svg
└── x.png
├── screenshots
├── XMouseAdd.gif
├── XMouseStart.gif
├── coffee.png
├── design.drawio.png
└── structure.drawio.png
├── src
├── extension.ts
├── modules
│ ├── XMFile.ts
│ ├── checkIgnore.ts
│ ├── solveDirectory.ts
│ ├── solveExports.ts
│ └── solvePackages.ts
├── panels
│ ├── FileRelationPanel.ts
│ ├── LegoEditorPanel.ts
│ ├── LegoList.data.ts
│ └── LegoListPanel
│ │ ├── LegoListPanel.ts
│ │ ├── Service
│ │ ├── CustomService.ts
│ │ ├── DirectoryService.ts
│ │ └── PackageService.ts
│ │ └── index.ts
├── storage
│ └── index.ts
└── utilities
│ ├── astTool.ts
│ ├── fileLoader.ts
│ ├── getNonce.ts
│ └── getUri.ts
├── tsconfig.json
└── vite.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = false
12 | insert_final_newline = true
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 | "configurations": [
4 | {
5 | "name": "Launch Extension",
6 | "type": "extensionHost",
7 | "request": "launch",
8 | "runtimeExecutable": "${execPath}",
9 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
10 | "sourceMaps": true,
11 | "outFiles": [
12 | "${workspaceRoot}/out/extension.js"
13 | ],
14 | "preLaunchTask": "npm: dev"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | // Enable the ESlint flat config support
4 | "eslint.experimental.useFlatConfig": true,
5 |
6 | // Disable the default formatter, use eslint instead
7 | "prettier.enable": false,
8 | "editor.formatOnSave": false,
9 |
10 | // Auto fix
11 | "editor.codeActionsOnSave": {
12 | "source.fixAll.eslint": "explicit",
13 | "source.organizeImports": "never"
14 | },
15 |
16 | // Silent the stylistic rules in you IDE, but still auto fix them
17 | "eslint.rules.customizations": [
18 | { "rule": "style/*", "severity": "off" },
19 | { "rule": "format/*", "severity": "off" },
20 | { "rule": "*-indent", "severity": "off" },
21 | { "rule": "*-spacing", "severity": "off" },
22 | { "rule": "*-spaces", "severity": "off" },
23 | { "rule": "*-order", "severity": "off" },
24 | { "rule": "*-dangle", "severity": "off" },
25 | { "rule": "*-newline", "severity": "off" },
26 | { "rule": "*quotes", "severity": "off" },
27 | { "rule": "*semi", "severity": "off" }
28 | ],
29 |
30 | // Enable eslint for all supported languages
31 | "eslint.validate": [
32 | "javascript",
33 | "javascriptreact",
34 | "typescript",
35 | "typescriptreact",
36 | "vue",
37 | "html",
38 | "markdown",
39 | "json",
40 | "jsonc",
41 | "yaml",
42 | "toml",
43 | "gql",
44 | "graphql"
45 | ],
46 | "files.exclude": {
47 | "out": false // set this to true to hide the "out" folder with the compiled JS files
48 | },
49 | "search.exclude": {
50 | "out": true // set this to false to include "out" folder in search results
51 | },
52 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
53 | "typescript.tsc.autoDetect": "off",
54 | "cSpell.words": [
55 | "bumpp",
56 | "cise",
57 | "cytoscape",
58 | "digichanges",
59 | "dustycoder",
60 | "jsesc",
61 | "nodepath",
62 | "vuecompiler",
63 | "xmouse"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/.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 | "label": "npm: build",
8 | "type": "shell",
9 | "command": "npm run build:dev"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/xmouse.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "group": "SolidJS",
4 | "name": "createSignal",
5 | "source": {
6 | "from": "solid-js",
7 | "import": "createSignal"
8 | },
9 | "code": " const [getState, setState] = createSignal('loading')"
10 | },
11 | {
12 | "group": "其他",
13 | "name": "ignore",
14 | "source": {
15 | "from": "",
16 | "import": ""
17 | },
18 | "code": "/** @ts-ignore */\n"
19 | },
20 | {
21 | "group": "其他",
22 | "name": "resolve",
23 | "source": {
24 | "from": "",
25 | "import": ""
26 | },
27 | "code": "\nconst test = require(path.relative('C:/Users/欧拯救/Desktop/XMouse/src', require.resolve('C:/Users/欧拯救/Desktop/win11React/node_modules/react')))"
28 | },
29 | {
30 | "group": "其他",
31 | "name": "child.exec",
32 | "source": {
33 | "from": "",
34 | "import": ""
35 | },
36 | "code": " const childProcess = exec(command, { cwd: context.extensionPath });\n console.log('workspacePath', workspacePath)\n // 监听标准输出\n childProcess.stdout?.on('data', (data) => {\n console.log(data.toString());\n });\n\n // 监听错误输出\n childProcess.stderr?.on('data', (data) => {\n console.error(data.toString());\n });\n // 监听命令执行完毕事件\n childProcess.on('close', (code) => {\n if (code === 0) {\n console.log('Command executed successfully!');\n } else {\n console.error(`Command execution failed with code ${code}`);\n }\n });"
37 | },
38 | {
39 | "group": "VSCode",
40 | "name": "遍历文件",
41 | "source": {
42 | "from": "vscode",
43 | "import": "* as vscode"
44 | },
45 | "code": " vscode.workspace.findFiles('{**/package.json,package.json}', '**/node_modules/**').then(uris => { \n \n })"
46 | },
47 | {
48 | "group": "VSCode",
49 | "name": "遍历目录",
50 | "source": {
51 | "from": "vscode",
52 | "import": "* as vscode"
53 | },
54 | "code": "vscode.workspace.fs.readDirectory(vscode.Uri.file(path.join(packages[0].root, 'node_modules', item))).then((rrr) => {})"
55 | },
56 | {
57 | "name": "test",
58 | "group": "test",
59 | "source": {
60 | "from": "",
61 | "import": ""
62 | },
63 | "code": "test"
64 | }
65 | ]
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | # This file contains all the files/directories that should
2 | # be ignored (i.e. not included) in the final packaged extension.
3 |
4 | # Ignore extension configs
5 | .vscode/**
6 | # Ignore test files
7 | .vscode-test/**
8 |
9 | out/test/**
10 |
11 | # Ignore source code
12 | src/**
13 |
14 | # Ignore all client files except the build directory
15 | client/**
16 |
17 | # Ignore Misc
18 | .yarnrc
19 | vsc-extension-quickstart.md
20 | **/.gitignore
21 | **/tsconfig.json
22 | **/vite.config.ts
23 | **/.eslintrc.json
24 | **/*.map
25 | **/*.ts
26 |
27 |
28 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing 贡献指南
2 |
3 | ## Tech Stack 项目技术栈
4 |
5 | - `pnpm` 包管理工具
6 | - `tsup` 打包插件
7 | - `solidjs` 开发插件UI
8 | - `typescript` 作为开发语言
9 | - `vite` 脚手架工具
10 | - `eslint` 格式化代码
11 | - `...`
12 |
13 | ## Extension commands 插件命令
14 |
15 | - `dev:client` 启动开发服务器(针对client项目)
16 | - `build:client` 为生产环境构建产物(针对client项目)
17 | - `preview:client` 本地预览生产构建产物(针对client项目)
18 | - `build:dev` 构建开发环境
19 | - `publish` 插件发布上线
20 | - `pack` 插件项目打包
21 | - `build` 打包输出文件
22 | - `build:ts` 打包`src`文件
23 | - `lint` 使用eslint对代码进行审查
24 | - `lint:fix` 使用eslint格式化
25 | - `lint:ui` 可视化查看eslint规则
26 | - `release` 发布新版本
27 | - `up` 更新项目依赖
28 |
29 | ## 目录分析
30 |
31 | - `client` webview ui界面
32 | - `src` 插件开发逻辑代码
33 | - `resource` 插件静态资源
34 | - `screenshots` 插件项目的一些截图
35 |
36 | ## Extension development cycle 插件开发周期
37 |
38 | 这个基于solidjs的webview扩展的预期开发周期与其他VS Code扩展略有不同。
39 |
40 | 由于`client`目录包含一个独立的SolidJS应用程序,我们可以利用它带来的一些好处。特别是,
41 | 使用Vite, UI开发和迭代周期可以更快地进行
42 | -依赖性管理和项目配置大大简化
43 |
44 | ### UI development cycle UI开发周期
45 |
46 | 因为我们可以利用更快的Vite开发服务器,所以我们鼓励通过运行`pnpm dev:client`命令开始开发webview UI,然后在`client`目录下编辑代码。
47 |
48 | _Tip:打开命令面板,运行“Simple Browser”命令,并在提示时填写`http://localhost:3000/`。这将在VS Code._中打开一个简单的浏览器环境
49 |
50 | ### Message passing 消息传递
51 |
52 | 如果你需要通过VS Code API实现webview上下文和扩展上下文之间的消息传递,`client/utilities/vscode`中提供了一个有用的实用工具。ts文件。
53 |
54 | 该文件包含一个围绕`acquireVsCodeApi()`函数的实用程序包装器,该函数支持在webview和扩展上下文之间传递消息和状态管理。
55 |
56 | 该实用程序还允许webview代码在Vite开发服务器中运行,方法是使用模拟acquireVsCodeApi启用的功能的本地web浏览器特性。这意味着即使使用VS Code API,你也可以继续使用Vite开发服务器构建你的webview UI。
57 |
58 | ### Move to traditional extension development 转向传统的扩展开发
59 |
60 | 一旦你准备好开始构建扩展的其他部分,只需切换到一个开发模型,在你做更改时运行`pnpm build`命令,按`F5`编译你的扩展并打开一个新的扩展开发主机窗口。在主机窗口中,打开命令面板(Mac上为“Ctrl+Shift+P”或“Cmd+Shift+P”)并键入“Hello World (SolidJS): Show”。
61 |
62 | ### Dependency management and project configuration 依赖管理和项目配置
63 |
64 | 如上所述,`client`目录包含一个独立的SolidJS应用程序,这意味着你可以(在大多数情况下)以与开发常规SolidJS应用程序相同的方式来开发你的webview UI。
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 dustycoder.tech
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XMouse
2 |
3 | 来用鼠标编程吧!
4 |
5 | ## 功能点
6 |
7 | 1. 可视化管理代码片段!
8 | 2. 拖拽!拖拽!拖拽!!
9 |
10 | ## 使用指南
11 |
12 | ### 拖拽组件
13 |
14 | 
15 |
16 | ### 添加组件
17 |
18 | 
19 |
20 | ## 支持尘码
21 |
22 | 
23 |
24 | ---
25 |
26 | ## 版本情况
27 |
28 | ### 0.4.0 done
29 |
30 | - 可视化管理你的【代码片段】,并支持拖拽!
31 |
32 | ## 贡献
33 |
34 | 请查看[贡献指南](./CONTRIBUTING.md)
35 |
--------------------------------------------------------------------------------
/client/components/Accordion/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope='accordion'][data-part='item-indicator'] {
2 | transition: all 0.1s;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | }
7 |
8 | [data-scope='accordion'][data-part='item-indicator'][data-state='open'] {
9 | rotate: 90deg;
10 | }
11 |
12 | [data-scope='accordion'][data-part='item-trigger'] {
13 | padding: 8px;
14 | display: inline-flex;
15 | gap: 6px;
16 | cursor: pointer;
17 | justify-content: flex-start;
18 | align-items: center;
19 | width: 100%;
20 | background: unset;
21 | border: unset;
22 | outline: none;
23 | color: var(--vscode-sideBarSectionHeader-foreground);
24 | transition: all 0.1s;
25 | }
26 |
27 | [data-scope='accordion'][data-part='item-trigger'] svg {
28 | width: 1em;
29 | height: 1em;
30 | }
31 |
32 |
33 | [data-scope="accordion"][data-part="item-content"] {
34 | overflow: hidden;
35 | background-color: var(--vscode-editor-background);
36 | max-width: 100%;
37 | color: var(--vscode-sideBarSectionHeader-foreground);
38 | }
--------------------------------------------------------------------------------
/client/components/Accordion/index.tsx:
--------------------------------------------------------------------------------
1 | import { Accordion } from '@ark-ui/solid'
2 | import { Index, createEffect, createSignal } from 'solid-js'
3 | import './index.scss'
4 | export default (props: any) => {
5 | const [getAccordion, setAccordion] = createSignal([props.items[0].name])
6 | return (
7 | { setAccordion(e.value) }} multiple collapsible>
8 | {props.items.map((item: any) => (
9 |
10 |
11 |
12 |
13 |
14 | {item.name}
15 |
16 |
17 | {props.children(item)}
18 |
19 |
20 | ))}
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/client/components/ContextMenu/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope='menu'][data-part='content'] {
2 | margin: 0;
3 | width: 160px;
4 | background-color: var(--vscode-menu-background);
5 | border: 1px solid var(--vscode-menu-border);
6 |
7 | border-radius: 0.25rem;
8 | padding: 0.25rem;
9 | list-style-type: none;
10 | }
11 |
12 | [data-scope='menu'][data-part*='item'] {
13 | all: unset;
14 | cursor: pointer;
15 | font-size: 1rem;
16 | line-height: 1;
17 | color: var(--vscode-menu-foreground);
18 | display: flex;
19 | align-items: center;
20 | height: 2rem;
21 | position: relative;
22 | user-select: none;
23 | border-radius: 3px;
24 | padding: 0 0.25rem 0 1rem;
25 | }
26 |
27 | [data-scope='menu'][data-part*='item'][data-highlighted] {
28 | outline: none;
29 | background-color: var(--vscode-menu-selectionBackground);
30 | color: var(--vscode-menu-selectionForeground);
31 | }
32 |
33 | [data-scope='menu'][data-part*='item'][data-disabled] {
34 | opacity: 0.4;
35 | }
36 |
37 | [data-scope='menu'][data-part='context-trigger'] {
38 | // border: 2px dashed blue;
39 | // border-radius: 4px;
40 | // font-size: 15px;
41 | // padding-block: 40px;
42 | // width: 300px;
43 | // text-align: center;
44 | background: unset;
45 | padding: unset;
46 | border: unset;
47 | color: var(--vscode-sideBarSectionHeader-foreground);
48 | }
49 |
50 | [data-scope='menu'][data-part='separator'] {
51 | width: 100%;
52 | border: 0;
53 | border-top: 1px solid var(--vscode-menu-separatorBackground);
54 | }
55 |
56 | [data-scope='menu'][data-part='label'] {
57 | display: block;
58 | padding-inline: 1rem;
59 | padding-block: 0.5rem;
60 | font-weight: 600;
61 | }
62 |
63 | [data-scope='menu'][data-part='content'][data-state='open'] {
64 | animation: menufadeIn 0.25s ease-out;
65 | }
66 |
67 | [data-scope='menu'][data-part='content'][data-state='closed'] {
68 | // animation: menufadeOut 0.2s ease-in;
69 | }
70 |
71 |
72 |
73 | @keyframes menufadeIn {
74 | from {
75 | opacity: 0;
76 | transform: translateY(-10px);
77 | }
78 |
79 | to {
80 | opacity: 1;
81 | transform: translateY(0);
82 | }
83 | }
84 |
85 | @keyframes menufadeOut {
86 | from {
87 | opacity: 1;
88 | transform: translateY(0);
89 | }
90 |
91 | to {
92 | opacity: 0;
93 | transform: translateY(-10px);
94 | }
95 | }
--------------------------------------------------------------------------------
/client/components/ContextMenu/index.tsx:
--------------------------------------------------------------------------------
1 | import { Menu } from '@ark-ui/solid'
2 | import { For } from 'solid-js'
3 | import './index.scss'
4 | const Basic = (props) => (
5 |
6 |
7 | {props.children}
8 |
9 |
10 |
11 | {(item: any) => (
12 | {item.label}
13 | )}
14 |
15 |
16 |
17 | )
18 |
19 | export default Basic
--------------------------------------------------------------------------------
/client/components/Dialog/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope='dialog'] {
2 |
3 | &[data-part='backdrop'] {
4 | background-color: var(--vscode-editor-background);
5 | /* 设置遮罩层的透明度 */
6 | position: fixed;
7 | opacity: 0.8;
8 | inset: 0px;
9 | }
10 |
11 | &[data-part='backdrop'][data-state='open'] {
12 | animation: backdropFadeIn 0.25s ease-out;
13 | }
14 |
15 | &[data-part='backdrop'][data-state='closed'] {
16 | animation: backdropFadeOut 0.2s ease-in;
17 | }
18 |
19 | &[data-part='positioner'] {
20 | height: 100vh;
21 | width: 100vw;
22 | position: fixed;
23 | inset: 0px;
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | }
28 |
29 | &[data-part='title'] {
30 | margin: 0;
31 | margin-bottom: 24px;
32 | font-weight: 700;
33 | color: var(--vscode-sideBarSectionHeader-foreground);
34 | font-size: 17px;
35 |
36 | }
37 |
38 | &[data-part='description'] {
39 | color: var(--vscode-sideBarSectionHeader-foreground);
40 | font-size: 15px;
41 | line-height: 1.5;
42 | display: flex;
43 | flex-flow: column;
44 | justify-content: space-around;
45 | }
46 |
47 | &[data-part='footer'] {
48 | color: var(--vscode-sideBarSectionHeader-foreground);
49 | margin-top: 32px;
50 | display: flex;
51 | flex-flow: row;
52 | gap: 16px;
53 | justify-content: space-around;
54 | }
55 |
56 | &[data-part='content'] {
57 | background-color: var(--vscode-menu-background);
58 | border: 1px solid var(--vscode-menu-border);
59 | border-radius: 6px;
60 | box-shadow:
61 | rgb(14 18 22 / 35%) 0px 10px 38px -10px,
62 | rgb(14 18 22 / 20%) 0px 10px 20px -15px;
63 | width: calc(100% - 100px);
64 | min-width: 100px;
65 | max-width: 450px;
66 | max-height: 85vh;
67 | padding: 10px 24px 16px;
68 | position: relative;
69 | overflow: auto;
70 | }
71 |
72 | &[data-part='content'][data-state='open'] {
73 | animation: scaleIn 0.25s ease-out;
74 | }
75 |
76 | &[data-part='content'][data-state='closed'] {
77 | animation: scaleOut 0.2s ease-in;
78 | }
79 |
80 | &[data-part='close-trigger'] {
81 | color: var(--vscode-sideBarSectionHeader-foreground);
82 | position: absolute;
83 | top: 10px;
84 | right: 10px;
85 | width: 24px;
86 | height: 24px;
87 | padding: 4px;
88 | outline: unset;
89 | border: unset;
90 | background: unset;
91 | cursor: pointer;
92 |
93 | }
94 |
95 |
96 |
97 | @keyframes backdropFadeIn {
98 | from {
99 | opacity: 0;
100 | }
101 |
102 | to {
103 | opacity: 0.8;
104 | }
105 | }
106 |
107 | @keyframes backdropFadeOut {
108 | from {
109 | opacity: 0.8;
110 | }
111 |
112 | to {
113 | opacity: 0;
114 | }
115 | }
116 |
117 | @keyframes scaleIn {
118 | from {
119 | scale: 0.8;
120 | }
121 |
122 | to {
123 | scale: 1;
124 | }
125 | }
126 |
127 | @keyframes scaleOut {
128 | to {
129 | scale: 0.8;
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/client/components/Dialog/index.tsx:
--------------------------------------------------------------------------------
1 | import { JSX, createSignal, Show, JSXElement } from 'solid-js'
2 | import { Dialog } from '@ark-ui/solid'
3 | import { Portal } from 'solid-js/web'
4 | import './index.scss'
5 | let hasOpen = false;
6 | type DDialogOpen = () => Promise | undefined;
7 | type DDialogClose = (data?: any | undefined) => void;
8 | type DDialogProps = {
9 | ref: (data?: { open: DDialogOpen, close: DDialogClose }) => {},
10 | title: (close: DDialogClose) => JSX.Element,
11 | content: (close: DDialogClose) => JSX.Element,
12 | footer: (close: DDialogClose) => JSX.Element,
13 | trigger: undefined | string | (() => JSX.Element),
14 | }
15 | export default function useDDialog() {
16 | const [getIsOpen, setIsOpen] = createSignal(false)
17 | const context = {
18 | resolveLock: (data?: any) => { },
19 | rejectLock: () => { }
20 | };
21 | const close: DDialogClose = (data?: any) => {
22 | if (data) {
23 | context.resolveLock(data)
24 | } else {
25 | context.rejectLock()
26 | }
27 | setIsOpen(false);
28 | hasOpen = false;
29 | }
30 | const open: DDialogOpen = () => {
31 | if (hasOpen) { return; } hasOpen = true;
32 |
33 | setIsOpen(true)
34 | const lock = new Promise(((resolve, reject) => {
35 | context.resolveLock = resolve;
36 | context.rejectLock = reject;
37 | }));
38 | return lock
39 | }
40 | const DDialog = (props: DDialogProps): JSXElement => {
41 | props?.ref?.({ open, close })
42 | return (
43 | { close() }} closeOnInteractOutside={false}>
44 |
45 |
46 | {typeof props.trigger === 'string' ? props.trigger : props.trigger()}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {props.title(close)}
58 |
59 |
60 |
61 | {props.content(close)}
62 |
63 |
64 | {props.footer(close)}
65 |
66 |
67 |
68 |
69 |
70 |
71 | )
72 | }
73 | return [DDialog, open]
74 | }
--------------------------------------------------------------------------------
/client/components/Layout/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope="layout"] {
2 |
3 | // &[data-layout="component-list"] {
4 | &[data-part="root"] {
5 | display: grid;
6 | grid-template-areas:
7 | 'head head'
8 | 'aside main'
9 | ;
10 | grid-template-rows: 2rem 1fr;
11 | grid-template-columns: 4rem 1fr;
12 | height: 100vh;
13 | width: 100vw;
14 | }
15 |
16 | &[data-part="head"] {
17 | grid-area: head;
18 | display: flex;
19 | flex-flow: row;
20 | }
21 |
22 | &[data-part="aside"] {
23 | grid-area: aside;
24 | overflow-y: auto;
25 | }
26 |
27 | &[data-part="main"] {
28 | grid-area: main;
29 | overflow-y: auto;
30 | }
31 |
32 | &[data-part="column"] {
33 | display: flex;
34 | flex-flow: column;
35 | }
36 |
37 | &[data-part="row"] {
38 | display: flex;
39 | flex-flow: row;
40 | }
41 | }
--------------------------------------------------------------------------------
/client/components/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.scss'
2 | export default {
3 | Root(props: any) {
4 | return ({props.children}
)
5 | },
6 | Head(props: any) {
7 | return ()
8 | },
9 | Main(props: any) {
10 | return ({props.children})
11 | },
12 | Aside(props: any) {
13 | return ()
14 | },
15 | Column(props: any) {
16 | return ({props.children}
)
17 | },
18 | Row(props: any) {
19 | return ({props.children}
)
20 | }
21 | }
--------------------------------------------------------------------------------
/client/components/SegmentGroup/index.ts:
--------------------------------------------------------------------------------
1 | import { SegmentGroup } from '@ark-ui/solid'
2 | import './segment-group.scss'
3 | export { SegmentGroup }
4 | export default SegmentGroup;
--------------------------------------------------------------------------------
/client/components/SegmentGroup/segment-group.scss:
--------------------------------------------------------------------------------
1 | [data-scope='segment-group'][data-part='root'] {
2 | position: relative;
3 | display: flex;
4 | align-items: center;
5 | gap: 16px;
6 | width: fit-content;
7 | padding: 4px;
8 | background-color: #f1f3f5;
9 | border-radius: 4px;
10 | gap: 8px;
11 | }
12 |
13 | [data-scope='segment-group'][data-part='item'] {
14 | display: inline-flex;
15 | align-items: center;
16 | gap: 8px;
17 | position: relative;
18 | z-index: 2;
19 | padding-left: 0;
20 | padding: 8px;
21 | width: 100%;
22 | text-align: center;
23 | }
24 |
25 | [data-scope='segment-group'][data-part='item'][data-disabled] {
26 | cursor: not-allowed;
27 | }
28 |
29 | [data-scope='segment-group'][data-part='item-text'][data-disabled] {
30 | opacity: 0.4;
31 | }
32 |
33 | [data-scope='segment-group'][data-part='item-control'] {
34 | height: 20px;
35 | width: 20px;
36 | background-color: #eee;
37 | border: solid 2px grey;
38 | border-radius: 50%;
39 | display: flex;
40 | align-items: center;
41 | justify-content: center;
42 | display: none;
43 | }
44 |
45 | [data-scope='segment-group'][data-part='item-control'][data-focus] {
46 | outline: 2px solid royalblue;
47 | }
48 |
49 | [data-scope='segment-group'][data-part='item-control'][data-hover] {
50 | background-color: #ccc;
51 | }
52 |
53 | [data-scope='segment-group'][data-part='item-control'][data-state='checked'] {
54 | background-color: #2196f3;
55 | border-color: #2196f3;
56 | }
57 |
58 | [data-scope='segment-group'][data-part='item-control'][data-state='checked']::after {
59 | display: initial;
60 | }
61 |
62 | [data-scope='segment-group'][data-part='item-control']::after {
63 | content: '';
64 | display: none;
65 | width: 4px;
66 | height: 9px;
67 | border: solid white;
68 | border-width: 0 2px 2px 0;
69 | transform: rotate(45deg);
70 | position: relative;
71 | top: -1px;
72 | }
73 |
74 | [data-scope='segment-group'][data-part='indicator'] {
75 | height: 4px;
76 | background-color: red;
77 | z-index: 1;
78 | border-radius: 0.25rem;
79 | box-shadow:
80 | rgba(0, 0, 0, 0.05) 0px 0.0625rem 0.1875rem,
81 | rgba(0, 0, 0, 0.1) 0px 0.0625rem 0.125rem;
82 | background-color: rgb(255, 255, 255);
83 | }
84 |
85 |
86 | //ceshi
87 |
88 | [data-scope='segment-group'] {
89 | &[data-part='root'] {
90 | position: relative;
91 | display: flex;
92 | flex-flow: column;
93 | align-items: center;
94 | gap: 16px;
95 | width: fit-content;
96 | padding: 4px;
97 | // background-color: #f1f3f5;
98 | border-radius: 4px;
99 | gap: 8px;
100 |
101 | }
102 |
103 | &[data-part='item'] {
104 | display: inline-flex;
105 | align-items: center;
106 | gap: 8px;
107 | position: relative;
108 | z-index: 2;
109 | padding-left: 0;
110 | padding: 8px;
111 | width: 100%;
112 | text-align: center;
113 | border-left: 1rem solid #f1f3f5;
114 |
115 | &[data-state="checked"] {
116 | border-left: 1rem solid #2196f3;
117 | }
118 |
119 | &[data-disabled] {
120 | cursor: not-allowed;
121 | }
122 | }
123 |
124 |
125 |
126 | &[data-part='item-text'][data-disabled] {
127 | opacity: 0.4;
128 | }
129 |
130 | &[data-part='item-control'] {
131 | display: none;
132 | }
133 |
134 |
135 | &[data-part='indicator'] {
136 | display: none;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/client/components/Select/index.tsx:
--------------------------------------------------------------------------------
1 | import { splitProps } from "solid-js";
2 | import { Select } from '@ark-ui/solid'
3 | import { Index, Portal } from 'solid-js/web'
4 | import './select.scss'
5 | export default (props: any) => {
6 | const [selectProps] = splitProps(props, ['onValueChange', 'multiple'])
7 | return (
8 |
9 | Framework
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Frameworks
23 |
24 | {(item) => (
25 |
26 | {item().name}
27 | ✓
28 |
29 | )}
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
--------------------------------------------------------------------------------
/client/components/Select/select.scss:
--------------------------------------------------------------------------------
1 | [data-scope='select'][data-part='content'][data-state='open'] {
2 | animation: fadeIn 0.25s ease-out;
3 | }
4 |
5 | [data-scope='select'][data-part='content'][data-state='closed'] {
6 | animation: fadeOut 0.2s ease-in;
7 | }
8 |
9 | [data-scope='select'][data-part='content'] {
10 | border: 1px solid var(--dropdown-border);
11 | background: var(--input-background);width: var(--reference-width);
12 | z-index: 1;
13 | padding: 12px;
14 | }
15 |
16 | [data-scope='select'][data-part='item-group-label'] {
17 | font-weight: bold;
18 | }
19 |
20 | [data-scope='select'][data-part='item'] {
21 | display: flex;
22 | justify-content: space-between;
23 | }
24 |
25 | [data-scope='select'][data-part='item'][data-disabled] {
26 | color: var(--input-foreground);
27 |
28 | }
29 |
30 | [data-scope='select'][data-part='control'] {
31 | box-sizing: border-box;
32 | position: relative;
33 | display: flex;
34 | flex-direction: row;
35 | justify-content: space-between;
36 | color: var(--input-foreground);
37 | background: var(--input-background);
38 | border-radius: calc(var(--corner-radius-round) * 1px);
39 | border: calc(var(--border-width) * 1px) solid var(--dropdown-border);
40 | height: calc(var(--input-height) * 1px);
41 | min-width: var(--input-min-width);
42 | }
43 |
44 | [data-scope='select'][data-part='trigger'] {
45 | display: inline-flex;
46 | gap: 8px;
47 | background: unset;
48 | color: var(--input-foreground);
49 | border: unset;
50 | align-items: center;
51 | flex-grow: 1;
52 | }
53 |
54 | [data-scope='select'][data-part='trigger'] svg {
55 | width: 1em;
56 | height: 1em;
57 | }
58 |
59 | @keyframes fadeIn {
60 | from {
61 | opacity: 0;
62 | transform: translateY(-10px);
63 | }
64 |
65 | to {
66 | opacity: 1;
67 | transform: translateY(0);
68 | }
69 | }
70 |
71 | @keyframes fadeOut {
72 | from {
73 | opacity: 1;
74 | transform: translateY(0);
75 | }
76 |
77 | to {
78 | opacity: 0;
79 | transform: translateY(-10px);
80 | }
81 | }
--------------------------------------------------------------------------------
/client/components/Switch/index.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | [data-scope='switch'] {
4 |
5 | &[data-part='root'] {
6 | display: flex;
7 | align-items: center;
8 | position: relative;
9 | width: fit-content;
10 |
11 | --switch-track-diff: calc(var(--switch-track-width) - var(--switch-track-height));
12 | --switch-thumb-x: var(--switch-track-diff);
13 | --switch-track-width: 1.875rem;
14 | --switch-track-height: 1rem;
15 | }
16 |
17 | &[data-part='control'] {
18 | display: inline-flex;
19 | flex-shrink: 0;
20 | -webkit-box-pack: start;
21 | justify-content: flex-start;
22 | box-sizing: content-box;
23 | cursor: pointer;
24 | border-radius: 9999px;
25 | padding: 0.125rem;
26 | width: var(--switch-track-width);
27 | height: var(--switch-track-height);
28 | transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
29 | transform;
30 | transition-duration: 150ms;
31 |
32 | --switch-bg: #cbd5e0;
33 | background: var(--switch-bg);
34 | }
35 |
36 | &[data-part='control'][data-state='checked'] {
37 | --switch-bg: #3182ce;
38 | }
39 |
40 | &[data-part='control'][data-focus] {
41 | box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
42 | }
43 |
44 | &[data-part='control'][data-disabled] {
45 | opacity: 0.4;
46 | cursor: not-allowed;
47 | }
48 |
49 | &[data-part='thumb'] {
50 | background: white;
51 | transition-property: transform;
52 | transition-duration: 200ms;
53 | border-radius: inherit;
54 | width: var(--switch-track-height);
55 | height: var(--switch-track-height);
56 | position: relative;
57 | }
58 |
59 | &[data-part='thumb']:before {
60 | transition: background-color 0.2s ease-in-out;
61 | position: absolute;
62 | --thumb-size: calc(var(--switch-track-height) + 0.7rem);
63 | height: var(--thumb-size);
64 | width: var(--thumb-size);
65 | background-color: transparent;
66 | content: '';
67 | z-index: 1;
68 | top: 50%;
69 | left: 50%;
70 | transform: translate(-50%, -50%);
71 | border-radius: inherit;
72 | }
73 |
74 | &[data-part='thumb'][data-hover]:before {
75 | background-color: rgba(0, 0, 0, 0.048);
76 | }
77 |
78 | &[data-part='thumb'][data-hover][data-state='checked']:before {
79 | background-color: rgba(144, 202, 249, 0.295);
80 | }
81 |
82 | &[data-part='thumb'][data-state='checked'] {
83 | transform: translateX(var(--switch-thumb-x));
84 | }
85 |
86 | &[data-part='label'] {
87 | user-select: none;
88 | margin-inline-start: 0.5rem;
89 | }
90 | }
--------------------------------------------------------------------------------
/client/components/Tab/index.tsx:
--------------------------------------------------------------------------------
1 | import { Tabs, } from '@ark-ui/solid'
2 |
3 | import { createSignal, Index, splitProps, } from 'solid-js'
4 |
5 | import './tab.scss'
6 |
7 | const Item = Tabs.Content
8 | const List = (props) => {
9 | return (
10 |
11 | {(tab) => (
12 | {tab().label}
13 | )}
14 |
15 | )
16 | }
17 | const Root = (props: any) => {
18 | const [, other] = splitProps(props, [])
19 | return (
20 |
21 | {props.children}
22 |
23 | )
24 | };
25 |
26 | export default {
27 | Item, List, Root
28 | }
--------------------------------------------------------------------------------
/client/components/Tab/tab.scss:
--------------------------------------------------------------------------------
1 | [data-scope='tabs'] {
2 | &[data-part='root'] {}
3 |
4 | &[data-part='root'][data-orientation='vertical'] {
5 | display: flex;
6 | }
7 |
8 | &[data-part='list'] {
9 | margin: 0 0 -0.1em;
10 | overflow: visible;
11 | text-overflow: ellipsis;
12 | border-bottom: 1px solid var(--vscode-editor-foreground);
13 | display: flex;
14 | flex-flow: row nowrap;
15 | }
16 |
17 | &[data-part='trigger'] {
18 | position: relative;
19 | margin: 0;
20 | padding: 0.3em 0.5em 0.4em;
21 | border-radius: 0.2em 0.2em 0 0;
22 | overflow: visible;
23 | text-overflow: ellipsis;
24 | white-space: nowrap;
25 | font-size: inherit;
26 | background-color: unset;
27 | border: unset;
28 | color: var(--vscode-editor-foreground);
29 | }
30 |
31 | &[data-part='trigger']:hover,
32 | &[data-part='trigger']:focus,
33 | &[data-part='trigger']:active {
34 | outline: 0;
35 | border-radius: 0;
36 | color: inherit;
37 | }
38 |
39 | &[data-part='trigger'][aria-selected='true'] {
40 | border-radius: 0;
41 | outline: 0;
42 | }
43 |
44 | &[data-part='trigger']:hover::after,
45 | &[data-part='trigger']:focus::after {
46 | border-color: hsl(20, 96%, 48%);
47 | }
48 |
49 | &[data-part='trigger']:hover::after,
50 | &[data-part='trigger']:focus::after,
51 | &[data-part='trigger'][aria-selected='true']::after {
52 | position: absolute;
53 | top: 100%;
54 | right: -1px;
55 | left: -1px;
56 | border-radius: 0.2em 0.2em 0 0;
57 | border-top: 3px solid hsl(20, 96%, 48%);
58 | content: '';
59 | }
60 |
61 | &[data-part='content'] {
62 | position: relative;
63 | padding: 0.5em 0.5em 0.7em;
64 | border-radius: 0 0.2em 0.2em 0.2em;
65 | }
66 |
67 | &[data-part='content'] p {
68 | margin: 0;
69 | }
70 |
71 | &[data-part='content'] *+p {
72 | margin-top: 1em;
73 | }
74 |
75 | &[data-part='indicator'] {
76 | background-color: red;
77 | }
78 |
79 | &[data-part='indicator'][data-orientation='horizontal'] {
80 | height: 4px;
81 | }
82 |
83 | &[data-part='indicator'][data-orientation='vertical'] {
84 | width: 4px;
85 | }
86 | }
--------------------------------------------------------------------------------
/client/components/Toast/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope='toast'][data-part='root'] {
2 | padding: 8px 12px;
3 | background: var(--vscode-notifications-background);
4 | min-width: 200px;
5 | border: 1px solid var(--vscode-notificationToast-border);
6 | box-shadow:
7 | 0 3px 10px var(--vscode-notificationToast-border),
8 | 0 3px 3px var(--vscode-notificationToast-border);
9 | border-radius: 4px;
10 | }
11 |
12 | [data-scope='toast'][data-part='root'][data-state='open'] {
13 | animation: toast-enter 0.35s cubic-bezier(0.21, 1.02, 0.73, 1) forwards;
14 | }
15 |
16 | [data-scope='toast'][data-part='root'][data-state='closed'] {
17 | animation: toast-exit var(--remove-delay) forwards cubic-bezier(0.06, 0.71, 0.55, 1);
18 | }
19 |
20 | [data-scope='toast'][data-part='root'][data-type='success'] {
21 | color: var(--vscode-notificationsInfoIcon-foreground);
22 | }
23 |
24 | @keyframes toast-enter {
25 | from {
26 | transform: translate3d(0, 200%, 0) scale(0.6);
27 | opacity: 0.5;
28 | }
29 |
30 | to {
31 | transform: translate3d(0, 0, 0) scale(1);
32 | opacity: 1;
33 | }
34 | }
35 |
36 | @keyframes toast-exit {
37 | from {
38 | transform: translate3d(0, 0, -1px) scale(1);
39 | opacity: 1;
40 | }
41 |
42 | to {
43 | transform: translate3d(0, 150%, -1px) scale(0.6);
44 | opacity: 0;
45 | }
46 | }
--------------------------------------------------------------------------------
/client/components/Toast/index.tsx:
--------------------------------------------------------------------------------
1 | import { Toast, createToaster } from '@ark-ui/solid'
2 | import './index.scss'
3 | const Basic = (props) => {
4 | const [Toaster, toast] = createToaster({
5 | placement: 'top-end',
6 | render(toast) {
7 | return (
8 |
9 | {toast().title}
10 | {toast().description}
11 | Close
12 |
13 | )
14 | },
15 | })
16 |
17 | // createEffect(() =>
18 | // toast().subscribe((toasts) => toasts.forEach((toast) => console.log('Toast:', toast))),
19 | // )
20 |
21 | const handleToast = (info: { title: string, description: string }) => {
22 | const id = toast().create(info)
23 | }
24 | props.ref({ open: handleToast })
25 | return (
26 |
27 | )
28 | }
29 |
30 | export default Basic
--------------------------------------------------------------------------------
/client/components/TreeSelect/index.less:
--------------------------------------------------------------------------------
1 | [class^='cm-'] {
2 | --cui-color-bg-0: var(--input-background);
3 | --cui-color-bg-3: var(--vscode-editor-background);
4 | --cui-color-text-1: var(--input-foreground);
5 | --cui-border-radius-small: calc(var(--corner-radius-round) * 1px);
6 | --cui-default-border: var(--dropdown-border);
7 | --cui-color-border-2: var(--dropdown-border);
8 | --height-control-default: calc(var(--input-height) * 1px);
9 | }
10 |
11 | .cm-dropdown.cm-dropdown-visible.cm-dropdown-open {
12 | border: 1px solid var(--vscode-button-secondaryForeground);
13 | }
14 |
15 | .cm-field-value:hover {
16 | border-color: var(--vscode-button-secondaryForeground);
17 | }
18 |
19 | .cm-field-value:focus {
20 | border-color: var(--vscode-button-secondaryForeground);
21 | }
22 |
23 | :focus-visible {
24 | outline: unset
25 | }
26 |
27 | .cm-tree-select {
28 | .cm-tag.cm-tag-large {
29 | background: var(--vscode-badge-background);
30 | color: var(--vscode-badge-foreground);
31 |
32 | .cm-tag-text {
33 | display: block;
34 | height: auto;
35 | line-height: normal;
36 | max-width: 80px;
37 | overflow: hidden;
38 | white-space: nowrap;
39 | text-overflow: ellipsis;
40 | }
41 |
42 | }
43 | }
--------------------------------------------------------------------------------
/client/components/TreeSelect/index.tsx:
--------------------------------------------------------------------------------
1 | import { TreeSelect } from 'cui-solid'
2 | import 'cui-solid/src/theme/TreeSelect.less'
3 | import 'cui-solid/src/theme/Dropdown.less'
4 | import 'cui-solid/src/theme/Tree.less'
5 | import 'cui-solid/src/theme/Value.less'
6 | import 'cui-solid/src/theme/Tag.less'
7 | import 'cui-solid/src/theme/Icon.less'
8 | import 'cui-solid/src/theme/Checkbox.less'
9 | import './index.less'
10 | export default TreeSelect
--------------------------------------------------------------------------------
/client/components/TreeView/index.css:
--------------------------------------------------------------------------------
1 | [data-scope='tree-view'][data-part='tree'],
2 | [data-scope='tree-view'][data-part='treeitem'],
3 | [data-scope='tree-view'][data-part='branch-content'] {
4 | padding-left: 0;
5 | list-style: none;
6 | }
7 |
8 | [data-scope='tree-view'][data-part='treeitem'],
9 | [data-scope='tree-view'][data-part='branch-text'],
10 | [data-scope='tree-view'][data-part='branch-control'] {
11 | display: flex;
12 | padding: 2px 0;
13 | padding-left: calc(var(--depth) * 6px);
14 | gap: 4px;
15 | overflow:hidden;
16 | white-space: nowrap;
17 | text-overflow: ellipsis;
18 | flex-flow: row wrap;
19 | flex-shrink: 0;
20 |
21 | }
22 |
23 | [data-scope='tree-view'][data-part='branch-control'] [data-scope='tree-view'][data-part='treeitem-expand'] {
24 | transition: all;
25 | translate: 0 -2px;
26 | width: 8px;
27 | }
28 |
29 | [data-scope='tree-view'][data-part='branch-control'][data-state="open"] [data-scope='tree-view'][data-part='treeitem-expand'] {
30 | translate: -1px 2px;
31 | transform: rotateZ(90deg);
32 | }
33 |
34 | [data-scope='tree-view'][data-part='branch-control']:hover {
35 | background: rgba(105, 105, 105, 0.475);
36 | }
37 |
38 | [data-scope='tree-view'][data-part='branch-control'][data-selected] {
39 | background: rgba(139, 110, 255, 0.475);
40 | }
--------------------------------------------------------------------------------
/client/components/TreeView/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.css'
2 |
3 | import { createSignal } from "solid-js";
4 | import { For, Show } from 'solid-js'
5 | import { TreeView } from '@ark-ui/solid'
6 |
7 |
8 | let UUID = 0;
9 | type treeNode = { id: string, children?: treeNode[], title: string };
10 |
11 | const LegoListDirTree = (props: { data: treeNode[] }, node: () => {}, onActive: () => {}, load: (data: treeNode) => {}) => {
12 | const dirTree = (data: any, level = 1) => {
13 | const id = UUID++;
14 | data?.children?.forEach?.(item => item.parent = data)
15 | const [getChildren, setChildren] = createSignal(data?.children || [])
16 | return (
17 |
18 | {
20 | props?.onActive?.(data?.xmfiles);
21 | const children = await props?.load?.(data);
22 | children.forEach(item => item.parent = data)
23 | setChildren(children)
24 | }}>
25 |
26 |
27 |
28 |
29 |
30 |
31 | {props?.node?.(data, level)}
32 |
33 |
34 |
35 |
36 | {(key) =>
37 | dirTree({ ...getChildren()[key], name: key }, level + 1)
38 | }
39 |
40 |
41 | )
42 | };
43 | return (
44 |
45 |
46 | {Object.values(props?.data)?.map(item => dirTree(item))}
47 |
48 |
49 |
)
50 | };
51 | export default LegoListDirTree
--------------------------------------------------------------------------------
/client/components/combobox/index.scss:
--------------------------------------------------------------------------------
1 | [data-scope='combobox'] {
2 | &[data-part='option'][data-highlighted] {
3 | background-color: greenyellow;
4 | }
5 |
6 | &[data-part='content'] {
7 | list-style-type: none;
8 | max-width: 240px;
9 | padding: 0;
10 | margin: 0;
11 | }
12 |
13 | &[data-part='content'][data-state='open'] {
14 | animation: comboboxfadeIn 0.25s ease-out;
15 | }
16 |
17 | &[data-part='content'][data-state='closed'] {
18 | animation: comboboxfadeOut 0.2s ease-in;
19 | }
20 |
21 | @keyframes comboboxfadeIn {
22 | from {
23 | opacity: 0;
24 | transform: translateY(-10px);
25 | }
26 |
27 | to {
28 | opacity: 1;
29 | transform: translateY(0);
30 | }
31 | }
32 |
33 | @keyframes comboboxfadeOut {
34 | from {
35 | opacity: 1;
36 | transform: translateY(0);
37 | }
38 |
39 | to {
40 | opacity: 0;
41 | transform: translateY(-10px);
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/client/components/combobox/index.tsx:
--------------------------------------------------------------------------------
1 | import { Combobox } from '@ark-ui/solid'
2 | import { For } from 'solid-js'
3 | import { Portal } from 'solid-js/web'
4 | import type { JSX } from 'solid-js';
5 | import './index.scss'
6 | type Component = (props: P) => JSX.Element;
9 |
10 | const Custom: Component = (props) => {
11 | const items = props.items || ['React', 'Solid', 'Vue'];
12 | return (
13 |
14 | Framework
15 |
16 |
17 | Open
18 | Clear
19 |
20 |
21 |
22 |
23 |
24 | Frameworks
25 |
26 | {(item) => (
27 |
28 | {item}
29 | ✓
30 |
31 | )}
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default Custom
--------------------------------------------------------------------------------
/client/components/index.tsx:
--------------------------------------------------------------------------------
1 | import DLayout from './Layout'
2 | import DSelect from './Select'
3 | import DTab from './Tab'
4 | import DTreeSelect from './TreeSelect'
5 | import DTreeView from './TreeView'
6 | import DAccordion from './Accordion'
7 | import DDialog from './Dialog'
8 | import DContextMenu from './ContextMenu'
9 | import DToast from './Toast'
10 | export { DTreeView, DSelect, DTab, DTreeSelect, DAccordion, DDialog, DContextMenu, DToast, DLayout }
11 |
--------------------------------------------------------------------------------
/client/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | }
5 |
6 |
7 | input,
8 | textarea {
9 | box-sizing: border-box;
10 | position: relative;
11 | display: flex;
12 | flex-direction: row;
13 | color: var(--input-foreground);
14 | background: var(--input-background);
15 | border-radius: calc(var(--corner-radius-round) * 1px);
16 | border: calc(var(--border-width) * 1px) solid var(--dropdown-border);
17 | height: calc(var(--input-height) * 1px);
18 | min-width: var(--input-min-width);
19 | }
20 |
21 | textarea {
22 | height: calc(var(--input-height) * 3px);
23 | }
24 |
25 | button {
26 | &[data-type='primary'] {
27 | align-items: center;
28 | border: 1px solid var(--vscode-button-border, transparent);
29 | border-radius: 2px;
30 | box-sizing: border-box;
31 | cursor: pointer;
32 | display: flex;
33 | justify-content: center;
34 | line-height: 18px;
35 | padding: 4px;
36 | text-align: center;
37 | width: 100%;
38 | color: var(--vscode-button-foreground);
39 | background-color: var(--vscode-button-background);
40 | }
41 |
42 | &[data-type='secondary'] {
43 | border: 1px solid var(--vscode-button-secondaryBorder, transparent);
44 | border-radius: 2px;
45 | box-sizing: border-box;
46 | cursor: pointer;
47 | display: flex;
48 | justify-content: center;
49 | line-height: 18px;
50 | padding: 4px;
51 | text-align: center;
52 | width: 100%;
53 | color: var(--vscode-button-secondaryForeground);
54 | background-color: var(--vscode-button-secondaryBackground);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/client/index.tsx:
--------------------------------------------------------------------------------
1 | /* @refresh reload */
2 | import { render } from "solid-js/web";
3 | import FileRelation from "./pages/FileRelation";
4 | import LegoEditor from "./pages/LegoEditor/LegoEditor";
5 | import LegoList from "./pages/LegoList/LegoList";
6 | import { Environment, useEnvironmentContext } from '@ark-ui/solid'
7 | // import 'cui-solid/src/theme/theme.less'
8 | import './index.scss'
9 | import './lib/font-awesome/css/font-awesome.css'
10 |
11 | if(document.getElementById("FileRelation"))
12 | render(() => , document.getElementById("FileRelation") as HTMLElement);
13 |
14 | if(document.getElementById("LegoEditor"))
15 | render(() => , document.getElementById("LegoEditor") as HTMLElement);
16 |
17 | if(document.getElementById("LegoList"))
18 | render(() => , document.getElementById("LegoList") as HTMLElement);
19 |
--------------------------------------------------------------------------------
/client/lib/font-awesome/css/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
5 |
--------------------------------------------------------------------------------
/client/lib/font-awesome/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/client/lib/font-awesome/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/client/lib/font-awesome/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/client/lib/font-awesome/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/client/lib/font-awesome/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/client/lib/font-awesome/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/client/lib/font-awesome/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/client/lib/font-awesome/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/client/lib/font-awesome/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/client/lib/font-awesome/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/client/pages/FileRelation.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 | import { createSignal, onMount } from 'solid-js'
3 | import { provideVSCodeDesignSystem, vsCodeButton, vsCodeTextArea } from "@vscode/webview-ui-toolkit";
4 | import { vscode } from "../utilities/vscode";
5 | import cytoscape from 'cytoscape';
6 | import cise from 'cytoscape-cise';
7 |
8 | cytoscape.use(cise);
9 | provideVSCodeDesignSystem().register(vsCodeButton(), vsCodeTextArea());
10 |
11 |
12 | const FileRelation: Component = () => {
13 | vscode.postMessage({ command: 'relation.init'});
14 | const [getRelation, setRelation] = createSignal([])
15 | vscode.listenMessage('relation.draw', (data: any) => {
16 | setRelation(data)
17 | draw(getRelation())
18 | })
19 | onMount(() => {
20 | draw(getRelation())
21 | })
22 | function draw(relations) {
23 | if(!relations?.length) return;
24 | const clusters = Object.values(relations?.reduce((res, item) => {
25 | if (item.group) {
26 | res[item.group] = [...res?.[item.group] || [], item.id + ''];
27 | }
28 | return res;
29 | }, {}))
30 |
31 | var cy = cytoscape({
32 | container: document.getElementById('cy'), // container to render in
33 | headless: false,
34 | elements: relations?.map(data => ({ data })) || [],
35 | style: [ // the stylesheet for the graph
36 | {
37 | selector: 'node',
38 | style: {
39 | 'background-color': '#666',
40 | 'label': 'data(label)',
41 | 'color': 'white'
42 | }
43 | },
44 | {
45 | selector: 'edge',
46 | style: {
47 | 'width': 3,
48 | 'line-color': '#ccc',
49 | 'target-arrow-color': '#ccc',
50 | 'target-arrow-shape': 'triangle',
51 | 'curve-style': 'bezier'
52 | }
53 | },
54 | {
55 | selector: '.group',
56 | style: {
57 | 'background-color': 'rgba(2,2,2 ,0.4)',
58 | 'width': 100,
59 | 'height': 100,
60 | 'shape': 'rectangle',
61 | 'label': 'data(label)',
62 | 'color': 'white'
63 | }
64 | }
65 | ],
66 | layout: {
67 | name: 'cise',
68 | allowNodesInsideCircle: true,
69 | refresh: 1,
70 | clusters: clusters,
71 | nodeSeparation:1,
72 | idealInterClusterEdgeLengthCoefficient:8
73 | }
74 | });
75 | cy.elements().forEach(function (element) {
76 | if (element.data('group')) {
77 | var groupId = 'group-' + element.data('group');
78 | var group = cy.getElementById(groupId);
79 |
80 | if (!group.length) {
81 | group = cy.add({
82 | group: 'nodes',
83 | data: { id: groupId, label: element.data('group') },
84 | classes: 'group',
85 | });
86 | }
87 |
88 | element.move({ parent: groupId });
89 | }
90 | });
91 | }
92 | return (
93 |
94 | )
95 | }
96 |
97 | export default FileRelation;
98 |
--------------------------------------------------------------------------------
/client/pages/LegoEditor/LayoutEditor.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 |
3 |
4 | const LegoEditor: Component = () => {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
--------------------------------------------------------------------------------
/client/pages/LegoEditor/LegoEditor.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 | import { createSignal, For, Switch, Match } from 'solid-js'
3 | import { provideVSCodeDesignSystem, vsCodeButton, vsCodeTextArea } from "@vscode/webview-ui-toolkit";
4 | import { vscode } from "../../utilities/vscode";
5 |
6 | provideVSCodeDesignSystem().register(vsCodeButton(), vsCodeTextArea());
7 |
8 |
9 | const LegoEditor: Component = () => {
10 | const [getLego, setLego] = createSignal({})
11 | vscode.listenMessage('lego.editor.updateLego', (data: any) => {
12 | setLego(data)
13 | })
14 |
15 | const handleChange = (name, value, event) => {
16 | vscode.postMessage({
17 | command: 'lego.editor.propChange',
18 | data: {
19 | name, value
20 | }
21 | });
22 | }
23 | return (
24 |
25 | {getLego().name}
26 | {(prop: any) => (
27 | is between 5 and 10}>
28 |
29 | handleChange(prop.name, event.target.value)}
32 | >
33 | {prop.name}
34 |
35 |
36 |
37 | handleChange(prop.name, event.target.value)}
42 | >
43 | {prop.name}
44 |
45 |
46 |
47 | handleChange(prop.name, event.target.checked)}
50 | > {prop.name}
51 |
52 |
53 | enum {prop.type}
54 |
55 |
56 | )}
57 |
58 | )
59 | }
60 |
61 | export default LegoEditor;
62 |
--------------------------------------------------------------------------------
/client/pages/LegoList/LegoList.tsx:
--------------------------------------------------------------------------------
1 | import ViewForCustom from './ViewForCustom';
2 | import ViewForFiles from './ViewForFiles';
3 | import ViewForPackages from './ViewForPackages';
4 | import { createSignal } from 'solid-js'
5 | import { DTab } from '../../components'
6 |
7 | export default () => {
8 | const [getActiveTab, setActiveTab] = createSignal(['json'])
9 |
10 | return (
11 |
12 | { setActiveTab(e.value) }}
15 | style='display: flex;flex-flow: column;height: 100vh;'
16 | >
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/CustomView.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 | import { createSignal, For, Match, Switch, } from 'solid-js'
3 | import { vscode } from "../../../utilities/vscode";
4 | import { DAccordion, DToast, DContextMenu, } from '../../../components'
5 |
6 | import { createStore } from "solid-js/store";
7 | import CustomViewPlugin from "./CustomViewPlugin";
8 | import CustomViewItem from "./CustomViewItem";
9 |
10 | type LegoMeta = {
11 | name: string,
12 | icon: string,
13 | group: string,
14 | tab: string, // UI |
15 | path: string, // npm@xxx | code@xxx
16 | origin: string,
17 | code: string,
18 | }
19 | function handleDrag(event: any, type: any, data: any) {
20 | event.dataTransfer.setData('text/plain', data.code);
21 | vscode.postMessage({ command: `lego.list.drag.${type}`, data: JSON.parse(JSON.stringify(data)) });
22 | }
23 |
24 | const LegoList: Component = (props: any) => {
25 | // 初始化数据
26 | const [legoGroupsStore, setLegoGroups] = createStore([])
27 | const updateLegoGroupStore = (data: any) => {
28 | setLegoGroups(Object.values(
29 | [
30 | ...data.filter(item => item).map((item: any) => ({
31 | group: item.group,
32 | name: item.name,
33 | source: item.source,
34 | code: item.code,
35 | }))
36 | ].reduce((result: any, current: LegoMeta) => {
37 | if (!result[current.group]) {
38 | result[current.group] = {
39 | name: current.group,
40 | legos: []
41 | }
42 | }
43 | result[current.group].legos.push(current)
44 | return result;
45 | }, {})
46 | ))
47 | }
48 | vscode.call('lego.list.get', {}).then(res => {
49 | updateLegoGroupStore(res)
50 | })
51 | vscode.listenMessage('lego.list.fresh', res => {
52 | updateLegoGroupStore(res)
53 | })
54 | const slots: any = {
55 | dialog: [],
56 | operate: [],
57 | itemMenu: [],
58 | emptyButton: [],
59 | }
60 |
61 | CustomViewPlugin.map(install => { install(slots, legoGroupsStore, updateLegoGroupStore) })
62 | return (<>
63 | {/* */}
64 | }>
65 |
66 |
67 |
感谢你这么好看还订阅咱这插件!
68 |
1. 点击【下方按钮】或【右上方加号】,都可添加组件。
69 |
2. 添加组件后,右键组件,可编辑或删除。
70 |
3. 添加组件后,拖拽组件,可以拖拽到代码中。
71 | {slots.emptyButton}
72 |
73 |
74 | 0}>
75 | {(legoGroup: any) => (
76 |
77 | {(lego: any) => (
78 | cb(lego))}
80 | >
81 | { handleDrag(event, 'start', lego) }}
85 | onDragEnd={(event: any) => { handleDrag(event, 'end', lego) }}
86 | >
87 |
88 | )}
89 |
90 | )}
91 |
92 |
93 | {slots.dialog}
94 | >);
95 | };
96 |
97 | export default LegoList;
98 |
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/CustomViewItem.tsx:
--------------------------------------------------------------------------------
1 | import { splitProps } from 'solid-js'
2 | import Test from './lego.svg'
3 | export default (props: any) => {
4 | const [lego, other] = splitProps(props, ['name'])
5 | return (
6 |
7 |
8 |

9 |
10 |
11 | {lego.name}
12 |
13 |
14 | )
15 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/CustomViewPlugin.tsx:
--------------------------------------------------------------------------------
1 | import useDialogForAddLego from "./DialogForAddLego";
2 | import useDialogForDelete from "./DialogForDelete";
3 | import useDialogForExport from "./DialogForExport";
4 | import useDialogForImport from "./DialogForImport";
5 | import useDialogForMultiDelete from "./DialogForMultiDelete";
6 | import { vscode } from "../../../utilities/vscode";
7 |
8 | // 添加/编辑
9 | export function withAdd(slots, legoGroupsStore, setLegoGroups) {
10 | const [DialogForAddLego, openDialogForAddLego] = useDialogForAddLego();
11 | const addLego = () => {
12 | openDialogForAddLego().then((item: any) => {
13 | vscode.call('lego.list.add', JSON.parse(JSON.stringify(item)))
14 | vscode.call('lego.list.get', {}).then(res => {
15 | setLegoGroups(res)
16 | })
17 | })
18 | }
19 | const updateLego = (lego?: any) => {
20 | openDialogForAddLego(lego).then((item: any) => {
21 | vscode.call('lego.list.update', {
22 | old: JSON.parse(JSON.stringify(lego)),
23 | new: JSON.parse(JSON.stringify(item))
24 | })
25 | vscode.call('lego.list.get', {}).then(res => {
26 | setLegoGroups(res)
27 | })
28 | })
29 | }
30 | slots.dialog.push(());
31 | slots.operate.push(());
32 | slots.emptyButton.push(());
33 | slots.itemMenu.push((item) => ({ id: 'edit', label: '编辑', onClick: () => { updateLego(item) } }));
34 | vscode.listenMessage('lego.list.add', () => { addLego() })
35 | }
36 | // 删除
37 | export function withDelete(slots, legoGroupsStore, setLegoGroups) {
38 | const [DialogForDelete, openDialogForDelete] = useDialogForDelete();
39 | slots.dialog.push(());
40 | slots.itemMenu.push((item) => ({
41 | id: 'delete', label: '删除', onClick: () => {
42 | openDialogForDelete(item).then(() => {
43 | vscode.call('lego.list.delete', JSON.parse(JSON.stringify(item)) )
44 | vscode.call('lego.list.get', {}).then(res => {
45 | setLegoGroups(res)
46 | })
47 | })
48 |
49 | }
50 | }));
51 | }
52 |
53 | // 导出
54 | export function withExport(slots, legoGroupsStore, setLegoGroups) {
55 | const [DialogForExport, openDialogForExport] = useDialogForExport();
56 | slots.dialog.push(());
57 | slots.operate.push(());
58 | vscode.listenMessage('lego.list.export', (data: any) => {
59 | openDialogForExport(legoGroupsStore).then((list: any[]) => {
60 | navigator.clipboard.writeText(JSON.stringify(list)).then(() => {
61 | // toastRef.open({
62 | // title: '导出成功!',
63 | // description: '导出内容已经复制到剪贴板啦,去粘贴吧!',
64 | // duration: 20000,
65 | // })
66 | })
67 | })
68 | })
69 | }
70 |
71 |
72 | // 导入
73 | export function withImport(slots, legoGroupsStore, setLegoGroups) {
74 | const [DialogForImport, openDialogForImport] = useDialogForImport();
75 | slots.dialog.push(());
76 | slots.operate.push(());
77 | vscode.listenMessage('command.lego.list.import', (data: any) => {
78 | openDialogForImport({}).then((list: any[]) => {
79 | vscode.call('lego.list.import',list);
80 | vscode.call('lego.list.get', {}).then(res => {
81 | setLegoGroups(res)
82 | })
83 | })
84 | })
85 | }
86 |
87 | // 批量删除
88 | export function withMultiDelete(slots, legoGroupsStore, setLegoGroups) {
89 | const [DialogForMultiDelete, openDialogForMultiDelete] = useDialogForMultiDelete();
90 | const handleMultiDelete = () => {
91 | openDialogForMultiDelete(legoGroupsStore).then((deleteList: any) => {
92 | vscode.postMessage({ command: 'lego.list.deleteMulti', data: JSON.parse(JSON.stringify(deleteList)) });
93 | vscode.call('lego.list.get', {}).then(res => {
94 | setLegoGroups(res)
95 | })
96 | })
97 | }
98 | slots.dialog.push(());
99 | slots.operate.push(());
100 | vscode.listenMessage('lego.list.multi-delete', () => {
101 | handleMultiDelete()
102 | })
103 | }
104 | export default [withAdd, withDelete, withExport, withImport, withMultiDelete]
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/DialogForAddLego.tsx:
--------------------------------------------------------------------------------
1 | import { JSXElement, createSignal } from 'solid-js'
2 | import { DDialog as useDDialog, DLayout } from '../../../components'
3 |
4 | export default () => {
5 | const [getIsAdd, setIsAdd] = createSignal(true)
6 | const [getName, setName] = createSignal('')
7 | const [getGroup, setGroup] = createSignal('')
8 | const [getSource, setSource] = createSignal({ from: '', import: '' })
9 | const [getCode, setCode] = createSignal('')
10 | const [DDialog, openDDialog] = useDDialog()
11 |
12 | return [
13 | (props): JSXElement => (
14 | (
16 | <>{getIsAdd() ? '添加组件' : '编辑组件'}>
17 | )}
18 | content={() => (
19 |
20 |
21 | 组件名:
22 | setName(event.target.value)}>
23 |
24 |
25 | 组件分类:
26 | setGroup(event.target.value)}>
27 |
28 | {/* 组件ICON: */}
29 |
46 |
47 | 组件内容
48 |
49 | )}
50 | footer={(close) => (
51 | <>
52 |
60 |
61 | >
62 | )}
63 | >
64 | ),
65 | (item) => {
66 | setName(item?.name || '');
67 | setGroup(item?.group || '');
68 | setSource(item?.source || { from: '', import: '' });
69 | setCode(item?.code || '');
70 | setIsAdd(!item)
71 | return openDDialog()
72 | }
73 | ]
74 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/DialogForDelete.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal, } from 'solid-js'
2 | import { DDialog as useDDialog } from '../../../components'
3 | export default () => {
4 | const [getData, setData] = createSignal({ name: '' })
5 | const [DDialog, openDDialog] = useDDialog()
6 | const open = (data: any) => {
7 | setData(data)
8 | return openDDialog()
9 | }
10 | const Dialog = () => (
11 | '删除组件'}
13 | content={() => `确定删除【${getData().name}】吗?`}
14 | footer={(close: () => {}) => (
15 | <>
16 |
19 |
22 | >
23 | )}
24 | />
25 | )
26 | return [Dialog, open]
27 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/DialogForExport.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal } from 'solid-js'
2 | import { vscode } from "../../../utilities/vscode";
3 | import { DDialog as useDDialog, DLayout, DTreeSelect, DToast } from '../../../components'
4 |
5 | export default () => {
6 | const [getGroups, setGroups] = createSignal([])
7 | const [getOptions, setOptions] = createSignal([])
8 | const [getValue, setValue] = createSignal([])
9 | const [DDialog, openDDialog] = useDDialog()
10 | let toastRef: any = {};
11 | const openDialogForExport = (data) => {
12 | setGroups(data);
13 | setValue([])
14 | setOptions(
15 | data?.map((group: any, groupIndex: number) => ({
16 | id: groupIndex + 1,
17 | title: group.name,
18 | children: group.legos.map((item: any, itemIndex: number) => ({
19 | id: (groupIndex + 1) + '_' + itemIndex, title: item.name, data: item
20 | }))
21 | })) || []
22 | )
23 | openDDialog().then((list: any[]) => {
24 | navigator.clipboard.writeText(JSON.stringify(list)).then(() => {
25 | toastRef.open({
26 | title: '导出成功!',
27 | description: '导出内容已经复制到剪贴板啦,去粘贴吧!',
28 | duration: 20000,
29 | })
30 | })
31 | })
32 | }
33 | const DialogForExport = (props: any) => {
34 | return (
35 | <>
36 |
37 | (<>
39 | 导出配置
40 | >)}
41 | content={() => (<>
42 |
43 | 请选择导出组件
44 | setValue(value)}
47 | multi
48 | data={getOptions()}
49 | clearable
50 | showMax={3}
51 | showMore
52 | transfer={true}
53 | mode='Shallow'
54 | />
55 |
56 | >)}
57 | footer={(close) => (<>
58 |
69 |
70 | >)}
71 | >
72 | >
73 |
74 | )
75 | }
76 | return [DialogForExport, openDialogForExport]
77 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/DialogForImport.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal } from 'solid-js'
2 | import { Show } from 'solid-js'
3 | import { DDialog as useDDialog, DSelect, DTab, DTreeSelect } from '../../../components'
4 | import { provideVSCodeDesignSystem, vsCodeButton, vsCodeTextArea, vsCodeTextField } from "@vscode/webview-ui-toolkit";
5 | import { vscode } from "../../../utilities/vscode";
6 |
7 | provideVSCodeDesignSystem().register(vsCodeButton(), vsCodeTextArea(), vsCodeTextField());
8 |
9 | export default () => {
10 | const [getJson, setJson] = createSignal('')
11 | const [getErrorInfo, setErrorInfo] = createSignal('')
12 | const [DDialog, openDDialog] = useDDialog()
13 |
14 | const DialogForImport = (props: any) => (
15 | (<>导入配置 >)}
18 | content={() => (<>
19 | 组件配置
20 | {getErrorInfo()}
21 |
27 |
28 | >)}
29 | footer={(close) => (<>
30 |
48 |
49 | >)}
50 | >
51 | )
52 | return [DialogForImport, openDDialog]
53 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/DialogForMultiDelete.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal } from 'solid-js'
2 | import { DDialog as useDDialog, DLayout } from '../../../components'
3 | import { TreeSelect } from 'cui-solid'
4 | export default () => {
5 | const [DDialog, openDDialog] = useDDialog()
6 | const [getGroups, setGroups] = createSignal([])
7 | const [getOptions, setOptions] = createSignal([])
8 | const [getValue, setValue] = createSignal([])
9 | const open = (data: any) => {
10 | setGroups(data);
11 | setValue([])
12 | setOptions(
13 | data?.map((group: any, groupIndex: number) => ({
14 | id: groupIndex + 1,
15 | title: group.name,
16 | children: group.legos.map((item: any, itemIndex: number) => ({
17 | id: (groupIndex + 1) + '_' + itemIndex, title: item.name, data: item
18 | }))
19 | })) || []
20 | )
21 | return openDDialog()
22 | }
23 |
24 | const DialogForMultiDelete = (props: any) => (
25 | (<>
28 | 批量删除
29 | >)}
30 | content={() => (<>
31 |
32 | 请选择组件进行删除
33 | setValue(value)}
37 | multi
38 | data={getOptions()}
39 | clearable
40 | showMax={3}
41 | showMore
42 | transfer={true}
43 | mode='Shallow' />
44 |
45 | >)}
46 | footer={(close) => (<>
47 |
58 |
59 | >)}
60 | >
61 |
62 | )
63 | return [DialogForMultiDelete, open]
64 | }
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/index.tsx:
--------------------------------------------------------------------------------
1 | import CustomView from './CustomView'
2 |
3 | export default CustomView
4 |
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForCustom/lego.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForFiles/index.tsx:
--------------------------------------------------------------------------------
1 | import type { Component } from "solid-js";
2 | import { createSignal, onMount, Show } from 'solid-js'
3 | import { vscode } from "../../../utilities/vscode";
4 | import { DTreeView } from '../../../components'
5 |
6 |
7 | function camelCase(str) {
8 | const nameList = str.split('/');
9 | return nameList[nameList.length - 1].replace(/[-_]([a-z])/g, function (match, group1) {
10 | return group1.toUpperCase();
11 | });
12 | }
13 | const handleDrag = (event: any, type: any, item: any) => {
14 | const name = camelCase((item.title === 'default' ? item.parent.title : item.title).split('.')[0]);
15 | const code = `console.log('${name}',${name})`;
16 | event.dataTransfer.setData('text/plain', code);
17 | vscode.postMessage({
18 | command: `lego.list.drag.${type}`, data: JSON.parse(JSON.stringify({
19 | name,
20 | code,
21 | source: { from: item.path, import: `{ ${name} }` },
22 | }))
23 | });
24 | }
25 | const LegoList: Component = () => {
26 | const [getDirectory, setDirectory] = createSignal([])
27 | vscode.call('lego.list.workspace', {}).then((res: any) => {
28 | console.log('workspace',res)
29 | setDirectory(res)
30 | })
31 |
32 | return (
33 | <>
34 | {
37 | if (item.fileType === 'File') {
38 | const res = await vscode.call('lego.list.file', JSON.parse(JSON.stringify(item)) );
39 | return res
40 | }
41 | if (item.fileType === 'Directory') {
42 | const res = await vscode.call('lego.list.directory', JSON.parse(JSON.stringify(item)));
43 | return res
44 | }
45 | return []
46 | }}
47 | node={(item: any) => (
48 | {
52 | // vscode.call('lego.list.file.open', item);
53 | // }}
54 | onDragStart={(event: any) => { handleDrag(event, 'start', item) }}
55 | onDragEnd={(event: any) => { handleDrag('end', event, item) }}
56 | >
57 |
58 |
59 |
60 |
61 | {item.title}
62 |
63 |
64 | )}
65 | >
66 | >
67 |
68 | );
69 | };
70 |
71 | export default LegoList;
72 |
--------------------------------------------------------------------------------
/client/pages/LegoList/ViewForPackages/index.tsx:
--------------------------------------------------------------------------------
1 | import { createSignal, Show } from 'solid-js'
2 | import { vscode } from "../../../utilities/vscode";
3 | import { DTreeView } from '../../../components'
4 |
5 | function camelCase(str) {
6 | const nameList = str.split('/');
7 | return nameList[nameList.length - 1].replace(/[-_]([a-z])/g, function (match, group1) {
8 | return group1.toUpperCase();
9 | });
10 | }
11 | const handleDrag = (event: any, type: any, item: any) => {
12 | const name = camelCase((item.title === 'default' ? item.parent.title : item.title).split('.')[0]);
13 | const code = `console.log('${name}',${name})`;
14 | event.dataTransfer.setData('text/plain', code);
15 | vscode.postMessage({
16 | command: `lego.list.drag.${type}`, data: JSON.parse(JSON.stringify({
17 | name,
18 | code,
19 | source: { from: item.parent.title, import: `{ ${name} }` },
20 | }))
21 | });
22 | }
23 | export default () => {
24 | const [getPackages, setPackages] = createSignal([])
25 | vscode.call('lego.list.packages', {}).then(data => {
26 | setPackages(data)
27 | })
28 | return (
29 | {
32 | if (item.fileType === 'Dependencie') {
33 | const res = await vscode.call('lego.list.exports', {
34 | root: item.parent.id,
35 | dependencie: item.title
36 | });
37 | return res
38 | }
39 | return item.children || []
40 | }}
41 | node={(item: any) => (
42 | {
46 | vscode.call('lego.list.file.open', item);
47 | }}
48 | onDragStart={(event: any) => { handleDrag(event, 'start', item) }}
49 | onDragEnd={(event: any) => { handleDrag('end', event, item) }}
50 | >
51 |
52 |
53 |
54 |
55 | {item.title}
56 |
57 | )}
58 | >
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/client/toolkit.d.ts:
--------------------------------------------------------------------------------
1 | import "solid-js";
2 |
3 | // An important part of getting the Webview UI Toolkit to work with
4 | // Solid + TypeScript + JSX is to extend the solid-js JSX.IntrinsicElements
5 | // type interface to include type annotations for each of the toolkit's components.
6 | //
7 | // Without this, type errors will occur when you try to use any toolkit component
8 | // in your Solid + TypeScript + JSX component code. (Note that this file shouldn't be
9 | // necessary if you're not using TypeScript or are using tagged template literals
10 | // instead of JSX for your Solid component code).
11 | //
12 | // Important: This file should be updated whenever a new component is added to the
13 | // toolkit. You can find a list of currently available toolkit components here:
14 | //
15 | // https://github.com/microsoft/vscode-webview-ui-toolkit/blob/main/docs/components.md
16 | //
17 | declare module "solid-js" {
18 | namespace JSX {
19 | interface IntrinsicElements {
20 | "vscode-badge": any;
21 | "vscode-button": any;
22 | "vscode-checkbox": any;
23 | "vscode-data-grid": any;
24 | "vscode-divider": any;
25 | "vscode-dropdown": any;
26 | "vscode-link": any;
27 | "vscode-option": any;
28 | "vscode-panels": any;
29 | "vscode-progress-ring": any;
30 | "vscode-radio": any;
31 | "vscode-radio-group": any;
32 | "vscode-tag": any;
33 | "vscode-text-area": any;
34 | "vscode-text-field": any;
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/client/utilities/vscode.ts:
--------------------------------------------------------------------------------
1 | import type { WebviewApi } from 'vscode-webview'
2 |
3 | /**
4 | * A utility wrapper around the acquireVsCodeApi() function, which enables
5 | * message passing and state management between the webview and extension
6 | * contexts.
7 | *
8 | * This utility also enables webview code to be run in a web browser-based
9 | * dev server by using native web browser features that mock the functionality
10 | * enabled by acquireVsCodeApi.
11 | */
12 | class VSCodeAPIWrapper {
13 | private readonly vsCodeApi: WebviewApi | undefined
14 | private callbacks: any = {}
15 | private callbackId = 0
16 |
17 | constructor() {
18 | // Check if the acquireVsCodeApi function exists in the current development
19 | // context (i.e. VS Code development window or web browser)
20 | if (typeof acquireVsCodeApi === 'function')
21 | this.vsCodeApi = acquireVsCodeApi()
22 |
23 | window.addEventListener('message', (event) => {
24 | const { command, data } = event.data
25 | const callbacks = Object.values(this.callbacks?.[command] || {})
26 | callbacks.forEach((callback: any) => callback(data))
27 | })
28 | }
29 |
30 | public listenMessage(command: string, callback: Function) {
31 | if (!this.callbacks[command])
32 | this.callbacks[command] = {}
33 |
34 | const id = this.callbackId++
35 | this.callbacks[command][id] = callback
36 |
37 | return () => {
38 | delete this.callbacks[command][id]
39 | }
40 | }
41 |
42 | /**
43 | * Post a message (i.e. send arbitrary data) to the owner of the webview.
44 | *
45 | * @remarks When running webview code inside a web browser, postMessage will instead
46 | * log the given message to the console.
47 | *
48 | * @param message Abitrary data (must be JSON serializable) to send to the extension context.
49 | */
50 | public postMessage(message: unknown) {
51 | if (this.vsCodeApi)
52 | this.vsCodeApi.postMessage({ command: message.command, data: message.data })
53 | else
54 | console.log(message)
55 | }
56 |
57 | public call(command: string, data: any) {
58 | return new Promise((resolve, reject) => {
59 | const eventId = setTimeout(() => { })
60 |
61 | const handler = (event: any) => {
62 | const response = event.data
63 | if (response.id === eventId) {
64 | window.removeEventListener('message', handler)
65 | response.body.code === 0 ? resolve(response.body.data) : reject(response.body.msg)
66 | }
67 | }
68 | window.addEventListener('message', handler)
69 |
70 | this.vsCodeApi?.postMessage({ id: eventId, command, data })
71 | })
72 | }
73 |
74 | /**
75 | * Get the persistent state stored for this webview.
76 | *
77 | * @remarks When running webview source code inside a web browser, getState will retrieve state
78 | * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
79 | *
80 | * @return The current state or `undefined` if no state has been set.
81 | */
82 | public getState(): unknown | undefined {
83 | if (this.vsCodeApi) {
84 | return this.vsCodeApi.getState()
85 | }
86 | else {
87 | const state = localStorage.getItem('vscodeState')
88 | return state ? JSON.parse(state) : undefined
89 | }
90 | }
91 |
92 | /**
93 | * Set the persistent state stored for this webview.
94 | *
95 | * @remarks When running webview source code inside a web browser, setState will set the given
96 | * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
97 | *
98 | * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved
99 | * using {@link getState}.
100 | *
101 | * @return The new state.
102 | */
103 | public setState(newState: T): T {
104 | if (this.vsCodeApi) {
105 | return this.vsCodeApi.setState(newState)
106 | }
107 | else {
108 | localStorage.setItem('vscodeState', JSON.stringify(newState))
109 | return newState
110 | }
111 | }
112 | }
113 |
114 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi.
115 | export const vscode = new VSCodeAPIWrapper()
116 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu({
4 | typescript: true,
5 | markdown: true,
6 | yaml: true,
7 | gitignore: true,
8 | solid: true,
9 | test: true,
10 | formatters: true,
11 | stylistics: true,
12 | ignores: [
13 | '**/node_modules/**',
14 | '**/dist/**',
15 | '**/build/**',
16 | '**/.git/**',
17 | '**/.vscode/**',
18 | '**/.vscode-test/**',
19 | ],
20 | }, {
21 | files: ['**/*.ts', '**/*.tsx'],
22 | rules: {
23 | 'no-console': 0,
24 | 'no-throw-literal': 'warn',
25 | 'unused-imports/no-unused-vars': 'warn',
26 | 'style/max-statements-per-line': 'warn',
27 | 'array-callback-return': 'warn',
28 | 'no-unreachable-loop': 'warn',
29 | 'ts/no-require-imports': 'warn',
30 | 'ts/ban-types': 'warn',
31 | 'ts/ban-ts-comment': 'off',
32 | 'ts/naming-convention': 'warn',
33 | 'curly': 'warn',
34 | 'eqeqeq': 'warn',
35 | 'semi': 'off',
36 | },
37 | })
38 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hello World
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "publisher": "dustycoder",
3 | "name": "xmouse",
4 | "displayName": "XMouse",
5 | "version": "0.4.13",
6 | "description": "let's code by mouse",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/Llyonss/XMouse.git"
11 | },
12 | "main": "./out/extension.js",
13 | "icon": "resources/x.png",
14 | "engines": {
15 | "vscode": "^1.88.0"
16 | },
17 | "contributes": {
18 | "commands": [
19 | {
20 | "title": "xmouse.lego.open",
21 | "command": "xmouse.lego.open"
22 | },
23 | {
24 | "title": "Xmouse:新增组件",
25 | "command": "xmouse.lego.add"
26 | },
27 | {
28 | "command": "xmouse.lego.list.add",
29 | "title": "添加组件",
30 | "icon": {
31 | "light": "resources/add.light.svg",
32 | "dark": "resources/add.dark.svg"
33 | }
34 | },
35 | {
36 | "command": "xmouse.lego.list.import",
37 | "title": "导入"
38 | },
39 | {
40 | "command": "xmouse.lego.list.export",
41 | "title": "导出"
42 | },
43 | {
44 | "command": "xmouse.lego.list.save",
45 | "title": "保存到项目中"
46 | },
47 | {
48 | "command": "xmouse.lego.list.multi-delete",
49 | "title": "批量删除"
50 | }
51 | ],
52 | "viewsContainers": {
53 | "activitybar": [
54 | {
55 | "id": "xmouse",
56 | "title": "xmouse",
57 | "icon": "resources/x.png"
58 | }
59 | ]
60 | },
61 | "menus": {
62 | "editor/context": [
63 | {
64 | "command": "xmouse.lego.add",
65 | "group": "navigation"
66 | }
67 | ],
68 | "view/title": [
69 | {
70 | "command": "xmouse.lego.list.save",
71 | "when": "view == xmouse.lego.list"
72 | },
73 | {
74 | "command": "xmouse.lego.list.add",
75 | "when": "view == xmouse.lego.list",
76 | "group": "navigation"
77 | },
78 | {
79 | "command": "xmouse.lego.list.export",
80 | "when": "view == xmouse.lego.list"
81 | },
82 | {
83 | "command": "xmouse.lego.list.import",
84 | "when": "view == xmouse.lego.list"
85 | },
86 | {
87 | "command": "xmouse.lego.list.multi-delete",
88 | "when": "view == xmouse.lego.list"
89 | }
90 | ]
91 | },
92 | "views": {
93 | "xmouse": [
94 | {
95 | "type": "webview",
96 | "id": "xmouse.lego.list",
97 | "name": "代码大全"
98 | }
99 | ]
100 | }
101 | },
102 | "scripts": {
103 | "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
104 | "dev:client": "vite build --sourcemap",
105 | "dev:extension": "npm run esbuild-base -- --sourcemap",
106 | "dev": "npm run dev:client && npm run dev:extension",
107 | "build:client": "vite build",
108 | "build:extension": "npm run esbuild-base -- --minify",
109 | "build": "rimraf out && pnpm run build:client && pnpm run build:extension ",
110 | "publish": "vsce publish --no-dependencies",
111 | "pack": "vsce package --no-dependencies",
112 | "lint": "eslint .",
113 | "lint:fix": "eslint . --fix",
114 | "lint:ui": "eslint --inspect-config",
115 | "release": "bumpp"
116 | },
117 | "dependencies": {
118 | "@ark-ui/solid": "^2.2.0",
119 | "@babel/generator": "^7.24.4",
120 | "@babel/parser": "^7.24.4",
121 | "@babel/traverse": "^7.24.1",
122 | "@babel/types": "^7.24.0",
123 | "@digichanges/solid-multiselect": "^0.0.10",
124 | "@suid/icons-material": "^0.7.0",
125 | "@suid/material": "^0.16.0",
126 | "@vscode/webview-ui-toolkit": "^1.4.0",
127 | "@vue/compiler-dom": "^3.4.23",
128 | "@zag-js/solid": "^0.47.0",
129 | "cui-solid": "^0.1.39",
130 | "cytoscape": "^3.29.0",
131 | "cytoscape-cise": "^1.0.0",
132 | "ignore": "^5.3.1",
133 | "local-pkg": "^0.5.0",
134 | "pkg-exports": "^0.2.0",
135 | "solid-js": "^1.8.16",
136 | "vue-template-compiler": "^2.7.16"
137 | },
138 | "devDependencies": {
139 | "@antfu/eslint-config": "^2.15.0",
140 | "@types/babel__generator": "^7.6.8",
141 | "@types/babel__traverse": "^7.20.5",
142 | "@types/cytoscape": "^3.21.0",
143 | "@types/glob": "^8.1.0",
144 | "@types/node": "^20.12.7",
145 | "@types/vscode": "^1.88.0",
146 | "@types/vscode-webview": "^1.57.5",
147 | "bumpp": "^9.4.0",
148 | "esbuild": "^0.20.2",
149 | "eslint": "^9.0.0",
150 | "eslint-plugin-format": "^0.1.1",
151 | "eslint-plugin-solid": "^0.13.2",
152 | "glob": "^10.3.12",
153 | "less": "^4.2.0",
154 | "rimraf": "^5.0.5",
155 | "sass": "^1.75.0",
156 | "taze": "^0.13.6",
157 | "tsup": "^8.0.2",
158 | "typescript": "^5.4.5",
159 | "vite": "^5.2.9",
160 | "vite-plugin-solid": "^2.10.2",
161 | "vscode-test": "^1.6.1"
162 | },
163 | "preview": true
164 | }
165 |
--------------------------------------------------------------------------------
/resources/add.dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/add.light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/resources/x.png
--------------------------------------------------------------------------------
/screenshots/XMouseAdd.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/screenshots/XMouseAdd.gif
--------------------------------------------------------------------------------
/screenshots/XMouseStart.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/screenshots/XMouseStart.gif
--------------------------------------------------------------------------------
/screenshots/coffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/screenshots/coffee.png
--------------------------------------------------------------------------------
/screenshots/design.drawio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/screenshots/design.drawio.png
--------------------------------------------------------------------------------
/screenshots/structure.drawio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/screenshots/structure.drawio.png
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import type { ExtensionContext } from 'vscode'
3 | import { commands } from 'vscode'
4 | import LegoListPanel from './panels/LegoListPanel'
5 | import { LegoEditorPanel } from './panels/LegoEditorPanel'
6 | import { FileRelationPanel } from './panels/FileRelationPanel'
7 | import { XMFile } from './modules/XMFile'
8 | import Storage from './storage'
9 |
10 | export async function activate(context: ExtensionContext) {
11 | (new Storage()).setContext(context)
12 | const xmFile = new XMFile()
13 | const legoListPanel = new LegoListPanel(context, xmFile)
14 |
15 | // const legoEditorPanel = new LegoEditorPanel(context, xmFile.files)
16 | // const fileRelationFile = new FileRelationPanel(context, xmFile.relations);
17 | // const workspacePath = vscode.workspace.workspaceFolders[0].uri.fsPath;
18 | }
19 |
--------------------------------------------------------------------------------
/src/modules/XMFile.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import * as fs from 'node:fs'
3 | import * as vscode from 'vscode'
4 | import * as types from '@babel/types'
5 | import * as parser from '@babel/parser'
6 | import traverse from '@babel/traverse'
7 | import Ignore from 'ignore'
8 | import * as vuecompiler from '@vue/compiler-dom'
9 | import { getExportsRuntime, getExportsStatic } from 'pkg-exports'
10 | import {
11 | getPackageInfo,
12 | importModule,
13 | isPackageExists,
14 | resolveModule,
15 | } from 'local-pkg'
16 |
17 | interface FileItem {
18 | id: string
19 | title: string
20 | fileType: string
21 | fileExt?: string
22 | children?: FileItem[]
23 | leaf?: boolean
24 | path: string
25 | }
26 | export class XMFile {
27 | packages: { root: string, uri: vscode.Uri, json: {}, alias: any[] }[] = []
28 | files: any[] = []
29 | relations: any[] = []
30 | direction: any[] = []
31 | gitignoreContents: Map = new Map()
32 | constructor() { }
33 | async init() {
34 | // this.packages = await this.solvePackageJson();
35 | // this.files = await this.solveFiles(this.packages);
36 | // this.direction = await this.solveDirection();
37 | // this.relations = this.solveRelation(this.files);
38 | }
39 |
40 | async saveWorkspaceConf(configData: any) {
41 | const workspaceFolders = vscode.workspace.workspaceFolders
42 | workspaceFolders?.forEach((folder) => {
43 | const snippetsFilePath = vscode.Uri.file(path.join(folder.uri.fsPath, '.vscode', 'xmouse.json'))
44 | const snippetsContent = JSON.stringify(configData, null, 2)
45 | vscode.workspace.fs.writeFile(snippetsFilePath, Buffer.from(snippetsContent, 'utf-8'))
46 | })
47 | }
48 |
49 | async readWorkspaceConf() {
50 | const workspaceFolders = vscode.workspace.workspaceFolders
51 | const result = workspaceFolders?.map(async (folder) => {
52 | const snippetsFilePath = vscode.Uri.file(path.join(folder.uri.fsPath, '.vscode', 'xmouse.json'))
53 | try {
54 | const file = await vscode.workspace.fs.readFile(snippetsFilePath)
55 | const conf = JSON.parse(file.toString())
56 | if (!Array.isArray(conf))
57 | return []
58 |
59 | return conf.filter(item => item.hasOwnProperty('name') && item.hasOwnProperty('group') && item.hasOwnProperty('code'))
60 | }
61 | catch (e) {
62 | return []
63 | }
64 | }) || []
65 | return (await Promise.all(result)).flat()
66 | }
67 |
68 | async solvePackageJson() {
69 | const uris = await vscode.workspace.findFiles('{package.json,**/package.json}', '{**/node_modules/**, node_modules/**,dist/**,**/dist/**}')
70 | const packages = uris.map(uri => ({
71 | root: path.dirname(uri.fsPath),
72 | uri,
73 | json: require(uri.fsPath),
74 | alias: [],
75 | }))
76 | return packages
77 | }
78 |
79 | async solveDirection() {
80 | const workspaceFolders = vscode.workspace.workspaceFolders
81 |
82 | if (workspaceFolders) {
83 | const result: FileItem[] = []
84 | for (const workspaceFolder of workspaceFolders)
85 | await this.walk(workspaceFolder.uri.fsPath, result, true)
86 | return result
87 | }
88 | return []
89 | }
90 |
91 | async walk(directory: string, parent: FileItem[], lazy: boolean = false) {
92 | const entries = await vscode.workspace.fs.readDirectory(vscode.Uri.file(directory))
93 |
94 | for (const [name, type] of entries) {
95 | const filePath = path.join(directory, name)
96 |
97 | // 检查文件路径是否应该忽略
98 | if (this.isPathIgnored(filePath, directory))
99 | continue // 如果文件被忽略,则跳过
100 |
101 | const item: FileItem = {
102 | id: filePath,
103 | path: filePath,
104 | title: name,
105 | fileType: type === vscode.FileType.Directory ? 'Directory' : 'File',
106 | leaf: true,
107 | }
108 | if (type === vscode.FileType.File) {
109 | // 如果是文件,获取文件扩展名
110 | const extname = path.extname(filePath).replace('.', '')
111 | item.fileExt = extname
112 | if (!['jsx', 'tsx', 'js', 'ts'].includes(extname))
113 | continue
114 |
115 | const fileInfo = await this.solveJSFile({
116 | uri: vscode.Uri.file(filePath),
117 | path: vscode.Uri.file(filePath).fsPath,
118 | exports: [],
119 | imports: [],
120 | })
121 | item.children = fileInfo.exports.map((item: any) => ({
122 | id: `${filePath}@${item.name}`,
123 | path: filePath,
124 | title: item.name,
125 | fileType: 'Export',
126 | leaf: true,
127 | }))
128 | item.leaf = false
129 | }
130 | if (type === vscode.FileType.Directory) {
131 | item.children = []
132 | item.leaf = false
133 | if (!lazy)
134 | await this.walk(filePath, item.children, lazy)
135 | }
136 |
137 | parent.push(item)
138 | }
139 | }
140 |
141 | isPathIgnored(filePath: string, directory: string): boolean {
142 | const workspaceFolders = vscode.workspace.workspaceFolders
143 | if (!workspaceFolders)
144 | return false
145 |
146 | for (const workspaceFolder of workspaceFolders) {
147 | const gitignorePath = path.join(workspaceFolder.uri.fsPath, '.gitignore')
148 |
149 | // 如果缓存中没有.gitignore内容,则读取并缓存
150 | if (!this.gitignoreContents.has(gitignorePath)) {
151 | if (fs.existsSync(gitignorePath)) {
152 | const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
153 | const ignoreRules = gitignoreContent.split('\n').filter(rule => !!rule.trim() && !rule.startsWith('#'))
154 | this.gitignoreContents.set(gitignorePath, ignoreRules)
155 | }
156 | else {
157 | this.gitignoreContents.set(gitignorePath, [])
158 | }
159 | }
160 |
161 | // 从缓存中获取.gitignore内容
162 | const ignoreRules = this.gitignoreContents.get(gitignorePath)!
163 | const ignore = Ignore()
164 | ignore.add(ignoreRules)
165 | // 对文件路径进行匹配
166 | const relativePath = path.relative(workspaceFolder.uri.fsPath, filePath)
167 | if (ignore.ignores(`${relativePath}\\`) || ignore.ignores(relativePath))
168 | return true
169 | else
170 | return false
171 | }
172 | return false
173 | }
174 |
175 | async solveFiles(pacakges: any) {
176 | const uris = await vscode.workspace.findFiles('{**/**.[jt]s,**/**.[jt]sx}', '{package.json,**/package.json ,**/node_modules/**, node_modules/**,dist/**,**/dist/**}')
177 | const xmfiles = await Promise.all(uris.map(async (uri) => {
178 | const dir = path.dirname(uri.fsPath)
179 | const root = pacakges.find((item: any) => dir.includes(item.root))
180 | const xmfile = {
181 | root,
182 | uri,
183 | relativePath: dir.replace(root.root, path.basename(root.root)),
184 | path: uri.fsPath,
185 | dir: path.dirname(uri.fsPath),
186 | name: path.basename(uri.fsPath),
187 | type: path.extname(uri.fsPath),
188 | exports: [],
189 | imports: [],
190 | }
191 | if (['.js', '.jsx', '.ts', '.tsx'].includes(path.extname(uri.fsPath)))
192 | return await this.solveJSFile(xmfile)
193 |
194 | return xmfile
195 | }))
196 | return xmfiles
197 | }
198 |
199 | async solveJSFile(xmfile: any) {
200 | try {
201 | const buffer = await vscode.workspace.fs.readFile(xmfile.uri)
202 | const fileType = path.extname(xmfile.uri.fsPath).replace('.', '')
203 | let docCode = ''
204 | if (fileType === 'vue') {
205 | const vue = vuecompiler.parse(buffer.toString())
206 | const vueScript: any = vue.children.find((item: any) => item.tag === 'script')
207 | const vueScriptContent = vueScript.children.find((item: any) => item.type === 2)
208 | docCode = vueScriptContent.content
209 | }
210 | if (['js', 'ts', 'jsx', 'tsx'].includes(fileType))
211 | docCode = buffer.toString()
212 |
213 | const ast = parser.parse(docCode, {
214 | sourceType: 'module',
215 | plugins: ['jsx', 'typescript', 'decorators'],
216 | errorRecovery: true,
217 | })
218 |
219 | traverse(ast, {
220 | 'ExportNamedDeclaration|ExportDefaultDeclaration': function (astPath) {
221 | const node = astPath.node
222 | const exportNames = (() => {
223 | if (types.isExportDefaultDeclaration(node)) {
224 | // const declaration = node.declaration
225 | // if (types.isIdentifier(declaration)) {
226 | // const from = path.scope.getOwnBinding(declaration.name)?.path?.parentPath?.node?.source?.value
227 | // if (from) {
228 |
229 | // }
230 | // // console.log(file.path, declaration.name, from,);
231 | // }
232 | return [{ name: 'default', desc: '', type: 'jsx', params: [] }]
233 | }
234 | if (!types.isExportNamedDeclaration(node))
235 | return []
236 |
237 | const specifiers = node.specifiers
238 | if (specifiers.length) {
239 | return specifiers.map((specifier) => {
240 | if (types.isExportSpecifier(specifier)) {
241 | const exported = specifier.exported as types.Identifier
242 | return { name: exported.name, desc: '', type: 'jsx', params: [] }
243 | }
244 | }) || []
245 | }
246 | const declaration = node.declaration
247 | if (declaration) {
248 | if (types.isFunctionDeclaration(declaration))
249 | return [{ name: declaration.id?.name, desc: 'function', type: 'jsx', params: [] }]
250 |
251 | if (types.isClassDeclaration(declaration))
252 | return [{ name: declaration.id?.name, desc: 'class', type: 'class', params: [] }]
253 |
254 | if (types.isTSInterfaceDeclaration(declaration)
255 | || types.isTSTypeAliasDeclaration(declaration)
256 | || types.isTSEnumDeclaration(declaration)
257 | || types.isTSDeclareFunction(declaration)
258 | || types.isTSInterfaceDeclaration(declaration)
259 | || types.isTSModuleBlock(declaration)
260 | || types.isTSModuleDeclaration(declaration)
261 | ) {
262 | /** @ts-expect-error */
263 | return [{ name: declaration.id?.name, desc: declaration.type, type: 'type', params: [] }]
264 | }
265 | if (types.isVariableDeclaration(declaration)) {
266 | return declaration?.declarations.map((item: any) => ({
267 | name: item.id?.name,
268 | desc: '',
269 | type: declaration.kind,
270 | params: [],
271 | }))
272 | }
273 | }
274 | })()
275 |
276 | xmfile.exports.push(...(exportNames || []))
277 | },
278 | ImportDeclaration(astPath) {
279 | const importPath = astPath.node.source.value
280 | const resolvePath = (() => {
281 | const isAlilas = /^@\//.test(importPath)
282 | if (isAlilas) {
283 | const basePath = path.normalize('c:\\Users\\欧拯救\\Desktop\\blog-client\\src')
284 | const normalizedImportPath = importPath.replace(/^@\//, '')
285 |
286 | return path.resolve(basePath, normalizedImportPath)
287 | }
288 | else {
289 | return path.resolve(path.dirname(xmfile.path), importPath)
290 | }
291 | })()
292 | xmfile.imports.push({
293 | dir: path.dirname(resolvePath),
294 | name: astPath.node.specifiers.map(specifier => specifier.local.name),
295 | from: importPath,
296 | path: resolvePath,
297 | })
298 | },
299 | })
300 | return xmfile
301 | }
302 | catch (e) {
303 | }
304 | }
305 |
306 | solveRelation(xmfiles: any) {
307 | const relations: any[] = [{ id: 'npm' }]
308 | // todo: 封装成通用模块
309 | xmfiles.forEach((xmfile: any, xmfileIndex: any) => {
310 | relations.push({ id: xmfileIndex, file: xmfile.path, label: xmfile.name, group: xmfile.relativePath.replace('c:\\Users\\欧拯救\\Desktop', '@') })
311 | xmfile.imports.forEach((importItem: any) => {
312 | let find = false
313 | xmfiles.forEach((item: any, itemIndex: any) => {
314 | if (
315 | importItem.path.toLowerCase() === item.path.toLowerCase()
316 | || importItem.path.toLowerCase() === item.path.replace(/\.\w*$/, '').toLowerCase()
317 | || path.join(importItem.path, 'index').toLowerCase() === item.path.replace(/\.\w*$/, '').toLowerCase()
318 | ) {
319 | relations.push({
320 | file: xmfile.path,
321 | target: xmfileIndex,
322 | use: item.path,
323 | source: itemIndex,
324 | id: `${xmfileIndex}-${itemIndex}`,
325 | })
326 | find = true
327 | }
328 | })
329 | if (!find) {
330 | // console.log('xxxxmm', importItem.from, importItem.path,xmfile.path, )
331 | relations.push({
332 | file: xmfile.path,
333 | target: xmfileIndex,
334 | use: importItem.from,
335 | source: 'npm',
336 | id: importItem.from,
337 | })
338 | }
339 | })
340 | })
341 | return relations
342 | }
343 |
344 | solveNodeModule() {
345 |
346 | }
347 |
348 | solveElementui() {
349 | }
350 |
351 | async solvePackage() {
352 | const uris = await vscode.workspace.findFiles('{**/package.json,package.json}', '**/node_modules/**')
353 | return Promise.all(
354 | uris.map(async (uri) => {
355 | const root = path.dirname(uri.fsPath)
356 | const json = require(uri.fsPath)
357 | const dependencies = await this.solveDependencies(json.dependencies, root)
358 | return {
359 | meta: {
360 | root,
361 | uri,
362 | json,
363 | alias: [],
364 | },
365 | id: root,
366 | title: path.basename(root),
367 | fileType: 'Directory',
368 | children: dependencies,
369 | }
370 | }),
371 | )
372 | }
373 |
374 | async solveDependencies(dependencies: any, root: any) {
375 | return Promise.all(
376 | Object.keys(dependencies).map((dependencie) => {
377 | return {
378 | id: `${root}/${dependencie}`,
379 | title: dependencie,
380 | fileType: 'Dependencie',
381 | children: [],
382 | }
383 | }),
384 | )
385 | }
386 |
387 | async solveDependencie(dependencie: any, root: any) {
388 | const meta: any = {}
389 | meta.name = dependencie
390 |
391 | const test = await getPackageInfo(dependencie, { paths: root })
392 | try {
393 | meta.resolve = resolveModule(dependencie, { paths: root })
394 | // const packagePath = path.join(root, 'node_modules', dependencie, 'package.json')
395 | const packageJson = await getPackageInfo(dependencie, { paths: root })
396 | if (packageJson?.packageJson?.['web-types']) {
397 | const file = await vscode.workspace.fs.readFile(vscode.Uri.file(path.join(root, 'node_modules', dependencie, packageJson['web-types'])))
398 | const json = file.toString()
399 | const components = json.contributions.html['vue-components'] || json.contributions.html.tags || []
400 | meta.content = json
401 | meta.children = components.map((item: any) => ({
402 | id: path.join(root, 'node_modules', dependencie, item.name),
403 | title: item.name,
404 | path: dependencie,
405 | fileType: 'Export',
406 | }))
407 | return meta
408 | }
409 | if (packageJson?.packageJson?.module) {
410 | const packageIndexPath = path.join(root, 'node_modules', dependencie, packageJson?.packageJson?.module)
411 | const json = await this.solveJSFile({
412 | uri: vscode.Uri.file(packageIndexPath),
413 | path: vscode.Uri.file(packageIndexPath).fsPath,
414 | exports: [],
415 | imports: [],
416 | })
417 | meta.content = json
418 | meta.children = json.exports.map((item: any) => ({
419 | id: path.join(root, 'node_modules', dependencie, item.name),
420 | title: item.name,
421 | path: dependencie,
422 | fileType: 'Export',
423 | }))
424 | return meta
425 | }
426 | // if (['js'].includes(path.extname(meta.resolve).replace('.', ''))) {
427 | // console.log('js', meta.resolve, packageJson)
428 | // const json = await import(meta.resolve)
429 | // console.log(json)
430 | // meta.content = json
431 |
432 | // meta.children = Object.keys(json).map(key => ({
433 | // id: path.join(root, 'node_modules', dependencie, key),
434 | // title: key,
435 | // path: dependencie,
436 | // fileType: 'Export',
437 | // }))
438 | // return meta
439 | // }
440 | if (['js', 'jsx', 'ts', 'tsx', 'vue'].includes(path.extname(meta.resolve).replace('.', ''))) {
441 | const json = await this.solveJSFile({
442 | uri: vscode.Uri.file(meta.resolve),
443 | path: vscode.Uri.file(meta.resolve).fsPath,
444 | exports: [],
445 | imports: [],
446 | })
447 | meta.content = json
448 | meta.children = Object.keys(json || {}).map(key => ({
449 | id: path.join(root, 'node_modules', dependencie, key),
450 | title: key,
451 | path: dependencie,
452 | fileType: 'Export',
453 | }))
454 | if (meta.children.length)
455 | return meta
456 | }
457 | // const staticExports = await getExportsStatic(dependencie, { url: root })
458 | // meta.content = 'unknow'
459 | // meta.children = staticExports?.map?.(name => ({
460 | // title: name,
461 | // id: path.join(root, 'node_modules', dependencie, name),
462 | // FileType: 'Export',
463 | // }))
464 |
465 | const runtimeExports = await getExportsRuntime(dependencie, { url: root })
466 | meta.content = 'unknow'
467 | meta.children = runtimeExports?.map?.(name => ({
468 | title: name,
469 | id: path.join(root, 'node_modules', dependencie, name),
470 | FileType: 'Export',
471 | }))
472 | if (meta.children.length)
473 | return meta
474 | }
475 | catch (e) {
476 | meta.content = e
477 | console.log(e)
478 | }
479 | return meta
480 | }
481 | }
482 |
--------------------------------------------------------------------------------
/src/modules/checkIgnore.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import * as fs from 'node:fs'
3 | import Ignore from 'ignore'
4 | import * as vscode from 'vscode'
5 |
6 | export default class MyIgnore {
7 | gitignoreContents = new Map()
8 | constructor() {
9 | this.setIgnoreRule()
10 | }
11 |
12 | setIgnoreRule() {
13 | const workspaceFolders = vscode.workspace.workspaceFolders || []
14 | for (const workspaceFolder of workspaceFolders) {
15 | const gitignorePath = path.join(workspaceFolder.uri.fsPath, '.gitignore')
16 |
17 | // 如果缓存中没有.gitignore内容,则读取并缓存
18 | if (!this.gitignoreContents.has(workspaceFolder.uri.fsPath)) {
19 | if (fs.existsSync(gitignorePath)) {
20 | const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8')
21 | const ignoreRules = gitignoreContent.split('\n').filter(rule => !!rule.trim() && !rule.startsWith('#'))
22 | this.gitignoreContents.set(workspaceFolder.uri.fsPath, ignoreRules)
23 | }
24 | else {
25 | this.gitignoreContents.set(workspaceFolder.uri.fsPath, [])
26 | }
27 | }
28 | }
29 | }
30 |
31 | check(filePath: string, workspaceFolder: string): boolean {
32 | try {
33 | const ignore = Ignore()
34 | const ignoreRules = this.gitignoreContents.get(workspaceFolder)
35 | ignore.add(ignoreRules)
36 | const relativePath = path.relative(workspaceFolder, filePath)
37 | if (ignore.ignores(`${relativePath}\\`) || ignore.ignores(relativePath))
38 | return true
39 | return false
40 | }
41 | catch (e) {
42 | console.log(e)
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/modules/solveDirectory.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/src/modules/solveDirectory.ts
--------------------------------------------------------------------------------
/src/modules/solveExports.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import * as vscode from 'vscode'
3 | import * as vuecompiler from '@vue/compiler-dom'
4 | import * as types from '@babel/types'
5 | import * as parser from '@babel/parser'
6 | import traverse from '@babel/traverse'
7 | import { getExportsRuntime } from 'pkg-exports'
8 | import {
9 | getPackageInfo,
10 | importModule,
11 | isPackageExists,
12 | resolveModule,
13 | } from 'local-pkg'
14 |
15 | export async function loadCode(uri) {
16 | const fileType = path.extname(uri).replace('.', '')
17 | const buffer = await vscode.workspace.fs.readFile(vscode.Uri.file(uri))
18 | switch (fileType) {
19 | case 'vue': return await loadVue(buffer)
20 | case 'js':
21 | case 'ts':
22 | case 'jsx':
23 | case 'tsx': return await loadJs(buffer)
24 | default: return ''
25 | }
26 |
27 | async function loadVue(buffer) {
28 | const vue = vuecompiler.parse(buffer.toString())
29 | const vueScript: any = vue.children.find((item: any) => item.tag === 'script')
30 | const vueScriptContent = vueScript.children.find((item: any) => item.type === 2)
31 | return vueScriptContent.content
32 | }
33 | async function loadJs(buffer) {
34 | return buffer.toString()
35 | }
36 | }
37 | export async function loadJson(jsonPath) {
38 | const packageFile = await vscode.workspace.fs.readFile(
39 | vscode.Uri.file(jsonPath),
40 | )
41 | const packageText = packageFile.toString()
42 | const packageJson = JSON.parse(packageText)
43 | return packageJson
44 | }
45 |
46 | export async function solveExports(code, uri) {
47 | try {
48 | const ast = parser.parse(code, {
49 | sourceType: 'module',
50 | plugins: ['jsx', 'typescript', 'decorators'],
51 | errorRecovery: true,
52 | })
53 |
54 | const exports: any = []
55 | traverse(ast, {
56 | ExportDefaultDeclaration(astPath: any) {
57 | exports.push({
58 | name: 'default',
59 | type: astPath.node.declaration.type,
60 | })
61 | },
62 | ExportNamedDeclaration(astPath: any) {
63 | const specifiers = astPath.node?.specifiers || []
64 | specifiers.forEach((specifier: any) => {
65 | if (types.isExportSpecifier(specifier))
66 | exports.push({ name: specifier.exported?.name })
67 | })
68 | exports.push({ name: astPath.node?.declaration?.id?.name, type: astPath.node?.declaration?.type })
69 | },
70 | })
71 | return exports
72 | }
73 | catch (e) {
74 | // console.log('eee', e)
75 | }
76 | }
77 | export async function solveWebType(dependencie, root) {
78 | try {
79 | const packageJson = await getPackageInfo(dependencie, { paths: root })
80 | const webTypePath = packageJson?.packageJson?.['web-types']
81 | if (!webTypePath)
82 | return
83 | const webTypeJson = await loadJson(path.join(packageJson.rootPath, webTypePath))
84 | const components = webTypeJson?.contributions?.html['vue-components'] || webTypeJson.contributions.html.tags || []
85 | const exports = components.map((item: any) => ({
86 | name: item.name,
87 | detail: webTypeJson,
88 | }))
89 | return exports
90 | }
91 | catch (e) {
92 | }
93 | }
94 |
95 | export async function autoSolve(packageUri, dependencieName) {
96 | const filePath = resolveModule(dependencieName, { paths: packageUri }) || ''
97 | if (!filePath)
98 | return []
99 |
100 | const result: any = {}
101 | const error: any = {}
102 | try {
103 | const code = await loadCode(filePath)
104 | const exports = await solveExports(code, filePath)
105 | if (exports?.length)
106 | result.exports = exports
107 | }
108 | catch (e) { error.exports = e }
109 |
110 | try {
111 | const exports = await getExportsRuntime(dependencieName, { url: packageUri })
112 | if (exports.length) {
113 | result.runtime = exports.map(item => ({
114 | name: item,
115 | }))
116 | }
117 | }
118 | catch (e) { error.runtime = e }
119 |
120 | try {
121 | const exports = await importModule(`file://${filePath}`)
122 | if (Object.values(exports).length) {
123 | result.imports = Object.keys(exports).map(item => ({
124 | name: item,
125 | detail: exports[item],
126 | }))
127 | }
128 | }
129 | catch (e) { error.imports = e }
130 |
131 | try {
132 | const exports = await solveWebType(dependencieName, packageUri)
133 | result.webtypes = exports
134 | }
135 | catch (e) { error.webtypes = e }
136 |
137 | // console.log('exports:', filePath, '\n', { dependencieName, packageUri }, '\n', result, '\n', error, result.webtypes || result.imports || result.runtime || result.statics || result.exports || [])
138 | return result.webtypes || result.imports || result.runtime || result.statics || result.exports || []
139 | }
140 |
141 | export default autoSolve
142 |
--------------------------------------------------------------------------------
/src/modules/solvePackages.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/src/modules/solvePackages.ts
--------------------------------------------------------------------------------
/src/panels/FileRelationPanel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path'
3 | import { getUri } from "../utilities/getUri";
4 | import { getNonce } from "../utilities/getNonce";
5 |
6 | export class FileRelationPanel implements vscode.WebviewViewProvider {
7 | public vscodeContext;
8 | public relations: any;
9 | public webviewView: vscode.WebviewView | undefined;
10 | constructor(context: vscode.ExtensionContext, relations: any) {
11 | this.vscodeContext = context;
12 | this.relations = relations;
13 | context.subscriptions.push(
14 | vscode.window.registerWebviewViewProvider('xmouse.file.relation', this)
15 | );
16 | }
17 | public resolveWebviewView(
18 | webviewView: vscode.WebviewView,
19 | context: vscode.WebviewViewResolveContext,
20 | _token: vscode.CancellationToken,
21 | ) {
22 | webviewView.webview.options = {
23 | enableScripts: true,
24 | };
25 | this.webviewView = webviewView;
26 |
27 | webviewView.webview.html = this._getWebviewContent(webviewView.webview, this.vscodeContext.extensionUri)
28 | webviewView.webview.postMessage({ command: 'relation.draw', data: this.relations })
29 | webviewView.webview.onDidReceiveMessage(message => {
30 | if (message.command === 'relation.init') {
31 | webviewView.webview.postMessage({ command: 'relation.draw', data: this.relations })
32 | }
33 | }, undefined, this.vscodeContext.subscriptions);
34 | }
35 | private _getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) {
36 | // 返回完整的HTML内容
37 | const stylesUri = getUri(webview, extensionUri, ["out", "client", "assets","index.css"]);
38 | // The JS file from the SolidJS build output
39 | const scriptUri = getUri(webview, extensionUri, ["out", "client", "assets","index.js"]);
40 | const nonce = getNonce();
41 |
42 | return /*html*/ `
43 |
44 |
45 |
46 |
47 |
48 |
49 | Hello World
50 |
51 |
52 |
53 |
54 |
55 |
56 | `;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/panels/LegoEditorPanel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { getUri } from "../utilities/getUri";
3 | import { getNonce } from "../utilities/getNonce";
4 | import { findJSXElement, findCompPath, setJSXAttr } from "../utilities/astTool"
5 |
6 | export class LegoEditorPanel implements vscode.WebviewViewProvider {
7 | public static readonly viewType = 'xmouse.component';
8 | public vscodeContext;
9 | public component: any | undefined;
10 | public position: vscode.Position | undefined;
11 | public activeLego: any;
12 | private webviewView: vscode.WebviewView | undefined;
13 | constructor(context: vscode.ExtensionContext, components: any) {
14 | this.vscodeContext = context;
15 | context.subscriptions.push(
16 | vscode.window.registerWebviewViewProvider('xmouse.lego.editor', this)
17 | );
18 | vscode.window.onDidChangeTextEditorSelection((event) => {
19 | const position = event.selections[0].start;
20 | this.setAttr2Editor(position)
21 | });
22 | vscode.workspace.onDidChangeTextDocument((event) => {
23 | if (this.activeLego) {
24 | this.setAttr2Editor(this.activeLego.position)
25 | }
26 | })
27 | }
28 | setAttr2Editor(position: any) {
29 | const editor = vscode.window.activeTextEditor; if (!editor) { return; }
30 | const doc = editor.document.getText();
31 | const jsxElement = findJSXElement(doc, position); if (!jsxElement) { return; }
32 | this.activeLego = { name: jsxElement.name, attr: jsxElement.attr, position };
33 | this.webviewView?.webview.postMessage({ command: 'lego.editor.updateLego', data: this.activeLego });
34 | }
35 | setAttr2Code(activeLego: any, name: string, value: string) {
36 | if (!activeLego) { return; }
37 | const editor = vscode.window.activeTextEditor;
38 | if (!editor) { return; }
39 |
40 | const doc = editor.document.getText();
41 | const path = findCompPath(doc, activeLego.name, activeLego.position);
42 | if (!path?.node.loc) { return; }
43 |
44 | const range = new vscode.Range(
45 | new vscode.Position(path.node.loc.start.line - 1, path.node.loc.start.column),
46 | new vscode.Position(path.node.loc.end.line - 1, path.node.loc.end.column)
47 | );
48 | const code = setJSXAttr(path, name, value);
49 | editor?.edit((editBuilder: vscode.TextEditorEdit) => {
50 | editBuilder.replace(range, code);
51 | });
52 | }
53 | public resolveWebviewView(
54 | webviewView: vscode.WebviewView,
55 | context: vscode.WebviewViewResolveContext,
56 | _token: vscode.CancellationToken,
57 | ) {
58 | this.webviewView = webviewView;
59 | webviewView.webview.options = {
60 | enableScripts: true,
61 | };
62 | webviewView.webview.html = this._getWebviewContent(webviewView.webview, this.vscodeContext.extensionUri);
63 | webviewView.webview.onDidReceiveMessage(message => {
64 | // 处理从Webview传递过来的消息
65 | if (message.command === 'lego.editor.propChange') {
66 | this.setAttr2Code(this.activeLego, message.data.name || "", message.data.value || "");
67 | }
68 | }, undefined, this.vscodeContext.subscriptions);
69 | }
70 | private _getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) {
71 | // 返回完整的HTML内容
72 | const stylesUri = getUri(webview, extensionUri, ["out", "client", "assets", "index.css"]);
73 | // The JS file from the SolidJS build output
74 | const scriptUri = getUri(webview, extensionUri, ["out", "client", "assets", "index.js"]);
75 | const nonce = getNonce();
76 |
77 | return /*html*/ `
78 |
79 |
80 |
81 |
82 |
83 |
84 | Hello World
85 |
86 |
87 |
88 |
89 |
90 |
91 | `;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/panels/LegoListPanel/LegoListPanel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import { getUri } from '../../utilities/getUri'
3 | import { updateImport } from '../../utilities/astTool'
4 | import CustomService from './Service/CustomService'
5 | import DirectoryService from './Service/DirectoryService'
6 | import PackageService from './Service/PackageService'
7 |
8 | interface Lego { id: string, name: string, code: string, source: string, group: string }
9 | export class LegoListPanel implements vscode.WebviewViewProvider {
10 | public vscodeContext
11 | public xmFiles: any
12 | public webviewView: vscode.WebviewView | undefined
13 | private draging: any
14 | private data: Lego[]
15 | private customService = new CustomService()
16 | private directoryService = new DirectoryService()
17 | private packageService = new PackageService()
18 | constructor(context: vscode.ExtensionContext, xmFiles: any) {
19 | this.vscodeContext = context
20 | this.xmFiles = xmFiles
21 | context.subscriptions.push(vscode.window.registerWebviewViewProvider(
22 | 'xmouse.lego.list',
23 | this,
24 | { webviewOptions: { retainContextWhenHidden: true } },
25 | ))
26 | this.registeCommand(context)
27 | }
28 |
29 | private registeCommand(context: vscode.ExtensionContext) {
30 | context.subscriptions.push(vscode.commands.registerCommand('xmouse.lego.list.add', () => {
31 | this.webviewView?.webview.postMessage({ command: 'lego.list.add' })
32 | }))
33 | context.subscriptions.push(vscode.commands.registerCommand('xmouse.lego.list.multi-delete', () => {
34 | this.webviewView?.webview.postMessage({ command: 'lego.list.multi-delete' })
35 | }))
36 | context.subscriptions.push(vscode.commands.registerCommand('xmouse.lego.list.import', () => {
37 | this.webviewView?.webview.postMessage({ command: 'command.lego.list.import' })
38 | }))
39 | context.subscriptions.push(vscode.commands.registerCommand('xmouse.lego.list.export', () => {
40 | this.webviewView?.webview.postMessage({ command: 'lego.list.export' })
41 | }))
42 | context.subscriptions.push(vscode.commands.registerCommand('xmouse.lego.list.save', () => {
43 | this.xmFiles.saveWorkspaceConf(this.data)
44 | }))
45 | this.xmFiles.readWorkspaceConf().then((res: any) => {
46 | const [err, data] = this.customService.importLego({ data: res })
47 | this.webviewView?.webview.postMessage({ command: 'lego.list.fresh', data })
48 | })
49 |
50 | // 注册到监听队列中
51 | context.subscriptions.push(vscode.commands.registerCommand(
52 | 'xmouse.lego.add',
53 | (uri: vscode.Uri) => {
54 | const editor = vscode.window.activeTextEditor
55 | if (!editor)
56 | return
57 | const text = editor.document.getText(editor.selection)
58 | const [err, data] = this.customService.addLego({
59 | data: {
60 | name: `未命名${(new Date()).getTime()}`,
61 | group: '快捷添加',
62 | code: text,
63 | },
64 | })
65 | this.webviewView?.webview.postMessage({ command: 'lego.list.fresh', data })
66 | },
67 | ))
68 | context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
69 | // 时序问题和代码位置问题,入队列
70 | const component = this.draging
71 | const depends = (() => {
72 | if (!component?.source)
73 | return []
74 |
75 | if ((Array.isArray(component?.source))) {
76 | return component?.source.map((item: any) => ({
77 | import: item.import,
78 | from: item.from,
79 | }))
80 | }
81 | if (component?.source?.import && component?.source?.from)
82 | return [component?.source]
83 |
84 | return []
85 | })()
86 | if (!depends.length)
87 | return
88 |
89 | const activeTextEditor = vscode.window.activeTextEditor
90 | if (!activeTextEditor)
91 | return
92 |
93 | const text = event.contentChanges?.[0].text || ''
94 | if (text.replaceAll(/[\s\n\t]*/g, '') === component.code.replaceAll(/[\s\n\t]*/g, '')) {
95 | depends.forEach(async (source: any, index: number) => {
96 | const dependsCodes = await updateImport(activeTextEditor.document, depends)
97 | dependsCodes?.forEach((item) => {
98 | setTimeout(async () => {
99 | activeTextEditor?.edit((editBuilder) => {
100 | if (item.loc) {
101 | const range = new vscode.Range(
102 | new vscode.Position(item.loc.start.line - 1, item.loc.start.column),
103 | new vscode.Position(item.loc.end.line - 1, item.loc.end.column),
104 | )
105 | editBuilder.replace(range, item.code)
106 | }
107 | else {
108 | editBuilder.replace(new vscode.Position(0, 0), `${item.code}\n`)
109 | }
110 | })
111 | }, 50 * index)
112 | })
113 | })
114 | }
115 | }))
116 | }
117 |
118 | public resolveWebviewView(
119 | webviewView: vscode.WebviewView,
120 | context: vscode.WebviewViewResolveContext,
121 | _token: vscode.CancellationToken,
122 | ) {
123 | webviewView.webview.options = { enableScripts: true }
124 | webviewView.webview.html = this._getWebviewContent(webviewView.webview, this.vscodeContext.extensionUri)
125 | webviewView.webview.onDidReceiveMessage((message) => {
126 | const responce = (err, data) => {
127 | webviewView.webview.postMessage({
128 | id: message.id,
129 | body: { data, code: err, msg: 'ok' },
130 | })
131 | }
132 | const apis = {
133 | // // PakcageApi:
134 | 'lego.list.packages': () => this.packageService.getPackages(message),
135 | 'lego.list.exports': () => this.packageService.getExports(message),
136 | // DirectoryApi
137 | 'lego.list.workspace': () => this.directoryService.getWorkspace(message),
138 | 'lego.list.directory': () => this.directoryService.getDirectory(message),
139 | 'lego.list.file': () => this.directoryService.getFile(message),
140 | // CustomApi
141 | 'lego.list.get': () => this.customService.getLego(message),
142 | 'lego.list.add': () => this.customService.addLego(message),
143 | 'lego.list.update': () => this.customService.updateLego(message),
144 | 'lego.list.delete': () => this.customService.deleteLego(message),
145 | 'lego.list.deleteMulti': () => this.customService.deleteMultiLego(message),
146 | 'lego.list.import': () => this.customService.importLego(message),
147 | }
148 |
149 | if (apis[message.command]) {
150 | Promise.resolve(apis[message.command]())?.then(([err, res]) => {
151 | responce(err, res)
152 | })
153 | }
154 |
155 | if (message.command === 'lego.list.drag.start')
156 | this.draging = message.data
157 |
158 | if (message.command === 'lego.list.drag.end') {
159 | // todo: 时序问题
160 | setTimeout(() => {
161 | this.draging = null
162 | }, 16)
163 | }
164 | }, undefined, this.vscodeContext.subscriptions)
165 | this.webviewView = webviewView
166 | }
167 |
168 | private _getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) {
169 | const stylesUri = getUri(webview, extensionUri, ['out', 'client', 'assets', 'index.css'])
170 | // The JS file from the SolidJS build output
171 | const scriptUri = getUri(webview, extensionUri, ['out', 'client', 'assets', 'index.js'])
172 | const fontUri = getUri(webview, extensionUri, ['out', 'client', 'assets', 'cui.ttf'])
173 | const assetsUrl = getUri(webview, extensionUri, ['out', 'client', 'assets'])
174 | return /* html */ `
175 |
176 |
177 |
178 |
179 |
180 |
181 | Hello World
182 |
199 |
200 |
201 |
202 |
203 |
204 |
205 | `
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/src/panels/LegoListPanel/Service/CustomService.ts:
--------------------------------------------------------------------------------
1 | import Storage from '../../../storage'
2 |
3 | const storage = new Storage()
4 |
5 | export default class CustomService {
6 | deleteLego(message) {
7 | const data = storage.get('LegoList', [])
8 | const index = data.findIndex(({ group, name }) => group === message.data.group && name === message.data.name)
9 | if (index === -1)
10 | return [404, data]
11 | data.splice(index, 1)
12 | storage.set('LegoList', data)
13 | return [0, data]
14 | }
15 |
16 | deleteMultiLego(message) {
17 | const data = storage.get('LegoList', [])
18 | message.data?.forEach((item: any) => {
19 | const index = data.findIndex(({ group, name }) => group === item.group && name === item.name)
20 | if (index === -1)
21 | return
22 | data.splice(index, 1)
23 | })
24 | storage.set('LegoList', data)
25 | return [0, data]
26 | }
27 |
28 | addLego(message: any) {
29 | const data = storage.get('LegoList', [])
30 | data.push(message.data)
31 | storage.set('LegoList', data)
32 | return [0, data]
33 | }
34 |
35 | getLego() {
36 | return [0, storage.get('LegoList', [])]
37 | }
38 |
39 | updateLego(message) {
40 | const data = storage.get('LegoList', [])
41 | const index = data.findIndex(({ group, name }) => group === message.data.old.group && name === message.data.old.name)
42 | if (index === -1)
43 | return [404, data]
44 |
45 | data[index] = message.data.new
46 | storage.set('LegoList', data)
47 | return [0, data]
48 | }
49 |
50 | importLego(message) {
51 | const data = storage.get('LegoList', [])
52 | message.data?.forEach((item: any) => {
53 | const index = data.findIndex(({ name, group }) => name === item.name && group === item.group)
54 | const isAdd = index === -1
55 | if (isAdd) {
56 | data.push(item)
57 | return
58 | }
59 | const isUpdate = true
60 | if (isUpdate)
61 | data[index] = item
62 | })
63 | storage.set('LegoList', data)
64 | return [0, data]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/panels/LegoListPanel/Service/DirectoryService.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import * as vscode from 'vscode'
3 |
4 | import { loadCode, solveExports } from '../../../modules/solveExports'
5 | import Ignore from '../../../modules/checkIgnore'
6 |
7 | interface FileItem {
8 | id: string
9 | title: string
10 | fileType: string
11 | fileExt?: string
12 | children?: FileItem[]
13 | leaf?: boolean
14 | path: string
15 | root: string
16 | }
17 |
18 | export default class DirectoryService {
19 | ignore = new Ignore()
20 | constructor() {
21 | }
22 |
23 | async getWorkspace() {
24 | const workspaceFolders = vscode.workspace.workspaceFolders || []
25 | const workspace: FileItem[] = workspaceFolders.map((item) => {
26 | return {
27 | id: item.uri.fsPath,
28 | path: item.uri.fsPath,
29 | title: item.name,
30 | fileType: 'Directory',
31 | leaf: false,
32 | children: [],
33 | root: item.uri.fsPath,
34 | }
35 | })
36 |
37 | return [0, workspace]
38 | }
39 |
40 | async getDirectory(message) {
41 | const directory = message.data.path
42 | const entries = await vscode.workspace.fs.readDirectory(vscode.Uri.file(directory))
43 | const resultPromise = entries.map(async ([name, type]) => {
44 | const filePath = path.join(directory, name)
45 | if (this.ignore.check(filePath, message.data.root))
46 | return
47 | const item: FileItem = {
48 | id: filePath,
49 | path: filePath,
50 | title: name,
51 | fileType: type === vscode.FileType.Directory ? 'Directory' : 'File',
52 | leaf: false,
53 | root: message.data.root,
54 | }
55 | if (type === vscode.FileType.File) {
56 | const extname = path.extname(filePath).replace('.', '')
57 | item.fileExt = extname
58 | }
59 | if (type === vscode.FileType.Directory)
60 | item.children = []
61 | return item
62 | })
63 |
64 | const results = await Promise.all(resultPromise)
65 | console.log(results)
66 | return [0, results.filter(item => item)]
67 | }
68 |
69 | async getFile(message) {
70 | const filePath = message.data.path
71 | const extname = path.extname(filePath).replace('.', '')
72 | if (!['jsx', 'tsx', 'js', 'ts'].includes(extname))
73 | return [0, []]
74 | const code = await loadCode(filePath)
75 | const exports = await solveExports(code, filePath)
76 |
77 | const result: FileItem[] = exports.map((item: any) => ({
78 | id: path.join(filePath, item.name),
79 | path: filePath,
80 | title: item.name,
81 | fileType: 'Export',
82 | leaf: true,
83 | root: message.data.root,
84 | meta: item,
85 | }))
86 |
87 | return [0, result]
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/panels/LegoListPanel/Service/PackageService.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import * as vscode from 'vscode'
3 | import { autoSolve, loadJson } from '../../../modules/solveExports'
4 |
5 | export default class PakcageService {
6 | async getPackages(message) {
7 | const uris = await vscode.workspace.findFiles('{**/package.json,package.json}', '**/node_modules/**')
8 |
9 | const packages = await Promise.all(
10 | uris.map(async (uri) => {
11 | const root = path.dirname(uri.fsPath)
12 | console.log(1)
13 |
14 | const json: any = await loadJson(uri.fsPath)
15 | console.log(2, json)
16 |
17 | const dependencies = Object.keys(json?.dependencies || [])
18 | console.log(3, dependencies)
19 | return {
20 | meta: {
21 | root,
22 | uri,
23 | json,
24 | alias: [],
25 | },
26 | id: root,
27 | title: path.basename(root),
28 | fileType: 'Directory',
29 | leaf: false,
30 | children: dependencies?.map((name) => {
31 | return {
32 | id: `${root}\\${name}`,
33 | title: name,
34 | fileType: 'Dependencie',
35 | children: [],
36 | leaf: false,
37 | }
38 | }) || [],
39 | }
40 | }),
41 | )
42 | return [0, packages]
43 | }
44 |
45 | async getExports(message) {
46 | const data = await autoSolve(message.data.root, message.data.dependencie)
47 | const result = data.map(item => ({
48 | id: `${message.data.root}\\${message.data.dependencie}\\${item.name}`,
49 | title: item.name,
50 | fileType: 'Export',
51 | leaf: false,
52 | }))
53 | return [0, result]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/panels/LegoListPanel/index.ts:
--------------------------------------------------------------------------------
1 | import { LegoListPanel } from './LegoListPanel'
2 |
3 | export default LegoListPanel
4 |
--------------------------------------------------------------------------------
/src/storage/index.ts:
--------------------------------------------------------------------------------
1 | import type * as vscode from 'vscode'
2 |
3 | export default class Storage {
4 | context: vscode.ExtensionContext
5 | instance: Storage
6 | constructor() {
7 | // 首次使用构造器实例
8 | if (!Storage.instance)
9 | Storage.instance = this
10 |
11 | return Storage.instance
12 | }
13 |
14 | setContext(context: vscode.ExtensionContext) {
15 | this.context = context
16 | }
17 |
18 | set(key: string, value: any) {
19 | this.context?.globalState.update(key, value)
20 | }
21 |
22 | get(key: string, defaultValue: any) {
23 | return this.context?.globalState.get(key) || defaultValue
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/utilities/astTool.ts:
--------------------------------------------------------------------------------
1 | import * as nodepath from 'node:path'
2 | import type * as vscode from 'vscode'
3 | import * as parser from '@babel/parser'
4 | import * as traverse from '@babel/traverse'
5 | import * as generate from '@babel/generator'
6 | import * as types from '@babel/types'
7 | import * as vuecompiler from '@vue/compiler-dom'
8 |
9 | export function findJSXElement(code: string, position: vscode.Position): any | undefined {
10 | const ast = parser.parse(code, {
11 | sourceType: 'module',
12 | plugins: ['jsx'],
13 | })
14 |
15 | let compPath: any
16 | traverse.default(ast, {
17 | JSXElement(path) {
18 | const hoverLine = position.line + 1
19 | const hoverCol = position.character
20 | if (!path.node.loc)
21 | return
22 | if (
23 | // start< hoverLine< end
24 | hoverLine >= path.node.loc.start.line
25 | && hoverLine <= path.node.loc.end.line
26 | // start< hoverCol < end
27 | && hoverCol >= path.node.loc.start.column
28 | && hoverCol <= path.node.loc.end.column
29 | )
30 | compPath = path
31 | },
32 | })
33 | if (!compPath)
34 | return null
35 |
36 | return {
37 | attr: compPath?.node.openingElement.attributes.map((item: any) => {
38 | return {
39 | name: item.name.name,
40 | value: item.value.value,
41 | type: 'string',
42 | }
43 | }),
44 | name: compPath?.node.openingElement.name.name,
45 | ast: compPath,
46 | }
47 | }
48 | export function findCompPath(code: string, name: string, position: vscode.Position): traverse.NodePath | undefined {
49 | const ast = parser.parse(code, {
50 | sourceType: 'module',
51 | plugins: ['jsx'],
52 | })
53 |
54 | let compPath
55 | traverse.default(ast, {
56 | JSXElement(path) {
57 | if ((path.node.openingElement.name as types.JSXIdentifier).name === name) {
58 | const hoverLine = position.line + 1
59 | const hoverCol = position.character
60 | if (!path.node.loc)
61 | return
62 | if (
63 | // start< hoverLine< end
64 | hoverLine >= path.node.loc.start.line
65 | && hoverLine <= path.node.loc.end.line
66 | // start< hoverCol < end
67 | && hoverCol >= path.node.loc.start.column
68 | && hoverCol <= path.node.loc.end.column
69 | )
70 | compPath = path
71 | }
72 | },
73 | })
74 | return compPath
75 | }
76 |
77 | export function setJSXAttr(path: traverse.NodePath, name: string, value: any): string {
78 | const attr = path.node.openingElement.attributes.find((item: any) => item.name.name === name)
79 | if (attr) {
80 | if (typeof value === 'string') {
81 | ((attr as types.JSXAttribute).value as types.StringLiteral).value = value
82 | if (!value) {
83 | // @ts-expect-error
84 | path.node.openingElement.attributes = path.node.openingElement.attributes.filter(item => item?.name.name !== name)
85 | }
86 | }
87 | else {
88 | // @ts-expect-error
89 | attr.value.expression = types.jSXExpressionContainer(types.booleanLiteral(true))
90 | }
91 | }
92 | else {
93 | if (typeof value === 'string') {
94 | if (value) {
95 | path.node.openingElement.attributes.push(
96 | types.jSXAttribute(
97 | types.jSXIdentifier(name),
98 | types.stringLiteral(value),
99 | ),
100 | )
101 | }
102 | }
103 | else {
104 | path.node.openingElement.attributes.push(
105 | types.jSXAttribute(
106 | types.jSXIdentifier(name),
107 | types.jSXExpressionContainer(types.booleanLiteral(true)),
108 | ),
109 | )
110 | }
111 | }
112 |
113 | const generatedCode = generate.default(path.node, { jsescOption: { minimal: true } }).code
114 | return generatedCode
115 | }
116 |
117 | export function updateImport(doc: vscode.TextDocument, dependsForAdd: any[]) {
118 | try {
119 | const fileType = nodepath.extname(doc.uri.fsPath).replace('.', '')
120 |
121 | let docLoc = { column: 0, line: 0, offset: 0 }
122 | let docCode = ''
123 | if (fileType === 'vue') {
124 | const vue = vuecompiler.parse(doc.getText())
125 | // console.log('vue',vue)
126 | const vueScript: any = vue.children.find((item: any) => item.tag === 'script')
127 | const vueScriptContent = vueScript.children.find((item: any) => item.type === 2)
128 | docCode = vueScriptContent.content
129 | docLoc = vueScript.loc.start
130 | }
131 | if (['js', 'ts', 'jsx', 'tsx'].includes(fileType))
132 | docCode = doc.getText()
133 |
134 | const dependsCode = dependsForAdd.map(depend => `import ${depend.import} from '${nodepath.isAbsolute(depend.from) ? nodepath.relative(doc.uri.fsPath, depend.from).replaceAll('\\', '/').replace('../', '').replace('.ts', '') : depend.from}'`).join('\n')
135 | const dependsAst = parser.parse(dependsCode, {
136 | sourceType: 'module',
137 | plugins: ['jsx', 'typescript', 'decorators'],
138 | errorRecovery: true,
139 | })
140 | const solvedDependsForAdd = dependsAst.program.body.map((item: any) => ({
141 | from: item.source.value,
142 | specifiers: item?.specifiers,
143 | node: item,
144 | }))
145 |
146 | const ast = parser.parse(docCode, {
147 | sourceType: 'module',
148 | plugins: ['jsx', 'typescript', 'decorators'],
149 | errorRecovery: true,
150 | })
151 |
152 | const imports: any[] = []
153 | traverse.default(ast, {
154 | ImportDeclaration(astPath) {
155 | imports.push({
156 | from: astPath.node.source.value,
157 | loc: astPath.node.loc,
158 | node: astPath.node,
159 | })
160 | },
161 | })
162 | const results = solvedDependsForAdd.map((depend) => {
163 | const importItem = imports.find(item => depend.from === item.from)
164 | if (importItem) {
165 | const specifiersMap: any = {}
166 | importItem.node.specifiers.forEach((item: any) => {
167 | specifiersMap[item.local.name] = item
168 | })
169 | depend.specifiers.forEach((item: any) => {
170 | specifiersMap[item.local.name] = item
171 | })
172 | const specifiers = Object.values(specifiersMap)
173 | importItem.node.specifiers = specifiers
174 | delete importItem.node.leadingComments
175 |
176 | const generatedCode = generate.default(importItem.node, { jsescOption: { minimal: true } }).code
177 | return {
178 | code: generatedCode,
179 | loc: importItem.loc,
180 | importItem,
181 | }
182 | }
183 | const code = generate.default(depend.node, { jsescOption: { minimal: true } }).code
184 | return { code }
185 | })
186 | results.forEach((item) => {
187 | if (fileType === 'vue') {
188 | if (item.loc) {
189 | item.loc.start.line += docLoc.line - 1
190 | item.loc.start.column += docLoc.column - 1
191 | item.loc.end.line += docLoc.line - 1
192 | item.loc.end.column += docLoc.column - 1
193 | }
194 | else {
195 | item.loc = {
196 | start: {
197 | line: docLoc.line + 1,
198 | column: docLoc.column - 1,
199 | },
200 | end: {
201 | line: docLoc.line + 1,
202 | column: docLoc.column - 1,
203 | },
204 | }
205 | item.code += '\n'
206 | }
207 | }
208 | })
209 |
210 | return results
211 | }
212 | catch (e) {
213 | console.log(e)
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/utilities/fileLoader.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Llyonss/XMouse/8605b68d2fd9b1d240e9a3736cab78ab91f18f64/src/utilities/fileLoader.ts
--------------------------------------------------------------------------------
/src/utilities/getNonce.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A helper function that returns a unique alphanumeric identifier called a nonce.
3 | *
4 | * @remarks This function is primarily used to help enforce content security
5 | * policies for resources/scripts being executed in a webview context.
6 | *
7 | * @returns A nonce
8 | */
9 | export function getNonce() {
10 | let text = "";
11 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
12 | for (let i = 0; i < 32; i++) {
13 | text += possible.charAt(Math.floor(Math.random() * possible.length));
14 | }
15 | return text;
16 | }
17 |
--------------------------------------------------------------------------------
/src/utilities/getUri.ts:
--------------------------------------------------------------------------------
1 | import { Uri, Webview } from "vscode";
2 |
3 | /**
4 | * A helper function which will get the webview URI of a given file or resource.
5 | *
6 | * @remarks This URI can be used within a webview's HTML as a link to the
7 | * given file/resource.
8 | *
9 | * @param webview A reference to the extension webview
10 | * @param extensionUri The URI of the directory containing the extension
11 | * @param pathList An array of strings representing the path to a file/resource
12 | * @returns A URI pointing to the file/resource
13 | */
14 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
15 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "jsx": "preserve",
5 | "jsxImportSource": "solid-js",
6 | "lib": [
7 | "dom",
8 | "dom.iterable",
9 | "esnext"
10 | ],
11 | "module": "ESNext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "types": ["vite/client"],
15 | "allowJs": true,
16 | "strict": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "noEmit": true,
19 | "outDir": "build",
20 | "sourceMap": true,
21 | "allowSyntheticDefaultImports": true,
22 | "esModuleInterop": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "isolatedModules": true,
25 | "skipLibCheck": true
26 | },
27 | "include": [
28 | "./client"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import Solid from 'vite-plugin-solid'
3 |
4 | export default defineConfig({
5 | plugins: [
6 | Solid(),
7 | ],
8 | build: {
9 | target: 'esnext',
10 | outDir: 'out/client',
11 | rollupOptions: {
12 | output: {
13 | entryFileNames: `assets/[name].js`,
14 | chunkFileNames: `assets/[name].js`,
15 | assetFileNames: `assets/[name].[ext]`,
16 | },
17 | },
18 | },
19 | })
20 |
--------------------------------------------------------------------------------