├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.CN.md ├── README.md ├── WORK.md ├── docs ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── MySandbox.vue │ │ └── index.ts ├── api │ ├── CodeLayout.md │ ├── CodeLayoutActionsRender.md │ ├── CodeLayoutCustomizeLayout.md │ ├── OverflowCollapseList.md │ ├── SplitLayout.md │ ├── SplitN.md │ └── SplitTabItem.md ├── change │ └── index.md ├── en │ ├── api │ │ ├── CodeLayout.md │ │ ├── CodeLayoutActionsRender.md │ │ ├── CodeLayoutCustomizeLayout.md │ │ ├── OverflowCollapseList.md │ │ ├── SplitLayout.md │ │ ├── SplitN.md │ │ └── SplitTabItem.md │ ├── guide │ │ ├── code-layout.md │ │ ├── customize.md │ │ ├── i18n.md │ │ ├── install.md │ │ ├── split-layout.md │ │ ├── start.md │ │ └── useage.md │ ├── index.md │ └── index.vue ├── guide │ ├── code-layout.md │ ├── customize.md │ ├── i18n.md │ ├── install.md │ ├── split-layout.md │ ├── start.md │ └── useage.md ├── images │ ├── CodeLayoutActions.png │ ├── CodeLayoutBase.jpg │ ├── CodeLayoutCustomizeLayout.png │ ├── CodeLayoutScrollbarDemo.gif │ ├── CodeLayoutSlots.jpg │ ├── CodeLayoutSlots.png │ ├── CodeLayoutTitle1.jpg │ ├── CodeLayoutTitle2.jpg │ ├── OverflowCollapseList.gif │ ├── SplitLayout.jpg │ ├── SplitLayoutTitle.jpg │ └── SplitLayoutTitleClose.jpg ├── index.md ├── index.scss ├── index.vue └── vite.config.ts ├── examples ├── App.vue ├── assets │ ├── icons │ │ ├── IconFile.vue │ │ ├── IconMarkdown.vue │ │ ├── IconSearch.vue │ │ └── IconVue.vue │ ├── images │ │ ├── logo.svg │ │ ├── placeholder.png │ │ ├── placeholder2.png │ │ ├── placeholder3.png │ │ ├── placeholder4.png │ │ └── placeholder5.png │ └── text │ │ ├── Useage.vue │ │ └── Useage2.vue ├── env.d.ts ├── index.html ├── main.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── tsconfig.json ├── views │ ├── BasicUseage.vue │ ├── DataSaveAndLoad.vue │ ├── EmptyTest.vue │ ├── SlotDisplay.vue │ ├── SlotsTest.vue │ └── SplitLayout.vue └── vite.config.ts ├── index.d.ts ├── library ├── CodeLayout.ts ├── CodeLayout.vue ├── CodeLayoutActionItem.vue ├── CodeLayoutActionsRender.vue ├── CodeLayoutActivityBar.vue ├── CodeLayoutBase.vue ├── CodeLayoutCollapseTitle.vue ├── CodeLayoutEmpty.vue ├── CodeLayoutGroupDraggerHost.vue ├── CodeLayoutGroupRender.vue ├── CodeLayoutPanelRender.vue ├── CodeLayoutTabItem.vue ├── CodeLayoutTagControl.vue ├── Components │ ├── CodeLayoutCustomizeLayout.vue │ ├── CodeLayoutScrollbar.vue │ ├── CodeLayoutVNodeStringRender.vue │ ├── OverflowCollapseList.vue │ └── SimpleTooltip.vue ├── Composeable │ ├── DragDrop.ts │ ├── DragEnterLeaveFilter.ts │ ├── KeyBoardController.ts │ ├── LateClass.ts │ ├── MiniTimeout.ts │ ├── MouseHandler.ts │ ├── PanelMenu.ts │ ├── ResizeChecker.ts │ ├── SimpleTooltipDelayLock.ts │ └── Vector2.ts ├── Icons │ ├── IconActionClose.vue │ ├── IconActionMax.vue │ ├── IconArrow.vue │ ├── IconCheck.vue │ ├── IconClose.vue │ ├── IconDot.vue │ ├── IconEyeClosedCodicon.vue │ ├── IconEyeCodicon.vue │ ├── IconGithub.vue │ ├── IconMenu.vue │ ├── IconMore.vue │ ├── IconResetDefault.vue │ ├── LayoutActivitybarLeftCodicon.vue │ ├── LayoutActivitybarRightCodicon.vue │ ├── LayoutCenteredCodicon.vue │ ├── LayoutCodicon.vue │ ├── LayoutMenubarCodicon.vue │ ├── LayoutPanelCenterCodicon.vue │ ├── LayoutPanelCodicon.vue │ ├── LayoutPanelJustifyCodicon.vue │ ├── LayoutPanelLeftCodicon.vue │ ├── LayoutPanelOffCodicon.vue │ ├── LayoutPanelRightCodicon.vue │ ├── LayoutSidebarLeftCodicon.vue │ ├── LayoutSidebarLeftOffCodicon.vue │ ├── LayoutSidebarRightCodicon.vue │ ├── LayoutSidebarRightOffCodicon.vue │ └── LayoutStatusbarCodicon.vue ├── Language │ ├── en.ts │ ├── index.ts │ └── zh.ts ├── Scss │ ├── Base.scss │ ├── Menu.scss │ └── Split.scss ├── SplitLayout │ ├── SplitLayout.vue │ ├── SplitN.ts │ ├── SplitN.vue │ ├── SplitNest.vue │ ├── SplitTab.vue │ ├── SplitTabControlItem.vue │ └── SplitTabItem.vue ├── Utils │ ├── EventEmitter.ts │ ├── HtmlUtils.ts │ └── Timer │ │ ├── Debounce.ts │ │ └── Timer.ts ├── env.d.ts ├── index.ts ├── shims-vue.d.ts ├── tsconfig.json └── vite.config.ts ├── package-lock.json ├── package.json └── screenshot └── first.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /lib 5 | /examples/dist 6 | /library/dist 7 | /docs/importlib/ 8 | 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | pnpm-debug.log* 19 | 20 | # Editor directories and files 21 | .idea 22 | .vscode 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | /docs/.vitepress/cache/ 30 | /docs/.vitepress/dist/ 31 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | #registry=https://registry.npmjs.com/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change log moved to [CHANGELOG](./docs/en/change/index.md). 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 梦欤 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 | -------------------------------------------------------------------------------- /README.CN.md: -------------------------------------------------------------------------------- 1 | 2 | # vue-code-layout 3 | 4 | 一个仿 VSCode 的 Vue 编辑器布局组件,可以用于开发Web编辑器。 5 | 6 |  7 | 8 | --- 9 | 10 | ## 特性 11 | 12 | * 简洁易用,体积小 13 | * 支持添加面板 14 | * 支持拖拽面板 15 | * 支持自定义面板图标、文字、渲染等 16 | * 支持VSCode外壳布局与编辑器区域布局 17 | * 支持保存、加载数据 18 | * 支持定义CSS样式 19 | 20 | ### 安装 21 | 22 | ``` 23 | npm install -save vue-code-layout 24 | ``` 25 | 26 | 在 main.ts 中导入: 27 | 28 | ```js 29 | import 'vue-code-layout/lib/vue-code-layout.css' 30 | import VueCodeLayout from 'vue-code-layout' 31 | 32 | createApp(App) 33 | .use(VueCodeLayout) 34 | 35 | ``` 36 | 37 | 关于详细的用法,请参考文档。 38 | 39 | ## 文档 40 | 41 | [查看文档](https://docs.imengyu.top/vue-code-layout-docs/) 42 | 43 | [查看在线演示](https://docs.imengyu.top/vue-code-layout-demo/) 44 | 45 | ## 开发 46 | 47 | ```shell 48 | git clone git@github.com:imengyu/vue-code-layout.git 49 | cd vue-code-layout 50 | npm install 51 | npm run dev # Development serve project 52 | npm run build-demo # Build example project 53 | npm run build-lib # Build library project 54 | ``` 55 | 56 | ## 问题 57 | 58 | 开源项目需要大家的支持才能越做越好。如果您遇到了问题,可以在仓库提出Issue,我会尽可能的给你解决。 59 | 60 | 如果您有好的修改,欢迎提交PR,成为项目的一员! 61 | 62 | ## 广告:作者的其他有用的项目 63 | 64 | * [vue3-context-menu Vue右键菜单组件](https://github.com/imengyu/vue3-context-menu) 65 | * [vue-dock-layout Vue仿Visual studio拖拽布局组件](https://github.com/imengyu/vue-dock-layout) 66 | * [vue-dynamic-form vue数据驱动的表单](https://github.com/imengyu/vue-dynamic-form) 67 | 68 | ## License 69 | 70 | [MIT](./LICENSE) 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # vue-code-layout 3 | 4 | A Vue editor layout component that like VSCode and can be used to develop web editors. 5 | 6 |  7 | 8 | --- 9 | 10 | English | [中文](./README.CN.md) 11 | 12 | ## Features 13 | 14 | * Simple and easy to use, small size 15 | * Support adding panels 16 | * Support drag and drop panel 17 | * Support customize panel icons, text, rendering, etc 18 | * Supports VSCode outer layout and inner editor area layout 19 | * Support saving and loading data 20 | * Support defining CSS styles 21 | 22 | ### Install 23 | 24 | ``` 25 | npm install -save vue-code-layout 26 | ``` 27 | 28 | Import in main.ts: 29 | 30 | ```js 31 | import 'vue-code-layout/lib/vue-code-layout.css' 32 | import VueCodeLayout from 'vue-code-layout' 33 | 34 | createApp(App) 35 | .use(VueCodeLayout) 36 | 37 | ``` 38 | 39 | For detailed usage, please refer to the documentation. 40 | 41 | ## Documentation 42 | 43 | [Documentation](https://docs.imengyu.top/vue-code-layout-docs/) 44 | 45 | [Demo](https://docs.imengyu.top/vue-code-layout-demo/) 46 | 47 | ## Develop 48 | 49 | ```shell 50 | git clone git@github.com:imengyu/vue-code-layout.git 51 | cd vue-code-layout 52 | npm install 53 | npm run dev # Development serve project 54 | npm run build-demo # Build example project 55 | npm run build-lib # Build library project 56 | ``` 57 | 58 | ## Problem 59 | 60 | Open source projects require everyone's support to get better and better. 61 | 62 | If you encounter any problems, you can submit an issue and I will do my best to solve it for you. 63 | 64 | If you have any good modifications, welecome submit a PR! 65 | 66 | ## AD: Author's other project 67 | 68 | * [vue3-context-menu](https://github.com/imengyu/vue3-context-menu) 69 | * [vue-dock-layout](https://github.com/imengyu/vue-dock-layout) 70 | * [vue-dynamic-form A data driven form component for vue3](https://github.com/imengyu/vue-dynamic-form) 71 | 72 | ## License 73 | 74 | [MIT](./LICENSE) 75 | -------------------------------------------------------------------------------- /WORK.md: -------------------------------------------------------------------------------- 1 | 🆗 基础框架 2 | 🆗 基础大框架拖拽调整大小 3 | 🆗 子面板组拖拽调整大小 4 | 🆗 面板移动拖 5 | 🆗 面板滚动条容器 6 | 🆗 面板拖放 7 | 🆗 基础框架拖拽细节 8 | 🆗 区域拖拽自动展开 9 | 🆗 顶部区域 10 | 🆗 快速调节大布局四个按钮 11 | 🆗 调节大布局弹出区域 12 | 🆗 primarySideBar位置切换 13 | 🆗 面板的右键菜单和隐藏控制 14 | 🆗 顶层网格逻辑内置 15 | 🆗 ActivityBar位置 16 | 🆗 TAB的超出隐藏 17 | 🆗 TAB和ActionItem的tooltip 18 | 🆗 SplitN 19 | 🆗 Codelayoutvue部分函数迁移至类中 20 | 🆗 SplitN对象整理合并 21 | 🆗 TAB整理 22 | 🆗 TAB拖拽高亮框 23 | 🆗 SplitN大小控制转为百分比以实现大小自动调整 24 | 🆗 主区域TAB 25 | 🆗 主区域拖拽切分 26 | 🆗 Codelayou拖拽高亮框闪烁优化 27 | 🆗 ScrollBar测试和问题修改 28 | 🆗 Codelayout,SplitLayout数据加载与保存 29 | 🆗 TAB拖拽高亮框2 30 | 🆗 基础框架根据配置自动生成 31 | 🆗 BottomPanel位置 32 | 🆗 XY链接拖动 33 | 🆗 文档整理 34 | 🆗 打包模块后测试 35 | 🆗 发布 36 | 2期:面板组件被卸载问题 37 | 2期:面板显示隐藏快捷键 38 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress'; 2 | import { renderSandbox } from 'vitepress-plugin-sandpack'; 3 | import MarkdownPreview, { } from 'vite-plugin-markdown-preview' 4 | import container from 'markdown-it-container'; 5 | import { resolve } from 'path'; 6 | 7 | export default defineConfig({ 8 | base: '/vue-code-layout-docs/', 9 | lang: 'zh-CN', 10 | title: 'vue-code-layout', 11 | description: 'Vue 仿 VSCode 布局组件', 12 | locales: { 13 | root: { 14 | label: '中文', 15 | lang: 'zh', 16 | }, 17 | en: { 18 | label: 'English', 19 | lang: 'en', 20 | description: 'A layout component like VSCode.', 21 | themeConfig: { 22 | socialLinks: [ 23 | { icon: 'github', link: 'https://github.com/imengyu/vue-code-layout' }, 24 | ], 25 | footer: { 26 | message: 'Released under the MIT License.', 27 | copyright: 'Copyright © 2024 imengyu.top' 28 | }, 29 | nav: [ 30 | { text: 'Guide', link: '/en/guide/start' }, 31 | { text: 'API Reference', link: '/en/api/CodeLayout' }, 32 | { text: 'Changelog', link: '/change/index' }, 33 | ], 34 | sidebar: { 35 | '/en/guide/': [ 36 | { 37 | text: 'Start', 38 | items: [ 39 | { text: 'Start', link: '/en/guide/start' }, 40 | { text: 'Install', link: '/en/guide/install' }, 41 | { text: 'Introduce', link: '/en/guide/useage' }, 42 | { text: 'I18n', link: '/en/guide/i18n' }, 43 | ] 44 | }, 45 | { 46 | text: 'Component', 47 | items: [ 48 | { text: 'CodeLayout', link: '/en/guide/code-layout' }, 49 | { text: 'SplitLayout', link: '/en/guide/split-layout' }, 50 | ] 51 | }, 52 | { 53 | text: 'Customize', 54 | items: [ 55 | { text: 'Customize', link: '/en/guide/customize' }, 56 | ] 57 | }, 58 | ], 59 | '/en/api/': [ 60 | { text: 'CodeLayout', link: '/en/api/CodeLayout' }, 61 | { text: 'SplitLayout', link: '/en/api/SplitLayout' }, 62 | { text: 'SplitN', link: '/en/api/SplitN' }, 63 | { text: 'SplitTabItem', link: '/en/api/SplitTabItem' }, 64 | { text: 'CodeLayoutActionsRender', link: '/en/api/CodeLayoutActionsRender' }, 65 | { text: 'CodeLayoutCustomizeLayout', link: '/en/api/CodeLayoutCustomizeLayout' }, 66 | { text: 'OverflowCollapseList', link: '/en/api/OverflowCollapseList' }, 67 | ] 68 | } 69 | } 70 | }, 71 | }, 72 | themeConfig: { 73 | socialLinks: [ 74 | { 75 | icon: { 76 | svg: '' 77 | }, 78 | link: 'https://gitee.com/imengyu/vue-code-layout' 79 | }, 80 | { icon: 'github', link: 'https://github.com/imengyu/vue-code-layout' }, 81 | ], 82 | footer: { 83 | message: 'Released under the MIT License.', 84 | copyright: 'Copyright © 2024 imengyu.top' 85 | }, 86 | nav: [ 87 | { text: '教程', link: '/guide/install' }, 88 | { text: 'API 参考', link: '/api/CodeLayout' }, 89 | { text: '更新日志', link: '/change/index' }, 90 | ], 91 | sidebar: { 92 | '/guide/': [ 93 | { 94 | text: '起步', 95 | items: [ 96 | { text: '开始之前', link: '/guide/start' }, 97 | { text: '安装', link: '/guide/install' }, 98 | { text: '介绍', link: '/guide/useage' }, 99 | { text: '国际化', link: '/guide/i18n' }, 100 | ] 101 | }, 102 | { 103 | text: '组件', 104 | items: [ 105 | { text: 'CodeLayout', link: '/guide/code-layout' }, 106 | { text: 'SplitLayout', link: '/guide/split-layout' }, 107 | ] 108 | }, 109 | { 110 | text: '美化与自定义', 111 | items: [ 112 | { text: '自定义样式', link: '/guide/customize' }, 113 | ] 114 | }, 115 | ], 116 | '/api/': [ 117 | { 118 | text: 'API 参考', 119 | items: [ 120 | { text: 'CodeLayout', link: '/api/CodeLayout' }, 121 | { text: 'SplitLayout', link: '/api/SplitLayout' }, 122 | { text: 'SplitN', link: '/api/SplitN' }, 123 | { text: 'SplitTabItem', link: '/api/SplitTabItem' }, 124 | { text: 'CodeLayoutActionsRender', link: '/api/CodeLayoutActionsRender' }, 125 | { text: 'CodeLayoutCustomizeLayout', link: '/api/CodeLayoutCustomizeLayout' }, 126 | { text: 'OverflowCollapseList', link: '/api/OverflowCollapseList' }, 127 | ] 128 | }, 129 | ] 130 | }, 131 | search: { 132 | provider: 'local' 133 | }, 134 | }, 135 | vite: { 136 | plugins: [ MarkdownPreview() as any ], 137 | ssr: { 138 | noExternal: [ 139 | '@imengyu/vue-scroll-rect', 140 | '@imengyu/vue3-context-menu', 141 | 'vue', 142 | ] 143 | }, 144 | resolve: { 145 | alias: { 146 | 'vue-code-layout': resolve(__dirname, '../../library') 147 | }, 148 | }, 149 | }, 150 | markdown: { 151 | config(md) { 152 | md 153 | // the second parameter is html tag name 154 | .use(container, 'sandbox', { 155 | render (tokens, idx) { 156 | return renderSandbox(tokens, idx, 'sandbox'); 157 | }, 158 | }) 159 | .use(container, 'my-sandbox', { 160 | render (tokens, idx) { 161 | return renderSandbox(tokens, idx, 'my-sandbox'); 162 | }, 163 | }) 164 | }, 165 | }, 166 | }); -------------------------------------------------------------------------------- /docs/.vitepress/theme/MySandbox.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import MySandbox from './MySandbox.vue'; 3 | import { Sandbox } from 'vitepress-plugin-sandpack'; 4 | import 'vitepress-plugin-sandpack/dist/style.css'; 5 | 6 | export default { 7 | ...DefaultTheme, 8 | async enhanceApp(ctx) { 9 | DefaultTheme.enhanceApp(ctx); 10 | if (!import.meta.env.SSR) { 11 | const plugin = await import('../../../library') 12 | ctx.app.use(plugin.default) 13 | } 14 | ctx.app.component('Sandbox', Sandbox); 15 | ctx.app.component('MySandbox', MySandbox); 16 | }, 17 | } -------------------------------------------------------------------------------- /docs/api/CodeLayoutActionsRender.md: -------------------------------------------------------------------------------- 1 | # CodeLayoutActionsRender 2 | 3 |  4 | 5 | 操作按钮组组件,这个组件用在 CodeLayout 的面板标题栏中,用于面板的额外操作,这里导出这个组件方便你使用。 6 | 7 | ## Props 8 | 9 | | 属性 | 描述 | 类型 | 默认值 | 10 | | :----: | :----: | :----: | :----: | 11 | | actions | 操作项目定义 | `CodeLayoutActionButton[]` | `[]` | 12 | 13 | ## CodeLayoutActionButton 14 | 15 | 操作按钮数据定义。 16 | 17 | | 属性 | 描述 | 类型 | 默认值 | 18 | | :----: | :----: | :----: | :----: | 19 | | render | 自己渲染这个按钮的全部内容,使用后下方的属性无效 | `() => VNode` | - | 20 | | icon | 渲染按钮的图标 | `() => VNode` | - | 21 | | text | 按钮的文字 | `string` | - | 22 | | tooltip | 按钮的鼠标悬浮提示文字 | `string` | - | 23 | | tooltipDirection | 按钮的鼠标悬浮提示弹出方向 | `'left'│'top'│'right'│'bottom'` | - | 24 | | onClick | 按钮点击事件回调 | `() => void` | - | 25 | -------------------------------------------------------------------------------- /docs/api/CodeLayoutCustomizeLayout.md: -------------------------------------------------------------------------------- 1 | # CodeLayoutCustomizeLayout 2 | 3 |  4 | 5 | 自定义布局组件,默认位于 [CodeLayout](./CodeLayout.md) 右上角。导出目的是因为如果你需要自定义标题栏可以自己控制显示位置。 6 | 7 | 应该与 CodeLayout 一起使用,建议放在 CodeLayout 的 `titleBarRight` 插槽中。 8 | -------------------------------------------------------------------------------- /docs/api/OverflowCollapseList.md: -------------------------------------------------------------------------------- 1 | # OverflowCollapseList 2 | 3 |  4 | 5 | 自动溢出计算折叠条目组件,用在 CodeLayout 的一系列按钮组中。导出方便你使用。 6 | 7 | ## Props 8 | 9 | | 属性 | 描述 | 类型 | 默认值 | 10 | | :----: | :----: | :----: | :----: | 11 | | items | 条目数据 | `any[]` | — | 12 | | getItemSize | 计算条目大小回调(必填) | `(item: T, horizontal: boolean, index: number) => number` | - | 13 | | activeItem | 激活条目,激活的条目不会被隐藏 | `any` | - | 14 | | direction | 布局方向 | `'vertical'│'horizontal'` | `'horizontal'` | 15 | | itemMenuLabel | 溢出菜单的文字创建 | `(item: any) => string` | - | 16 | | itemCollapseMergin | 溢出计算边距 | `(item: any) => string` | `30` | 17 | | itemKey | 用于指定唯一标识符在 item 的哪个字段 | `string` | `'name'` | 18 | | visibleKey | 用于指定控制条目是否显示的 visible 字段在 item 的哪个字段 | `string` | `''` | 19 | 20 | ## Slots 21 | 22 | | 插槽名 | 描述 | 参数 | 23 | | :----: | :----: | :----: | 24 | | item | 条目渲染插槽 | `{ visible: boolean, item: any, index: number }` | 25 | 26 | ## Events 27 | 28 | | 事件名 | 描述 | 参数 | 29 | | :----: | :----: | :----: | 30 | | overflowItemClicked | 当用户点击溢出菜单条目时触发 | `item: any` | 31 | -------------------------------------------------------------------------------- /docs/api/SplitLayout.md: -------------------------------------------------------------------------------- 1 | # SplitLayout 2 | 3 | 文件编辑器中心分割布局组件。 4 | 5 | ## Props 6 | 7 | | 属性 | 描述 | 类型 | 默认值 | 8 | | :----: | :----: | :----: | :----: | 9 | | saveBeforeUnload | 是否应该在 `window.beforeunload` 时触发 `canSaveLayout` 事件 | `boolean` | `true` | 10 | | showTabHeader | 是否显示TAB分组组件,当为 `true` 时,支持一个网格多个子面板,应该在tabContentRender插槽中渲染组件;当为 `false` 时,仅支持网格分割不支持面板与拖拽功能,应该在gridRender中自己渲染内容。 | `boolean` | `true` | 11 | | rootGridType | 指定根网格的类型,通常用于在多个组件中设置不同的类型以限制互相拖拽 | `CodeLayoutGrid` | `'centerArea'` | 12 | | layoutConfig | 基础布局数据 | [`CodeLayoutSplitNConfig`](#codelayoutsplitnconfig) | — | 13 | 14 | ## Events 15 | 16 | | 事件名 | 描述 | 参数 | 17 | | :----: | :----: | :----: | 18 | | panelClose | 面板关闭时触发此事件,可以异步调用 resolve,或者 reject 阻止关闭 | `panel: CodeLayoutSplitNPanelInternal, resolve: () => void, reject: (e?: any) => void)` | 19 | | panelContextMenu | 用户在面板上点击右键菜单时触发此事件 | `panel: CodeLayoutSplitNPanelInternal, event: MouseEvent` | 20 | | panelActive | 当用户点击激活面板时触发事件 | `lastActivePanel: CodeLayoutSplitNPanelInternal, panel: CodeLayoutSplitNPanelInternal` | 21 | | panelDrop | 当用户拖拽面板放置时触发事件 | `panel: CodeLayoutSplitNPanelInternal, referencePanel: CodeLayoutSplitNPanelInternal, referencePosition: CodeLayoutDragDropReferencePosition` | 22 | | gridActive | 当用户点击激活网格时触发事件 | `lastActivePanel: CodeLayoutSplitNGridInternal, panel: CodeLayoutSplitNGridInternal` | 23 | | canLoadLayout | 组件加载时触发此事件,可在此事件中执行加载布局操作 | `ref: CodeLayoutSplitNInstance` | 24 | | canSaveLayout | 组件卸载时触发此事件,可在此事件中执行加载保存操作 | `ref: CodeLayoutSplitNInstance` | 25 | 26 | ## Slots 27 | 28 | | 插槽名 | 描述 | 参数 | 29 | | :----: | :----: | :----: | 30 | | tabContentRender | 面板内容渲染插槽 | `{ panel: CodeLayoutSplitNPanelInternal }` | 31 | | tabEmptyContentRender | 当不收缩网格没有子面板时的渲染插槽 | `{ grid: CodeLayoutSplitNGridInternal }` | 32 | | tabHeaderExtraRender | TAB头部额外区域渲染插槽 | `{ grid: CodeLayoutSplitNGridInternal }` | 33 | | tabItemRender | TAB标签的自定义渲染 | `{ index: number, panel: CodeLayoutSplitNPanelInternal, active: boolean, onTabClick: () => void, onContextMenu: (e: MouseEvent) => void }` | 34 | | tabRender | 自定义TAB渲染,通常你不需要自定义 | `{ grid: CodeLayoutSplitNGridInternal }` | 35 | | gridRender | 当 `showTabHeader` 为 `false` 时,仅支持网格分割不支持面板与拖拽功能,在此插槽中自己渲染内容 | `{ grid: CodeLayoutSplitNGridInternal }` | 36 | 37 | ## CodeLayoutSplitNConfig 38 | 39 | 组件的额外配置类。 40 | 41 | | 属性 | 描述 | 类型 | 默认值 | 42 | | :----: | :----: | :----: | :----: | 43 | | onNonPanelDrag | 当用户将非面板数据拖动到组件中时,会触发此回调。您可以在此处检查是否允许拖动。 | `(e: DragEvent, sourcePosition: CodeLayoutDragDropReferenceAreaType) => boolean` | - | 44 | | onNonPanelDrop | 当用户将非面板数据拖放入组件时,会触发此回调。 | `(e: DragEvent, sourcePosition: CodeLayoutDragDropReferenceAreaType, reference: CodeLayoutPanelInternal︱undefined, referencePosition: CodeLayoutDragDropReferencePosition︱undefined) => void` | - | 45 | 46 | ## CodeLayoutSplitNInstance 47 | 48 | ### `getRootGrid(): CodeLayoutSplitNGridInternal` 49 | 50 | 说明: 51 | 52 | 获取根网格。 53 | 54 | 返回值: 55 | 56 | | 类型 | 说明 | 57 | | :----: | :----: | 58 | | CodeLayoutSplitNGridInternal | 返回的组实例 | 59 | 60 | ### `getPanelByName(name: string): CodeLayoutSplitNPanelInternal | undefined` 61 | 62 | 说明: 63 | 64 | 通过名称获取指定的面板。 65 | 66 | 参数: 67 | 68 | | 名称 | 说明 | 69 | | :----: | :----: | 70 | | name | 面板名称 | 71 | 72 | 返回值: 73 | 74 | | 类型 | 说明 | 75 | | :----: | :----: | 76 | | `CodeLayoutSplitNPanelInternal` or `undefined` | 找到的面板实例,如果在组件中未找到此面板,则返回undefined | 77 | 78 | ### `getGridByName(name: string): CodeLayoutSplitNGridInternal | undefined` 79 | 80 | 说明: 81 | 82 | 通过名称获取指定的网格。 83 | 84 | 参数: 85 | 86 | | 名称 | 说明 | 87 | | :----: | :----: | 88 | | name | 网格名称 | 89 | 90 | 返回值: 91 | 92 | | 类型 | 说明 | 93 | | :----: | :----: | 94 | | `CodeLayoutSplitNGridInternal` or `undefined` | 找到的网格实例,如果在组件中未找到此网格,则返回undefined | 95 | 96 | ### `getActiveGird(): CodeLayoutSplitNGridInternal|undefined` 97 | 98 | 说明: 99 | 100 | 获取用户当前激活的可用于添加面板的网格。 101 | 102 | ### `activePanel(name: string): void` 103 | 104 | 说明: 105 | 106 | 通过名称激活指定的面板,如果指定名称面板在组件中不存在,则没有效果。 107 | 108 | 此函数会同时修改激活的网格。 109 | 110 | 参数: 111 | 112 | | 名称 | 说明 | 113 | | :----: | :----: | 114 | | name | 网格名称 | 115 | 116 | ### `clearLayout(): void` 117 | 118 | 说明: 119 | 120 | 清空当前组件中的所有面板与网格数据。 121 | 122 | ### `loadLayout(json: any, instantiatePanelCallback: (data: CodeLayoutSplitNPanel) => CodeLayoutSplitNPanel): void` 123 | 124 | 说明: 125 | 126 | 加载布局数据,根据面板名称实例化面板。 127 | 128 | 注,由于保存布局数据仅保存每个布局的基础位置、大小等信息,并不包含无法序列化的信息(例如回调函数,图标),所以加载布局数据时需要在 instantiatePanelCallback 中,根据传入的面板名称填充这些数据。 129 | 130 | ```ts 131 | const data = localStorage.getItem('LayoutData'); 132 | if (data) { 133 | //If load layout from data, need fill panel data 134 | splitLayout.value.loadLayout(JSON.parse(data), (panel) => { 135 | switch (panel.name) { 136 | case 'file1': 137 | panel.title = 'File 1'; 138 | panel.tooltip = 'File path c://...'; 139 | panel.badge = '2'; 140 | panel.iconSmall = () => h(IconFile); 141 | break; 142 | } 143 | return panel; 144 | }); 145 | } else { 146 | //No data, create new layout 147 | //... 148 | } 149 | ``` 150 | 151 | 参数: 152 | 153 | | 名称 | 说明 | 154 | | :----: | :----: | 155 | | json | `saveLayout` 返回的布局数据 | 156 | | instantiatePanelCallback | 实例化面板回调,传入参数为面板基础信息 | 157 | 158 | ### `saveLayout(): any` 159 | 160 | 说明: 161 | 162 | 保存用户拖拽后的布局至JSON数据中,在下一次进入后可调用 `loadLayout` 重新从JSON数据加载恢复原布局。 163 | 164 | 返回值: 165 | 166 | | 类型 | 说明 | 167 | | :----: | :----: | 168 | | `object` | 布局数据 | 169 | 170 | ## CodeLayoutSplitNPanelInternal 171 | 172 | 面板实例。 173 | 174 | | 属性 | 描述 | 类型 | 默认值 | 175 | | :----: | :----: | :----: | :----: | 176 | | name | 面板名称,用于查找面板 | `string` | - | 177 | | title | 面板标题 | `string` | - | 178 | | tooltip | 面板的工具提示 | `string` | - | 179 | | badge | 面板的标记 | `string` | - | 180 | | iconSmall | 面板图标 | `() => VNode` | - | 181 | | size | 当前面板的大小(百分比),创建时指定为0将由组件自动分配大小 | `number` | 0 | 182 | | parentGroup | 获取当前面板的父级组 | `CodeLayoutSplitNPanelInternal` | - | 183 | | parentGrid | 获取当前面板所属的顶级组 | `CodeLayoutGrid` | - | 184 | | accept | 设置当前面板可以拖放到哪些顶级网格上 | `CodeLayoutGrid[]` | - | 185 | | actions | 当前面板的自定义操作 | [`CodeLayoutActionButton[]`](../api/CodeLayoutActionsRender.md#codelayoutactionbutton) | `false` | 186 | | data | 面板的自定义数据 | `any` | - | 187 | | onResize | 当面板大小更改时回调 | `(this: CodeLayoutPanelInternal, size: boolean) => void` | - | 188 | | onVisibleChange | 当面板显示(visible)状态更改时回调 | `(this: CodeLayoutPanelInternal, state: boolean) => void` | - | 189 | 190 | ### `closePanel(): void` 191 | 192 | 说明: 193 | 194 | 手动触发当前面板的关闭操作。 195 | 196 | ### `splitCopy(direction: CodeLayoutSplitCopyDirection, instanceCb: (panel: CodeLayoutSplitNPanel) => CodeLayoutSplitNPanel): void` 197 | 198 | 说明: 199 | 200 | 克隆当前面板并且向指定方向分割。通常用于文件编辑器需要分成两个窗口编辑时使用。 201 | 202 | 参数: 203 | 204 | | 名称 | 描述 | 205 | | :----: | :----: | 206 | | direction | 分割方向 | 207 | | instanceCb | 实例化新面板回调 | 208 | 209 | 例如,以下参考代码绑定在面板的右键菜单事件中,用户右键点击菜单项可以将选中的面板向四个方向克隆并分割。 210 | 211 | ```ts 212 | function onPanelMenu(panel: CodeLayoutPanelInternal, e: MouseEvent) { 213 | e.stopPropagation(); 214 | e.preventDefault(); 215 | 216 | ContextMenuGlobal.showContextMenu({ 217 | x: e.x, 218 | y: e.y, 219 | items: [ 220 | { 221 | label: "Split Up", 222 | onClick: () => { 223 | (panel as CodeLayoutSplitNPanelInternal).splitCopy('top', (panel) => { 224 | panel.name = panel.name + '.copy'; 225 | panel.title = panel.title + ' Clone'; 226 | return panel; 227 | }); 228 | } 229 | }, 230 | { 231 | label: "Split Down", 232 | onClick: () => { 233 | (panel as CodeLayoutSplitNPanelInternal).splitCopy('bottom', (panel) => { 234 | panel.name = panel.name + '.copy'; 235 | panel.title = panel.title + ' Clone'; 236 | return panel; 237 | }); 238 | } 239 | }, 240 | { 241 | label: "Split Left", 242 | onClick: () => { 243 | (panel as CodeLayoutSplitNPanelInternal).splitCopy('left', (panel) => { 244 | panel.name = panel.name + '.copy'; 245 | panel.title = panel.title + ' Clone'; 246 | return panel; 247 | }); 248 | } 249 | }, 250 | { 251 | label: "Split Right", 252 | onClick: () => { 253 | (panel as CodeLayoutSplitNPanelInternal).splitCopy('right', (panel) => { 254 | panel.name = panel.name + '.copy'; 255 | panel.title = panel.title + ' Clone'; 256 | return panel; 257 | }); 258 | } 259 | }, 260 | ], 261 | }); 262 | } 263 | 264 | ``` 265 | 266 | ## CodeLayoutSplitNGridInternal 267 | 268 | 网格数据实例。 269 | 270 | | 属性 | 描述 | 类型 | 默认值 | 271 | | :----: | :----: | :----: | :----: | 272 | | canMinClose | Set whether users can close the current panel by continuously shrinking it. | `boolean` | `false` | 273 | | direction | Layout direction. | `'vertical' or 'horizontal'` | `'vertical'` | 274 | | childGrid | Child grid of this grid. | `CodeLayoutSplitNGridInternal[]` | - | 275 | 276 | ### `addGrid(grid: CodeLayoutSplitNGrid): CodeLayoutSplitNPanelInternal` 277 | 278 | 说明: 279 | 280 | 向当前网格添加子网格。 281 | 282 | 参数: 283 | 284 | | 名称 | 说明 | 285 | | :----: | :----: | 286 | | grid | 网格数据 | 287 | 288 | 返回值: 289 | 290 | 子网格实例。 291 | 292 | ### `removePanel(grid: CodeLayoutSplitNGrid)` 293 | 294 | 说明: 295 | 296 | 向当前网格移除子网格。 297 | 298 | 参数: 299 | 300 | | 名称 | 说明 | 301 | | :----: | :----: | 302 | | grid | 网格数据 | 303 | 304 | ### `addPanel(panel: CodeLayoutSplitNPanel, startOpen = false): CodeLayoutSplitNPanelInternal` 305 | 306 | 说明: 307 | 308 | 向当前组添加子面板。 309 | 310 | 参数: 311 | 312 | | 名称 | 说明 | 313 | | :----: | :----: | 314 | | panel | 面板数据 | 315 | | startOpen | 添加时是否是打开状态 | 316 | 317 | 返回值: 318 | 319 | 子面板实例。 320 | 321 | ### `removePanel(panel: CodeLayoutSplitNPanelInternal, shrink = false): CodeLayoutSplitNPanelInternal` 322 | 323 | 说明: 324 | 325 | 向当前组移除子面板。 326 | 327 | 参数: 328 | 329 | | 名称 | 说明 | 330 | | :----: | :----: | 331 | | panel | 面板实例 | 332 | | shrink | 是否执行收缩操作 | 333 | 334 | ### `setActiveChild(child: CodeLayoutSplitNPanelInternal|null): void` 335 | 336 | 说明: 337 | 338 | 设置激活的面板。 339 | 340 | 参数: 341 | 342 | | 名称 | 说明 | 343 | | :----: | :----: | 344 | | child | 要激活的面板 | 345 | 346 | ### `reselectActiveChild(): void` 347 | 348 | 说明: 349 | 350 | 重新选中一个可用的面板作为激活的面板。 351 | -------------------------------------------------------------------------------- /docs/api/SplitN.md: -------------------------------------------------------------------------------- 1 | # SplitN 2 | 3 | 无限分割层级组件,此组件是 SplitLayout 的核心功能实现组件。导出方便你使用。 4 | 5 | ## Props 6 | 7 | | 属性 | 描述 | 类型 | 默认值 | 8 | | :----: | :----: | :----: | :----: | 9 | | grid | 网格数据 | `CodeLayoutSplitNGridInternal` | — | 10 | | horizontal | 是否是水平 | `boolean` | `true` | 11 | | draggerSize | 设置拖拽分割线大小(像素) | `number` | `1` | 12 | 13 | ## Slots 14 | 15 | | 插槽名 | 描述 | 参数 | 16 | | :----: | :----: | :----: | 17 | | grid | 网格内容渲染插槽 | - | 18 | -------------------------------------------------------------------------------- /docs/api/SplitTabItem.md: -------------------------------------------------------------------------------- 1 | # SplitTabItem 2 | 3 | 用于自定义渲染 SplitLayout 的标签页。需要在 SplitLayout 的插槽中使用。 4 | 5 | [请参考文档使用](../guide/split-layout.md#自定义渲染面板头)。 6 | 7 | ## Props 8 | 9 | | 属性 | 描述 | 类型 | 默认值 | 10 | | :----: | :----: | :----: | :----: | 11 | | panel | 面板数据 | `CodeLayoutSplitNPanelnternal` | — | 12 | | active | 当前是否是激活状态 | `boolean` | `true` | 13 | 14 | ## Slots 15 | 16 | | 插槽名 | 描述 | 参数 | 17 | | :----: | :----: | :----: | 18 | | icon | 图标渲染插槽 | - | 19 | | title | 标题内容渲染插槽 | - | 20 | | badge | 标记渲染插槽 | - | 21 | | close | 关闭按钮渲染插槽 | - | 22 | -------------------------------------------------------------------------------- /docs/change/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Change log 3 | nav: 4 | title: Change log 5 | order: 5 6 | --- 7 | 8 | ## v1.2.1 - 2025/05/06 9 | 10 | * [Add] Add panel resize,visible,open change callback (#11). 11 | * [Add] Add CodeLayout panel bottom position choice. 12 | * [Add] Add bottom panel position left,right,top. 13 | * [Add] Add CodeLayout custom panel menu config (#21, 4, 5). 14 | * [Add] Make activitybar can collapse when overflow (#19). 15 | * [Add] Add panel to last when drop on tab header (#13). 16 | * [Add] Add custom tooltip for header (#18). 17 | * [Add] Add that auto adjust position function when Simple tooltip overflow screen (#12). 18 | 19 | * [Fix] Fix instance reference error after drag (#22). 20 | * [Fix] Fix secondarySideBar menu issue. 21 | * [Fix] Fix drag panel error (#14). 22 | * [Fix] Fix tab item not hidden issue. 23 | * [Fix] Fix activityBarPosition control logic (#21) 24 | * [Fix] Fix Tab header context menu issue (#21, 6). 25 | * [Fix] Fix issue that menu showBadge does not take effect (#21). 26 | * [Fix] Fix split layout drag issue (#15, #20). 27 | * [Fix] Fix cursor error state when click panel top (#17). 28 | * [Fix] Fix nest drag hover issue. 29 | * [Fix] Fix SimpleTooltip stuck issue. 30 | * [Fix] No hide primary side bar menu when it was hidden. 31 | * [Fix] Fix drag light box issue (#23). 32 | * [Fix] Fix drag event wrong preventing default (#23). 33 | * [Fix] Remove console.log for debugging. 34 | 35 | * [Change] SplitN drag function improvement. 36 | * [Change] Update vue3-context-menu to latest 37 | * [Change] Move CodeLayoutScrollbar to standalone package ([@imengyu/vue-scroll-rect](https://github.com/imengyu/vue-scroll-rect)). 38 | * [Change] Change bottomAlignment to panelAlignment 39 | * [Change] Change root data of codeLayout. Unify root data of the two components. 40 | 41 | * [Docs] Update document (#16). 42 | * [Docs] Update docs of CodeLayout menu. 43 | 44 | ## v1.1.2 - 2024/12/20 45 | 46 | * [Add] 增加回调事件允许处理拖拽进入组件的非面板数据。 47 | 48 | --- 49 | 50 | * [Add] Adding callback events allows handling non panel data dragged into components. 51 | 52 | ## v1.1.1 - 2024/12/07 53 | 54 | * [Change] 为导出包增加了 es 模块定义。。 55 | * [Change] 更新 vue3-context-menu 至最新。 56 | 57 | --- 58 | 59 | * [Change] Add es module and update vue3-context-menu. 60 | 61 | ## v1.1.0 - 2024/10/17 62 | 63 | * [Add] 为 SplitLayout 添加了自定义渲染标签页功能。 64 | * [Docs] 文档细节修改。 65 | 66 | --- 67 | 68 | * [Add] Added custom rendering tab slot for SplitLayout. 69 | * [Docs] Update document. 70 | 71 | ## v1.0.9 - 2024/10/10 72 | 73 | * [Add] 为 SplitLayout 每个面板添加了 `splitCopy` 用于实现分割编辑器的功能。 74 | * [Fix] 修复 SplitLayout 标签页过多时无法滚动问题(#7)。 75 | 76 | --- 77 | 78 | * [Add] Added `splitCopy` to each panel of SplitlLayout to implement the functionality of a split editor. 79 | * [Fix] Fixed the issue of not being able to scroll when there are too many SplitLayout tabs (#7). 80 | 81 | ## v1.0.8 - 2024/10/08 82 | 83 | * [Add] 为每个面板添加 `draggable` 属性用于控制此面板是否可以拖动。 84 | * [Add] 为 SplitLayout 添加 `gridActive` 事件用于收到激活网格更改事件。 85 | * [Fix] 修复 CodeLayout 拖拽子边栏收缩后自定义布局按钮状态没有修改问题。 86 | * [Fix] 修复 SplitLayout 激活的面板事件触发不正确问题。 87 | * [Docs] 文档细节修改。 88 | 89 | --- 90 | 91 | * [Add] Add the `draggable` attribute to each panel to control whether it can be dragged. 92 | * [Add] Add a `gridActive` event to SplitLayout to receive activation grid change events. 93 | * [Fix] Fixed the issue where the Customize layout button status was not change after the CodeLayout drag sidebar was shrunk. 94 | * [Fix] Fix the issue of incorrect triggering of `panelActive` event by SplitlLayout. 95 | * [Docs] Update Document. 96 | 97 | ## v1.0.7 - 2024/09/13 98 | 99 | * [Add] 第二侧边栏支持大侧栏模式和Tab模式切换 `secondarySideBarAsActivityBar` 。 100 | * [Fix] 修复拖拽子面板到边栏的当前条目上会导致其隐藏的细节问题。 101 | * [Fix] 修复拖拽子面板后,父面板因为切换而卸载后,再切换回来,无法再拖拽的问题。 102 | * [Fix] 修复拖拽子面板到顶级侧边栏消失的问题。 103 | 104 | --- 105 | 106 | * [Add] The second sidebar supports ActionBar mode and Tab mode switching, `secondarySideBarAsActivityBar`. 107 | * [Fix] Fixed a issue where dragging subpanels to the current entry in the ActionBar would cause it to be hidden. 108 | * [Fix] Fixed an issue where the parent panel could not be dragged again after being unmounted due to switching. 109 | * [Fix] Fixed where drag subpanels to the top sidebar disappeared. 110 | 111 | ## v1.0.6 - 2024/07/12 112 | 113 | * [Add] Add `titleBarBottom` and `titleBarTop` slot. 114 | * [Add] CodeLayoutScrollbar can get container ref and scroll. 115 | * [Fix] Fix a problem that `activeSelf` unable to activate the closed hidden panel. 116 | * [Fix] Fix problem activeSelf can not active top actionbar. 117 | 118 | ## v1.0.5 119 | 120 | * [Fix] Fix type warning. 121 | 122 | ## v1.0.4 123 | 124 | * [Add] activeSelf can be activated recursively. 125 | * [Add] Export CodeLayoutCollapseTitle component. 126 | * [Fix] Fix problem that click panel action button will collapse panel. 127 | 128 | ## v1.0.3 - 2024/04/07 129 | 130 | * [Fix] Fix the height lost issue after maximizing and restoring the panel. 131 | * [Fix] Fix panel maximizing button state issue. 132 | 133 | ## v1.0.2 - 2024/04/05 134 | 135 | * [Added] Add zh language string. 136 | * [Fix] Fix the overflow display issue of the main after maximizing the panel. 137 | 138 | ## v1.0.0 - 2024/03/30 139 | 140 | * Officially release 141 | -------------------------------------------------------------------------------- /docs/en/api/CodeLayoutActionsRender.md: -------------------------------------------------------------------------------- 1 | # CodeLayoutActionsRender 2 | 3 |  4 | 5 | The operation button group component is used in the panel title bar of CodeLayout for additional panel operations. This component is exported here for your convenience. 6 | 7 | ## Props 8 | 9 | | Property | Description | Type | Default | 10 | | :----: | :----: | :----: | :----: | 11 | | actions | Action items | `CodeLayoutActionButton[]` | `[]` | 12 | 13 | ## CodeLayoutActionButton 14 | 15 | Panel Action button Type Definition. 16 | 17 | | Property | Description | Type | Default | 18 | | :----: | :----: | :----: | :----: | 19 | | render | Render the entire content of this button on your own | `() => VNode` | - | 20 | | icon | Render the icon of this button | `() => VNode` | - | 21 | | text | Text of this button | `string` | - | 22 | | tooltip | Tooltip text of this button | `string` | - | 23 | | tooltipDirection | The tooltip direction of the pop-up | `'left'│'top'│'right'│'bottom'` | - | 24 | | onClick | Button click event callback | `() => void` | - | 25 | -------------------------------------------------------------------------------- /docs/en/api/CodeLayoutCustomizeLayout.md: -------------------------------------------------------------------------------- 1 | # CodeLayoutCustomizeLayout 2 | 3 |  4 | 5 | CustomizeLayout custom layout component, located . 6 | 7 | Customize Layout popup window component,in the upper right corner of [CodeLayout](./CodeLayout.md) by default. The export purpose is because if you need to customize the title bar, you can control the display position yourself. 8 | 9 | It should be used together with CodeLayout, Suggest placing it in the 'titleBarRight' slot of CodeLayout. 10 | -------------------------------------------------------------------------------- /docs/en/api/OverflowCollapseList.md: -------------------------------------------------------------------------------- 1 | # OverflowCollapseList 2 | 3 |  4 | 5 | Automatic overflow calculation folding item component, used in a series of button groups in CodeLayout. Exporting is convenient for you to use. 6 | 7 | ## Props 8 | 9 | | Property | Description | Type | Default | 10 | | :----: | :----: | :----: | :----: | 11 | | items | Items to display, render by slot | `any[]` | — | 12 | | getItemSize | Call back that calc item size. (Required) | `(item: T, horizontal: boolean, index: number) => number` | - | 13 | | activeItem | Activated item, activated item will not be hidden | `any` | - | 14 | | direction | Direction of the list, horizontal or vertical | `'vertical'│'horizontal'` | `'horizontal'` | 15 | | itemMenuLabel | Label of the item in the context menu | `(item: any) => string` | - | 16 | | itemCollapseMergin | Mergin of the item when collapse | `(item: any) => string` | `30` | 17 | | itemKey | Key of the item.key, used to identify the item | `string` | `'name'` | 18 | | visibleKey | Key of the item.visible, used to identify the item visibility | `string` | `''` | 19 | 20 | ## Slots 21 | 22 | | Slot name | Description | Param | 23 | | :----: | :----: | :----: | 24 | | item | Item rendering slot | `{ visible: boolean, item: any, index: number }` | 25 | 26 | ## Events 27 | 28 | | Event name | Description | Param | 29 | | :----: | :----: | :----: | 30 | | overflowItemClicked | Triggered when the user clicks on the overflow menu item | `item: any` | 31 | -------------------------------------------------------------------------------- /docs/en/api/SplitN.md: -------------------------------------------------------------------------------- 1 | # SplitN 2 | 3 | Split layout core component, which is the core functional implementation component of SplitLayout. Exporting is convenient for you to use. 4 | 5 | ## Props 6 | 7 | | Property | Description | Type | Default | 8 | | :----: | :----: | :----: | :----: | 9 | | grid | Grid instance | `CodeLayoutSplitNGridInternal` | — | 10 | | horizontal | Is layout horizontal? | `boolean` | `true` | 11 | | draggerSize | Set drag and drop split line size (in pixels) | `number` | `1` | 12 | 13 | ## Slots 14 | 15 | | Slot name | Description | Param | 16 | | :----: | :----: | :----: | 17 | | grid | Grid content rendering slot | - | 18 | -------------------------------------------------------------------------------- /docs/en/api/SplitTabItem.md: -------------------------------------------------------------------------------- 1 | # SplitTabItem 2 | 3 | A tab for customizing the rendering of SplitLayout. It needs to be used in the slot of SplitLayout. 4 | 5 | [Please refer to the documentation](../guide/split-layout.md#customize-render-tab-header)。 6 | 7 | ## Props 8 | 9 | | Property | Description | Type | Default | 10 | | :----: | :----: | :----: | :----: | 11 | | panel | Panel instance | `CodeLayoutSplitNPanelnternal` | — | 12 | | active | Is active state? | `boolean` | `true` | 13 | 14 | ## Slots 15 | 16 | | Slot name | Description | Param | 17 | | :----: | :----: | :----: | 18 | | icon | Icon render slot | - | 19 | | title | Title render slot | - | 20 | | badge | Badge render slot | - | 21 | | close | Close icon render slot | - | 22 | -------------------------------------------------------------------------------- /docs/en/guide/customize.md: -------------------------------------------------------------------------------- 1 | # Custom style 2 | 3 | If you think the default style doesn't look good and want to modify it, you can also overwrite the default CSS style. All CSS style definitions are in the [Base.scss](https://github.com/imengyu/vue-code-layout/blob/master/library/Scss/Base.scss). You can copy all styles, modify them as needed, and store them in your file. Then overwrite the default style at the import location: 4 | 5 | ```js 6 | import 'vue-code-layout/lib/vue-code-layout.css' 7 | import 'Your style scss file path.scss' 8 | ``` 9 | 10 | ## CSS variables 11 | 12 | The style of the component has extracted some CSS variables for you to use, making it easy for you to modify colors without having to specify each state specifically. 13 | 14 | ```scss 15 | //Common color and size variables 16 | :root { 17 | --code-layout-color-background: #1e1e1e; //background color 18 | --code-layout-color-background-second: #252526; 19 | --code-layout-color-background-light: #333333; 20 | --code-layout-color-background-highlight: #04395e; 21 | --code-layout-color-background-hover: #363737; 22 | --code-layout-color-background-hover-light: #464646; 23 | --code-layout-color-background-mask-light: rgba(255,255,255,0.2); 24 | --code-layout-color-highlight: #0078d4; //highlight color 25 | --code-layout-color-text: #ccc; //text color 26 | --code-layout-color-text-light: #fff; 27 | --code-layout-color-text-highlight: #2f94f1; 28 | --code-layout-color-text-gray: #818181; 29 | --code-layout-color-text-disabled: #727272; 30 | --code-layout-color-border: #474747; //border color 31 | --code-layout-color-border-light: #cccccc; 32 | --code-layout-color-border-background: #2a2a2a; 33 | --code-layout-color-border-white: #fff; 34 | --code-layout-color-shadow: rgba(0,0,0,0.15); //shadow size 35 | --code-layout-color-scrollbar-thumb: rgba(204, 204, 204, 0.4); //scrollbar color 36 | --code-layout-color-scrollbar-thumb-light: rgba(204, 204, 204, 0.6); 37 | --code-layout-border-size: 1px; //border size 38 | --code-layout-border-size-larger: 2px; 39 | --code-layout-border-size-dragger: 4px; 40 | --code-layout-sash-size: 8px; //xy merge drag bar size 41 | --code-layout-border-radius-small: 5px; //border radius 42 | --code-layout-border-radius-large: 5px; 43 | --code-layout-header-height: 22px; //CodeLayout panel title size 44 | --code-layout-titlebar-background: #3c3c3c; //CodeLayout Title Bar Background Color 45 | --code-layout-titlebar-height: 35px; //CodeLayout Title height 46 | --code-layout-status-height: 20px; //CodeLayout status height 47 | --code-layout-font-size: 13px; //CodeLayou font size 48 | --code-layout-font-size-small: 11px; 49 | } 50 | 51 | //SplitLayout Tab Component specific variables 52 | .code-layout-split-tab { 53 | --tab-height: 35px; //Base Height 54 | --tab-font-size: 12px; //Font size 55 | --tab-icon-size: 14px; //Icon size 56 | --tab-text-color: var(--code-layout-color-text); //TAB component text and background color, inherited by default from CodeLayout to ensure consistent style 57 | --tab-active-text-color: var(--code-layout-color-text-light); 58 | --tab-mormal-color: var(--code-layout-color-background-light); 59 | --tab-active-color: var(--code-layout-color-background); 60 | --tab-border-color: var(--code-layout-color-background-second); 61 | --tab-button-normal-color: transparent; 62 | --tab-button-hover-color: var(--code-layout-color-background-hover-light); 63 | --tab-close-size: 18px; //The size of the TAB component close button 64 | } 65 | ``` 66 | 67 | ## Custom font 68 | 69 | You can customize the font through CSS: 70 | 71 | ```scss 72 | //Set the font for all components 73 | .code-layout-root { 74 | font-family: 'Times New Roman', Times, serif; 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/en/guide/i18n.md: -------------------------------------------------------------------------------- 1 | # i18n 2 | 3 | CodeLayout supports multiple languages for internal strings, Chinese and English are supported, and you can specify the language in the `langConfiguration` attribute of the CodeLayout component. 4 | 5 | ```vue 6 | 7 | 12 | 13 | 14 | 22 | ``` 23 | 24 | ## Overwrite language text 25 | 26 | If a string in the language does not meet your requirements, you can overwrite it in langConfig. 27 | 28 | ```ts 29 | const langConfig = reactive({ 30 | lang: 'zh', 31 | stringsOverride: { 32 | togglePanel: '切换面板', 33 | }, 34 | }); 35 | ``` 36 | 37 | ## Add language 38 | 39 | Currently, Chinese and English are supported, but you can add languages through the interface. You should call `addCodeLayoutLang` before creating the CodeLayout component. 40 | 41 | ```ts 42 | import { addCodeLayoutLang } from 'vue-code-layout'; 43 | 44 | addCodeLayoutLang('zh', { 45 | customizeLayout: '自定义布局', 46 | togglePanel: '切换面板', 47 | //省略... 48 | }); 49 | 50 | ``` 51 | 52 | The complete list of languages can be viewed in the [library\Language\en.ts](https://github.com/imengyu/vue-code-layout/blob/master/library/Language/en.ts) 53 | -------------------------------------------------------------------------------- /docs/en/guide/install.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | ```shell 4 | npm install -save vue-code-layout 5 | ``` 6 | 7 | ## Global Import Components 8 | 9 | Suggest that you globally import components for the most convenient use. 10 | 11 | ```js 12 | //main.js 13 | import 'vue-code-layout/lib/vue-code-layout.css' 14 | import CodeLayout from 'vue-code-layout' 15 | 16 | createApp(App) 17 | .use(CodeLayout) 18 | .mount('#app') 19 | ``` 20 | 21 | ## Local import components 22 | 23 | First import the style file: 24 | 25 | ```js 26 | //main.js 27 | import 'vue-code-layout/lib/vue-code-layout.css' 28 | ``` 29 | 30 | Then import the components where you need to use them: 31 | 32 | ```vue 33 | 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/en/guide/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Before starting 3 | --- 4 | 5 | # Before starting 6 | 7 | You can find the complete source code in the [Github](https://github.com/imengyu/vue-code-layout/tree/master/examples/views) repository, [Here are the online demos for all examples](https://docs.imengyu.top/vue-code-layout-demo/)。 8 | 9 | The project is in the early release stage, and there may be bugs. You can submit an issue in the repository, and I will do my best to solve it for you. 10 | 11 | If you also have good modifications, you can submit a PR in the repository. Welcome to become a member of the project! 12 | 13 | If you're ready, let's start~ 14 | 15 | ::: danger 16 | This component is not support Vue2. 17 | ::: 18 | -------------------------------------------------------------------------------- /docs/en/guide/useage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduce 3 | order: 2 4 | --- 5 | 6 | # Introduce 7 | 8 | ::: info 9 | Tip: This component library is not packaging the original VScode code, but is a component re implemented on Vue based on the original functionality, so the functionality may differ from the original VScode logic. 10 | 11 | If this component library missing some features, you can submit an issue on Github. 12 | ::: 13 | 14 | ::: danger 15 | This component is not support SSR. 16 | ::: 17 | 18 | ## CodeLayout 19 | 20 | CodeLayout is a layout component similar to the VScode, It can be used to build the interface of your editor. 21 | 22 | It consists of four major areas: 23 | 24 | * primarySideBar: The primary sideBar, usually displayed on the left, places the most important content. 25 | * secondarySideBar: Secondary sideBar, usually displayed on the right side. 26 | * bottomPanel: Bottom Panel area. 27 | * centerArea: The central area, usually displaying the editor area, is exposed through a slot where the SplitLayout component can be embedded to make editor layout. 28 | 29 | The content of primarySideBar, secondarySideBar, and bottomPanel can be customized through slots and data, and all content can be dragged and placed between them. 30 | 31 | In addition, the title bar area and status bar area are implemented together in the component, which can be customized through slots. 32 | 33 | The default layout position is shown in the figure: 34 | 35 |  36 | 37 | CodeLayout also comes with a custom layout feature similar to VScode. By default, clicking the control button in the upper right corner of the title bar will bring up this pop-up window. 38 | 39 | CodeLayout also supports saving layout data and restoring it in the next load. 40 | 41 | [👉 CodeLayout usage](./code-layout.md) 42 | 43 | ## SplitLayout 44 | 45 | SplitLayout is designed for split layouts in multiple editors, supporting infinite levels of panel nesting and splitting. It is typically used in file editors when multiple files need to be opened and edited simultaneously. 46 | 47 |  48 | 49 | [👉 SplitLayout usage](./split-layout.md) 50 | 51 | ## CodeLayoutScrollbar 52 | 53 |  54 | 55 | CodeLayoutScrollbar is a Vue scrollbar component. If you feel that the system scrollbar does not match CodeLayout, you can try using CodeLayoutScrollbar, which has a unified style built-in. 56 | 57 | [👉 CodeLayoutScrollbar reference](https://docs.imengyu.top/vue-scroll-rect-docs/en/) 58 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | --- 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/en/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | {{ description }} 6 | 7 | 8 | 9 | Get started 10 | View Demo 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 31 | 32 | -------------------------------------------------------------------------------- /docs/guide/customize.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 自定义样式 3 | order: 5 4 | --- 5 | 6 | # 自定义样式 7 | 8 | 如果你觉得默认样式不好看,想修改掉它,还可以覆盖默认css样式,所有的css样式定义都在[源代码/Base.scss](https://github.com/imengyu/vue-code-layout/blob/master/library/Scss/Base.scss) 中。你可以将所有样式复制出来,按需修改,存放在你的文件中。然后在导入的地方覆盖默认样式: 9 | 10 | ```js 11 | import 'vue-code-layout/lib/vue-code-layout.css' 12 | import '你的样式scss文件路径.scss' 13 | ``` 14 | 15 | ## css变量 16 | 17 | 组件的样式抽离了一些css变量供您使用,你可以很方便的修改颜色,无须每个状态都特殊指定。 18 | 19 | ```scss 20 | //公用颜色与大小变量 21 | :root { 22 | --code-layout-color-background: #1e1e1e; //背景颜色 23 | --code-layout-color-background-second: #252526; 24 | --code-layout-color-background-light: #333333; 25 | --code-layout-color-background-highlight: #04395e; 26 | --code-layout-color-background-hover: #363737; 27 | --code-layout-color-background-hover-light: #464646; 28 | --code-layout-color-background-mask-light: rgba(255,255,255,0.2); 29 | --code-layout-color-highlight: #0078d4; //高亮颜色 30 | --code-layout-color-text: #ccc; //文字颜色 31 | --code-layout-color-text-light: #fff; 32 | --code-layout-color-text-highlight: #2f94f1; 33 | --code-layout-color-text-gray: #818181; 34 | --code-layout-color-text-disabled: #727272; 35 | --code-layout-color-border: #474747; //边框颜色 36 | --code-layout-color-border-light: #cccccc; 37 | --code-layout-color-border-background: #2a2a2a; 38 | --code-layout-color-border-white: #fff; 39 | --code-layout-color-shadow: rgba(0,0,0,0.15); //阴影颜色 40 | --code-layout-color-scrollbar-thumb: rgba(204, 204, 204, 0.4); //滚动条颜色 41 | --code-layout-color-scrollbar-thumb-light: rgba(204, 204, 204, 0.6); 42 | --code-layout-border-size: 1px; //边框大小 43 | --code-layout-border-size-larger: 2px; 44 | --code-layout-border-size-dragger: 4px; 45 | --code-layout-sash-size: 8px; //合并拖拽条大小 46 | --code-layout-border-radius-small: 5px; //圆角大小 47 | --code-layout-border-radius-large: 5px; 48 | --code-layout-header-height: 22px; //CodeLayout面板标题大小 49 | --code-layout-titlebar-background: #3c3c3c; //CodeLayout标题栏背景颜色 50 | --code-layout-titlebar-height: 35px; //CodeLayout标题栏插槽大小 51 | --code-layout-status-height: 20px; //CodeLayout状态栏颜色 52 | --code-layout-font-size: 13px; //CodeLayou基础字体大小 53 | --code-layout-font-size-small: 11px; 54 | } 55 | 56 | //SplitLayout Tab组件特殊变量 57 | .code-layout-split-tab { 58 | --tab-height: 35px; //TAB组件高度 59 | --tab-font-size: 12px; //TAB组件字体大小 60 | --tab-icon-size: 14px; //TAB组件图标大小 61 | --tab-text-color: var(--code-layout-color-text); //TAB组件文字与背景颜色,默认继承于CodeLayout以保证风格统一 62 | --tab-active-text-color: var(--code-layout-color-text-light); 63 | --tab-mormal-color: var(--code-layout-color-background-light); 64 | --tab-active-color: var(--code-layout-color-background); 65 | --tab-border-color: var(--code-layout-color-background-second); 66 | --tab-button-normal-color: transparent; 67 | --tab-button-hover-color: var(--code-layout-color-background-hover-light); 68 | --tab-close-size: 18px; //TAB组件关闭按钮的大小 69 | } 70 | ``` 71 | 72 | ## 自定义字体 73 | 74 | 你可以通过 css 自定义设置字体: 75 | 76 | ```scss 77 | //设置所有组件的字体 78 | .code-layout-root { 79 | font-family: 'Times New Roman', Times, serif; 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/guide/i18n.md: -------------------------------------------------------------------------------- 1 | # 国际化 2 | 3 | CodeLayout 内部字符串支持多语言,目前支持中文与英文,你可以在 CodeLayout 组件的 `langConfig` 属性中指定语言。 4 | 5 | ```vue 6 | 7 | 12 | 13 | 14 | 22 | ``` 23 | 24 | ## 覆盖语言文字 25 | 26 | 如果语言中某个字符串不满足你的要求,可以在 langConfig 中覆盖它。 27 | 28 | ```ts 29 | const langConfig = reactive({ 30 | lang: 'zh', 31 | stringsOverride: { 32 | togglePanel: '切换面板', 33 | }, 34 | }); 35 | ``` 36 | 37 | ## 添加语言 38 | 39 | 目前支持中文与英文,但你可以通过接口添加语言,应该在创建 CodeLayout 组件之前调用 addCodeLayoutLang 。 40 | 41 | ```ts 42 | import { addCodeLayoutLang } from 'vue-code-layout'; 43 | 44 | addCodeLayoutLang('zh', { 45 | customizeLayout: '自定义布局', 46 | togglePanel: '切换面板', 47 | //省略... 48 | }); 49 | 50 | ``` 51 | 52 | 完整的语言列表可以在源代码 [library\Language\en.ts](https://github.com/imengyu/vue-code-layout/blob/master/library/Language/en.ts) 中查看。 53 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 安装 3 | --- 4 | 5 | # 安装 6 | 7 | ```shell 8 | npm install -save vue-code-layout 9 | ``` 10 | 11 | ## 全局导入组件 12 | 13 | 建议你全局导入组件,这样使用最方便。 14 | 15 | ```js 16 | //main.js 17 | import 'vue-code-layout/lib/vue-code-layout.css' 18 | import CodeLayout from 'vue-code-layout' 19 | 20 | createApp(App) 21 | .use(CodeLayout) 22 | .mount('#app') 23 | ``` 24 | 25 | ## 局部导入组件 26 | 27 | 首先导入样式文件: 28 | 29 | ```js 30 | //main.js 31 | import 'vue-code-layout/lib/vue-code-layout.css' 32 | ``` 33 | 34 | 然后在你需要使用的地方导入组件使用: 35 | 36 | ```vue 37 | 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/guide/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 开始之前 3 | --- 4 | 5 | # 开始之前 6 | 7 | 文档中所示案例,你都可在 [Github 仓库](https://github.com/imengyu/vue-code-layout/tree/master/examples/views) 中找到完整的源代码,也可以在 [这里查看所有示例的在线Demo](https://docs.imengyu.top/vue-code-layout-demo/)。 8 | 9 | 作者开发不易,如果这个项目对您有帮助,希望你可以去 [Github](https://github.com/imengyu/vue-code-layout) 或者 [Gitee](https://gitee.com/imengyu/vue-code-layout) 帮我点个 ⭐ ,这将是对我极大的鼓励。谢谢啦 (●'◡'●) 10 | 11 | 项目正处于早期发布阶段,这时可能会存在BUG或者功能不满意的地方,你可以在仓库中提出Issue,我会尽可能的为你解决。 12 | 13 | 如果你也有好的修改,可以在仓库提交PR,欢迎成为项目的一员! 14 | 15 | 如果你准备好了,那我们就开始吧~ 16 | 17 | ::: danger 18 | 本库不支持 Vue2. 19 | ::: 20 | -------------------------------------------------------------------------------- /docs/guide/useage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 介绍 3 | order: 2 4 | --- 5 | 6 | # 介绍 7 | 8 | ::: info 9 | 提示:本组件库不是包装 VScode 原版的代码,而是根据原版的功能重新在Vue上实现的组件,所以功能上可能与原版 VScode 逻辑有所不同。 10 | 11 | 若本组件库缺少某些实用功能,您可以在 Github 上提出 Issue。 12 | ::: 13 | 14 | ::: danger 15 | 本组件不支持 SSR。 16 | ::: 17 | 18 | ## CodeLayout 19 | 20 | CodeLayout 是类似 VScode 最外层的界面布局(不包括中心的编辑器分割布局), 21 | 它可以用来搭建你的编辑器外层。 22 | 23 | 它有四个大板块组成: 24 | 25 | * primarySideBar 第一侧边栏,通常在左侧显示,放置最重要的内容. 26 | * secondarySideBar 第二侧边栏,通常在右侧显示。 27 | * bottomPanel 底栏区域。 28 | * centerArea 中心区域,通常在这里显示编辑器区域,这部分内容通过插槽暴露,可以在此插槽中嵌入SplitLayout组件来实现编辑器分割布局。 29 | 30 | primarySideBar、secondarySideBar、bottomPanel三个板块的内容均可通过插槽与数据自定义,并且所有内容均可他们之间拖拽、放置。 31 | 32 | 另外有标题栏区域与状态栏区域一并在组件中实现,这部分内容可通过插槽自定义。 33 | 34 | 默认布局位置如图示: 35 | 36 |  37 | 38 | CodeLayout还内置了类似 VScode 的 Customize layout 自定义布局的功能,默认在标题栏中点击右上角控制按钮,可以弹出此弹窗。 39 | 40 | CodeLayout还支持保存布局数据并在下次加载还原。 41 | 42 | [👉 CodeLayout使用方法](./code-layout.md) 43 | 44 | ## SplitLayout 45 | 46 | SplitLayout专用于多个编辑器的分割布局,它支持无限层级面板嵌套和分割,通常可以用于文件编辑器中,需要同时打开编辑多个文件时。 47 | 本组件不与CodeLayout耦合,可以拿出来单独使用。 48 | 49 |  50 | 51 | [👉 SplitLayout使用方法](./split-layout.md) 52 | 53 | ## CodeLayoutScrollbar 54 | 55 |  56 | 57 | CodeLayoutScrollbar是一个Vue的滚动条封装组件,如果你觉得系统内置滚动条与CodeLayout用起来不搭,可以试试使用CodeLayoutScrollbar,它内置了统一的样式。 58 | 59 | [👉 CodeLayoutScrollbar参考](https://docs.imengyu.top/vue-scroll-rect-docs/) 60 | -------------------------------------------------------------------------------- /docs/images/CodeLayoutActions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutActions.png -------------------------------------------------------------------------------- /docs/images/CodeLayoutBase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutBase.jpg -------------------------------------------------------------------------------- /docs/images/CodeLayoutCustomizeLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutCustomizeLayout.png -------------------------------------------------------------------------------- /docs/images/CodeLayoutScrollbarDemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutScrollbarDemo.gif -------------------------------------------------------------------------------- /docs/images/CodeLayoutSlots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutSlots.jpg -------------------------------------------------------------------------------- /docs/images/CodeLayoutSlots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutSlots.png -------------------------------------------------------------------------------- /docs/images/CodeLayoutTitle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutTitle1.jpg -------------------------------------------------------------------------------- /docs/images/CodeLayoutTitle2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/CodeLayoutTitle2.jpg -------------------------------------------------------------------------------- /docs/images/OverflowCollapseList.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/OverflowCollapseList.gif -------------------------------------------------------------------------------- /docs/images/SplitLayout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/SplitLayout.jpg -------------------------------------------------------------------------------- /docs/images/SplitLayoutTitle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/SplitLayoutTitle.jpg -------------------------------------------------------------------------------- /docs/images/SplitLayoutTitleClose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/docs/images/SplitLayoutTitleClose.jpg -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | --- 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/index.scss: -------------------------------------------------------------------------------- 1 | $primary-color: var(--vp-button-brand-bg); 2 | $text-color: var(--vp-c-text-1); 3 | $second-text-color: var(--vp-c-text-2); 4 | 5 | html[class="dark"] { 6 | .hidden-dark { 7 | visibility: hidden; 8 | } 9 | .hidden-light { 10 | visibility: visible; 11 | } 12 | } 13 | 14 | .hidden-light { 15 | visibility: hidden; 16 | } 17 | 18 | .big-home-area { 19 | max-width: 1152px; 20 | margin: 0 auto; 21 | } 22 | .big-title { 23 | margin-top: 100px; 24 | 25 | h1 { 26 | color:$primary-color; 27 | line-height: 64px; 28 | font-size: 56px; 29 | font-weight: bold; 30 | } 31 | p { 32 | font-size: 26px; 33 | line-height: 34px; 34 | color: $second-text-color; 35 | 36 | &.big { 37 | font-size: 36px; 38 | line-height: 55px; 39 | color: $text-color; 40 | font-weight: bold; 41 | } 42 | 43 | } 44 | } 45 | .big-demo { 46 | img { 47 | position: absolute; 48 | width: 600px; 49 | right: 0; 50 | top: 60px; 51 | z-index: -1; 52 | } 53 | } 54 | 55 | @media screen and (max-width: 1600px) { 56 | .big-demo img { 57 | width: 650px; 58 | right: 0; 59 | } 60 | } 61 | @media screen and (max-width: 1450px) { 62 | .big-demo img { 63 | width: 550px; 64 | right: 10px; 65 | } 66 | } 67 | @media screen and (max-width: 1200px) { 68 | .big-demo img { 69 | width: 350px; 70 | position: relative; 71 | margin-top: 30px; 72 | right: 0; 73 | top: 0; 74 | } 75 | .big-home-area { 76 | padding: 0 50px; 77 | } 78 | } 79 | 80 | .big-start { 81 | padding: 20px 0 40px 0; 82 | 83 | button { 84 | font-size: 16px; 85 | padding: 10px 20px; 86 | border-radius: 30px; 87 | outline: none; 88 | appearance: none; 89 | margin-right: 10px; 90 | 91 | border-color: var(--vp-button-alt-border); 92 | color: var(--vp-button-alt-text); 93 | background-color: var(--vp-button-alt-bg); 94 | 95 | &.primary { 96 | border: 1px solid var(--vp-button-brand-border); 97 | color: var(--vp-button-brand-text); 98 | background-color: var(--vp-button-brand-bg); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /docs/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | {{ description }} 6 | 7 | 8 | 9 | 立即开始 10 | 查看演示 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 31 | 32 | -------------------------------------------------------------------------------- /docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve } from 'path' 3 | export default defineConfig({ 4 | build: { 5 | ssr: false 6 | }, 7 | resolve: { 8 | alias: { 9 | 'vue-code-layout': resolve(__dirname, '../library') 10 | }, 11 | }, 12 | }) -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Code layout 6 | Base Layou useage 7 | Split Layout 8 | Load and save 9 | Slots 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 222 | -------------------------------------------------------------------------------- /examples/assets/icons/IconFile.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /examples/assets/icons/IconMarkdown.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | file_type_markdown 4 | 5 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /examples/assets/icons/IconSearch.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /examples/assets/icons/IconVue.vue: -------------------------------------------------------------------------------- 1 | file_type_vue -------------------------------------------------------------------------------- /examples/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 12 | 14 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/assets/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/examples/assets/images/placeholder.png -------------------------------------------------------------------------------- /examples/assets/images/placeholder2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/examples/assets/images/placeholder2.png -------------------------------------------------------------------------------- /examples/assets/images/placeholder3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/examples/assets/images/placeholder3.png -------------------------------------------------------------------------------- /examples/assets/images/placeholder4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/examples/assets/images/placeholder4.png -------------------------------------------------------------------------------- /examples/assets/images/placeholder5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/examples/assets/images/placeholder5.png -------------------------------------------------------------------------------- /examples/assets/text/Useage.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | Center Area 9 | 10 | 11 | 12 | 13 | 14 | 15 | Render Panel Explorer File 16 | 17 | 18 | Render Panel Explorer outline 19 | 20 | 21 | Render bottom panel ports 22 | 23 | 24 | Render bottom panel terminal 25 | 26 | Panel {{ panel.name }}, no content 27 | 28 | 29 | Custom render Status bar area 30 | 31 | 32 | 33 | 34 | 35 | 145 | 146 | -------------------------------------------------------------------------------- /examples/assets/text/Useage2.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | Center Area 9 | 10 | 11 | 12 | 13 | 14 | 15 | Render Panel Explorer File 16 | 17 | 18 | Render Panel Explorer outline 19 | 20 | 21 | Render bottom panel ports 22 | 23 | 24 | Render bottom panel terminal 25 | 26 | Panel {{ panel.name }}, no content 27 | 28 | 29 | Custom render Status bar area 30 | 31 | 32 | 33 | 34 | 35 | 145 | 146 | -------------------------------------------------------------------------------- /examples/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-code-layout 9 | 10 | 11 | 12 | 13 | 14 | We're sorry but vue3-context-menu doesn't work properly without JavaScript enabled. Please enable it to continue. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import { install as VueMonacoEditorPlugin } from '@guolao/vue-monaco-editor' 5 | import '@imengyu/vue-scroll-rect/lib/vue-scroll-rect.css'; 6 | 7 | createApp(App) 8 | .use(router) 9 | .use(VueMonacoEditorPlugin, { 10 | paths: { 11 | // The recommended CDN config 12 | vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs' 13 | }, 14 | }) 15 | .mount('#app') 16 | -------------------------------------------------------------------------------- /examples/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router' 2 | import BasicUseage from '../views/BasicUseage.vue' 3 | import SplitLayout from '../views/SplitLayout.vue' 4 | import DataSaveAndLoad from '../views/DataSaveAndLoad.vue' 5 | import SlotsTest from '../views/SlotsTest.vue' 6 | import EmptyTest from '../views/EmptyTest.vue' 7 | 8 | const routes: Array = [ 9 | { 10 | path: '/', 11 | name: 'BasicUseage', 12 | component: BasicUseage, 13 | }, 14 | { 15 | path: '/SplitLayout', 16 | name: 'SplitLayout', 17 | component: SplitLayout, 18 | }, 19 | { 20 | path: '/DataSaveAndLoad', 21 | name: 'DataSaveAndLoad', 22 | component: DataSaveAndLoad, 23 | }, 24 | { 25 | path: '/SlotsTest', 26 | name: 'SlotsTest', 27 | component: SlotsTest, 28 | }, 29 | { 30 | path: '/EmptyTest', 31 | name: 'EmptyTest', 32 | component: EmptyTest, 33 | }, 34 | ] 35 | 36 | const router = createRouter({ 37 | history: createWebHashHistory(import.meta.env.BASE_URL), 38 | routes 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /examples/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "../**/*", "../**/*.vue"], 4 | "exclude": ["../**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | 10 | "vue-code-layout": [ "../library" ], 11 | "@/*": ["./*"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/views/DataSaveAndLoad.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Save data 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /examples/views/EmptyTest.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/views/SlotDisplay.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /examples/views/SlotsTest.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [ 10 | vue(), 11 | vueJsx(), 12 | ], 13 | base: './', 14 | resolve: { 15 | alias: { 16 | 'vue-code-layout': '../../library', 17 | '@': fileURLToPath(new URL('./', import.meta.url)) 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | import Default from './lib'; 3 | export default Default; 4 | 5 | declare module 'vue-code-layout' { 6 | } 7 | -------------------------------------------------------------------------------- /library/CodeLayoutActionItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/CodeLayoutActionsRender.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 44 | 45 | -------------------------------------------------------------------------------- /library/CodeLayoutActivityBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | $emit('activityBarAcitve', p as CodeLayoutPanelInternal)" 14 | > 15 | 16 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /library/CodeLayoutCollapseTitle.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 60 | 61 | -------------------------------------------------------------------------------- /library/CodeLayoutEmpty.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 76 | 77 | -------------------------------------------------------------------------------- /library/CodeLayoutTabItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 23 | {{ panel.title }} 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/CodeLayoutTagControl.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/Components/CodeLayoutScrollbar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 16 | 21 | 22 | 28 | 33 | 34 | 35 | 36 | 37 | 268 | 269 | -------------------------------------------------------------------------------- /library/Components/CodeLayoutVNodeStringRender.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /library/Components/OverflowCollapseList.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 210 | 211 | -------------------------------------------------------------------------------- /library/Composeable/DragDrop.ts: -------------------------------------------------------------------------------- 1 | import { computed, inject, onBeforeUnmount, onMounted, provide, ref, type Ref } from "vue"; 2 | import type { CodeLayoutConfig, CodeLayoutDragDropReferencePosition, CodeLayoutPanelInternal } from "../CodeLayout"; 3 | import HtmlUtils from '../Utils/HtmlUtils'; 4 | import { createMiniTimeOut } from "./MiniTimeout"; 5 | import { useDragEnterLeaveFilter } from "./DragEnterLeaveFilter"; 6 | 7 | export function checkDropPanelDefault( 8 | dragPanel: CodeLayoutPanelInternal, 9 | referencePanel: CodeLayoutPanelInternal, 10 | dragOverState: Ref, 11 | ) { 12 | return ( 13 | dragPanel !== referencePanel 14 | && (!dragPanel.accept || dragPanel.accept.includes(referencePanel.parentGrid)) 15 | && (!dragPanel.preDropCheck || dragPanel.preDropCheck(dragPanel, referencePanel.parentGrid, referencePanel, dragOverState.value)) 16 | ); 17 | } 18 | 19 | const dragEndEventDispatch : (() => void)[] = []; 20 | const currentDragPanels : CodeLayoutPanelInternal[] = []; 21 | 22 | function dispatchAllDragEndEvent() { 23 | for (const f of dragEndEventDispatch) { 24 | f(); 25 | } 26 | } 27 | function clearAllCurrentDragPanels() { 28 | currentDragPanels.splice(0, currentDragPanels.length); 29 | } 30 | 31 | //获取当前的拖拽面板 32 | export function getCurrentDragPanel() { 33 | return currentDragPanels[0] || null; 34 | } 35 | export function getCurrentDragExternalPanels() { 36 | return currentDragPanels.length > 0 ? currentDragPanels : null; 37 | } 38 | 39 | export const FLAG_CODE_LAYOUT = 'CodeLayout'; 40 | export const FLAG_SPLIT_LAYOUT = 'SplitLayout'; 41 | export const SYMBOL_DRAG_FLAG = Symbol('codeLayoutDragFlag'); 42 | 43 | export function usePanelDraggerRoot(key: string) { 44 | const dragPanelState = ref(false); 45 | 46 | provide(SYMBOL_DRAG_FLAG, key); 47 | provide('dragPanelState', dragPanelState); 48 | provide('setDragPanelState', () => dragPanelState.value = true); 49 | provide('resetDragPanelState', () => dragPanelState.value = false); 50 | } 51 | 52 | //拖拽开始函数封装 53 | export function usePanelDragger(config?: { 54 | onBeforeDragAddPanels?: () => CodeLayoutPanelInternal[], 55 | onDragEnd?: () => void, 56 | }) { 57 | const dragPanelState = inject('dragPanelState') as Ref; 58 | const setDragPanelState = inject('setDragPanelState') as () => void; 59 | const resetDragPanelState = inject('resetDragPanelState') as () => void; 60 | const layoutConfig = inject('codeLayoutConfig', undefined) as Ref|undefined; 61 | const cornerSize = 40; 62 | const dragSelfState = ref(false); 63 | 64 | function draggingMouseMoveHandler(e: MouseEvent) { 65 | if (layoutConfig) { 66 | if (e.x < cornerSize) { 67 | layoutConfig.value.primarySideBar = true; 68 | } 69 | if (e.x >= window.innerWidth - cornerSize) { 70 | layoutConfig.value.secondarySideBar = true; 71 | } 72 | if (e.y >= window.innerHeight - cornerSize) { 73 | layoutConfig.value.bottomPanel = true; 74 | } 75 | } 76 | } 77 | function handleDragStart(panel: CodeLayoutPanelInternal, ev: DragEvent) { 78 | if (config?.onBeforeDragAddPanels) { 79 | const panels = config.onBeforeDragAddPanels(); 80 | for (const p of panels) { 81 | if (!currentDragPanels.includes(p)) 82 | currentDragPanels.push(p); 83 | } 84 | } 85 | if (!currentDragPanels.includes(panel)) 86 | currentDragPanels.push(panel); 87 | const userCancel = layoutConfig?.value?.onStartDrag?.(currentDragPanels) ?? false; 88 | if (userCancel) { 89 | clearAllCurrentDragPanels(); 90 | return; 91 | } 92 | 93 | ev.stopPropagation(); 94 | (ev.target as HTMLElement).classList.add("dragging"); 95 | dragSelfState.value = true; 96 | dispatchAllDragEndEvent(); 97 | setDragPanelState(); 98 | document.addEventListener('dragover', draggingMouseMoveHandler); 99 | } 100 | function handleDragEnd(ev: DragEvent) { 101 | if (currentDragPanels.length > 0) { 102 | layoutConfig?.value?.onEndDrag?.(currentDragPanels); 103 | clearAllCurrentDragPanels(); 104 | } 105 | (ev.target as HTMLElement).classList.remove("dragging"); 106 | dragSelfState.value = false; 107 | resetDragPanelState(); 108 | document.removeEventListener('dragover', draggingMouseMoveHandler); 109 | config?.onDragEnd?.(); 110 | dispatchAllDragEndEvent(); 111 | } 112 | return { 113 | dragSelfState, 114 | handleDragStart, 115 | handleDragEnd, 116 | } 117 | } 118 | 119 | //拖拽进入和悬浮效果控制 120 | export function usePanelDragOverDetector( 121 | container: Ref, 122 | selfPanel: Ref|undefined, 123 | horizontal: Ref|'four'|'center', 124 | focusPanel: (dragPanel: CodeLayoutPanelInternal) => void, 125 | dragCustomHandler: (e: DragEvent) => boolean, 126 | dragoverChecking?: ((dragPanel: CodeLayoutPanelInternal) => boolean)|undefined, 127 | tag?: string, 128 | ) { 129 | 130 | const codeLayoutDragFlag = inject(SYMBOL_DRAG_FLAG, ''); 131 | const dragPanelState = inject('dragPanelState') as Ref; 132 | const resetDragPanelState = inject('resetDragPanelState') as () => void; 133 | const dragEnterState = ref(false); 134 | const dragOverState = ref(''); 135 | const focusTimer = createMiniTimeOut(600, () => { 136 | const drag = getCurrentDragPanel(); 137 | if (drag != null) 138 | focusPanel(drag); 139 | }); 140 | const delayLeaveTimer = createMiniTimeOut(200, () => { 141 | dragOverState.value = ''; 142 | }); 143 | let currentDropBaseScreenPosX = 0; 144 | let currentDropBaseScreenPosY = 0; 145 | 146 | const { 147 | onDragEnter: handleDragEnter, 148 | onDragLeave: handleDragLeave, 149 | reset: handleDragReset, 150 | } = useDragEnterLeaveFilter((e) => { 151 | focusTimer.start(); 152 | delayLeaveTimer.stop(); 153 | 154 | currentDropBaseScreenPosX = HtmlUtils.getLeft(container.value!); 155 | currentDropBaseScreenPosY = HtmlUtils.getTop(container.value!); 156 | dragOverState.value = ''; 157 | dragEnterState.value = true; 158 | 159 | handleDragOver(e); 160 | }, (e) => { 161 | dragEnterState.value = false; 162 | dragOverState.value = ''; 163 | focusTimer.stop(); 164 | delayLeaveTimer.start(); 165 | }, container, 'CodeLayoutDragDropDetector') 166 | 167 | function handleDragOver(e: DragEvent) { 168 | if (!e.dataTransfer) 169 | return; 170 | 171 | delayLeaveTimer.stop(); 172 | 173 | //检查面板,必须存在面板 174 | const panel = getCurrentDragPanel(); 175 | if (!panel) { 176 | dragOverState.value = ''; 177 | e.dataTransfer.dropEffect = 'none'; 178 | return; 179 | } 180 | //面板来源必须一致不能混用 181 | if (panel.sourceFlag != codeLayoutDragFlag) { 182 | dragOverState.value = ''; 183 | e.dataTransfer.dropEffect = 'none'; 184 | return; 185 | } 186 | 187 | // 如果是内部拖拽数据,则不应该让浏览器处理弹出窗口 188 | e.preventDefault(); 189 | 190 | //检查面板,面板并且不能是自己或者自己的父级 191 | if ( 192 | ( 193 | selfPanel && panel !== selfPanel.value 194 | && !panel.children.includes(selfPanel.value) 195 | && (!dragoverChecking || dragoverChecking(panel)) 196 | ) 197 | || dragCustomHandler(e) 198 | ) { 199 | e.stopPropagation(); 200 | e.dataTransfer.dropEffect = 'copy'; 201 | 202 | if (!container.value) 203 | return; 204 | 205 | if (horizontal === 'four') { 206 | const posX = (e.x - currentDropBaseScreenPosX); 207 | const posY = (e.y - currentDropBaseScreenPosY); 208 | 209 | if (posX < container.value.offsetWidth / 4) { 210 | dragOverState.value = 'left'; 211 | } else if (posX > container.value.offsetWidth - container.value.offsetWidth / 4) { 212 | dragOverState.value = 'right'; 213 | } else if (posY < container.value.offsetHeight / 4) { 214 | dragOverState.value = 'up'; 215 | } else if (posY > container.value.offsetHeight - container.value.offsetHeight / 4) { 216 | dragOverState.value = 'down'; 217 | } else { 218 | dragOverState.value = 'center'; 219 | } 220 | } else if (horizontal === 'center') { 221 | dragOverState.value = 'center'; 222 | } else if (horizontal) { 223 | const pos = (horizontal.value ? 224 | (e.x - currentDropBaseScreenPosX) : 225 | (e.y - currentDropBaseScreenPosY) 226 | ) ; 227 | dragOverState.value = (pos > (horizontal.value ? 228 | container.value.offsetWidth : 229 | container.value.offsetHeight) / 2 230 | ) ? 231 | (horizontal.value ? 'right' : 'down') 232 | : (horizontal.value ? 'left' : 'up'); 233 | } else { 234 | dragOverState.value = ''; 235 | e.stopPropagation(); 236 | e.dataTransfer.dropEffect = 'none'; 237 | } 238 | 239 | } 240 | } 241 | function resetDragOverState() { 242 | handleDragReset(); 243 | focusTimer.stop(); 244 | dragEnterState.value = false; 245 | dragOverState.value = ''; 246 | } 247 | function resetDragState() { 248 | handleDragReset(); 249 | resetDragPanelState(); 250 | } 251 | 252 | const dragLightBoxState = computed(() => { 253 | return dragEnterState.value && dragOverState.value !== ''; 254 | }); 255 | 256 | function globalReset() { 257 | resetDragOverState(); 258 | resetDragState(); 259 | } 260 | 261 | onMounted(() => { 262 | dragEndEventDispatch.push(globalReset) 263 | }); 264 | onBeforeUnmount(() => { 265 | const index = dragEndEventDispatch.indexOf(globalReset); 266 | if (index > -1) 267 | dragEndEventDispatch.splice(index, 1); 268 | }) 269 | 270 | return { 271 | dragPanelState, 272 | dragEnterState, 273 | dragLightBoxState, 274 | dragOverState, 275 | handleDropPreCheck(e: DragEvent) { 276 | return dragCustomHandler(e); 277 | }, 278 | handleDragOver, 279 | handleDragEnter, 280 | handleDragLeave, 281 | resetDragOverState, 282 | resetDragState, 283 | } 284 | } -------------------------------------------------------------------------------- /library/Composeable/DragEnterLeaveFilter.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | import { SimpleDelay } from "../Utils/Timer/Timer"; 3 | 4 | const globalDragStateHolder = new Map(); 5 | 6 | interface DragStateHolder { 7 | element: Ref, 8 | forceLeave: (e: DragEvent) => void, 9 | } 10 | 11 | /** 12 | * 筛选反人类的 dragenter、dragleave事件,以保证在进入子节点后不会触发dragleave事件 13 | * @param ref 14 | * @param onEnter 15 | * @param onLeave 16 | * @returns 17 | */ 18 | export function useDragEnterLeaveFilter( 19 | onEnter: (e: DragEvent) => void, 20 | onLeave: (e: DragEvent) => void, 21 | element: Ref, 22 | storageHolderTag?: string, 23 | ) { 24 | 25 | const delayLeaveTimer = new SimpleDelay(undefined, () => { 26 | lockLeaveState--; 27 | if (lockLeaveState > 0) 28 | delayLeaveTimer.start(); 29 | }, 0); 30 | 31 | let lockLeaveState = 0; 32 | let enterState = false; 33 | 34 | function reset() { 35 | delayLeaveTimer.stop(); 36 | lockLeaveState = 0; 37 | enterState = false; 38 | removeFromHolder(); 39 | } 40 | 41 | const holder : DragStateHolder = { 42 | element, 43 | forceLeave: (e: DragEvent) => { 44 | onDragLeave(e); 45 | }, 46 | } 47 | 48 | function addToHolder(e: DragEvent) { 49 | if (storageHolderTag) { 50 | const oldHolder = globalDragStateHolder.get(storageHolderTag); 51 | if (oldHolder && oldHolder.element.value !== element.value) 52 | oldHolder.forceLeave(e); 53 | globalDragStateHolder.set(storageHolderTag, holder); 54 | } 55 | } 56 | function removeFromHolder() { 57 | if (storageHolderTag) { 58 | const oldHolder = globalDragStateHolder.get(storageHolderTag); 59 | if (oldHolder && oldHolder === holder) 60 | globalDragStateHolder.delete(storageHolderTag); 61 | } 62 | } 63 | 64 | //在进入子节点时,先会触发一次子节点的dragenter, 65 | //再触发一次之前节点dragleave 66 | //所以这里会用延时筛掉这次无效的dragleave 67 | 68 | function onDragEnter(e: DragEvent) { 69 | if (enterState) { 70 | lockLeaveState++; 71 | delayLeaveTimer.start(); 72 | return; 73 | } 74 | enterState = true; 75 | onEnter(e); 76 | addToHolder(e); 77 | } 78 | 79 | function onDragLeave(e: DragEvent) { 80 | if (lockLeaveState > 0) 81 | return; 82 | if (!enterState) 83 | return; 84 | enterState = false; 85 | 86 | onLeave(e); 87 | removeFromHolder(); 88 | } 89 | 90 | return { 91 | onDragEnter, 92 | onDragLeave, 93 | reset, 94 | } 95 | } -------------------------------------------------------------------------------- /library/Composeable/KeyBoardController.ts: -------------------------------------------------------------------------------- 1 | import { inject, onBeforeUnmount, onMounted, provide } from "vue"; 2 | 3 | type ShortcutCallback = () => void; 4 | export interface ShortcutConfig { 5 | id?: number; 6 | /** 7 | * Main Key 8 | */ 9 | key: string; 10 | /** 11 | * Is trigger in keydown event, otherwise in keyup event. 12 | * @default false 13 | */ 14 | keyDown?: boolean; 15 | /** 16 | * Need Ctrl key be pressed 17 | * @default false 18 | */ 19 | keyControl?: boolean; 20 | /** 21 | * Need CAltrl key be pressed 22 | * @default false 23 | */ 24 | keyAlt?: boolean; 25 | /** 26 | * Need Shift key be pressed 27 | * @default false 28 | */ 29 | keyShift?: boolean; 30 | /** 31 | * Handler Callback 32 | */ 33 | callback: ShortcutCallback; 34 | } 35 | 36 | export const KeyBorardControllerSymbol = Symbol('KeyBoardController'); 37 | 38 | export interface KeyBoardController { 39 | onKeyDown: (e: KeyboardEvent) => void; 40 | onKeyUp: (e: KeyboardEvent) => void; 41 | isKeyAltDown: () => boolean; 42 | isKeyShiftDown: () => boolean; 43 | isKeyControlDown: () => boolean; 44 | registerShortcut(config: ShortcutConfig): number; 45 | unregisterShortcut(id: number): void; 46 | } 47 | 48 | export function useKeyBoardControllerTop() : KeyBoardController { 49 | 50 | let keyControlDown = false; 51 | let keyShiftDown = false; 52 | let keyAltDown = false; 53 | 54 | let shortcutsLastId = 0; 55 | const shortcuts = new Map(); 56 | 57 | function onKeyDown(e : KeyboardEvent) { 58 | switch(e.code) { 59 | case 'ShiftRight': 60 | case 'ShiftLeft': 61 | keyShiftDown = true; 62 | break; 63 | case 'ControlRight': 64 | case 'ControlLeft': 65 | keyControlDown = true; 66 | break; 67 | case 'AltRight': 68 | case 'AltLeft': 69 | keyAltDown = true; 70 | break; 71 | default: { 72 | const shortcut = shortcuts.get(e.code); 73 | if ( 74 | shortcut && !shortcut.keyDown 75 | && ( 76 | (!shortcut.keyAlt || keyAltDown) 77 | || (!shortcut.keyShift || keyShiftDown) 78 | || (!shortcut.keyControl || keyControlDown) 79 | ) 80 | ) 81 | shortcut.callback(); 82 | break; 83 | } 84 | } 85 | } 86 | function onKeyUp(e : KeyboardEvent) { 87 | switch(e.code) { 88 | case 'ShiftRight': 89 | case 'ShiftLeft': 90 | keyShiftDown = false; 91 | break; 92 | case 'ControlRight': 93 | case 'ControlLeft': 94 | keyControlDown = false; 95 | break; 96 | case 'AltRight': 97 | case 'AltLeft': 98 | keyAltDown = false; 99 | break; 100 | default: { 101 | const shortcut = shortcuts.get(e.code); 102 | if ( 103 | shortcut && shortcut.keyDown 104 | && ( 105 | (!shortcut.keyAlt || keyAltDown) 106 | || (!shortcut.keyShift || keyShiftDown) 107 | || (!shortcut.keyControl || keyControlDown) 108 | ) 109 | ) 110 | shortcut.callback(); 111 | break; 112 | } 113 | } 114 | 115 | } 116 | 117 | onMounted(() => { 118 | document.addEventListener('keydown', onKeyDown); 119 | document.addEventListener('keyup', onKeyUp); 120 | }); 121 | onBeforeUnmount(() => { 122 | document.removeEventListener('keydown', onKeyDown); 123 | document.removeEventListener('keyup', onKeyUp); 124 | }); 125 | 126 | const topCotext = inject(KeyBorardControllerSymbol, undefined); 127 | 128 | const context = { 129 | isKeyAltDown: () => keyAltDown, 130 | isKeyShiftDown: () => keyShiftDown, 131 | isKeyControlDown: () => keyControlDown, 132 | registerShortcut(config: ShortcutConfig) { 133 | const id = ++ shortcutsLastId; 134 | config.id = id; 135 | shortcuts.set(config.key, config); 136 | return id; 137 | }, 138 | unregisterShortcut(id: number) { 139 | for (const element of shortcuts) { 140 | if (element[1].id === id) { 141 | shortcuts.delete(element[0]); 142 | break; 143 | } 144 | } 145 | }, 146 | } 147 | 148 | provide(KeyBorardControllerSymbol, topCotext ?? context); 149 | 150 | return { 151 | onKeyDown, 152 | onKeyUp, 153 | ...context, 154 | } 155 | } 156 | 157 | export function useKeyBoardController() : KeyBoardController { 158 | return inject(KeyBorardControllerSymbol, undefined) as unknown as KeyBoardController; 159 | } -------------------------------------------------------------------------------- /library/Composeable/LateClass.ts: -------------------------------------------------------------------------------- 1 | import { markRaw } from "vue"; 2 | 3 | export type LateClassCallback = (...args: any[]) => any; 4 | 5 | /** 6 | * 用于将对象发生上的调用进行延迟发送 7 | */ 8 | export class LateClass { 9 | private _lateNotifyCallbacks = markRaw(new Map()); 10 | private _lateNotifyItems : { 11 | name: string, 12 | args: any[], 13 | resolve: (d: any) => void, 14 | reject: (e: any) => void, 15 | }[] = markRaw([]); 16 | 17 | listenLateAction(name: string, cb: LateClassCallback) { 18 | this._lateNotifyCallbacks.set(name, cb); 19 | this.applyLateActions(name); 20 | } 21 | unlistenLateAction(name: string) { 22 | this._lateNotifyCallbacks.delete(name); 23 | } 24 | unlistenAllLateAction() { 25 | this._lateNotifyCallbacks.clear(); 26 | } 27 | applyLateActions(name: string) { 28 | const cb = this._lateNotifyCallbacks.get(name); 29 | if (cb) { 30 | for(let i = this._lateNotifyItems.length - 1; i >= 0; i--) { 31 | const iterator = this._lateNotifyItems[i]; 32 | if (iterator.name === name) { 33 | try { 34 | iterator.resolve(cb(...iterator.args)); 35 | } catch (e) { 36 | iterator.reject(e); 37 | } 38 | this._lateNotifyItems.splice(i, 1); 39 | } 40 | } 41 | } 42 | } 43 | 44 | pushLateAction(name: string, ...args: any[]) : Promise { 45 | const cb = this._lateNotifyCallbacks.get(name); 46 | if (cb) { 47 | try { 48 | return Promise.resolve(cb(...args)); 49 | } catch (e) { 50 | return Promise.reject(e); 51 | } 52 | } 53 | else { 54 | return new Promise((resolve, reject) => { 55 | const instance = { name, args, resolve, reject }; 56 | this._lateNotifyItems.push(instance); 57 | }); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /library/Composeable/MiniTimeout.ts: -------------------------------------------------------------------------------- 1 | export function createMiniTimeOut(interval: number, cb: () => void) { 2 | let timeOut = 0; 3 | 4 | return { 5 | start() { 6 | if (timeOut > 0) 7 | clearTimeout(timeOut); 8 | timeOut = setTimeout(() => { 9 | timeOut = 0; 10 | cb(); 11 | }, interval) as any; 12 | }, 13 | stop() { 14 | if (timeOut > 0) { 15 | clearTimeout(timeOut); 16 | timeOut = 0; 17 | } 18 | }, 19 | } 20 | } 21 | export function createMiniTimer(interval: number, cb: () => void) { 22 | let timerId = 0; 23 | 24 | return { 25 | start() { 26 | if (timerId > 0) 27 | clearInterval(timerId); 28 | timerId = setInterval(() => { 29 | timerId = 0; 30 | cb(); 31 | }, interval) as any; 32 | }, 33 | stop() { 34 | if (timerId > 0) { 35 | clearInterval(timerId); 36 | timerId = 0; 37 | } 38 | }, 39 | } 40 | } -------------------------------------------------------------------------------- /library/Composeable/MouseHandler.ts: -------------------------------------------------------------------------------- 1 | import { Vector2 } from "./Vector2"; 2 | 3 | export type IMouseEventHandlerEntry = (e: MouseEvent, param?: T) => boolean; 4 | 5 | /** 6 | * 创建鼠标按下移动处理器 7 | * @param options 处理器 8 | * @returns 返回入口,入口需要在 mousedown 事件中调用 9 | */ 10 | export function createMouseDragHandler(options: { 11 | /** 12 | * 按下事件 13 | * @param e 14 | * @returns 15 | */ 16 | onDown: (e: MouseEvent, param?: T) => boolean; 17 | /** 18 | * 按下并且移动事件 19 | * @param e 20 | * @returns 21 | */ 22 | onMove: (downPos: Vector2, movedPos: Vector2, e: MouseEvent, param?: T) => void; 23 | /** 24 | * 释放事件 25 | * @param e 26 | * @returns 27 | */ 28 | onUp: (e: MouseEvent, param?: T) => void; 29 | }) : IMouseEventHandlerEntry { 30 | 31 | const { onDown, onMove, onUp } = options; 32 | const mouseDownPosition = new Vector2(); 33 | const movedPosition = new Vector2(); 34 | let currentDownParam : any = undefined; 35 | 36 | function mousemove(e: MouseEvent) { 37 | e.stopPropagation(); 38 | movedPosition.set(e.x, e.y); 39 | movedPosition.substract(mouseDownPosition); 40 | onMove(mouseDownPosition, movedPosition, e, currentDownParam); 41 | } 42 | function mouseup(e: MouseEvent) { 43 | onUp(e, currentDownParam); 44 | mouseDownPosition.set(0, 0); 45 | document.removeEventListener('mousemove', mousemove); 46 | document.removeEventListener('mouseup', mouseup); 47 | } 48 | 49 | //MouseDown handler 50 | return (e: MouseEvent, param?: any) => { 51 | if (onDown(e, param)) { 52 | currentDownParam = param; 53 | mouseDownPosition.set(e.x, e.y); 54 | document.addEventListener('mousemove', mousemove); 55 | document.addEventListener('mouseup', mouseup); 56 | e.stopPropagation(); 57 | return true; 58 | } 59 | return false; 60 | }; 61 | } 62 | 63 | /** 64 | * 创建鼠标按下并且放开处理器 65 | * @param options 66 | * @returns 67 | */ 68 | export function createMouseDownAndUpHandler(options: { 69 | /** 70 | * 按下事件 71 | * @param e 72 | * @returns 73 | */ 74 | onDown: (e: MouseEvent) => boolean; 75 | /** 76 | * 释放事件 77 | * @param e 78 | * @returns 79 | */ 80 | onUp: (e: MouseEvent) => void; 81 | }) : IMouseEventHandlerEntry { 82 | const { onDown, onUp } = options; 83 | 84 | function mouseup(e: MouseEvent) { 85 | onUp(e); 86 | document.removeEventListener('mouseup', mouseup); 87 | } 88 | //MouseDown handler 89 | return (e: MouseEvent) => { 90 | if (onDown(e)) { 91 | document.addEventListener('mouseup', mouseup); 92 | e.preventDefault(); 93 | e.stopPropagation(); 94 | return true; 95 | } 96 | return false; 97 | }; 98 | } -------------------------------------------------------------------------------- /library/Composeable/ResizeChecker.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | import { createMiniTimer } from "./MiniTimeout"; 3 | 4 | const globalResizeCheckerList = [] as (() => void)[]; 5 | const globalResizeCheckerTimer = createMiniTimer(100, () => { 6 | for (const cb of globalResizeCheckerList) 7 | cb(); 8 | }); 9 | 10 | export function useResizeChecker( 11 | ref: Ref, 12 | onWidthChange?: (newWidth: number) => void, 13 | onHeightChange?: (newHeight: number) => void, 14 | onSizeChange?: (newWidth: number, newHeight: number) => void, 15 | ) { 16 | 17 | let sizeChangeLastWidth = 0; 18 | let sizeChangeLastHeight = 0; 19 | 20 | function checkerCallback() { 21 | if (ref.value) { 22 | if (onWidthChange && sizeChangeLastWidth !== ref.value.offsetWidth) 23 | onWidthChange(ref.value.offsetWidth); 24 | if (onHeightChange && sizeChangeLastHeight !== ref.value.offsetHeight) 25 | onHeightChange(ref.value.offsetHeight); 26 | if (onSizeChange && (sizeChangeLastWidth !== ref.value.offsetWidth || sizeChangeLastHeight !== ref.value.offsetHeight)) 27 | onSizeChange(ref.value.offsetWidth, ref.value.offsetHeight); 28 | sizeChangeLastWidth = ref.value.offsetWidth; 29 | sizeChangeLastHeight = ref.value.offsetHeight; 30 | } 31 | } 32 | 33 | return { 34 | startResizeChecker() { 35 | globalResizeCheckerTimer.start(); 36 | globalResizeCheckerList.push(checkerCallback); 37 | }, 38 | stopResizeChecker() { 39 | const index = globalResizeCheckerList.indexOf(checkerCallback); 40 | if (index >= 0) 41 | globalResizeCheckerList.splice(index, 1); 42 | if (globalResizeCheckerList.length === 0) 43 | globalResizeCheckerTimer.stop(); 44 | }, 45 | } 46 | } -------------------------------------------------------------------------------- /library/Composeable/SimpleTooltipDelayLock.ts: -------------------------------------------------------------------------------- 1 | 2 | let showDelayTimer = 0; 3 | let hideDelayTimer = 0; 4 | let showState = false; 5 | 6 | export function useSimpleTooltipDelayLock() { 7 | 8 | let leaveState = false; 9 | 10 | function onEnter(cb: () => void) { 11 | leaveState = false; 12 | if (hideDelayTimer > 0) { 13 | clearTimeout(hideDelayTimer); 14 | hideDelayTimer = 0; 15 | } 16 | if (showState) { 17 | cb(); 18 | return; 19 | } 20 | if (showDelayTimer > 0) 21 | clearTimeout(showDelayTimer); 22 | showDelayTimer = setTimeout(() => { 23 | if (!leaveState) { 24 | cb(); 25 | showState = true; 26 | } 27 | }, 650) as any; 28 | } 29 | function onLeave() { 30 | if (hideDelayTimer > 0) 31 | clearTimeout(hideDelayTimer); 32 | if (showDelayTimer > 0) { 33 | clearTimeout(showDelayTimer); 34 | showDelayTimer = 0; 35 | } 36 | hideDelayTimer = setTimeout(() => { 37 | showState = false; 38 | leaveState = true; 39 | }, 250) as any; 40 | } 41 | 42 | return { 43 | onEnter, 44 | onLeave, 45 | } 46 | } -------------------------------------------------------------------------------- /library/Composeable/Vector2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 2D Vector 3 | */ 4 | export class Vector2 { 5 | 6 | /** 7 | * X axis 8 | */ 9 | public x = 0; 10 | /** 11 | * Y axis 12 | */ 13 | public y = 0; 14 | 15 | public constructor(x: number|Vector2 = 0, y = 0) { 16 | if (typeof x === 'object') { 17 | this.y = x.y; 18 | this.x = x.x; 19 | } else { 20 | this.y = y; 21 | this.x = x; 22 | } 23 | } 24 | 25 | /** 26 | * Set new vector values 27 | * @param x X axis or other Vector 28 | * @param y Y axis or none 29 | */ 30 | public set(x : number|Vector2, y = 0) : Vector2 { 31 | if(typeof x === "number") { 32 | this.y = typeof y === 'number' ? y : x; 33 | this.x = x; 34 | } else { 35 | this.y = x.y; 36 | this.x = x.x; 37 | } 38 | return this; 39 | } 40 | /** 41 | * Clone a new item 42 | * @returns 43 | */ 44 | public clone() : Vector2 { 45 | return new Vector2(this.x, this.y); 46 | } 47 | /** 48 | * 将当前二维向量加指定数字 【Adds the specified number to the current 2D vector】 49 | * @param v 50 | * @returns 51 | */ 52 | public add(v : number|Vector2) : Vector2 { 53 | if(typeof v === "number") { 54 | this.x += v; 55 | this.y += v; 56 | } 57 | else if(typeof v === "object") { 58 | this.x += v.x; 59 | this.y += v.y; 60 | } 61 | return this; 62 | } 63 | /** 64 | * 将当前二维向量减以指定数字【Subtract the current 2D vector to specify a number】 65 | * @param v 66 | * @returns 67 | */ 68 | public substract(v : number|Vector2) : Vector2 { 69 | if(typeof v === "number") { 70 | this.x -= v; 71 | this.y -= v; 72 | } 73 | else if(typeof v === "object") { 74 | this.x -= v.x; 75 | this.y -= v.y; 76 | } 77 | return this; 78 | } 79 | /** 80 | * 将当前二维向量乘以指定数字 【Multiplies the current 2D vector by the specified number】 81 | * @param v 82 | * @returns 83 | */ 84 | public multiply(v : number) : Vector2 { 85 | this.x *= v; 86 | this.y *= v; 87 | return this; 88 | } 89 | /** 90 | * 将当前二维向量除以指定数字 【Divides the current 2D vector by the specified number】 91 | * @param v 92 | * @returns 93 | */ 94 | public divide(v : number) : Vector2 { 95 | this.x /= v; 96 | this.y /= v; 97 | return this; 98 | } 99 | /** 100 | * Test two vector2's value is equal 101 | */ 102 | public equal(another : Vector2) : boolean { 103 | return this.x === another.x && this.y === another.y; 104 | } 105 | /** 106 | * 转为字符串 107 | * @returns 108 | */ 109 | public toString() : string { 110 | return `{x=${this.x},y=${this.y}}`; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /library/Icons/IconActionClose.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/Icons/IconActionMax.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /library/Icons/IconArrow.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/IconCheck.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/Icons/IconClose.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/IconDot.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/IconEyeClosedCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/Icons/IconEyeCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /library/Icons/IconGithub.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/Icons/IconMenu.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/IconMore.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/IconResetDefault.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /library/Icons/LayoutActivitybarLeftCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutActivitybarRightCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutCenteredCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutMenubarCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelCenterCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelJustifyCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelLeftCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelOffCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutPanelRightCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutSidebarLeftCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutSidebarLeftOffCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutSidebarRightCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutSidebarRightOffCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Icons/LayoutStatusbarCodicon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/Language/en.ts: -------------------------------------------------------------------------------- 1 | export const LangString = { 2 | customizeLayout: 'Customize Layout', 3 | togglePanel: 'Toggle Panel', 4 | togglePrimarySideBar: 'Toggle Primary Side Bar', 5 | toggleSecondarySideBar: 'Toggle Secondary Side Bar', 6 | primarySideBar: 'Primary Side Bar', 7 | secondarySideBar: 'Secondary Side Bar', 8 | panel: 'Panel', 9 | alignPanel: 'Align Panel', 10 | menuBar: 'Menu Bar', 11 | activityBar: 'Activity Bar', 12 | statusBar: 'Status Bar', 13 | left: 'Left', 14 | right: 'Right', 15 | center: 'Center', 16 | justify: 'Justify', 17 | primarySideBarPosition: 'Primary Side Bar Position', 18 | panelAlignment: 'Panel Alignment', 19 | panelPosition: 'Panel Position', 20 | visibility: 'Visibility', 21 | restoreDefault: 'Restore Default', 22 | close: 'Close', 23 | hide: 'Hide', 24 | hidden: 'Hidden', 25 | show: 'Show', 26 | side: 'Side', 27 | top: 'Top', 28 | leftSide: 'Left', 29 | rightSide: 'Right', 30 | bottom: 'Bottom', 31 | badge: 'Badge', 32 | additionalViews: 'Additional Views', 33 | activityBarPosition: 'Activity Bar Position', 34 | secondaryActivityBarPosition: 'Secondary Activity Bar Position', 35 | movePrimarySideBarRight: 'Move Primary Side Bar Right', 36 | movePrimarySideBarLeft: 'Move Primary Side Bar Left', 37 | moveSecondarySideBarRight: 'Move Secondary Side Bar Right', 38 | moveSecondarySideBarLeft: 'Move Secondary Side Bar Left', 39 | hideSecondarySideBar: 'Hide Secondary Side Bar', 40 | hidePanel: 'Hide Panel', 41 | maximizePanel: 'Maximize Panel', 42 | restorePanelSize: 'Restore Panel Size', 43 | }; -------------------------------------------------------------------------------- /library/Language/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, inject } from 'vue'; 2 | import { LangString } from './en'; 3 | import { LangString as LangStringZh } from './zh'; 4 | import type { CodeLayoutLangConfig } from '../CodeLayout'; 5 | 6 | export type CodeLayoutLangDefine = typeof LangString; 7 | 8 | const internalLangs = { 9 | 'en': LangString, 10 | 'zh': LangStringZh, 11 | } as Record; 12 | 13 | /** 14 | * Add custom lang to CodeLayout language, should be called before CodeLayout component create. 15 | * @param lang Lang name 16 | * @param strings Content 17 | */ 18 | export function addCodeLayoutLang(lang: string, strings: CodeLayoutLangDefine) { 19 | internalLangs[lang] = strings; 20 | } 21 | 22 | /** 23 | * CodeLayout language string wrapper 24 | * @returns 25 | */ 26 | export function useCodeLayoutLang() { 27 | 28 | const langConfig = inject('codeLayoutLangConfig') as CodeLayoutLangConfig; 29 | const langStrings = computed(() => { 30 | return { 31 | ...internalLangs[langConfig.lang], 32 | ...langConfig.stringsOverride, 33 | }; 34 | }); 35 | 36 | function t(key: keyof CodeLayoutLangDefine) { 37 | return langStrings.value[key] || key; 38 | } 39 | 40 | return { 41 | t, 42 | } 43 | } -------------------------------------------------------------------------------- /library/Language/zh.ts: -------------------------------------------------------------------------------- 1 | export const LangString = { 2 | customizeLayout: '自定义布局', 3 | togglePanel: '切换面板', 4 | togglePrimarySideBar: '切换主侧栏', 5 | toggleSecondarySideBar: '切换辅助侧栏', 6 | primarySideBar: '主侧栏', 7 | secondarySideBar: '辅助侧栏', 8 | panel: '面板', 9 | alignPanel: '对齐面板', 10 | menuBar: '菜单栏', 11 | activityBar: '活动栏', 12 | statusBar: '状态栏', 13 | left: '左对齐', 14 | right: '右对齐', 15 | center: '居中', 16 | justify: '两端对齐', 17 | primarySideBarPosition: '主侧栏位置', 18 | panelAlignment: '对齐面板', 19 | panelPosition: '面板位置', 20 | visibility: '可见性', 21 | restoreDefault: '还原默认值', 22 | close: '关闭', 23 | hide: '隐藏', 24 | hidden: '隐藏', 25 | show: '显示', 26 | side: '侧边', 27 | top: '顶部', 28 | leftSide: '左侧', 29 | rightSide: '右侧', 30 | bottom: '底部', 31 | badge: '徽章', 32 | additionalViews: '额外视图', 33 | activityBarPosition: '活动栏位置', 34 | secondaryActivityBarPosition: '第二活动栏位置', 35 | movePrimarySideBarRight: '向右移动主侧栏', 36 | movePrimarySideBarLeft: '向左移动主侧栏', 37 | moveSecondarySideBarRight: '向右移动辅助侧栏', 38 | moveSecondarySideBarLeft: '向左移动辅助侧栏', 39 | hideSecondarySideBar: '隐藏辅助侧栏', 40 | hidePanel: '隐藏面板', 41 | maximizePanel: '最大化面板大小', 42 | restorePanelSize: '恢复面板大小', 43 | }; -------------------------------------------------------------------------------- /library/Scss/Base.scss: -------------------------------------------------------------------------------- 1 | 2 | :root { 3 | --code-layout-color-background: #1e1e1e; 4 | --code-layout-color-background-second: #252526; 5 | --code-layout-color-background-light: #333333; 6 | --code-layout-color-background-highlight: #04395e; 7 | --code-layout-color-background-hover: #363737; 8 | --code-layout-color-background-hover-light: #464646; 9 | --code-layout-color-background-mask-light: rgba(255,255,255,0.2); 10 | --code-layout-color-highlight: #0078d4; 11 | --code-layout-color-text: #ccc; 12 | --code-layout-color-text-light: #fff; 13 | --code-layout-color-text-highlight: #2f94f1; 14 | --code-layout-color-text-gray: #818181; 15 | --code-layout-color-text-disabled: #727272; 16 | --code-layout-color-border: #474747; 17 | --code-layout-color-border-light: #cccccc; 18 | --code-layout-color-border-background: #2a2a2a; 19 | --code-layout-color-border-white: #fff; 20 | --code-layout-color-shadow: rgba(0,0,0,0.15); 21 | --code-layout-color-scrollbar-thumb: rgba(204, 204, 204, 0.4); 22 | --code-layout-color-scrollbar-thumb-light: rgba(204, 204, 204, 0.6); 23 | --code-layout-border-size: 1px; 24 | --code-layout-border-size-larger: 2px; 25 | --code-layout-border-size-dragger: 4px; 26 | --code-layout-sash-size: 8px; 27 | --code-layout-border-radius-small: 5px; 28 | --code-layout-border-radius-large: 5px; 29 | --code-layout-header-height: 22px; 30 | --code-layout-titlebar-background: #3c3c3c; 31 | --code-layout-titlebar-height: 35px; 32 | --code-layout-status-height: 20px; 33 | --code-layout-font-size: 13px; 34 | --code-layout-font-size-small: 11px; 35 | } 36 | 37 | .code-layout-root { 38 | display: flex; 39 | width: 100%; 40 | height: 100%; 41 | flex-direction: column; 42 | align-items: stretch; 43 | color: var(--code-layout-color-text); 44 | background-color: var(--code-layout-color-background); 45 | font-size: var(--code-layout-font-size); 46 | overflow: hidden; 47 | 48 | > .code-layout-title-bar { 49 | display: flex; 50 | flex-direction: row; 51 | justify-content: space-between; 52 | align-items: stretch; 53 | height: var(--code-layout-titlebar-height); 54 | background-color: var(--code-layout-titlebar-background); 55 | 56 | > div { 57 | display: flex; 58 | flex-direction: row; 59 | justify-content: space-between; 60 | align-items: center; 61 | } 62 | } 63 | 64 | .code-layout-inner-0 { 65 | position: relative; 66 | width: 100%; 67 | 68 | > .code-layout-split-base { 69 | position: absolute; 70 | left: 0; 71 | right: 0; 72 | top: 0; 73 | bottom: 0; 74 | } 75 | } 76 | > .code-layout-activity { 77 | position: relative; 78 | display: flex; 79 | flex-direction: row; 80 | align-items: stretch; 81 | flex: 1; 82 | height: calc(100% - var(--code-layout-titlebar-height) - var(--code-layout-status-height)); 83 | 84 | $activity-bar-width: 45px; 85 | 86 | .code-layout-activity-bar { 87 | position: relative; 88 | display: flex; 89 | flex-direction: column; 90 | justify-content: space-between; 91 | align-items: center; 92 | background-color: var(--code-layout-color-background-light); 93 | width: $activity-bar-width; 94 | overflow: hidden; 95 | 96 | .OverflowItem { 97 | width: $activity-bar-width; 98 | height: $activity-bar-width; 99 | } 100 | 101 | > .top { 102 | position: relative; 103 | flex-grow: 1; 104 | flex-shrink: 1; 105 | max-height: 90%; 106 | } 107 | > .bottom { 108 | position: relative; 109 | flex-grow: 0; 110 | flex-shrink: 0; 111 | } 112 | 113 | .mx-menu-bar.mini { 114 | justify-content: center; 115 | } 116 | 117 | .activity-bar-items { 118 | display: flex; 119 | flex-direction: column; 120 | justify-content: flex-start; 121 | align-items: center; 122 | flex: 1 1 100%; 123 | } 124 | 125 | .item { 126 | position: relative; 127 | display: flex; 128 | flex-direction: column; 129 | justify-content: center; 130 | align-items: center; 131 | width: $activity-bar-width; 132 | height: $activity-bar-width; 133 | color: var(--code-layout-color-text-gray); 134 | cursor: pointer; 135 | 136 | span { 137 | display: block; 138 | pointer-events: none; 139 | max-width: 100%; 140 | overflow: hidden; 141 | text-overflow: ellipsis; 142 | } 143 | svg { 144 | pointer-events: none; 145 | fill: var(--code-layout-color-text-gray); 146 | } 147 | 148 | .icon { 149 | position: relative; 150 | max-width: 100%; 151 | } 152 | .badge { 153 | position: absolute; 154 | right: 5px; 155 | bottom: 2px; 156 | display: inline-block; 157 | padding: 3px; 158 | border-radius: var(--code-layout-font-size-small); 159 | font-size: var(--code-layout-font-size-small); 160 | min-width: var(--code-layout-font-size-small); 161 | line-height: var(--code-layout-font-size-small); 162 | text-align: center; 163 | background-color: var(--code-layout-color-highlight); 164 | color: var(--code-layout-color-text-light); 165 | transform: scale(0.9); 166 | } 167 | 168 | &.active, &:hover { 169 | color: var(--code-layout-color-text-light); 170 | 171 | svg { 172 | fill: var(--code-layout-color-text-light); 173 | } 174 | } 175 | 176 | &.active { 177 | 178 | &::after { 179 | content: ''; 180 | position: absolute; 181 | display: block; 182 | width: 2px; 183 | top: 0; 184 | bottom: 0; 185 | left: 0; 186 | background-color: var(--code-layout-color-text-light); 187 | } 188 | } 189 | 190 | &.drag-enter * { 191 | pointer-events: none; 192 | } 193 | &.drag-over-left, 194 | &.drag-over-right, 195 | &.drag-over-up, 196 | &.drag-over-down { 197 | &::before { 198 | position: absolute; 199 | content: ''; 200 | left: 0; 201 | right: 0; 202 | height: var(--code-layout-border-size-larger); 203 | background-color: var(--code-layout-color-border-light); 204 | } 205 | } 206 | &.drag-over-left, &.drag-over-up { 207 | &::before { 208 | top: calc(var(--code-layout-border-size-larger) / 2 * -1); 209 | } 210 | } 211 | &.drag-over-right, &.drag-over-down { 212 | &::before { 213 | bottom: calc(var(--code-layout-border-size-larger) / 2 * -1); 214 | } 215 | } 216 | } 217 | 218 | &.right .item.active { 219 | &::after { 220 | left: unset; 221 | right: 2px; 222 | } 223 | } 224 | } 225 | } 226 | > .code-layout-status { 227 | display: flex; 228 | flex-direction: row; 229 | justify-content: space-between; 230 | align-items: stretch; 231 | flex-grow: 0; 232 | height: var(--code-layout-status-height); 233 | background-color: var(--code-layout-color-highlight); 234 | 235 | > .left { 236 | position: relative; 237 | flex-shrink: 0; 238 | } 239 | > .right { 240 | position: relative; 241 | flex-shrink: 0; 242 | } 243 | } 244 | } -------------------------------------------------------------------------------- /library/Scss/Menu.scss: -------------------------------------------------------------------------------- 1 | .mx-menu-bar.code-layout { 2 | --mx-menu-backgroud: transparent; 3 | --mx-menu-text: var(--code-layout-color-text); 4 | --mx-menu-hover-backgroud: var(--code-layout-color-background-highlight); 5 | --mx-menu-hover-text: var(--code-layout-color-text); 6 | --mx-menu-active-backgroud: var(--code-layout-color-background-highlight); 7 | --mx-menu-active-text: var(--code-layout-color-text); 8 | --mx-menu-disabled-text: var(--code-layout-color-text-disabled); 9 | } 10 | .mx-context-menu.code-layout { 11 | 12 | & { 13 | 14 | //Backgroud 15 | --mx-menu-backgroud: var(--code-layout-color-background-second); 16 | --mx-menu-hover-backgroud: var(--code-layout-color-background-highlight); 17 | --mx-menu-active-backgroud: var(--code-layout-color-background-highlight); 18 | --mx-menu-open-backgroud: var(--code-layout-color-background-highlight); 19 | --mx-menu-open-hover-backgroud: var(--code-layout-color-background-highlight); 20 | --mx-menu-divider: var(--code-layout-color-border); 21 | 22 | //Text 23 | --mx-menu-text: var(--code-layout-color-text); 24 | --mx-menu-hover-text: var(--code-layout-color-text); 25 | --mx-menu-active-text: var(--code-layout-color-text); 26 | --mx-menu-open-text: var(--code-layout-color-text); 27 | --mx-menu-open-hover-text: var(--code-layout-color-text); 28 | --mx-menu-disabled-text: var(--code-layout-color-text-disabled); 29 | 30 | //Shadow 31 | --mx-menu-shadow-color: rgba(0, 0, 0, 0.1); 32 | --mx-menu-backgroud-radius: var(--code-layout-border-radius-small); 33 | 34 | //Shortcut badge 35 | --mx-menu-shortcut-backgroud: transparent; 36 | --mx-menu-shortcut-backgroud-hover:transparent; 37 | --mx-menu-shortcut-backgroud-active:transparent; 38 | --mx-menu-shortcut-backgroud-open:transparent; 39 | --mx-menu-shortcut-backgroud-disabled:transparent; 40 | --mx-menu-shortcut-text: var(--code-layout-color-text-gray); 41 | --mx-menu-shortcut-text-hover: var(--code-layout-color-text-light); 42 | --mx-menu-shortcut-text-active: var(--code-layout-color-text-light); 43 | --mx-menu-shortcut-text-open: var(--code-layout-color-text-light); 44 | --mx-menu-shortcut-text-disabled: var(--code-layout-color-text-disabled); 45 | 46 | //Focus border color 47 | --mx-menu-focus-color: transparent; 48 | --mx-menu-border-color: var(--code-layout-color-border); 49 | } 50 | 51 | padding: 8px 0; 52 | box-shadow: 0px 5px 7px 1px var(--mx-menu-shadow-color); 53 | border: 1px solid var(--mx-menu-border-color); 54 | 55 | .mx-context-menu-item { 56 | border-radius: 5px; 57 | margin: 0 6px; 58 | padding: 3px 6px; 59 | 60 | .label { 61 | font-size: var(--code-layout-font-size); 62 | } 63 | 64 | //Focus by keyboard 65 | &.keyboard-focus { 66 | background-color: var(--mx-menu-active-backgroud); 67 | color: var(--mx-menu-active-text); 68 | outline: none; 69 | 70 | .mx-right-arrow, .mx-checked-mark { 71 | fill: var(--mx-menu-active-text); 72 | } 73 | .mx-shortcut { 74 | background-color: var(--mx-menu-shortcut-backgroud-active); 75 | color: var(--mx-menu-shortcut-text-active); 76 | } 77 | } 78 | } 79 | .mx-context-menu-item-sperator { 80 | margin: 0 12px; 81 | } 82 | } -------------------------------------------------------------------------------- /library/SplitLayout/SplitNest.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | handlerChilldOrthogonalDraggerHover(index, a, b)" 20 | @orthogonalDraggerDrag="(a, b) => handlerChilldOrthogonalDraggerDrag(index, a, b)" 21 | > 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /library/SplitLayout/SplitTab.vue: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 24 | 25 | 37 | 38 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /library/SplitLayout/SplitTabControlItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /library/SplitLayout/SplitTabItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{ panel.title }} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /library/Utils/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 简易浏览器端使用的 EventEmitter 3 | */ 4 | export class EventEmitter { 5 | 6 | private listeners = new Map; 7 | private anyHandler : ((event: string, args: any[]) => any) |null = null; 8 | 9 | /** 10 | * 监听事件 11 | * @param event 事件名称 12 | * @param cb 回调函数 13 | * @returns 14 | */ 15 | public on(event: string, cb: Function) : this { 16 | let array = this.listeners.get(event); 17 | if (!array) 18 | array = []; 19 | if (!array.includes(cb)) 20 | array.push(cb); 21 | this.listeners.set(event, array); 22 | return this; 23 | } 24 | /** 25 | * 监听全部事件。 26 | * 此回调只能设置一个,设置后,其他通过 on 函数设置的监听器无效。 27 | * @returns 28 | */ 29 | public any(cb: (event: string, args: any[]) => any) : this { 30 | this.anyHandler = cb; 31 | return this; 32 | } 33 | /** 34 | * 触发事件 35 | * @param event 事件名称 36 | * @param a 事件参数 37 | * @returns 38 | */ 39 | public emit(event: string, ...a: any[]) : this { 40 | const args = Array.prototype.slice.call(arguments); 41 | 42 | if (this.anyHandler) { 43 | this.anyHandler(event, args); 44 | return this; 45 | } 46 | 47 | const array = this.listeners.get(event); 48 | args.shift(); 49 | if (array) { 50 | array.forEach(cb => { 51 | cb.apply(null, args); 52 | }); 53 | } 54 | return this; 55 | } 56 | /** 57 | * 触发异步事件,并获取返回值 58 | * @param event 事件名称 59 | * @param a 事件参数 60 | * @returns 如果有多个事件监听器,则返回包含所有事件监听器返回值的数组。如果只有一个事件监听器,返回此监听器返回值。 61 | */ 62 | public async emitAsync(event: string, ...a: any[]) : Promise { 63 | const args = Array.prototype.slice.call(arguments); 64 | 65 | if (this.anyHandler) 66 | return await this.anyHandler(event, args); 67 | 68 | const array = this.listeners.get(event); 69 | args.shift(); 70 | if (array) { 71 | let result : any[] = []; 72 | for (const cb of array) 73 | result.push(await cb.apply(null, args)); 74 | return result.length === 1 ? result[0] : result; 75 | } 76 | return undefined; 77 | } 78 | /** 79 | * 取消监听事件 80 | * @param event 事件名称 81 | * @param listener 监听器,如果为空,则移除全部监听器 82 | */ 83 | public off(event: string, listener?: Function|undefined) { 84 | if (!listener) { 85 | this.clear(event); 86 | return; 87 | } 88 | const array = this.listeners.get(event); 89 | if (array) { 90 | const index = array.indexOf(listener); 91 | if (index !== -1) 92 | array.splice(index, 1); 93 | } 94 | } 95 | /** 96 | * 监听一次事件 97 | * @param event 事件名 98 | * @param listener 监听器 99 | * @returns 100 | */ 101 | public once(event: string, listener: Function) : this { 102 | const self = this; 103 | 104 | function handler() { 105 | const args = Array.prototype.slice.call(arguments); 106 | listener.apply(null, args); 107 | self.off(event, handler); 108 | } 109 | 110 | this.on(event, handler); 111 | return this; 112 | } 113 | /** 114 | * 清除指定事件监听器 115 | * @param event 事件名 116 | */ 117 | public clear(event: string) { 118 | this.listeners.delete(event); 119 | } 120 | /** 121 | * 获取事件监听器 122 | * @param event 123 | * @returns 124 | */ 125 | public get(event: string) { 126 | return this.listeners.get(event); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /library/Utils/HtmlUtils.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | getTop, 3 | getLeft, 4 | getElementIndex, 5 | getElementAbsolutePositionInParent, 6 | }; 7 | 8 | /** 9 | * 获取元素的绝对纵坐标 10 | * @param e 元素 11 | * @param stopClass 递归向上查找,遇到指定类的父级时停止 12 | */ 13 | function getTop(e: HTMLElement, stopClassOrEle ? : string|HTMLElement) : number { 14 | let offset = e.offsetTop; 15 | const parent = e.offsetParent as HTMLElement; 16 | if ( 17 | e.offsetParent !== null && ( 18 | !stopClassOrEle 19 | || (typeof stopClassOrEle === 'string' ? 20 | !parent.classList.contains(stopClassOrEle) : 21 | e.offsetParent !== stopClassOrEle 22 | ) 23 | ) 24 | ) 25 | offset += getTop(parent, stopClassOrEle) - parent.scrollTop; 26 | if (e.offsetParent === null) 27 | offset -= document.documentElement.scrollTop; 28 | return offset; 29 | } 30 | /** 31 | * 获取元素的绝对横坐标 32 | * @param e 元素 33 | * @param stopClass 递归向上查找,遇到指定类的父级时停止 34 | */ 35 | function getLeft(e: HTMLElement, stopClassOrEle ? : string|HTMLElement) : number { 36 | let offset = e.offsetLeft; 37 | const parent = e.offsetParent as HTMLElement; 38 | if ( 39 | e.offsetParent !== null && ( 40 | !stopClassOrEle 41 | || (typeof stopClassOrEle === 'string' ? 42 | !parent.classList.contains(stopClassOrEle) : 43 | e.offsetParent !== stopClassOrEle 44 | ) 45 | ) 46 | ) 47 | offset += getLeft(parent, stopClassOrEle) - parent.scrollLeft; 48 | 49 | if (e.offsetParent === null) 50 | offset -= document.documentElement.scrollLeft; 51 | return offset; 52 | } 53 | 54 | /** 55 | * 获取元素在指定父级的绝对坐标 56 | * @param e 元素 57 | * @param parent 计算的父级,未指定则是body 58 | * @returns 59 | */ 60 | function getElementAbsolutePositionInParent(e: HTMLElement, parent: HTMLElement|undefined) { 61 | return { 62 | x: getLeft(e, parent), 63 | y: getTop(e, parent), 64 | } 65 | } 66 | 67 | /** 68 | * 获取一个元素在它父元素的DOM树位置 69 | * @param element 元素 70 | * @returns 索引,如果没有,则返回-1 71 | */ 72 | function getElementIndex(element: HTMLElement) : number { 73 | for (let i = 0, c = (element.parentNode as HTMLElement).childNodes.length; i < c; i++) 74 | if ((element.parentNode as HTMLElement).childNodes.item(i) === element) return i; 75 | return -1; 76 | } 77 | -------------------------------------------------------------------------------- /library/Utils/Timer/Debounce.ts: -------------------------------------------------------------------------------- 1 | export class Debounce { 2 | 3 | private timer = 0; 4 | private delay; 5 | private cb : () => void; 6 | 7 | constructor(delay: number, cb: () => void) { 8 | this.cb = cb; 9 | this.delay = delay; 10 | } 11 | 12 | execute() { 13 | if (this.timer > 0) 14 | return; 15 | this.cb(); 16 | this.timer = setTimeout(() => this.timer = 0, this.delay); 17 | } 18 | executeWithDelay(delay = -1) { 19 | if (this.timer > 0) 20 | return; 21 | if (delay <= 0) 22 | delay = this.delay; 23 | this.timer = setTimeout(() => { 24 | this.timer = 0; 25 | this.execute(); 26 | }, delay); 27 | } 28 | } -------------------------------------------------------------------------------- /library/Utils/Timer/Timer.ts: -------------------------------------------------------------------------------- 1 | export class SimpleDelay { 2 | 3 | private executor: (data: T) => void; 4 | private interval: number; 5 | private data: T; 6 | private timer = 0; 7 | 8 | public constructor(data: T, executor: (d: T) => void, interval: number) { 9 | this.executor = executor; 10 | this.interval = interval; 11 | this.data = data; 12 | } 13 | 14 | public isWaiting() { 15 | return this.timer > 0; 16 | } 17 | public start() { 18 | if (this.timer) 19 | clearTimeout(this.timer); 20 | this.timer = setTimeout(() => 21 | this.executor(this.data), 22 | this.interval 23 | ) as unknown as number; 24 | } 25 | public stop() { 26 | if (this.timer) { 27 | clearTimeout(this.timer); 28 | this.timer = 0; 29 | } 30 | } 31 | 32 | } 33 | export class SimpleTimer { 34 | 35 | private executor: (data: T) => void; 36 | private interval: number; 37 | private data: T; 38 | private timer = 0; 39 | private executorTimeLimitWarnCount = 0; 40 | private lasExecuteTime = 0; 41 | 42 | public constructor(data: T, executor: (d: T) => void, interval: number) { 43 | this.executor = executor; 44 | this.interval = interval; 45 | this.data = data; 46 | } 47 | 48 | public start() { 49 | if (this.timer) 50 | clearInterval(this.timer); 51 | this.timer = setInterval(() => { 52 | 53 | const startTime = new Date(); 54 | try { 55 | this.executor(this.data); 56 | } catch { 57 | clearInterval(this.timer); 58 | this.timer = 0; 59 | } 60 | 61 | //计算执行时间 62 | const executeTime = new Date().getTime() - startTime.getTime(); 63 | 64 | //如果与上一次触发时间过近,则取消 65 | const lastInterval = startTime.getTime() - this.lasExecuteTime 66 | if (this.lasExecuteTime > 0 && lastInterval < (this.interval - 200)) { 67 | console.warn('The execution time of the timer is too fast, lastInterval: ' + lastInterval, 'executor:', this.executor); 68 | return; 69 | } 70 | 71 | this.lasExecuteTime = startTime.getTime(); 72 | 73 | //如果执行时间过长,则警告 74 | if (executeTime > this.interval * 0.5 && this.executorTimeLimitWarnCount < 1) { 75 | console.warn('The execution time of the timer is too long, exceeding the timing for ' + this.interval + 'ms. executor:', this.executor, 'count:', this.executorTimeLimitWarnCount); 76 | this.executorTimeLimitWarnCount++; 77 | } else 78 | this.executorTimeLimitWarnCount--; 79 | 80 | }, this.interval) as unknown as number; 81 | } 82 | public stop() { 83 | if (this.timer) { 84 | clearInterval(this.timer); 85 | this.timer = 0; 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /library/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /library/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | 3 | export * from './CodeLayout' 4 | export * from './SplitLayout/SplitN'; 5 | export * from './Composeable/DragDrop'; 6 | export * from './Composeable/LateClass'; 7 | export * from './Composeable/Vector2'; 8 | export * from './Composeable/ResizeChecker'; 9 | export * from './Composeable/MiniTimeout'; 10 | export * from './Composeable/PanelMenu'; 11 | export * from './Language/index'; 12 | 13 | import CodeLayout from './CodeLayout.vue' 14 | import CodeLayoutActionsRender from './CodeLayoutActionsRender.vue' 15 | import CodeLayoutCollapseTitle from './CodeLayoutCollapseTitle.vue' 16 | import CodeLayoutCustomizeLayout from './Components/CodeLayoutCustomizeLayout.vue' 17 | import CodeLayoutScrollbar from './Components/CodeLayoutScrollbar.vue' 18 | import CodeLayoutVNodeStringRender from './Components/CodeLayoutVNodeStringRender.vue' 19 | import OverflowCollapseList from './Components/OverflowCollapseList.vue' 20 | import SimpleTooltip from './Components/SimpleTooltip.vue' 21 | import SplitLayout from './SplitLayout/SplitLayout.vue' 22 | import SplitTab from './SplitLayout/SplitTab.vue' 23 | import SplitTabItem from './SplitLayout/SplitTabItem.vue' 24 | import SplitN from './SplitLayout/SplitN.vue' 25 | 26 | export { 27 | CodeLayout, 28 | CodeLayoutActionsRender, 29 | CodeLayoutCollapseTitle, 30 | CodeLayoutCustomizeLayout, 31 | CodeLayoutScrollbar, 32 | CodeLayoutVNodeStringRender, 33 | OverflowCollapseList, 34 | SimpleTooltip, 35 | SplitLayout, 36 | SplitTab, 37 | SplitTabItem, 38 | SplitN, 39 | } 40 | 41 | export default { 42 | install(app: App) { 43 | app.component('CodeLayout', CodeLayout); 44 | app.component('SplitLayout', SplitLayout); 45 | }, 46 | } -------------------------------------------------------------------------------- /library/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "./**/*", "./**/*.vue"], 4 | "exclude": ["./**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /library/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import dts from 'vite-plugin-dts' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | vueJsx(), 13 | dts(), 14 | ], 15 | build: { 16 | lib: { 17 | entry: 'index.ts', 18 | name: 'vue-code-layout', 19 | fileName: (format) => `vue-code-layout.${format}.js`, 20 | }, 21 | rollupOptions: { 22 | external: ['vue'], 23 | output: { 24 | globals: { 25 | vue: 'Vue' 26 | }, 27 | assetFileNames: 'vue-code-layout.[ext]', 28 | }, 29 | }, 30 | sourcemap: true, 31 | outDir: '../lib', 32 | }, 33 | resolve: { 34 | alias: { 35 | '@': fileURLToPath(new URL('./', import.meta.url)) 36 | } 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-code-layout", 3 | "version": "1.2.1", 4 | "description": "A editor layout for Vue", 5 | "main": "lib/vue-code-layout.umd.js", 6 | "module": "lib/vue-code-layout.es.js", 7 | "files": [ 8 | "lib", 9 | "index.d.ts" 10 | ], 11 | "publishConfig": { 12 | "access": "public", 13 | "registry": "https://registry.npmjs.com/" 14 | }, 15 | "scripts": { 16 | "dev": "vite serve examples", 17 | "build-demo": "vite build examples", 18 | "build-lib": "vite build library", 19 | "docs:dev": "cd docs && vitepress dev", 20 | "docs:build": "vitepress build ./docs" 21 | }, 22 | "dependencies": { 23 | "@imengyu/vue-scroll-rect": "^0.1.3", 24 | "@imengyu/vue3-context-menu": "^1.5.0" 25 | }, 26 | "devDependencies": { 27 | "@guolao/vue-monaco-editor": "^1.5.1", 28 | "@tsconfig/node18": "^18.2.2", 29 | "@types/node": "^18.17.17", 30 | "@vitejs/plugin-vue": "^4.3.4", 31 | "@vitejs/plugin-vue-jsx": "^3.0.2", 32 | "@vue/tsconfig": "^0.4.0", 33 | "markdown-it-container": "^4.0.0", 34 | "npm-run-all2": "^6.0.6", 35 | "rimraf": "^5.0.5", 36 | "sass": "^1.69.5", 37 | "typescript": "~5.2.0", 38 | "vite": "^4.4.9", 39 | "vite-plugin-dts": "^3.6.4", 40 | "vite-plugin-markdown-preview": "^1.1.1", 41 | "vite-plugin-raw": "^1.0.3", 42 | "vitepress": "^1.0.1", 43 | "vitepress-plugin-sandpack": "^1.1.4", 44 | "vue": "^3.3.4", 45 | "vue-router": "^4.2.5", 46 | "vue-tsc": "^1.8.11" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/imengyu/vue-code-layout.git" 51 | }, 52 | "keywords": [ 53 | "vue3", 54 | "vue", 55 | "drag", 56 | "drop", 57 | "dock", 58 | "window", 59 | "tab", 60 | "panel", 61 | "code", 62 | "vscode", 63 | "editor", 64 | "layout", 65 | "组件", 66 | "布局", 67 | "编辑器", 68 | "context", 69 | "menu" 70 | ], 71 | "author": "imengyu", 72 | "license": "MIT", 73 | "bugs": { 74 | "url": "https://github.com/imengyu/vue-code-layout/issues" 75 | }, 76 | "homepage": "https://github.com/imengyu/vue-code-layout#readme" 77 | } 78 | -------------------------------------------------------------------------------- /screenshot/first.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imengyu/vue-code-layout/588e9183e37f68eba807214d09461365c7af34e5/screenshot/first.jpg --------------------------------------------------------------------------------
{{ description }}