├── .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 | ![alt text](./screenshots/XMouseStart.gif) 15 | 16 | ### 添加组件 17 | 18 | ![alt text](./screenshots/XMouseAdd.gif) 19 | 20 | ## 支持尘码 21 | 22 | ![alt text](./screenshots/coffee.png) 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 (
{props.children}
) 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 | {/*
{slots.operate}
*/} 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 |
30 | 组件依赖: 31 | 32 | 38 | 44 | 45 |
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 | --------------------------------------------------------------------------------