Deploying to gh-pages from @ 55e21eacb3140bac6879375fb13e905599af1757 🚀

This commit is contained in:
mathuo 2022-05-26 20:31:34 +00:00
parent 567afa6aba
commit 07f6616ae9
287 changed files with 56543 additions and 0 deletions

10
build/.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
quote_type = single

2
build/.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist/
*.scss

19
build/.eslintrc.js Normal file
View File

@ -0,0 +1,19 @@
module.exports = {
root: true,
parserOptions: {
sourceType: 'module',
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
rules: {
'no-case-declarations': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
};

15
build/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
node_modules/
coverage/
dist/
output/
.idea/
typedocs/
.DS_Store
*-debug.log
.build
storybook-static/
.rollup.cache/
test-report.xml
*.code-workspace
yarn-error.log
/build

3
build/.prettierignore Normal file
View File

@ -0,0 +1,3 @@
typedocs/
dist/
build/

6
build/.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true
}

14
build/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"esbenp.prettier-vscode",
"redhat.vscode-yaml",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []
}

1
build/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1 @@
{}

21
build/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 mathuo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

52
build/README.md Normal file
View File

@ -0,0 +1,52 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support written in TypeScript</p>
</div>
---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview)
##
Please see the website: https://mathuo.github.io/dockview/docs
Want to inspect the latest deployment? Go to https://unpkg.com/browse/dockview@latest/
## Features
- Simple splitviews, nested splitviews (i.e. gridviews) supporting full layout managment with
dockable and tabular views
- Extensive API support at the component level and view level
- Themable and customizable
- Serialization / deserialization support
- Tabular docking and Drag and Drop support
- Documentation and examples
This project was inspired by many popular IDE editors. Some parts of the core resizable panelling are inspired by code found in the VSCode codebase, [splitview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/splitview) and [gridview](https://github.com/microsoft/vscode/tree/main/src/vs/base/browser/ui/grid).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview). Please see the [Getting Started Guide](https://mathuo.github.io/dockview/docs/).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

20
build/docs/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

41
build/docs/README.md Normal file
View File

@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@ -0,0 +1,24 @@
---
slug: dockview-1.4.1-release
title: Dockview 1.4.1
tags: [release]
---
# Release Notes
## 🚀 Features
- Fix Drag and Drop issues in Dockview on Firefox [#103](https://github.com/mathuo/dockview/pull/103)
## 🛠 Miscs
- Documentation enhancements https://mathuo.github.io/dockview/docs/
## 🔥 Breaking changes
All breaking changes here are designed to simplify the library with only one way to do something.
- Remove `setVisible` and `setActive` from the Splitview API. You can still achieve the same behaviors through calling `setVisible` and `setActive` on the Splitview Panel API. [#105](https://github.com/mathuo/dockview/pull/105)
- Remove `setVisible`, `setActive` and `toggleVisiblity` from Gridview API. You can still achieve the same behaviors through calling `setVisible` and `setActive` on the Gridview Panel API [#105](https://github.com/mathuo/dockview/pull/105)
- Remove `onFocusEvent` from Panel API as this was not intended to be a public method. You can use `onDidFocusChange` instead [#105](https://github.com/mathuo/dockview/pull/105)
- Remove HOC `<DockviewComponents.Panel\>`, `<DockviewComponents.Content>`, `<DockviewComponents.Tab>` and `<DockviewComponents.Actions>` [#105](https://github.com/mathuo/dockview/pull/105)

View File

@ -0,0 +1,15 @@
---
slug: dockview-1.4.2-release
title: Dockview 1.4.2
tags: [release]
---
# Release Notes
## 🚀 Features
- Fix deserialization issue where previously active panel wasn't display correctly after deserialization [#108](https://github.com/mathuo/dockview/pull/108)
## 🔥 Breaking changes
- Rename `onDidAddGroup` to `onDidAddPanel`, `onDidRemoveGroup` to `onDidRemovePanel` and `onDidActiveGroupChange` to `onDidActivePanelChange` on the Gridview API [#106](https://github.com/mathuo/dockview/pull/106)

View File

@ -0,0 +1,23 @@
---
slug: dockview-1.4.3-release
title: Dockview 1.4.3
tags: [release]
---
# Release Notes
## 🚀 Features
- Small adjusted to behaviours of default paneview header componnet [#116](https://github.com/mathuo/dockview/pull/116) [#120](https://github.com/mathuo/dockview/pull/120)
- Improved support for external dnd events in the dockview component. `showDndOverlay` prop on `DockviewReact` exposes more parameters to interact with [#110](https://github.com/mathuo/dockview/pull/110)
- Improved to underlying events exposes through all components [#114](https://github.com/mathuo/dockview/pull/114)
- Add .clear() to the component APIs providing an easy way to clear a layout [#119](https://github.com/mathuo/dockview/pull/119)
- Udate orientation via componnet APIs is now working correctly [#119](https://github.com/mathuo/dockview/pull/119)
## 🛠 Miscs
- Documentation enhancements https://mathuo.github.io/dockview/docs/ [#101](https://github.com/mathuo/dockview/pull/101)
## 🔥 Breaking changes
- Fix typo by renaming `onDidLayoutfromJSON` to `onDidLayoutFromJSON` in dockview component api [#112](https://github.com/mathuo/dockview/pull/112/files)

View File

@ -0,0 +1,17 @@
endi:
name: Endilie Yacop Sucipto
title: Maintainer of Docusaurus
url: https://github.com/endiliey
image_url: https://github.com/endiliey.png
yangshun:
name: Yangshun Tay
title: Front End Engineer @ Facebook
url: https://github.com/yangshun
image_url: https://github.com/yangshun.png
slorber:
name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png

View File

@ -0,0 +1,7 @@
{
"label": "API",
"position": 2,
"link": {
"type": "generated-index"
}
}

View File

@ -0,0 +1,285 @@
import { SimpleDockview } from '../../src/components/simpleDockview';
import {
RenderingDockview,
Checkbox,
} from '../../src/components/dockview/rendering';
import { DndDockview } from '../../src/components/dockview/dnd';
import { EventsDockview } from '../../src/components/dockview/events';
import Link from '@docusaurus/Link';
# Dockview
## Introduction
Dockview is an abstraction built on top of [Gridviews](/docs/api/gridview) where each view is a tabbed container.
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleDockview />
</div>
```tsx
const panel = event.api.addPanel(...);
const anotherPanel = event.api.getPanel('somePanelid');
```
You can access the panels associated group through the `panel.group` variable.
The group will always be defined and will change if a panel is moved into another group.
## DockviewReact Component
```tsx
import { ReactDockview } from 'dockview';
```
| Property | Type | Optional | Default | Description |
| ------------------- | ------------------------------------ | -------- | ------- | --------------------------------------------------------------- |
| onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | |
| tabComponents | object | Yes | | |
| watermarkComponent | object | Yes | | |
| hideBorders | boolean | Yes | false | |
| className | string | Yes | '' | |
| disableAutoResizing | boolean | Yes | false | See <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| onTabContextMenu | Event | Yes | false | |
| onDidDrop | Event | Yes | false | |
| showDndOverlay | Event | Yes | false | |
## Dockview API
```tsx
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
// props.containerApi...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
```tsx
const onReady = (event: DockviewReadyEvent) => {
// event.api...
};
```
| Property | Type | Description |
| ---------------------- | ---------------------------------------------------- | ----------------------------------------------------------- |
| height | `number` | Component pixel height |
| width | `number` | Component pixel width |
| minimumHeight | `number` | |
| maximumHeight | `number` | |
| maximumWidth | `number` | |
| maximumWidth | `number` | |
| length | `number` | Number of panels |
| size | `number` | Number of Groups |
| panels | `IDockviewPanel[]` | |
| groups | `GroupPanel[]` | |
| activePanel | `IDockviewPanel \| undefined` | |
| activeGroup | `IDockviewPanel \| undefined` | |
| | | |
| onDidLayoutChange | `Event<void>` | |
| onDidLayoutFromJSON | `Event<void>` | |
| onDidAddGroup | `Event<GroupPanel>` | |
| onDidRemoveGroup | `Event<GroupPanel>` | |
| onDidActiveGroupChange | `Event<GroupPanel \| undefined>` | |
| onDidAddPanel | `Event<IDockviewPanel>` | |
| onDidRemovePanel | `Event<IDockviewPanel>` | |
| onDidActivePanelChange | `Event<IDockviewPanel \| undefined>` | |
| onDidDrop | `Event<DockviewDropEvent` | |
| | | |
| addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | |
| getPanel | `(id: string) \| IDockviewPanel \| undefined` | |
| addEmptyGroup | `(options? AddGroupOptions): void` | |
| closeAllGroups | `(): void` | |
| removeGroup | `(group: GroupPanel): void` | |
| getGroup | `(id: string): GroupPanel \| undefined` | |
| | | |
| getTabHeight | `(): number \| undefined` | |
| setTabHeight | `(hegiht: number \| undefined): void` | |
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
| focus | `(): void` | |
| layout | `(width: number, height:number): void` | <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| fromJSON | `(data: SerializedDockview): void` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| toJSON | `(): SerializedDockview` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| clear | `(): void` | Clears the current layout |
## Dockview Panel API
```tsx
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
// props.api...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
| Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | --------------- |
| id | `string` | Panel id |
| isFocused | `boolean` | Is panel focsed |
| isActive | `boolean` | Is panel active |
| width | `number` | Panel width |
| height | `number` | Panel height |
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
| onDidFocusChange | `Event<FocusEvent>` | |
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
| onDidActiveChange | `Event<ActiveEvent>` | |
| setActive | `(): void` | |
| | | |
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
| setSize | `(event: SizeEvent): void` | |
| | | |
| group | `GroupPanel | undefined` |
| isGroupActive | `boolean` | |
| title | `string` | |
| suppressClosable | `boolean` | |
| close | `(): void` | |
| setTitle | `(title: string): void` | |
## Advanced Features
### Locked group
Locking a group will disable all drop events for this group ensuring a user can not add additional panels to the group.
You can still add groups to a locked panel programatically using the api.
```tsx
panel.group.locked = true;
```
### Group header
You may wish to hide the header section of a group. This can achieved through setting the `hidden` variable on `panel.group.header`.
```tsx
panel.group.header.hidden = true;
```
### Context Menu
import { ContextMenuDockview } from '../../src/components/dockview/contextMenu';
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<ContextMenuDockview />
</div>
### Rendering
Although `DockviewReact` will only add those tabs that are visible to the DOM all associated React Components for each tab including those that
are not initially visible will be created.
This will mean that any hooks in those components will run and if you running expensive operations in the tabs you may end up doing a lot of initial
work for what are hidden tabs.
This is the default behaviour to ensure the greatest flexibility for the user but you can create a Higher-Order component wrapping your components that
will ensure the component is only created if the tab is visible as below:
```tsx
import { PanelApi } from 'dockview';
import * as React from 'react';
function RenderWhenVisible<
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> }
>(component: React.FunctionComponent<T>) {
const HigherOrderComponent = (props: T) => {
const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible
);
React.useEffect(() => {
const disposable = props.api.onDidVisibilityChange((event) =>
setVisible(event.isVisible)
);
return () => {
disposable.dispose();
};
}, [props.api]);
if (!visible) {
return null;
}
return React.createElement(component, props);
};
return HigherOrderComponent;
}
```
```tsx
const component = RenderWhenVisible(MyComponent);
```
Through toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
<Checkbox />
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<RenderingDockview renderVisibleOnly={false} />
</div>
### Drag And Drop
The component exposes some method to help determine whether external drag events should be interacted with or not.
```tsx
/**
* called when an ondrop event which does not originate from the dockview libray and
* passes the showDndOverlay condition occurs
**/
const onDidDrop = (event: DockviewDropEvent) => {
const { group } = event;
event.api.addPanel({
id: 'test',
component: 'default',
position: {
referencePanel: group.activePanel.id,
direction: 'within',
},
});
};
/**
* called for drag over events which do not originate from the dockview library
* allowing the developer to decide where the overlay should be shown for a
* particular drag event
**/
const showDndOverlay = (event: DockviewDndOverlayEvent) => {
return true;
};
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-dark"
onDidDrop={onDidDrop}
showDndOverlay={showDndOverlay}
/>
);
```
<DndDockview />
### Events
<EventsDockview />

View File

@ -0,0 +1,116 @@
import { SimpleGridview } from '../../src/components/simpleGridview';
import { EventsGridview } from '../../src/components/gridview/events';
import Link from '@docusaurus/Link';
# Gridview
## Introduction
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleGridview />
</div>
## GridviewReact Component
```tsx
import { ReactGridview } from 'dockview';
```
| Property | Type | Optional | Default | Description |
| ------------------- | ------------------------------------ | -------- | ---------------------- | --------------------------------------------------------------------------- |
| onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | |
| orientation | Orientation | Yes | Orientation.HORIZONTAL | |
| proportionalLayout | boolean | Yes | true | See <Link to="/docs/basics/#proportional-layout">Proportional layout</Link> |
| hideBorders | boolean | Yes | false | |
| className | string | Yes | '' | |
| disableAutoResizing | boolean | Yes | false | See <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
## Gridview API
```tsx
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
// props.containerApi...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
```tsx
const onReady = (event: GridviewReadyEvent) => {
// event.api...
};
```
| Property | Type | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| height | `number` | Component pixel height |
| width | `number` | Component pixel width |
| minimumHeight | `number` | |
| maximumHeight | `number` | |
| maximumWidth | `number` | |
| maximumWidth | `number` | |
| length | `number` | Number of panels |
| panels | `ISplitviewPanel[]` | all panels |
| orientation | `Orientation` | |
| | | |
| onDidLayoutChange | `Event<void>` | Fires on layout change |
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
| onDidAddPanel | `Event<IGridviewPanel>` | Fires when a view is added |
| onDidRemovePanel | `Event<IGridviewPanel>` | Fires when a view is removed |
| onDidActivePanelChange | `Event<IGridviewPanel \| undefined>` | Fires when the active group changes |
| | | |
| addPanel | `addPanel(options: AddComponentOptions): IGridviewPanel` | |
| removePanel | `(panel: IGridviewPanel, sizing?: Sizing): void` | |
| movePanel | `(panel: IGridviewPanel, options: {direction: Direction, refernece:string, size?: number}): void` | |
| getPanel | `(id: string) \| IGridviewPanel \| undefined` | |
| | | |
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
| focus | `(): void` | Focus the active panel, if exists |
| layout | `(width: number, height:number): void` | <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| fromJSON | `(data: SerializedGridview): void` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| toJSON | `(): SerializedGridview` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| clear | `(): void` | Clears the current layout |
## Gridview Panel API
```tsx
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
// props.api...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
| Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | ---------------- |
| id | `string` | Panel id |
| isFocused | `boolean` | Is panel focsed |
| isActive | `boolean` | Is panel active |
| isVisible | `boolean` | Is panel visible |
| width | `number` | Panel width |
| height | `number` | Panel height |
| | | |
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
| onDidFocusChange | `Event<FocusEvent>` | |
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
| onDidActiveChange | `Event<ActiveEvent>` | |
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
| | | |
| setVisible | `(isVisible: boolean): void` | |
| setActive | `(): void` | |
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
| setSize | `(event: SizeEvent): void` | |
## Events
`GridviewReact` exposes a number of events that the developer can listen to and below is a simple example with a log panel showing those events that occur.
<EventsGridview />

View File

@ -0,0 +1,185 @@
import { SimplePaneview } from '../../src/components/simplePaneview';
import Link from '@docusaurus/Link';
# Paneview
# Introduction
<div
style={{
height: '400px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimplePaneview />
</div>
## PaneviewReact Component
```tsx
import { ReactPaneview } from 'dockview';
```
| Property | Type | Optional | Default | Description |
| ------------------- | ------------------------------------ | -------- | ------- | ----------------------------------------------------------- |
| onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | |
| headerComponents | object | Yes | | |
| className | string | Yes | '' | |
| disableAutoResizing | boolean | Yes | false | <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| disableDnd | boolean | Yes | false | |
| onDidDrop | Event | Yes | | |
## Paneview API
```tsx
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
// props.containerApi...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
```tsx
const onReady = (event: GridviewReadyEvent) => {
// event.api...
};
```
| Property | Type | Description |
| ------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| height | `number` | Component pixel height |
| width | `number` | Component pixel width |
| minimumSize | `number` | The sum of the `minimumSize` property for each panel |
| maximumSize | `number` | The sum of the `maximumSize` property for each panel |
| length | `number` | Number of panels |
| panels | `IPaneviewPanel[]` | All panels |
| | | |
| onDidLayoutChange | `Event<void>` | Fires on layout change |
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
| onDidAddView | `Event<IPaneviewPanel>` | Fires when a view is added |
| onDidRemoveView | `Event<IPaneviewPanel>` | Fires when a view is removed |
| onDidDrop | `Event<PaneviewDropEvent` | Fires on an external drop event (See <Link to="/docs/api/paneview/#drag-and-drop">Drag and Drop</Link>) |
| | | |
| addPanel | `addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel` | |
| removePanel | `(panel: IPaneviewPanel): void` | |
| movePanel | `(from: number, to: number): void` | |
| getPanel | `(id:string): IPaneviewPanel \| undefined` | |
| | | |
| focus | `(): void` | Focus the active panel, if exists |
| layout | `(width: number, height:number): void` | See <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| fromJSON | `(data: SerializedPaneview): void` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| toJSON | `(): SerializedPaneview` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| clear | `(): void` | Clears the current layout |
## Gridview Panel API
```tsx
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
// props.api...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
| Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | ---------------- |
| id | `string` | Panel id |
| isFocused | `boolean` | Is panel focsed |
| isActive | `boolean` | Is panel active |
| isVisible | `boolean` | Is panel visible |
| width | `number` | Panel width |
| height | `number` | Panel height |
| | |
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
| onDidFocusChange | `Event<FocusEvent>` | |
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
| onDidActiveChange | `Event<ActiveEvent>` | |
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
| | |
| setVisible | `(isVisible: boolean): void` | |
| setActive | `(): void` | |
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
| setSize | `(event: SizeEvent): void` | |
## Advanced Features
### Custom Header
The above example shows the default header and will render the `title` along with a small icon to indicate a collapsed or expanded state.
You can provide a custom header:
```tsx
const onReady = (event: PaneviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
headerComponent: 'myHeaderComponent',
params: {
valueA: 'A',
},
title: 'Panel 1',
});
};
```
The above example shows the default header and will render the `title` along with a small icon to indicate a collapsed or expanded state.
You can provide a custom header:
```tsx
const onReady = (event: PaneviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
headerComponent: 'myHeaderComponent',
params: {
valueA: 'A',
},
title: 'Panel 1',
});
};
```
You can define a header component and listen to the expanded state to update the component accordingly.
```tsx
const CustomHeader = (props: IPaneviewPanelProps) => {
const [expanded, setExpanded] = React.useState<boolean>(
props.api.isExpanded
);
React.useEffect(() => {
const disposable = props.api.onDidExpansionChange((event) => {
setExpanded(event.isExpanded);
});
return () => {
disposable.dispose();
};
}, []);
const onClick = () => {
props.api.setExpanded(!expanded);
};
return (
<div>
<a
onClick={onClick}
className={expanded ? 'expanded' : 'collapsed'}
/>
<span>{props.params.title}</span>
</div>
);
};
```
You should provide a value for the `headerComponents` React prop.
```tsx
const headerComponents = { myHeaderComponent: CustomHeader };
```
### Drag And Drop

View File

@ -0,0 +1,243 @@
import { SimpleSplitview } from '../../src/components/simpleSplitview';
import { SplitviewExample1 } from '../../src/components/splitview/active';
import Link from '@docusaurus/Link';
# Splitview
## Introduction
A Splitview is a collection resizable horizontally or vertically stacked panels.
The Splitview exposes a component level API through the `onReady` event and through the `props.containerApi` variable on the panel props.
A panel level API is exposed on each panel through it's props as `props.api`.
<div
style={{
height: '100px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleSplitview />
</div>
```tsx title="Simple Splitview example"
import {
ISplitviewPanelProps,
Orientation,
SplitviewReact,
SplitviewReadyEvent,
} from 'dockview';
const components = {
default: (props: ISplitviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const SimpleSplitview = () => {
const onReady = (event: SplitviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
};
return (
<SplitviewReact
components={components}
onReady={onReady}
orientation={Orientation.HORIZONTAL}
className="dockview-theme-dark"
/>
);
};
```
## SplitviewReact Component
You can create a Splitview through the use of the `ReactSplitview` component.
```tsx
import { ReactSplitview } from 'dockview';
```
Using the `onReady` prop you can access to the component `api` and add panels either through deserialization or indivial addition of panels.
| Property | Type | Optional | Default | Description |
| ------------------- | -------------------------------------- | -------- | ------------------------ | --------------------------------------------------------------------------- |
| onReady | `(event: SplitviewReadyEvent) => void` | No | | Function |
| components | `Record<string, ISplitviewPanelProps>` | No | | Panel renderers |
| orientation | `Orientation` | Yes | `Orientation.HORIZONTAL` | Orientation of the Splitview |
| proportionalLayout | `boolean` | Yes | `true` | See <Link to="/docs/basics/#proportional-layout">Proportional layout</Link> |
| hideBorders | `boolean` | Yes | `false` | Hide the borders between panels |
| className | `string` | Yes | `''` | Attaches a classname |
| disableAutoResizing | `boolean` | Yes | `false` | See <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
## Splitview API
The Splitview API is exposed both at the `onReady` event and on each panel through `props.containerApi`.
```tsx title="Splitview API via Panel component"
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
// props.containerApi...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
```tsx title="Splitview API via the onReady callback"
const onReady = (event: SplitviewReadyEvent) => {
// event.api...
};
```
| Property | Type | Description |
| ------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------- |
| height | `number` | Component pixel height |
| width | `number` | Component pixel width |
| minimumSize | `number` | The sum of the `minimumSize` property for each panel |
| maximumSize | `number` | The sum of the `maximumSize` property for each panel |
| length | `number` | Number of panels |
| panels | `ISplitviewPanel[]` | All panels |
| | | |
| onDidLayoutChange | `Event<void>` | Fires on layout change |
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
| onDidAddView | `Event<IView>` | Fires when a view is added |
| onDidRemoveView | `Event<IView>` | Fires when a view is removed |
| | | |
| addPanel | `addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel` | |
| removePanel | `(panel: ISplitviewPanel, sizing?: Sizing): void` | |
| getPanel | `(id:string): ISplitviewPanel \| undefined` | |
| movePanel | `(from: number, to: number): void` | |
| | |
| updateOptions | `(options: SplitviewComponentUpdateOptions): void` | |
| focus | `(): void` | Focus the active panel, if exists |
| layout | `(width: number, height:number): void` | See <Link to="/docs/basics/#auto-resizing">Auto Resizing</Link> |
| fromJSON | `(data: SerializedSplitview): void` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| toJSON | `(): SerializedSplitview` | <Link to="/docs/basics/#serialization">Serialization</Link> |
| clear | `(): void` | Clears the current layout |
## Splitview Panel API
The Splitview panel API is exposed on each panel and contains actions and variables specific to that panel.
```tsx title="Splitview panel API via Panel component"
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
// props.api...
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
| Property | Type | Description |
| ---------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| id | `string` | Panel id |
| isFocused | `boolean` | Is panel focsed |
| isActive | `boolean` | Is panel active |
| isVisible | `boolean` | Is panel visible |
| width | `number` | Panel width |
| height | `number` | Panel height |
| | | |
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | Fires when panel dimensions change |
| onDidFocusChange | `Event<FocusEvent>` | Fire when panel is focused and blurred |
| onDidVisibilityChange | `Event<VisibilityEvent>` | Fires when the panels visiblity property is changed (see <Link to="/docs/api/splitview/#visibility">Panel Visibility</Link>) |
| onDidActiveChange | `Event<ActiveEvent>` | Fires when the panels active property is changed (see <Link to="/docs/api/splitview/#active">Active Panel</Link>) |
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | Fires when the panels size contrainsts change (see <Link to="/docs/api/splitview/#contraints">Panel Constraints</Link>) |
| | | |
| setVisible | `(isVisible: boolean): void` | |
| setActive | `(): void` | |
| | | |
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
| setSize | `(event: PanelSizeEvent): void` | |
## Advanced Features
Listed below are some functionality avalaible to you through both the panel and component APIs. The live demo shows examples of these in real-time.
<div
style={{
height: '200px',
margin: '20px 0px',
}}
>
<SplitviewExample1 />
</div>
### Visibility
A panels visibility can be controlled and monitoring through the following code.
A panel with visibility set to `false` will remain as a part of the components list of panels but will not be rendered.
```tsx
const disposable = props.api.onDidVisibilityChange(({ isVisible }) => {
//
});
```
```tsx
api.setVisible(true);
```
### Active
Only one panel in the `splitview` can be the active panel at any one time.
Setting a panel as active will set all the others as inactive.
A focused panel is always the active panel but an active panel is not always focused.
```tsx
const disposable = props.api.onDidActiveChange(({ isActive }) => {
//
});
```
```tsx
api.setActive();
```
### Contraints
When adding a panel you can specify pixel size contraints
```tsx
event.api.addPanel({
id: 'panel_3',
component: 'default',
minimumSize: 100,
maximumSize: 1000,
});
```
These contraints can be updated throughout the lifecycle of the `splitview` using the panel API
```tsx
props.api.onDidConstraintsChange(({ maximumSize, minimumSize }) => {
//
});
```
```tsx
api.setConstraints({
maximumSize: 200,
minimumSize: 400,
});
```

117
build/docs/docs/basics.mdx Normal file
View File

@ -0,0 +1,117 @@
---
sidebar_position: 1
---
import { SimpleSplitview } from '../src/components/simpleSplitview';
import { SimpleSplitview2 } from '../src/components/simpleSplitview2';
# Basics
This section will take you through a number of concepts that can be applied to all dockview components.
## Panels
The below examples use `ReactSplitview` but the logic holds for `ReactPaneview`, `ReactGridview` and `ReactDockview` using their respective implementations and interfaces.
All components require you to provide an `onReady` prop which you can use to build and control your component.
### Adding a panel with parameters
You can pass parameters to a panel through the `params` object
```tsx
const onReady = (event: SplitviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'myComponent',
params: {
title: 'My Title',
},
});
};
```
and you can access those properties through the `props.params` object. The TypeScript interface accepts an optional generic type `T` that corresponds to the params objects type.
```tsx
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
};
```
## API
There are two types of API you will interact with using `dockview`.
- The `panel API` is accessible via `props.api` in user defined panels and via the `.api` variable found on panel instances. This API contains actions and variable related to the the individual panel.
- The `container API` is accessible via `event.api` in the `onReady` events and `props.containerApi` in user defined panels. This API contains actions and variable related to the component as a whole.
```tsx
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
React.useEffect(() => {
const disposable = props.api.onDidActiveChange((event) => {
console.log(`is panel active: ${event.isActive}`);
});
return () => {
disposable.dispose(); // remember to dispose of any subscriptions
};
}, [props.api]);
const addAnotherPanel = React.useCallback(() => {
props.containerApi.addPanel({
id: 'another_id',
component: 'anotherComponent',
});
}, [props.containerApi]);
return (
<div>
<span>{`My first panel has the title: ${props.params.title}`}</span>
<button onClick={addAnotherPanel}>Add Panel</button>
</div>
);
};
```
### Serialization
All components support `toJSON(): T` which returns a Typed object representation of the components state. This same Typed object can be used to deserialize a view using `fromJSON(object: T): void`.
## Auto resizing
`SplitviewReact`, `GridviewReact`, `PaneviewReact` and `DockviewReact` will all automatically resize to fill the size of their parent element.
Internally this is achieved using a [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) which some users may need to polyfill.
You can disable this by settings the `disableAutoResizing` prop to be `true`.
You can manually resize a component using the API method `layout(width: number, height: number): void`.
An advanced case may use this in conjunction with `disableAutoResizing=true` to allow a parent component to have ultimate control over the dimensions of the component.
## Events
Many API properties can be listened on using the `Event` pattern. For example `api.onDidFocusChange(() => {...})`.
You should dispose of any event listeners you create cleaning up any listeners you would have created.
```tsx
React.useEffect(() => {
const disposable = api.onDidFocusChange(() => {
// write some code
});
return () => {
disposable.dispose();
};
}, []);
```
## Proportional layout
The `proportionalLayout` property indicates the expected behaviour of the component as it's container resizes, should all views resize equally or should just one view expand to fill the new space. `proportionalLayout` can be set as a property on `SplitviewReact` and `GridviewReact` components.
Although not configurable on `DockviewReact` and `PaneviewReact` these both behave as if `proportionalLayout=true` was set for them.
<SimpleSplitview2 proportional={false} />
<SimpleSplitview2 proportional={true} />
## Browser support
dockview is intended to support all major browsers. Some users may require a polyfill for [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).

150
build/docs/docs/index.mdx Normal file
View File

@ -0,0 +1,150 @@
---
sidebar_position: 0
---
import { SimpleSplitview } from '../src/components/simpleSplitview';
import { SimpleGridview } from '../src/components/simpleGridview';
import { SimplePaneview } from '../src/components/simplePaneview';
import { SimpleDockview } from '../src/components/simpleDockview';
# Dockview
## Introduction
**dockview** is a zero dependency layout manager that supports tab, grids and splitviews.
## Features
- Themable and customizable
- Support for the serialization and deserialization of layouts
- Drag and drop support
## Quick start
`dockview` has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. To install `dockview` you can run:
```shell
npm install dockview
```
You must also import the dockview stylesheet found under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css),
depending on your solution this might be:
```css
@import './node_modules/dockview/dist/styles/dockview.css';
```
A dark and light theme are provided, one of these classes (or a custom theme) must be attached at any point above your components in the HTML tree. To cover the entire web page you might attach the class to the `body` component:
```html
<body classname="dockview-theme-dark">
...
</body>
<body classname="dockview-theme-light">
...
</body>
```
There are 4 components you may want to use:
Splitview
<div
style={{
height: '100px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleSplitview />
</div>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleGridview />
</div>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimplePaneview />
</div>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<SimpleDockview />
</div>
```tsx
import {
DockviewReact,
DockviewReadyEvent,
PanelCollection,
IDockviewPanelProps,
IDockviewPanelHeaderProps,
} from 'dockview';
const components: PanelCollection<IDockviewPanelProps> = {
default: (props: IDockviewPanelProps<{ someProps: string }>) => {
return <div>{props.params.someProps}</div>;
},
};
const headers: PanelCollection<IDockviewPanelHeaderProps> = {
customTab: (props: IDockviewPanelHeaderProps) => {
return (
<div>
<span>{props.api.title}</span>
<span onClick={() => props.api.close()}>{'[x]'}</span>
</div>
);
},
};
const Component = () => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel1',
component: 'default',
tabComponent: 'customTab', // optional custom header
params: {
someProps: 'Hello',
},
});
event.api.addPanel({
id: 'panel2',
component: 'default',
params: {
someProps: 'World',
},
position: { referencePanel: 'panel1', direction: 'below' },
});
};
return (
<DockviewReact
components={components}
tabComponents={headers} // optional headers renderer
onReady={onReady}
/>
);
};
```

80
build/docs/docs/theme.mdx Normal file
View File

@ -0,0 +1,80 @@
---
sidebar_position: 3
---
import { CustomCSSDockview } from '../src/components/dockview/customCss';
# Theme
## Introduction
`dockview` requires some css to work correctly.
The css is exported as one file under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css)
and depending can be imported
```css
@import './node_modules/dockview/dist/styles/dockview.css';
```
## Customizing Theme
`dockview` supports theming through the use of css properties.
You can view the built-in themes at [`dockview/src/theme.scss`](https://github.com/mathuo/dockview/blob/master/packages/dockview/src/theme.scss)
and are free to build your own themes based on these css properties.
| CSS Property | Description |
| ---------------------------------------------------- | ----------- |
| --dv-paneview-active-outline-color | |
| --dv-tabs-and-actions-container-font-size | |
| --dv-tabs-and-actions-container-height | |
| --dv-tab-close-icon | |
| --dv-drag-over-background-color | |
| --dv-drag-over-border-color | |
| --dv-tabs-container-scrollbar-color | |
| | |
| --dv-group-view-background-color | |
| | |
| --dv-tabs-and-actions-container-background-color | |
| | |
| --dv-activegroup-visiblepanel-tab-background-color | |
| --dv-activegroup-hiddenpanel-tab-background-color | |
| --dv-inactivegroup-visiblepanel-tab-background-color | |
| --dv-inactivegroup-hiddenpanel-tab-background-color | |
| --dv-tab-divider-color | |
| | |
| --dv-activegroup-visiblepanel-tab-color | |
| --dv-activegroup-hiddenpanel-tab-color | |
| --dv-inactivegroup-visiblepanel-tab-color | |
| --dv-inactivegroup-hiddenpanel-tab-color | |
| | |
| --dv-separator-border | |
| --dv-paneview-header-border-color | |
You can further customise the theme through adjusting class properties but this is up you.
As an example if you wanted to add a bottom border to the tab container for an active group in the `DockviewReact` component you could write:
```css
.groupview {
&.active-group {
> .tabs-and-actions-container {
border-bottom: 2px solid var(--dv-activegroup-visiblepanel-tab-background-color);
}
}
&.inactive-group {
> .tabs-and-actions-container {
border-bottom: 2px solid var(--dv-inactivegroup-visiblepanel-tab-background-color);
}
}
}
```
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<CustomCSSDockview />
</div>

View File

@ -0,0 +1,170 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const path = require('path');
console.log(`isCI: ${process.env.CI}`);
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Dockview',
tagline: 'Zero dependency layout manager for React',
url: 'https://your-docusaurus-test-site.com',
baseUrl: process.env.CI ? `/` : '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'img/favicon.ico',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'mathuo', // Usually your GitHub org/user name.
projectName: 'dockview', // Usually your repo name.
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
plugins: [
'docusaurus-plugin-sass',
(context, options) => {
return {
name: 'webpack',
configureWebpack: (config, isServer, utils) => {
return {
// externals: ['react', 'react-dom'],
devtool: 'source-map',
resolve: {
alias: {
react: path.join(
__dirname,
'node_modules',
'react'
),
'react-dom': path.join(
__dirname,
'node_modules',
'react-dom'
),
},
},
};
},
};
},
],
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
blog: {
showReadingTime: true,
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: 'Dockview',
logo: {
alt: 'My Site Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'doc',
docId: 'index',
position: 'left',
label: 'Docs',
},
{ to: '/blog', label: 'Blog', position: 'left' },
{
href: 'https://github.com/mathuo/dockview',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Learn',
items: [
{
label: 'Docs',
to: '/docs',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/dockview',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/mathuo/dockview',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Dockview, Inc. Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
additionalLanguages: ['java', 'markdown', 'latex'],
magicComments: [
{
className: 'theme-code-block-highlighted-line',
line: 'highlight-next-line',
block: {
start: 'highlight-start',
end: 'highlight-end',
},
},
{
className: 'code-block-error-line',
line: 'This will error',
},
],
},
}),
};
module.exports = config;

View File

@ -0,0 +1,27 @@
<!-- <!DOCTYPE html> -->
<html>
<head>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/dockview@latest/dist/dockview.js"></script>
<link rel="stylesheet" href="https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css">
<style>
body {
margin: 0;
}
#root {
height: 100vh;
width: 100vw;
background-color: grey;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
const root = document.getElementById("root")
const dockviewComponent = new dockview.DockviewComponent(root, {})
</script>
</body>
</html>

54
build/docs/package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "dockview-docs",
"version": "1.4.2",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc",
"deploy-docs": "node scripts/package-docs.js"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.20",
"@docusaurus/preset-classic": "2.0.0-beta.20",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"dockview": "^1.4.2",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"recoil": "^0.7.3-alpha.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.20",
"@tsconfig/docusaurus": "^1.0.5",
"docusaurus-plugin-sass": "^0.2.2",
"fs-extra": "^10.1.0",
"install": "^0.13.0",
"sass": "^1.52.1",
"typescript": "^4.6.4"
},
"resolutions": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -0,0 +1,8 @@
const fs = require('fs-extra');
const path = require('path');
const output = path.join(__dirname, '../../build');
const docsDir = path.join(__dirname, '../build');
fs.copySync(docsDir, path.join(output));

31
build/docs/sidebars.js Normal file
View File

@ -0,0 +1,31 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};
module.exports = sidebars;

View File

@ -0,0 +1,70 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: JSX.Element;
};
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures(): JSX.Element {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@ -0,0 +1,27 @@
.console-container {
background-color: black;
color: white;
padding-left: 8px;
max-height: 200px;
overflow-y: scroll;
overflow-x: auto;
.console-line {
height: 20px;
line-height: 20px;
font-size: 13px;
border-bottom: 1px solid rgb(30, 30, 30);
display: flex;
padding-left: 4px;
.console-line-timestamp {
color: lightgray;
padding-right: 4px;
}
.console-line-text {
padding: 0px 4px;
flex-grow: 1;
}
}
}

View File

@ -0,0 +1,52 @@
import * as React from 'react';
import './console.scss';
const formatTime = (now: Date) => {
const pad = (x: number) => (x < 10 ? `0${x}` : `${x}`);
return `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(
now.getSeconds()
)}.${now.getMilliseconds()}`;
};
export interface Line {
timestamp: Date;
text: string;
css?: React.CSSProperties;
}
export interface IConsoleProps {
lines: Line[];
}
export const Console = (props: IConsoleProps) => {
const ref = React.useRef<HTMLDivElement>();
React.useLayoutEffect(() => {
if (!ref.current) {
return;
}
ref.current.scrollTop = Math.max(
0,
ref.current.scrollHeight - ref.current.clientHeight
);
}, [props.lines]);
return (
<div ref={ref} className="console-container">
{props.lines.map((line, i) => {
return (
<div key={i} className="console-line">
<span className="console-line-timestamp">
{formatTime(line.timestamp)}
</span>
<span className="console-line-text" style={line.css}>
{line.text}
</span>
</div>
);
})}
</div>
);
};

View File

@ -0,0 +1,115 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelHeaderProps,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
//
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
const DefaultTab = (props: IDockviewPanelHeaderProps) => {
const [active, setActive] = React.useState<boolean>(props.api.isActive);
const [groupActive, setGroupActive] = React.useState<boolean>(
props.api.isGroupActive
);
React.useEffect(() => {
const disposable1 = props.api.onDidActiveChange((e) => {
setActive(e.isActive);
});
const disposable2 = props.containerApi.onDidActiveGroupChange((e) => {
setGroupActive(props.api.isGroupActive);
});
return () => {
disposable1.dispose();
disposable2.dispose();
};
}, [props.api]);
return (
<div
style={{
display: 'flex',
padding: '0px 8px',
alignItems: 'center',
height: '100%',
}}
>
<span style={{ padding: '0px 8px', flexGrow: 1 }}>
{props.api.title}
</span>
<span
className=""
onClick={() => props.api.setActive()}
style={{
display: 'flex',
alignItems: 'center',
paddingRight: '8px',
}}
>
{'✕'}
</span>
</div>
);
};
const tabComponents = {
default: DefaultTab,
};
export const ContextMenuDockview = () => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
tabComponent: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
tabComponent: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
tabComponent: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
tabComponent: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
};
return (
<DockviewReact
components={components}
tabComponents={tabComponents}
onReady={onReady}
className="dockview-theme-dark"
/>
);
};

View File

@ -0,0 +1,58 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
//
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const CustomCSSDockview = () => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
};
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-vs"
/>
);
};

View File

@ -0,0 +1,105 @@
import {
DockviewDndOverlayEvent,
DockviewDropEvent,
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return (
<div style={{ padding: '20px' }}>
<div>{props.params.title}</div>
</div>
);
},
};
export const DndDockview = (props: { renderVisibleOnly: boolean }) => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
};
const onDidDrop = (event: DockviewDropEvent) => {
const { group } = event;
event.api.addPanel({
id: 'test',
component: 'default',
position: {
referencePanel: group.activePanel.id,
direction: 'within',
},
});
};
const showDndOverlay = (event: DockviewDndOverlayEvent) => {
return true;
};
return (
<>
<div
style={{
backgroundColor: 'orange',
padding: '0px 8px',
borderRadius: '4px',
width: '100px',
cursor: 'pointer',
}}
draggable={true}
>
Drag me
</div>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-dark"
onDidDrop={onDidDrop}
showDndOverlay={showDndOverlay}
/>
</div>
</>
);
};

View File

@ -0,0 +1,342 @@
import {
Orientation,
DockviewReact,
DockviewReadyEvent,
DockviewApi,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
import { Console, Line } from '../console/console';
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const EventsDockview = () => {
const [lines, setLines] = React.useState<Line[]>([]);
const [checked, setChecked] = React.useState<boolean>(false);
const [api, setApi] = React.useState<DockviewApi | undefined>();
React.useEffect(() => {
if (!api) {
return () => {
//noop
};
}
const disposables = [
api.onDidAddPanel((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidAddPanel: ${panel.id}`,
},
]);
}),
api.onDidRemovePanel((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidRemovePanel: ${panel.id}`,
},
]);
}),
api.onDidActivePanelChange((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidActivePanelChange: ${panel?.id}`,
},
]);
}),
api.onDidAddGroup((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidAddGroup: ${panel.id}`,
},
]);
}),
api.onDidRemoveGroup((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidRemoveGroup: ${panel.id}`,
},
]);
}),
api.onDidActiveGroupChange((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidActiveGroupChange: ${panel?.id}`,
},
]);
}),
api.onDidLayoutChange((panel) => {
setLines((lines) => [
...lines,
{ timestamp: new Date(), text: `onDidLayoutChange` },
]);
}),
api.onDidLayoutFromJSON((panel) => {
setLines((lines) => [
...lines,
{ timestamp: new Date(), text: `onDidLayoutFromJSON` },
]);
}),
];
return () => {
disposables.forEach((disposable) => disposable.dispose());
};
}, [api]);
React.useEffect(() => {
if (!api) {
return;
}
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `Rebuilding view fromJSON:${checked}`,
css: { color: 'yellow', backgroundColor: 'grey' },
},
]);
if (checked) {
api.fromJSON({
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel_1', 'panel_2', 'panel_3'],
activeView: 'panel_3',
id: '77',
},
size: 262,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel_5'],
activeView: 'panel_5',
id: '79',
},
size: 100,
},
{
type: 'leaf',
data: {
views: ['panel_6', 'panel_8'],
activeView: 'panel_8',
id: '80',
},
size: 100,
},
{
type: 'leaf',
data: {
views: ['panel_7'],
activeView: 'panel_7',
id: '81',
},
size: 100,
},
],
size: 262,
},
{
type: 'leaf',
data: {
views: ['panel_4'],
activeView: 'panel_4',
id: '78',
},
size: 263.75,
},
],
size: 300,
},
width: 787.75,
height: 300,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel_1: {
id: 'panel_1',
view: { content: { id: 'default' } },
params: { title: 'Panel 1' },
title: 'panel_1',
},
panel_2: {
id: 'panel_2',
view: { content: { id: 'default' } },
params: { title: 'Panel 2' },
title: 'panel_2',
},
panel_3: {
id: 'panel_3',
view: { content: { id: 'default' } },
params: { title: 'Panel 3' },
title: 'panel_3',
},
panel_4: {
id: 'panel_4',
view: { content: { id: 'default' } },
params: { title: 'Panel 4' },
title: 'panel_4',
},
panel_5: {
id: 'panel_5',
view: { content: { id: 'default' } },
params: { title: 'Panel 5' },
title: 'panel_5',
},
panel_6: {
id: 'panel_6',
view: { content: { id: 'default' } },
params: { title: 'Panel 6' },
title: 'panel_6',
},
panel_8: {
id: 'panel_8',
view: { content: { id: 'default' } },
params: { title: 'Panel 8' },
title: 'panel_8',
},
panel_7: {
id: 'panel_7',
view: { content: { id: 'default' } },
params: { title: 'Panel 7' },
title: 'panel_7',
},
},
activeGroup: '80',
options: {},
});
return;
}
api.clear();
api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
api.addPanel({
id: 'panel_5',
component: 'default',
params: {
title: 'Panel 5',
},
position: { referencePanel: 'panel_3', direction: 'right' },
});
api.addPanel({
id: 'panel_6',
component: 'default',
params: {
title: 'Panel 6',
},
position: { referencePanel: 'panel_5', direction: 'below' },
});
api.addPanel({
id: 'panel_7',
component: 'default',
params: {
title: 'Panel 7',
},
position: { referencePanel: 'panel_6', direction: 'below' },
});
api.addPanel({
id: 'panel_8',
component: 'default',
params: {
title: 'Panel 8',
},
position: { referencePanel: 'panel_6', direction: 'within' },
});
}, [api, checked]);
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
};
return (
<>
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
<span>{'fromJSON'}</span>
</label>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-dark"
/>
</div>
<Console lines={lines} />
</>
);
};

View File

@ -0,0 +1,158 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
PanelApi,
} from 'dockview';
import * as React from 'react';
import { atom, useRecoilState, useRecoilValue } from 'recoil';
const renderVisibleComponentsOnlyAtom = atom<boolean>({
key: 'renderVisibleComponentsOnlyAtom',
default: false,
});
function RenderWhenVisible<
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> }
>(component: React.FunctionComponent<T>) {
const HigherOrderComponent = (props: T) => {
const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible
);
const render = useRecoilValue(renderVisibleComponentsOnlyAtom);
React.useEffect(() => {
const disposable = props.api.onDidVisibilityChange((event) =>
setVisible(event.isVisible)
);
return () => {
disposable.dispose();
};
}, [props.api]);
if (!visible && render) {
return null;
}
return React.createElement(component, props);
};
return HigherOrderComponent;
}
const formatLine = (line: string) => {
const now = new Date();
const pad = (x: number) => (x < 10 ? `0${x}` : `${x}`);
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(
now.getSeconds()
)}`;
return `[${time}] ${line}`;
};
const components = {
default: RenderWhenVisible(
(props: IDockviewPanelProps<{ title: string }>) => {
const [lines, setLines] = React.useState<string[]>([
formatLine('Component created'),
]);
React.useEffect(() => {
setLines((lines) => [
...lines,
formatLine('Running task for 5 seconds'),
]);
const timeout = setTimeout(() => {
setLines((lines) => [
...lines,
formatLine('Task completed'),
]);
}, 5000);
return () => {
clearTimeout(timeout);
};
}, []);
return (
<div style={{ padding: '20px' }}>
<div>{props.params.title}</div>
{lines.map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
);
}
),
};
export const RenderingDockview = (props: { renderVisibleOnly: boolean }) => {
const [render, setRender] = useRecoilState(renderVisibleComponentsOnlyAtom);
React.useEffect(
() => setRender(props.renderVisibleOnly),
[props.renderVisibleOnly]
);
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
};
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-dark"
/>
);
};
export const Checkbox = () => {
const [render, setRender] = useRecoilState(renderVisibleComponentsOnlyAtom);
return (
<label>
Render only when visible
<input
type="checkbox"
checked={render}
onChange={(e) => setRender(e.target.checked)}
/>
</label>
);
};

View File

@ -0,0 +1,339 @@
import {
IGridviewPanelProps,
Orientation,
GridviewReact,
GridviewReadyEvent,
GridviewApi,
} from 'dockview';
import * as React from 'react';
import { Console, Line } from '../console/console';
const components = {
default: (props: IGridviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const EventsGridview = () => {
const [lines, setLines] = React.useState<Line[]>([]);
const [checked, setChecked] = React.useState<boolean>(false);
const [api, setApi] = React.useState<GridviewApi | undefined>();
React.useEffect(() => {
if (!api) {
return () => {
//noop
};
}
const disposables = [
api.onDidAddPanel((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidAddPanel: ${panel.id}`,
},
]);
}),
api.onDidRemovePanel((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidRemovePanel: ${panel.id}`,
},
]);
}),
api.onDidActivePanelChange((panel) => {
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `onDidActivePanelChange: ${panel?.id}`,
},
]);
}),
api.onDidLayoutChange((panel) => {
setLines((lines) => [
...lines,
{ timestamp: new Date(), text: `onDidLayoutChange` },
]);
}),
api.onDidLayoutFromJSON((panel) => {
setLines((lines) => [
...lines,
{ timestamp: new Date(), text: `onDidLayoutFromJSON` },
]);
}),
];
return () => {
disposables.forEach((disposable) => disposable.dispose());
};
}, [api]);
React.useEffect(() => {
if (!api) {
return;
}
setLines((lines) => [
...lines,
{
timestamp: new Date(),
text: `Rebuilding view fromJSON:${checked}`,
css: { color: 'yellow', backgroundColor: 'grey' },
},
]);
if (checked) {
api.fromJSON({
grid: {
root: {
type: 'branch',
data: [
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
id: 'panel_3',
component: 'default',
params: { title: 'Panel 3' },
snap: false,
},
size: 394,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
id: 'panel_5',
component: 'default',
params: {
title: 'Panel 5',
},
snap: false,
},
size: 50,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
id: 'panel_6',
component:
'default',
params: {
title: 'Panel 6',
},
minimumWidth: 10,
snap: false,
},
size: 131,
},
{
type: 'leaf',
data: {
id: 'panel_8',
component:
'default',
params: {
title: 'Panel 8',
},
minimumWidth: 10,
snap: false,
},
size: 131,
},
{
type: 'leaf',
data: {
id: 'panel_7',
component:
'default',
params: {
title: 'Panel 7',
},
minimumWidth: 10,
snap: false,
},
size: 132,
},
],
size: 50,
},
],
size: 394,
},
],
size: 100,
},
{
type: 'leaf',
data: {
id: 'panel_2',
component: 'default',
params: { title: 'Panel 2' },
snap: false,
},
size: 100,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
id: 'panel_1',
component: 'default',
params: { title: 'Panel 1' },
snap: false,
},
size: 394,
},
{
type: 'leaf',
data: {
id: 'panel_4',
component: 'default',
params: { title: 'Panel 4' },
snap: false,
},
size: 394,
},
],
size: 100,
},
],
size: 788,
},
width: 788,
height: 300,
orientation: Orientation.VERTICAL,
},
activePanel: 'panel_8',
});
return;
}
api.clear();
api.orientation = Orientation.VERTICAL;
api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
console.log('sdf');
api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
api.addPanel({
id: 'panel_5',
component: 'default',
params: {
title: 'Panel 5',
},
position: { referencePanel: 'panel_3', direction: 'right' },
});
api.addPanel({
id: 'panel_6',
component: 'default',
params: {
title: 'Panel 6',
},
position: { referencePanel: 'panel_5', direction: 'below' },
minimumWidth: 10,
});
api.addPanel({
id: 'panel_7',
component: 'default',
params: {
title: 'Panel 7',
},
position: { referencePanel: 'panel_6', direction: 'right' },
minimumWidth: 10,
});
api.addPanel({
id: 'panel_8',
component: 'default',
params: {
title: 'Panel 8',
},
position: { referencePanel: 'panel_6', direction: 'right' },
minimumWidth: 10,
});
}, [api, checked]);
const onReady = (event: GridviewReadyEvent) => {
setApi(event.api);
};
return (
<>
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(e.target.checked)}
/>
<span>{'fromJSON'}</span>
</label>
<div
style={{
height: '300px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
margin: '20px 0px',
}}
>
<GridviewReact
components={components}
onReady={onReady}
proportionalLayout={false}
orientation={Orientation.VERTICAL}
className="dockview-theme-dark"
/>
</div>
<Console lines={lines} />
</>
);
};

View File

@ -0,0 +1,2 @@
import * as React from 'react';
const URL = 'https://api.github.com/repos/mathuo/dockview/releases';

View File

@ -0,0 +1,116 @@
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
PanelApi,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
const RenderWhenVisible = <T,>(
props: T & {
children: React.FunctionComponent<T>;
api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'>;
}
) => {
const [visible, setVisible] = React.useState<boolean>(props.api.isVisible);
React.useEffect(() => {
const disposable = props.api.onDidVisibilityChange((event) =>
setVisible(event.isVisible)
);
return () => {
disposable.dispose();
};
}, [props.api]);
if (!visible) {
return null;
}
return React.createElement(props.children, props);
};
export const SimpleDockview = () => {
const onReady = (event: DockviewReadyEvent) => {
const panel = event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
panel.group.locked = true;
panel.group.header.hidden = true;
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
const panel5 = event.api.addPanel({
id: 'panel_5',
component: 'default',
params: {
title: 'Panel 5',
},
position: { referencePanel: 'panel_3', direction: 'right' },
});
// panel5.group!.model.header.hidden = true;
// panel5.group!.model.locked = true;
event.api.addPanel({
id: 'panel_6',
component: 'default',
params: {
title: 'Panel 6',
},
position: { referencePanel: 'panel_5', direction: 'below' },
});
event.api.addPanel({
id: 'panel_7',
component: 'default',
params: {
title: 'Panel 7',
},
position: { referencePanel: 'panel_6', direction: 'right' },
});
};
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-dark"
/>
);
};

View File

@ -0,0 +1,99 @@
import {
IGridviewPanelProps,
Orientation,
GridviewReact,
GridviewReadyEvent,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IGridviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const SimpleGridview = () => {
const onReady = (event: GridviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
});
event.api.addPanel({
id: 'panel_4',
component: 'default',
params: {
title: 'Panel 4',
},
position: { referencePanel: 'panel_1', direction: 'right' },
});
event.api.addPanel({
id: 'panel_5',
component: 'default',
params: {
title: 'Panel 5',
},
position: { referencePanel: 'panel_3', direction: 'right' },
});
event.api.addPanel({
id: 'panel_6',
component: 'default',
params: {
title: 'Panel 6',
},
position: { referencePanel: 'panel_5', direction: 'below' },
minimumWidth: 10,
});
event.api.addPanel({
id: 'panel_7',
component: 'default',
params: {
title: 'Panel 7',
},
position: { referencePanel: 'panel_6', direction: 'right' },
minimumWidth: 10,
});
event.api.addPanel({
id: 'panel_8',
component: 'default',
params: {
title: 'Panel 8',
},
position: { referencePanel: 'panel_6', direction: 'right' },
minimumWidth: 10,
});
};
return (
<GridviewReact
components={components}
onReady={onReady}
proportionalLayout={false}
orientation={Orientation.VERTICAL}
className="dockview-theme-dark"
/>
);
};

View File

@ -0,0 +1,102 @@
import {
IPaneviewPanelProps,
PaneviewReact,
PaneviewReadyEvent,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IPaneviewPanelProps<{ title: string }>) => {
return (
<div
style={{
padding: '10px',
height: '100%',
backgroundColor: 'rgb(60,60,60)',
}}
>
{props.params.title}
</div>
);
},
};
const MyHeaderComponent = (props: IPaneviewPanelProps<{ title: string }>) => {
const [expanded, setExpanded] = React.useState<boolean>(
props.api.isExpanded
);
React.useEffect(() => {
const disposable = props.api.onDidExpansionChange((event) => {
setExpanded(event.isExpanded);
});
return () => {
disposable.dispose();
};
}, []);
const onClick = () => {
props.api.setExpanded(!expanded);
};
return (
<div
style={{
padding: '10px',
height: '100%',
backgroundColor: 'rgb(60,60,60)',
}}
>
<a
onClick={onClick}
className={expanded ? 'expanded' : 'collapsed'}
/>
<span>{props.params.title}</span>
</div>
);
};
const headerComponents = {
myHeaderComponent: MyHeaderComponent,
};
export const SimplePaneview = () => {
const onReady = (event: PaneviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
title: 'Panel 1',
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
title: 'Panel 2',
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
title: 'Panel 3',
});
};
return (
<PaneviewReact
components={components}
headerComponents={headerComponents}
onReady={onReady}
className="dockview-theme-dark"
/>
);
};

View File

@ -0,0 +1,54 @@
import {
ISplitviewPanelProps,
Orientation,
SplitviewReact,
SplitviewReadyEvent,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: ISplitviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
},
};
export const SimpleSplitview = (props: { proportional?: boolean }) => {
const onReady = (event: SplitviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
minimumSize: 100,
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
minimumSize: 100,
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
minimumSize: 100,
});
};
return (
<SplitviewReact
components={components}
proportionalLayout={props.proportional}
onReady={onReady}
orientation={Orientation.HORIZONTAL}
className="dockview-theme-dark"
/>
);
};

View File

@ -0,0 +1,60 @@
import { SimpleSplitview } from './simpleSplitview';
import * as React from 'react';
export const SimpleSplitview2 = (props: { proportional?: boolean }) => {
const [value, setValue] = React.useState<number>(50);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(Number(event.target.value));
};
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100px',
margin: '10px 0px',
}}
>
<div
style={{
height: '25px',
display: 'flex',
alignItems: 'center',
}}
>
<input
type={'range'}
min={20}
max={100}
defaultValue={50}
value={value}
onChange={onChange}
/>
<span style={{ padding: '0px 8px' }}>
Slide to resize the splitview container
</span>
</div>
<div
style={{
flexGrow: 1,
display: 'grid',
gridTemplateColumns: `${value}fr ${100 - value}fr`,
}}
>
<div
style={{
backgroundColor: 'rgb(30,30,30)',
color: 'white',
flexGrow: 1,
border: '1px solid grey',
}}
>
<SimpleSplitview proportional={props.proportional} />
</div>
<div></div>
</div>
</div>
);
};

View File

@ -0,0 +1,161 @@
import {
ISplitviewPanel,
ISplitviewPanelProps,
Orientation,
SplitviewReact,
SplitviewReadyEvent,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: ISplitviewPanelProps<{ title: string }>) => {
const [active, setActive] = React.useState<boolean>(props.api.isActive);
const [visible, setVisible] = React.useState<boolean>(
props.api.isVisible
);
const [focused, setFocused] = React.useState<boolean>(
props.api.isFocused
);
const [dimensions, setDimensions] = React.useState<{
height: number;
width: number;
}>({
height: props.api.height,
width: props.api.width,
});
React.useEffect(() => {
const disposable1 = props.api.onDidActiveChange((event) =>
setActive(event.isActive)
);
const disposable2 = props.api.onDidVisibilityChange((event) =>
setVisible(event.isVisible)
);
const disposable3 = props.api.onDidFocusChange((event) =>
setFocused(event.isFocused)
);
const disposable4 = props.api.onDidDimensionsChange((event) => {
setDimensions({ height: event.height, width: event.width });
});
return () => {
disposable1.dispose();
disposable2.dispose();
disposable3.dispose();
disposable4.dispose();
};
}, []);
return (
<div
style={{
padding: '20px',
display: 'grid',
gridTemplateColumns: '100px 100px',
lineHeight: '20px',
gridTemplateRows: 'repeat(6, 20px)',
}}
>
<span>{'Panel ID: '}</span>
<span>{props.api.id}</span>
<span>{'Height: '}</span>
<span>{`${dimensions.height}px`}</span>
<span>{'Width: '}</span>
<span>{`${dimensions.width}px`}</span>
<span>{'Focused: '}</span>
<span style={{ color: focused ? 'green' : 'red' }}>{`${
focused ? 'True' : 'False'
}`}</span>
<span>{'Active: '}</span>
<span style={{ color: active ? 'green' : 'red' }}>{`${
active ? 'True' : 'False'
}`}</span>
<span>{'Visible: '}</span>
<span style={{ color: visible ? 'green' : 'red' }}>{`${
visible ? 'True' : 'False'
}`}</span>
</div>
);
},
};
export const SplitviewExample1 = (props: { proportional?: boolean }) => {
const [panels, setPanels] = React.useState<ISplitviewPanel[]>([]);
const onReady = React.useCallback((event: SplitviewReadyEvent) => {
event.api.onDidAddView((panel) => setPanels(event.api.panels));
event.api.onDidRemoveView((panel) => setPanels(event.api.panels));
event.api.addPanel({
id: 'panel_1',
component: 'default',
params: {
title: 'Panel 1',
},
minimumSize: 100,
});
event.api.addPanel({
id: 'panel_2',
component: 'default',
params: {
title: 'Panel 2',
},
minimumSize: 100,
});
event.api.addPanel({
id: 'panel_3',
component: 'default',
params: {
title: 'Panel 3',
},
minimumSize: 100,
});
}, []);
return (
<>
<div
style={{
height: '150px',
backgroundColor: 'rgb(30,30,30)',
color: 'white',
}}
>
<SplitviewReact
components={components}
proportionalLayout={props.proportional}
onReady={onReady}
orientation={Orientation.HORIZONTAL}
className="dockview-theme-dark"
/>
</div>
<div style={{ height: '20px', display: 'flex' }}>
{panels.map((panel) => {
return (
<div style={{ padding: '0px 20px' }}>
<div>{panel.id}</div>
<div>
<button
onClick={() =>
panel.api.setVisible(
!panel.api.isVisible
)
}
>
Toggle Visiblity
</button>
<button onClick={() => panel.api.setActive()}>
Set Active
</button>
</div>
</div>
);
})}
</div>
</>
);
};

View File

@ -0,0 +1,63 @@
.splitview-math-blog {
.sash {
background-color: orange;
position: absolute;
top: 0px;
width: 4px;
height: 100%;
z-index: 2;
cursor: ew-resize;
user-select: none;
}
.debug-sash-max {
height: 10px;
width: 1px;
position: absolute;
z-index: 999;
top: -10px;
}
.debug-sash-min {
height: 10px;
width: 1px;
position: absolute;
z-index: 999;
top: 100%;
}
.debug-sash-text {
height: 20px;
line-height: 20px;
width: 80px;
display: flex;
justify-content: center;
position: absolute;
z-index: 999;
font-size: 14px;
}
.sash-container {
position: absolute;
height: 100%;
}
.view-container {
position: relative;
height: 100%;
}
.view {
position: absolute;
height: 100%;
background-color: dodgerblue;
z-index: 1;
top: 0px;
padding: 10px;
color: white;
}
.sash.drag-sash {
background-color: red;
}
}

View File

@ -0,0 +1,342 @@
import * as React from 'react';
import './math.scss';
const min = 100;
const max = 300;
interface IDebugResize {
leftmin: number;
leftmax: number;
rightmin: number;
rightmax: number;
min: number;
max: number;
}
const resize = (
index: number,
delta: number,
sizes: number[],
mode: number
) => {
const nextSizes = [...sizes];
const left = nextSizes.filter((_, i) => i <= index);
const right = nextSizes.filter((_, i) => i > index);
let result: IDebugResize = {
leftmin: undefined,
leftmax: undefined,
rightmin: undefined,
rightmax: undefined,
max: undefined,
min: undefined,
};
// step 3
if (mode > 2) {
const leftMinimumsDelta = left
.map((x) => min - x)
.reduce((x, y) => x + y, 0);
const leftMaximumsDelta = left
.map((x) => max - x)
.reduce((x, y) => x + y, 0);
const rightMinimumsDelta = right
.map((x) => x - min)
.reduce((x, y) => x + y, 0);
const rightMaximumsDelta = right
.map((x) => x - max)
.reduce((x, y) => x + y, 0);
const _min = Math.max(leftMinimumsDelta, rightMaximumsDelta);
const _max = Math.min(leftMaximumsDelta, rightMinimumsDelta);
const clamp = Math.max(_min, Math.min(_max, delta));
result = {
leftmin: leftMinimumsDelta,
leftmax: leftMaximumsDelta,
rightmin: rightMinimumsDelta,
rightmax: rightMaximumsDelta,
max: _max,
min: _min,
};
delta = clamp;
}
let usedDelta = 0;
let remainingDelta = delta;
// Step 1
for (let i = left.length - 1; i > -1; i--) {
const x = Math.max(min, Math.min(max, left[i] + remainingDelta));
const viewDelta = x - left[i];
usedDelta += viewDelta;
remainingDelta -= viewDelta;
left[i] = x;
}
// Step 2
if (mode > 1) {
for (let i = 0; i < right.length; i++) {
const x = Math.max(min, Math.min(max, right[i] - usedDelta));
const viewDelta = x - right[i];
usedDelta += viewDelta;
right[i] = x;
}
}
return { ...result, sizes: [...left, ...right] };
};
interface ILayoutState {
sashes: number[];
views: number[];
deltas: number[];
left: number;
right: number;
debug: IDebugResize;
drag: number;
}
export const Splitview = (props: { mode: number; debug: boolean }) => {
// keep the sashes and views in one state to prevent weird out-of-sync-ness
const [layout, setLayout] = React.useState<ILayoutState>({
sashes: [200, 400],
views: [200, 200, 200],
deltas: [0, 0, 0],
left: 0,
right: 0,
debug: undefined,
drag: -1,
});
const ref = React.useRef<HTMLDivElement>();
const onMouseDown = (index: number) => (ev: React.MouseEvent) => {
const start = ev.clientX;
const sizes = [...layout.views];
const mousemove = (ev: MouseEvent) => {
const current = ev.clientX;
const delta = current - start;
const {
sizes: nextLayout,
rightmin,
rightmax,
leftmin,
leftmax,
max,
min,
} = resize(index, delta, sizes, props.mode);
const sashes = nextLayout.reduce(
(x, y) => [...x, y + (x.length === 0 ? 0 : x[x.length - 1])],
[]
);
sashes.splice(sashes.length - 1, 1);
const deltas = sizes.map((x, i) => nextLayout[i] - x);
const offset = start - ref.current?.getBoundingClientRect().left;
setLayout({
views: nextLayout,
sashes,
deltas,
left: deltas
.filter((_, i) => i <= index)
.reduce((x, y) => x + y, 0),
right: deltas
.filter((_, i) => i > index)
.reduce((x, y) => x + y, 0),
debug: {
leftmax: leftmax + offset,
leftmin: leftmin + offset,
rightmax: rightmax + offset,
rightmin: rightmin + offset,
min: min + offset,
max: max + offset,
},
drag: index,
});
};
const end = (ev: MouseEvent) => {
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', end);
setLayout((_) => ({
..._,
deltas: _.deltas.map((_) => 0),
left: 0,
right: 0,
drag: -1,
}));
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', end);
};
const extras = React.useMemo(() => {
if (!props.debug || !layout.debug || props.mode < 3) {
return null;
}
return (
<>
<div
style={{
left: `${layout.debug.leftmax - 40}px`,
top: '-30px',
}}
className="debug-sash-text"
>
left-max
</div>
<div
style={{
left: `${layout.debug.leftmin - 40}px`,
top: '-30px',
}}
className="debug-sash-text"
>
left-min
</div>
<div
style={{
left: `${layout.debug.rightmax - 40}px`,
bottom: '-30px',
}}
className="debug-sash-text"
>
right-max
</div>
<div
style={{
left: `${layout.debug.rightmin - 40}px`,
bottom: '-30px',
}}
className="debug-sash-text"
>
right-min
</div>
<div
className="debug-sash-max"
style={{
left: `${layout.debug.leftmax - 1}px`,
border: '2px solid purple',
}}
/>
<div
className="debug-sash-max"
style={{
left: `${layout.debug.leftmin - 1}px`,
border: '2px solid green',
}}
/>
<div
className="debug-sash-min"
style={{
left: `${layout.debug.rightmax - 1}px`,
border: '2px solid cyan',
}}
/>
<div
className="debug-sash-min"
style={{
left: `${layout.debug.rightmin - 1}px`,
border: '2px solid pink',
}}
/>
</>
);
}, [layout.debug]);
return (
<div
className="splitview-math-blog"
style={{
marginBottom: '40px',
marginTop: '30px',
backgroundColor: 'gray',
}}
>
{props.debug && (
<div style={{ marginBottom: extras ? '25px' : '0px' }}>
<span>{`Change to left ${layout?.left}`}</span>
<span
style={{
marginLeft: '10px',
backgroundColor:
-layout?.right !== layout?.left ? 'red' : '',
}}
>{`Change to right ${layout?.right}`}</span>
<span
style={{ marginLeft: '10px' }}
>{`Total size ${layout?.views.reduce(
(x, y) => x + y,
0
)}`}</span>
</div>
)}
<div
ref={ref}
style={{
height: '100px',
width: '100%',
position: 'relative',
backgroundColor: 'dimgray',
}}
>
<div className="sash-container">
{layout.sashes.map((x, i) => {
const className =
layout.drag === i ? 'sash drag-sash' : 'sash';
return (
<div
key={i}
onMouseDown={onMouseDown(i)}
style={{
left: `${x - 2}px`,
}}
className={className}
></div>
);
})}
{extras}
</div>
<div className="view-container">
{layout.views.map((x, i) => {
const isMax = x >= max;
const isMin = x <= min;
return (
<div
key={i}
style={{
left: `${
i === 0 ? 0 : layout.sashes[i - 1]
}px`,
width: `${x}px`,
}}
className="view"
>
{props.debug && (
<>
<div>
{`${layout.views[i]} (${
layout.deltas[i] > -1 ? '+' : ''
}${layout.deltas[i]})`}
</div>
<div
style={{ fontSize: '12px' }}
>{`isMin = ${isMin}`}</div>
<div
style={{ fontSize: '12px' }}
>{`isMax = ${isMax}`}</div>
</>
)}
</div>
);
})}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,32 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
@import "~dockview/dist/styles/dockview.css"

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -0,0 +1,378 @@
import { Splitview } from '../../src/components/splitview/math';
Wondering how to create a resizable split view, pane or window; whichever you may call it?
Here's a langauge indepedant walkthrough of how to design a split view control from scratch and the simple math behind it.
You will find links to all examples which are written using TypeScript.
Here's the end result and you can continue to read on to see how we got here.
<Splitview mode={3} debug={false} />
## First of all, what is a split view control?
Not all split view controls are born equal, but for our purposes
> A split view control is comprised from a number of 'views' layed out either horizontally or vertically.
> Each view can be indepedantly resized by dragging on the edge of a particular view.
In order to explain how this works we are going to need some more in-depth definitions so lets start with the two
fundamental components of this control, the <b>View</b> and the <b>Sash</b> (I will explain).
The below assumes the split view control has <b>n</b> views, where n is positive number.
For example if n=4 then our split view controls has 4 views. This generic approach will make it much easier to explain going forwards.
<div>
<div>View</div>
<ul style={{ marginLeft: '40px' }}>
<li>
The size of the n<sup>th</sup> view will be known as V<sub>n</sub>
</li>
<li>
The minimum size of the n<sup>th</sup> view will be known as V
<sup>min</sup>
<sub>n</sub>
</li>
<li>
The maximum size of the n<sup>th</sup> view will be known as V
<sup>max</sup>
<sub>n</sub>
</li>
</ul>
</div>
Additional by definition we can known V<sup>min</sup><sub>n</sub> <= V<sub>n</sub> <= V<sup>max</sup><sub>n</sub>
To be able to resize a view we need to be able to drag on the edge of a view to increase or decrease it's size.
This can be achieved by introducing a narrow component that sits between each view acting as a 'drag handle'.
We will call this component a <b>Sash</b> because sash is also the word for those [windows](https://en.wikipedia.org/wiki/Sash_window) with movable panels.
<div>
<div>Sash</div>
<ul style={{marginLeft: "40px"}}>
<li>If we have n views then we will have n-1 sashes. There is no sash before V<sub>0</sub> nor after V<sub>n</sub></li>
<li>The sash between V<sub>n</sub> and V<sub>n+1</sub> is known as S<sub>n</sub></li>
<li>The sash is of fixed width, and it's sole purpose is to act a drag-handle for resizing views</li>
</ul>
</div>
This now gives us a definition of the split view control but in additional to that, to calculate the new view sizes
after a sash is dragged we need to know which sash is being dragged.
We will denote the sash S<sub>i</sub> as the sash to drag going forwards we can reference the following diagram.
![alt txt](./visual_1.jpg)
If we are to drag the sash S<sub>i</sub> then we need to also know how far along the x-axis, or the horizontal-axis we have travelled.
We can denote this as the delta, using the symbol Δ.
Delta is only limited by how wide the screen is so for this purpose we can say it ranges from negative to positive infinity, that is -∞ < Δ < ∞ .
In reality as you will see we will apply a set of constraints on the value of Δ reducing it's overall set of valid values.
## Iteration #1 - The naive approach (aka. the accordian)
As I add delta I increase view sizes and as I remove delta I decrease view sizes.
A rather naive approach but it could look something like this:
- as the sash moves left shrink each view to the left and as the sash moves right expand each view to left, from right-most to left-most in both caes.
- If there is enough delta to shrink a view to it's mimimum size then move onto the next view, and if we have enough delta to expand a view to it's maximum size then again move onto the next view.
- Shrink no more once everything to the left is at minimums and expand no more once everything to the left is at maximums
- We don't manipulate any views to the right of the active sash S<sub>i</sub>
You should be able to show each of the four points above hold true for the below interactive example.
You'll see that changes to the right will always remain at zero because we are not manipulating views to the right of the active sash.
<Splitview mode={1} debug={true} />
There are some obvious flaws with this approach but before we go into that lets put this implemenation in psuedocode using our definitions
from above where we drag sash S<sub>i</sub> by an amount Δ
<div
style={{
display: 'inline-block',
marginLeft: '20px',
marginBottom: '20px',
borderLeft: '2px solid black',
}}
>
<div className="markdown-line">
Δ<sub>remaining</sub> = Δ
</div>
<div className="markdown-line">
<span style={{ fontWeight: 'bold' }}>for</span>
<span>
(<span style={{ fontStyle: 'italic' }}>j = i; j >= 0; i--</span>)
</span>
<span style={{ fontWeight: 'bold' }}> do</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sup>next</sup>
<sub>j</sub> = <span style={{ fontWeight: 'bold' }}>Min</span>(V<sup>
max
</sup>
<sub>j</sub>, <span style={{ fontWeight: 'bold' }}>Max</span>(V
<sup>min</sup>
<sub>j</sub>, V<sub>j</sub> + Δ<sub>remaining</sub>))
</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sup>Δ</sup>
<sub>j</sub> = V<sup>next</sup>
<sub>j</sub> - V<sub>j</sub>
</span>
</div>
<div className="markdown-line indent-1">
<span>
Δ<sub>remaining</sub> = Δ<sub>remaining</sub> - V<sup>Δ</sup>
<sub>j</sub>
</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sub>j</sub> = V<sup>next</sup>
<sub>j</sub>
</span>
</div>
</div>
or in plain text
<div
style={{
borderLeft: '2px solid black',
margin: '0px 20px 20px 20px',
display: 'flex',
flexDirection: 'row',
}}
>
<div
style={{ borderRight: '2px solid black', flexShrink: 0, width: '20px' }}
>
<div style={{ display: 'flex', justifyContent: 'center' }}>1</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>2</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>3</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>4</div>
</div>
<div style={{ flexGrow: 1, marginLeft: '20px' }}>
<div>
For each view j to the left of the sash we have dragged, from
right-most to left-most
</div>
<div>
Add the delta to the view j (clamped at either the maximum or
minimum value)
</div>
<div>
Subtract the different between the new and old size (the used delta)
from the remaining delta
</div>
<div>repeat</div>
</div>
</div>
Going back to that obvious flaw, the width of the control does not remain constant. It flexes with Δ.
## Iteration #2 - Δ is added. Then Δ must be removed
We want a component of constant width and we dont want to think too hard.
So If I have added Δ to the left then I should add -Δ (or remove Δ) on the right, and vice-versa; right? Nearly.
<Splitview mode={2} debug={true} />
There are some more subtle flaws with this approach; but lets describe it in psuedocode first.
We now need another variable to track the delta we've added on the left, Δ<sub>used</sub>.
After we've applied changes to the left side we'll substract this Δ<sub>used</sub> from the right side which should keep the width of our control at a
constant size.
<div style={{display: "inline-block", marginLeft: "20px", marginBottom: "20px", borderLeft: "2px solid black"}}>
<div className="markdown-line">
Δ<sub>remaining</sub> = Δ
</div>
<div className="markdown-line markdown-highlight">
Δ<sub>used</sub> = 0
</div>
<div className="markdown-line">
<span style={{fontWeight: "bold"}}>for</span><span>(<span style={{fontStyle: "italic"}}>j = i; j >= 0; i--</span>)</span>
<span style={{fontWeight: "bold"}}> do</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sup>next</sup><sub>j</sub> = <span style={{fontWeight: "bold"}}>Min</span>(V<sup>max</sup><sub>j</sub>, <span style={{fontWeight: "bold"}}>Max</span>(V<sup>min</sup><sub>j</sub>, V<sub>j</sub> + Δ<sub>remaining</sub>))
</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sup>Δ</sup><sub>j</sub> = V<sup>next</sup><sub>j</sub> - V<sub>j</sub>
</span>
</div>
<div className="markdown-line indent-1">
<span>
Δ<sub>remaining</sub> = Δ<sub>remaining</sub> - V<sup>Δ</sup><sub>j</sub>
</span>
</div>
<div className="markdown-line indent-1 markdown-highlight">
<span>
Δ<sub>used</sub> = Δ<sub>used</sub> + V<sup>Δ</sup><sub>j</sub>
</span>
</div>
<div className="markdown-line indent-1">
<span>
V<sub>j</sub> = V<sup>next</sup><sub>j</sub>
</span>
</div>
<div style={{height: "0px", width: "100%", marginBottom: "20px"}}/>
<div className="markdown-line markdown-highlight">
<span style={{fontWeight: "bold"}}>for</span><span>(<span style={{fontStyle: "italic"}}>{"j = i+1; j < n; i++"}</span>)</span>
<span style={{fontWeight: "bold"}}> do</span>
</div>
<div className="markdown-line markdown-highlight indent-1">
<span>
V<sup>next</sup><sub>j</sub> = <span style={{fontWeight: "bold"}}>Min</span>(V<sup>max</sup><sub>j</sub>, <span style={{fontWeight: "bold"}}>Max</span>(V<sup>min</sup><sub>j</sub>,V<sub>j</sub> - Δ<sub>used</sub>))
</span>
</div>
<div className="markdown-line markdown-highlight indent-1">
<span>
V<sup>Δ</sup><sub>j</sub> = V<sup>next</sup><sub>j</sub> - V<sub>j</sub>
</span>
</div>
<div className="markdown-line markdown-highlight indent-1">
<span>
Δ<sub>used</sub> = Δ<sub>used</sub> + V<sup>Δ</sup><sub>j</sub>
</span>
</div>
<div style={{marginBottom: "20px"}} className="markdown-line markdown-highlight indent-1">
<span>
V<sub>j</sub> = V<sup>next</sup><sub>j</sub>
</span>
</div>
</div>
Go back and try to minimise or maximise every view in the container. The width is no longer preserved, you can see at some point the change to the left
is not longer eqaul to the change on the right, which causes the container to once again flex.
## Iteration #3 - Constraining the values of Δ
In the cases where iteration #2 is not working correctly this can be explained as either adding or removing too much delta.
Fortunately it turns out there are a few constraints we can define on delta to help with this.
For a sash S<sub>i</sub> lets think about the minimum and maximum amount of delta we can use.
Try to follow the explainations first and then we will use another interactive example and diagram to reinforce the explaination.
> S<sub>i</sub> can go no further left that the sum of the minimum sizes of the views to the left, and can go no further right than the sum of the
> minimum sizes of the views to the right; because otherwise you would end up with at least one view that is smaller than it's specified minimum.
> Another less obvious condition on S<sub>i</sub> is that it can go no further left that the sum of the maximum sizes of the views to the right, and can go no further
> right than the sum of maximum sizes to the left; because otherwise you would end up with at least one view that is larger than it's specified maximum.
This leaves us with 4 constraints we need to apply on Δ; but since Δ is relative to S<sub>i</sub> first of all we need these
constraints relative to S<sub>i</sub>. That is to say what amount of Δ is equivalent to the constraints we just described.
When the views to the left of S<sub>i</sub> are all at minimum size we will call the distance between here and Δ to be Δ<sup>min</sup><sub>left</sub>.
This distance would be the sum of the differences between
V<sup>min</sup><sub>j</sub> and V<sub>j</sub> for each view, or in more format notation we could write this as
<div style={{ margin: '20px' }}>
<span>
Δ<sup>min</sup>
<sub>left</sub> = Σ V<sup>min</sup>
<sub>j</sub> - V<sub>j</sub>
</span>
<span style={{ marginLeft: '20px' }}>j = i,...0</span>
</div>
Similarly we can work out the distance between S<sub>i</sub> and the point at each every view to the left is at its
maximum size as the sum of differences between V<sup>max</sup><sub>j</sub> an V<sub>j</sub>
<div style={{ margin: '20px' }}>
<span>
Δ<sup>max</sup>
<sub>left</sub> = Σ V<sup>max</sup>
<sub>j</sub> - V<sub>j</sub>
</span>
<span style={{ marginLeft: '20px' }}>j = i,...0</span>
</div>
The same logic can be applied to work out those values for Δ<sup>min</sup><sub>right</sub> and Δ<sup>max</sup><sub>right</sub>
<div style={{ margin: '20px' }}>
<div style={{ marginBottom: '10px' }}>
<span>
Δ<sup>min</sup>
<sub>right</sub> = Σ V<sub>j</sub> - V<sup>min</sup>
<sub>j</sub>
</span>
<span style={{ marginLeft: '20px' }}>j = i+1...n</span>
</div>
<div>
<span>
Δ<sup>max</sup>
<sub>right</sub> = Σ V<sub>j</sub> - V<sup>min</sup>
<sub>j</sub>
</span>
<span style={{ marginLeft: '20px' }}>j = i+1...n</span>
</div>
</div>
We now have two minimum constraints which are V<sup>min</sup><sub>left</sub> and V<sup>max</sup><sub>right</sub> and two maximum
constraints V<sup>max</sup><sub>left</sub> and V<sup>min</sup><sub>right</sub>.
To get one minimum and maximum value for the Δ we should take the maximum of the two minimums and the minimum of the two maximums which will
ensure all four constraints will hold true.
<div style={{ margin: '20px' }}>
<div style={{ marginBottom: '10px' }}>
Δ<sub>min</sub> = MAX ( V<sup>min</sup>
<sub>left</sub> , V<sup>max</sup>
<sub>right</sub> )
</div>
<div>
Δ<sub>max</sub> = MIN ( V<sup>max</sup>
<sub>left</sub> , V<sup>min</sup>
<sub>right</sub> )
</div>
</div>
Finally we must clamp our Δ to be within this minimum and maxium boundary.
This clamped delta can be used in place of delta in the pseudocode from iteration #2.
<div style={{ margin: '20px' }}>
Δ<sub>clamped</sub> = MIN ( V<sub>max</sub> , MAX ( V<sub>min</sub> , Δ ) )
</div>
You can see how this works in this interactive example which also shows the current values of our four constraints for each sash drag event.
<Splitview mode={3} debug={true} />
Additionally you can see in the below diagram how all of our above calculations come together to give a minimum and maximum constraints on Δ.
![alt txt](./constraints.jpg)
## Appendix
### The clamp function
To clamp a value is to say given a value, a minimum and a maximum return
- the minimum, if value < minimum
- the maximum if value > maximum
- otherwise returning the value
In otherwords we have 'clamped' the value to be within our defined minimum and maximum values. Mathematically we can write this as
<div style={{ marginBottom: '20px' }} className="markdown-line">
<b>f</b>(value, value<sub>min</sub>, value<sub>max</sub>) = <b>MIN</b>(value
<sub>max</sub>, <b>MAX</b>(value<sub>min</sub>, value))
</div>
with some examples
<div style={{ marginLeft: '20px', marginBottom: '20px' }}>
<div className="markdown-small-line">
<b>f</b>(10, 20, 30) = 20
</div>
<div className="markdown-small-line">
<b>f</b>(40, 20, 30) = 30
</div>
<div className="markdown-small-line">
<b>f</b>(25, 20, 30) = 25
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,23 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,46 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import styles from './index.module.css';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import { SimpleDockview } from '../components/simpleDockview';
function HomepageHeader() {
const { siteConfig } = useDocusaurusContext();
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<h1 className="hero__title">{siteConfig.title}</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs"
>
Get Started
</Link>
</div>
</div>
</header>
);
}
export default function Home(): JSX.Element {
const { siteConfig } = useDocusaurusContext();
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />"
>
<HomepageHeader />
<main className="container">
<div style={{ height: '500px', padding: '20px 0px' }}>
<SimpleDockview />
</div>
{/* <HomepageFeatures /> */}
</main>
</Layout>
);
}

View File

@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

View File

@ -0,0 +1,7 @@
import React from 'react';
import { RecoilRoot } from 'recoil';
// Default implementation, that you can customize
export default function Root({ children }) {
return <RecoilRoot>{children}</RecoilRoot>;
}

BIN
build/docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
build/docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

1
build/docs/static/img/logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

7
build/docs/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
}
}

7764
build/docs/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

20
build/jest.config.base.js Normal file
View File

@ -0,0 +1,20 @@
const {join, normalize} = require("path");
const tsconfig = normalize(join(__dirname, "tsconfig.test.json"))
module.exports = {
displayName: { name: "root" },
preset: "ts-jest",
projects: ["<rootDir>/packages/*/jest.config.js"],
transform: {
"^.+\\.tsx?$":"ts-jest"
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
globals: {
"ts-jest": {
tsconfig,
experimental: true,
compilerHost: true
}
}
}

17
build/jest.config.js Normal file
View File

@ -0,0 +1,17 @@
const baseConfig = require("./jest.config.base");
module.exports = {
...baseConfig,
displayName: { name: "root", color: "blue" },
projects: ["<rootDir>/packages/*/jest.config.js"],
collectCoverage: true,
collectCoverageFrom:[
"<rootDir>/packages/*/src/**/*.{js,jsx,ts,tsx}",
],
coveragePathIgnorePatterns: [
"/node_modules/",
"<rootDir>packages/*/src/__tests__/",
],
coverageDirectory: "coverage",
testResultsProcessor: 'jest-sonar-reporter',
};

13
build/lerna.json Normal file
View File

@ -0,0 +1,13 @@
{
"packages": [
"packages/*"
],
"version": "1.4.2",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
"publish": {
"message": "chore(release): publish %s"
}
}
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "ES2020",
"declaration": true,
"target": "es6",
"moduleResolution": "node",
"esModuleInterop": true,
"downlevelIteration": true,
"incremental": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"lib": [
"ES2015",
"ES2016.Array.Include",
"ES2017.String",
"ES2018.Promise",
"ES2019",
"DOM",
]
}
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"target": "es5",
"esModuleInterop": true,
"downlevelIteration": true,
"incremental": true,
"sourceMap": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"allowUnreachableCode": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"lib": [
"ES2015",
"ES2016.Array.Include",
"ES2017.String",
"ES2018.Promise",
"ES2019",
"DOM",
]
}
}

63
build/package.json Normal file
View File

@ -0,0 +1,63 @@
{
"name": "dockview-monorepo-root",
"private": true,
"description": "Monorepo for https://github.com/mathuo/dockview",
"workspaces": [
"packages/*"
],
"scripts": {
"test": "jest",
"lint": "eslint packages/**/src/** --ext .ts,.tsx,.js,.jsx",
"package": "node scripts/package.js",
"package-all": "npm run build-demo && npm run docs && node scripts/package.js",
"build": "lerna run build --scope dockview",
"build-demo": "lerna run build --scope dockview-demo",
"docs": "lerna run docs --scope dockview",
"clean": "lerna run clean",
"bootstrap": "lerna bootstrap",
"test:cov": "jest --coverage",
"version-beta-build": "lerna version prerelease --preid beta",
"publish-app": "lerna publish"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mathuo/dockview.git"
},
"author": "https://github.com/mathuo",
"license": "MIT",
"bugs": {
"url": "https://github.com/mathuo/dockview/issues"
},
"homepage": "https://github.com/mathuo/dockview#readme",
"devDependencies": {
"@testing-library/dom": "^8.13.0",
"@types/jest": "^27.5.0",
"@typescript-eslint/eslint-plugin": "^5.22.0",
"@typescript-eslint/parser": "^5.22.0",
"codecov": "^3.8.3",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"eslint": "^8.15.0",
"fs-extra": "^10.1.0",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-dart-sass": "^1.0.2",
"jest": "^28.1.0",
"jest-environment-jsdom": "^28.1.0",
"jest-sonar-reporter": "^2.0.0",
"jsdom": "^19.0.0",
"lerna": "^4.0.0",
"merge2": "^1.4.1",
"rimraf": "^3.0.2",
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"ts-jest": "^28.0.2",
"ts-loader": "^9.3.0",
"tslib": "^2.4.0",
"typescript": "^4.6.4",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0"
}
}

View File

@ -0,0 +1,8 @@
{
"extends": [
"../../.eslintrc"
],
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}

View File

@ -0,0 +1,23 @@
module.exports = {
stories: [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
addons: [
{
name: '@storybook/addon-docs',
options: {
sourceLoaderOptions: {
injectStoryParameters: false,
},
},
},
"@storybook/addon-storysource",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-controls"
],
core: {
builder: "webpack5"
}
}

View File

@ -0,0 +1,5 @@
import '!style-loader!css-loader!sass-loader!../node_modules/dockview/dist/styles/dockview.css';
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
}

View File

@ -0,0 +1,51 @@
{
"name": "dockview-demo",
"private": true,
"version": "1.4.2",
"description": "Demo project for https://github.com/mathuo/dockview",
"scripts": {
"build": "npm run build-webpack",
"build-webpack": "cross-env ../../node_modules/.bin/webpack --config webpack.config.js",
"start": "cross-env ../../node_modules/.bin/webpack serve --config webpack.config.js",
"clean": "rimraf dist/ .build/",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"author": "https://github.com/mathuo",
"license": "MIT",
"dependencies": {
"@mdx-js/react": "^2.1.1",
"dockview": "^1.4.2",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"recoil": "^0.7.2"
},
"devDependencies": {
"@babel/core": "^7.17.10",
"@storybook/addon-actions": "^6.5.0-beta.0",
"@storybook/addon-controls": "^6.5.0-beta.0",
"@storybook/addon-docs": "^6.5.0-beta.0",
"@storybook/addon-essentials": "^6.5.0-beta.0",
"@storybook/addon-links": "^6.5.0-beta.0",
"@storybook/addon-storysource": "^6.5.0-beta.0",
"@storybook/builder-webpack5": "^6.5.0-beta.0",
"@storybook/manager-webpack5": "^6.5.0-beta.0",
"@storybook/react": "^6.5.0-beta.0",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.3",
"babel-loader": "^8.2.5",
"cross-env": "^7.0.3",
"rimraf": "^3.0.2",
"source-map-loader": "^3.0.1"
},
"resolutions": {
"webpack": "^5.0.0",
"css-loader": "^5.0.0",
"dotenv-webpack": "^6.0.0",
"html-webpack-plugin": "^5.0.0",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.0.0",
"webpack-dev-middleware": "^4.1.0",
"webpack-virtual-modules": "^0.4.2"
}
}

View File

@ -0,0 +1,14 @@
<!-- <!DOCTYPE html> -->
<html>
<head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<div id="anchor"></div>
<script src="/dist/bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
export const toggleClass = (
element: HTMLElement,
className: string,
isToggled: boolean
) => {
const hasClass = element.classList.contains(className);
if (isToggled && !hasClass) {
element.classList.add(className);
}
if (!isToggled && hasClass) {
element.classList.remove(className);
}
};

View File

@ -0,0 +1,122 @@
import { IDisposable } from './lifecycle';
export interface Event<T> {
(listener: (e: T) => any): IDisposable;
}
export interface EmitterOptions {
replay?: boolean;
}
export namespace Event {
export const any = <T>(...children: Event<T>[]): Event<T> => {
return (listener: (e: T) => void) => {
const disposables = children.map((child) => child(listener));
return {
dispose: () => {
disposables.forEach((d) => {
d.dispose();
});
},
};
};
};
}
// dumb event emitter with better typings than nodes event emitter
// https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
export class Emitter<T> implements IDisposable {
private _event?: Event<T>;
private _last?: T;
private _listeners: Array<(e: T) => any> = [];
private _disposed = false;
constructor(private readonly options?: EmitterOptions) {}
get event() {
if (!this._event) {
this._event = (listener: (e: T) => void): IDisposable => {
if (this.options?.replay && this._last !== undefined) {
listener(this._last);
}
this._listeners.push(listener);
return {
dispose: () => {
const index = this._listeners.indexOf(listener);
if (index > -1) {
this._listeners.splice(index, 1);
}
},
};
};
}
return this._event;
}
public fire(e: T) {
this._last = e;
for (const listener of this._listeners) {
listener(e);
}
}
public dispose() {
this._listeners = [];
this._disposed = true;
}
}
export function addDisposableWindowListener<K extends keyof WindowEventMap>(
element: Window,
type: K,
listener: (this: Window, ev: WindowEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): IDisposable {
element.addEventListener(type, listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener);
},
};
}
export function addDisposableListener<K extends keyof HTMLElementEventMap>(
element: HTMLElement,
type: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): IDisposable {
element.addEventListener(type, listener, options);
return {
dispose: () => {
element.removeEventListener(type, listener);
},
};
}
export class TickDelayedEvent implements IDisposable {
private timer: any;
private readonly _onFired = new Emitter<void>();
readonly onEvent = this._onFired.event;
fire(): void {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this._onFired.fire();
clearTimeout(this.timer);
});
}
dispose(): void {
this._onFired.dispose();
}
}

View File

@ -0,0 +1,66 @@
@import '~dockview/dist/styles/dockview.css';
body {
margin: 0;
font-family: 'Roboto', sans-serif;
overflow: hidden;
color: rgb(204, 204, 204);
font-size: 13px;
}
::-webkit-scrollbar {
height: 8px;
width: 8px;
}
/* Track */
::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: var(--dv-tabs-container-scrollbar-color);
}
// *,
// *::after,
// *::before {
// -webkit-user-drag: none;
// -webkit-app-region: no-drag;
// -webkit-user-select: none;
// }
.close-action {
&:active {
-webkit-mask-size: 100% 100% !important;
mask-size: 100% 100% !important;
}
background-color: white;
height: 16px;
width: 16px;
display: block;
-webkit-mask: var(--dv-tab-close-icon) 50% 50% / 90% 90% no-repeat;
mask: var(--dv-tab-close-icon) 50% 50% / 90% 90% no-repeat;
margin-right: '0.5em';
cursor: pointer;
}
button {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
border: none;
background-color: rgb(14, 99, 156);
color: white;
font-family: inherit;
outline: none;
padding: 2px 14px;
margin: 2px 0px;
&:hover {
background-color: rgb(17, 119, 187);
cursor: pointer;
}
}

View File

@ -0,0 +1,15 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { Application } from './layout-grid/application';
import { RecoilRoot } from 'recoil';
import './index.scss';
document.getElementById('app').classList.add('dockview-theme-dark');
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(
<RecoilRoot>
<Application />
</RecoilRoot>
);

View File

@ -0,0 +1,34 @@
.activity-bar {
height: 100%;
background-color: rgb(51, 51, 51);
.activity-bar-item {
width: 100%;
padding-bottom: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
position: relative;
&:hover {
background-color: rgba(90, 90, 90, 0.5);
}
.activity-bar-item-image {
-webkit-mask-size: 65% 65%;
mask-size: 65% 65%;
-webkit-mask-position: 50% 50%;
mask-position: 50% 50%;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
height: 100%;
width: 100%;
display: block;
background-color: gray;
position: absolute;
top: 0;
left: 0;
}
}
}

View File

@ -0,0 +1,71 @@
import * as React from 'react';
import { GridviewApi, IGridviewPanelProps } from 'dockview';
import './activitybar.scss';
import { useLayoutRegistry } from './registry';
import { CompositeDisposable } from '../lifecycle';
const ActivitybarImage = (props: { url: string }) => (
<a
style={{
WebkitMaskImage: `url(${props.url})`,
maskImage: `url(${props.url})`,
}}
className="activity-bar-item-image"
/>
);
export const Activitybar = (props: IGridviewPanelProps) => {
const registry = useLayoutRegistry();
const [isActive, setActive] = React.useState<boolean>();
const onOpenSidebar = (event: React.MouseEvent<HTMLDivElement>) => {
const api = registry.get<GridviewApi>('gridview');
const sidebarPanel = api.getPanel('sidebar');
if (sidebarPanel.api.isVisible) {
sidebarPanel.api.setVisible(false);
} else {
event.preventDefault(); // prevent focus
sidebarPanel.api.setVisible(true);
sidebarPanel.focus();
}
};
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidActiveChange((event) => {
setActive(event.isActive);
})
);
return () => {
disposable.dispose();
};
}, []);
return (
<div className="activity-bar" onClick={onOpenSidebar}>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
<div className="activity-bar-item">
<ActivitybarImage
url={
'https://fonts.gstatic.com/s/i/materialicons/search/v7/24px.svg'
}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,108 @@
import * as React from 'react';
import {
Orientation,
GridviewReact,
LayoutPriority,
GridviewReadyEvent,
IGridviewPanelProps,
SerializedGridview,
GridviewApi,
} from 'dockview';
import { Activitybar } from '../services/widgets';
import { Footer } from './footer';
import { Panel } from './panel';
import { TestGrid } from './layoutGrid';
import { useLayoutRegistry } from './registry';
import { Sidebar } from '../services/widgets';
const rootcomponents: {
[index: string]: React.FunctionComponent<IGridviewPanelProps>;
} = {
sidebar: Sidebar,
activitybar: Activitybar,
editor: TestGrid,
footer: Footer,
panel: Panel,
};
export const Application = () => {
const api = React.useRef<GridviewApi>();
const registry = useLayoutRegistry();
const onReady = (event: GridviewReadyEvent) => {
api.current = event.api;
registry.register('gridview', event.api);
let success = false;
const state = localStorage.getItem('dockview-layout');
if (state) {
try {
event.api.fromJSON(JSON.parse(state));
success = true;
} catch (err) {
console.error('failed to load layout', err);
}
}
if (!success) {
event.api.addPanel({
id: 'i',
component: 'activitybar',
minimumWidth: 48,
maximumWidth: 48,
location: [0],
});
event.api.addPanel({
id: 'footer',
component: 'footer',
location: [1],
maximumHeight: 22,
minimumHeight: 22,
});
event.api.addPanel({
id: 'editor',
component: 'editor',
snap: true,
location: [0, 1],
priority: LayoutPriority.High,
});
event.api.addPanel({
id: 'sidebar',
component: 'sidebar',
snap: true,
position: { referencePanel: 'editor', direction: 'left' },
minimumWidth: 170,
size: 100,
});
event.api.addPanel({
id: 'panel',
component: 'panel',
position: { referencePanel: 'editor', direction: 'below' },
size: 200,
snap: true,
});
}
event.api.onDidLayoutChange(() => {
localStorage.setItem(
'dockview-layout',
JSON.stringify(event.api.toJSON())
);
});
};
return (
<GridviewReact
components={rootcomponents}
onReady={onReady}
orientation={Orientation.VERTICAL}
hideBorders={true}
proportionalLayout={true}
/>
);
};

View File

@ -0,0 +1,30 @@
.control-center {
display: flex;
flex-direction: column;
height: 100%;
padding: 4px 8px;
box-sizing: border-box;
.control-center-row {
height: 25px;
box-sizing: border-box;
button {
width: 175px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
border: none;
background-color: rgb(14, 99, 156);
color: white;
font-family: inherit;
outline: none;
padding: 2px 14px;
margin: 2px 0px;
&:hover {
background-color: rgb(17, 119, 187);
}
}
}
}

View File

@ -0,0 +1,150 @@
import * as React from 'react';
import { DockviewApi, GridviewApi, SplitviewApi } from 'dockview';
import { useLayoutRegistry } from './registry';
import './controlCenter.scss';
export const ControlCenter = () => {
const registry = useLayoutRegistry();
const onAdd = () => {
const api = registry.get<DockviewApi>('dockview');
const _id = Date.now();
const id = `${_id}`;
api.addPanel({
component: 'test_component',
id,
title: `Item ${id}`,
});
};
const onAddTheSamePanel = () => {
const api = registry.get<DockviewApi>('dockview');
const id = `duplicate_panel`;
const panel = api.getPanel(id);
if (panel) {
panel.api.setActive();
return;
}
api.addPanel({
component: 'test_component',
id,
title: `Item ${id}`,
});
};
const onAddEmpty = () => {
const api = registry.get<DockviewApi>('dockview');
api.addEmptyGroup();
};
const nextPanel = (ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
const api = registry.get<DockviewApi>('dockview');
api.moveToNext({ includePanel: true });
};
const nextGroup = (ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
const api = registry.get<DockviewApi>('dockview');
api.moveToNext();
};
const previousPanel = (ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
const api = registry.get<DockviewApi>('dockview');
api.moveToPrevious({ includePanel: true });
};
const previousGroup = (ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
const api = registry.get<DockviewApi>('dockview');
api.moveToNext();
};
const onConfig = () => {
const api = registry.get<DockviewApi>('dockview');
const data = api.toJSON();
const stringData = JSON.stringify(data, null, 4);
console.log(stringData);
localStorage.setItem('layout', stringData);
};
const onLoad = async () => {
const api = registry.get<DockviewApi>('dockview');
api.closeAllGroups();
const data = localStorage.getItem('layout');
if (data) {
const jsonData = JSON.parse(data);
api.fromJSON(jsonData);
}
};
const onClear = () => {
const api = registry.get<DockviewApi>('dockview');
api.closeAllGroups();
};
const saveApplicationLayout = () => {
const api = registry.get<GridviewApi>('dockview');
console.log(JSON.stringify(api.toJSON(), null, 4));
};
const onAddSettings = () => {
const api = registry.get<DockviewApi>('dockview');
const settingsPanel = api.getPanel('settings');
if (settingsPanel) {
settingsPanel.api.setActive();
return;
}
api.addPanel({
id: 'settings',
component: 'settings',
title: 'Settings',
});
};
return (
<div className="control-center">
<div className="control-center-row">
<button onMouseDown={onAdd}>Add new</button>
</div>
<div className="control-center-row">
<button onMouseDown={onAddTheSamePanel}>Add identical</button>
</div>
<div className="control-center-row">
<button onMouseDown={onAddSettings}>Settings</button>
</div>
<div className="control-center-row">
<button onMouseDown={onAddEmpty}>Add empty</button>
</div>
<div className="control-center-row">
<button onMouseDown={nextPanel}>Next panel</button>
</div>
<div className="control-center-row">
<button onMouseDown={nextGroup}>Next Group</button>
</div>
<div className="control-center-row">
<button onMouseDown={previousPanel}>Previous Panel</button>
</div>
<div className="control-center-row">
<button onMouseDown={previousGroup}>Previous Group</button>
</div>
<div className="control-center-row">
<button onClick={onConfig}>Save</button>
</div>
<div className="control-center-row">
<button onClick={onLoad}>Load</button>
</div>
<div className="control-center-row">
<button onClick={onClear}>Clear</button>
</div>
<div className="control-center-row">
<button onClick={saveApplicationLayout}>
Save application layout
</button>
</div>
</div>
);
};

View File

@ -0,0 +1,6 @@
import * as React from 'react';
import { IGroupPanelBaseProps } from 'dockview';
export const CustomTab = (props: IGroupPanelBaseProps) => {
return <div>hello</div>;
};

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { IGridviewPanelProps } from 'dockview';
import { atom, useRecoilValue } from 'recoil';
export const selectedPanelAtom = atom<string>({
key: 'selectedPanelAtom',
default: '',
});
export const Footer = (props: IGridviewPanelProps) => {
const selectedPanel = useRecoilValue(selectedPanelAtom);
return (
<div
style={{
height: '22px',
backgroundColor: 'dodgerblue',
display: 'flex',
alignItems: 'center',
padding: '0px 8px',
}}
>
<span style={{ flexGrow: 1 }} />
<span>{selectedPanel}</span>
</div>
);
};

View File

@ -0,0 +1,101 @@
{
"grid": {
"root": {
"type": "branch",
"data": [
{
"type": "leaf",
"data": {
"id": "group_1",
"views": [
"split_panel"
],
"activeView": "split_panel"
},
"size": 371
},
{
"type": "branch",
"data": [
{
"type": "leaf",
"data": {
"id": "group_2",
"views": [
"item2"
],
"activeView": "item2"
},
"size": 792
},
{
"type": "leaf",
"data": {
"id": "group_3",
"views": [
"panel_2"
],
"activeView": "panel_2"
},
"size": 792
}
],
"size": 371
},
{
"type": "leaf",
"data": {
"id": "group_4",
"views": [
"panel_0"
],
"activeView": "panel_0"
},
"size": 372
}
],
"size": 1584
},
"height": 1114,
"width": 1584,
"orientation": "HORIZONTAL"
},
"panels": {
"panel_0": {
"id": "panel_0",
"contentId": "test_component",
"tabId": "__DEFAULT_TAB__",
"props": {
"text": "how low?"
},
"title": "Item 1",
"state": {}
},
"item2": {
"id": "item2",
"contentId": "test_component",
"tabId": "__DEFAULT_TAB__",
"props": {},
"title": "Item 2",
"state": {}
},
"split_panel": {
"id": "split_panel",
"contentId": "split_panel",
"tabId": "__DEFAULT_TAB__",
"props": {},
"title": "Item 3 with a long title",
"state": {}
},
"panel_2": {
"id": "panel_2",
"contentId": "test_component",
"tabId": "__DEFAULT_TAB__",
"props": {},
"title": "Item 3",
"suppressClosable": true,
"state": {}
}
},
"activeGroup": "group_2"
}

View File

@ -0,0 +1,22 @@
.context-menu {
position: absolute;
height: 100px;
width: 170px;
font-size: 13px;
z-index: 999;
color: rgb(204, 204, 204);
background: rgb(37, 37, 38);
box-shadow: rgb(0, 0, 0) 0px 2px 4px;
padding: 10px 0px;
.context-action {
height: 20px;
line-height: 20px;
padding: 2px 10px;
cursor: pointer;
&:hover {
background-color: rgb(51, 51, 51);
}
}
}

View File

@ -0,0 +1,334 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {
IDockviewPanelProps,
IGridviewPanelProps,
TabContextMenuEvent,
DockviewReadyEvent,
DockviewReact,
DockviewApi,
IWatermarkPanelProps,
IDockviewPanel,
PanelCollection,
} from 'dockview';
import { CustomTab } from './customTab';
import { Settings } from './settingsPanel';
import { useLayoutRegistry } from './registry';
import { SplitPanel } from './splitPanel';
import './layoutGrid.scss';
import { WelcomePanel } from '../panels/welcome/welcome';
import { SplitviewPanel } from '../panels/splitview/splitview';
import { GridviewDemoPanel } from '../panels/gridview/gridview';
import { useRecoilCallback } from 'recoil';
import { selectedPanelAtom } from './footer';
import { ExampleFunctions } from './panels/exampleFunctions';
import { CompositeDisposable } from '../lifecycle';
const WatermarkComponent = () => {
return (
<div style={{ backgroundColor: 'black', color: 'white' }}>
Watermark component
</div>
);
};
const Test = (props: IDockviewPanelProps) => {
const [counter, setCounter] = React.useState<number>(0);
React.useEffect(() => {
const interval = setInterval(() => {
setCounter((_) => _ + 1);
}, 2000);
return () => {
clearInterval(interval);
};
}, []);
return (
<div>
<div>{`custom body ${counter}`}</div>
<button>Toggle</button>
</div>
);
};
const components: PanelCollection<IDockviewPanelProps> = {
test: Test,
welcome: WelcomePanel,
splitview: SplitviewPanel,
gridview: GridviewDemoPanel,
inner_component: (props: IDockviewPanelProps) => {
const _api = React.useRef<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => {
_api.current = event.api;
event.api.addPanel({
component: 'test_component',
id: 'inner-1',
title: 'inner-1',
});
event.api.addPanel({
component: 'test_component',
id: 'inner-2',
title: 'inner-2',
});
event.api.addPanel({
component: 'test_component',
id: nextGuid(),
title: 'inner-3',
position: {
direction: 'within',
referencePanel: 'inner-1',
},
});
event.api.addPanel({
component: 'test_component',
id: nextGuid(),
title: 'inner-4',
position: {
direction: 'within',
referencePanel: 'inner-2',
},
});
};
return (
<div
style={{
boxSizing: 'border-box',
// borderTop: "1px solid var(--dv-separator-border)",
}}
>
<DockviewReact
onReady={onReady}
components={components}
watermarkComponent={WatermarkComponent}
tabHeight={20}
/>
</div>
);
},
test_component: ExampleFunctions,
settings: Settings,
split_panel: SplitPanel,
};
const tabComponents = {
default: CustomTab,
};
export const nextGuid = (() => {
let counter = 0;
return () => 'panel_' + (counter++).toString();
})();
export const TestGrid = (props: IGridviewPanelProps) => {
const [api, setApi] = React.useState<DockviewApi>();
const registry = useLayoutRegistry();
const onReady = (event: DockviewReadyEvent) => {
const api = event.api;
setApi(api);
registry.register('dockview', api);
};
const setSelectedPanel = useRecoilCallback(
({ set }) =>
(value: string) =>
set(selectedPanelAtom, value),
[]
);
React.useEffect(() => {
if (!api) {
return () => {
//
};
}
props.api.setConstraints({
minimumWidth: () => api.minimumWidth,
minimumHeight: () => api.minimumHeight,
});
const disposable = new CompositeDisposable(
api.onDidLayoutChange(() => {
const state = api.toJSON();
localStorage.setItem('dockview', JSON.stringify(state));
}),
api.onDidActivePanelChange((e) => {
setSelectedPanel(e?.id || '');
})
);
const state = localStorage.getItem('dockview');
let success = false;
if (state) {
try {
api.fromJSON(JSON.parse(state));
success = true;
} catch (err) {
console.error('failed to load layout', err);
}
}
const welcomePanel = api.getPanel('welcome');
if (!welcomePanel) {
api.addPanel({
component: 'welcome',
id: 'welcome',
title: 'Welcome',
suppressClosable: true,
});
}
return () => {
disposable.dispose();
};
}, [api]);
const [coord, setCoord] =
React.useState<{
x: number;
y: number;
panel: IDockviewPanel;
}>(undefined);
const onTabContextMenu = React.useMemo(
() => (event: TabContextMenuEvent) => {
event.event.preventDefault();
console.log(event.panel);
const cb = (event: MouseEvent) => {
let element: HTMLElement = event.target as HTMLElement;
while (element) {
if (element.classList.contains('context-menu')) {
return;
}
element = element.parentElement;
}
window.removeEventListener('mousedown', cb);
setCoord(undefined);
};
window.addEventListener('mousedown', cb);
setCoord({
x: event.event.clientX,
y: event.event.clientY,
panel: event.panel,
});
},
[]
);
const onClose = () => {
setCoord(undefined);
coord.panel.api.close();
};
const onChangeName = () => {
setCoord(undefined);
coord.panel.api.setTitle('This looks new?');
};
return (
<>
{coord &&
ReactDOM.createPortal(
<div
className="context-menu"
style={{
left: `${coord.x}px`,
top: `${coord.y}px`,
}}
>
<div className="context-action" onClick={onClose}>
Close
</div>
<div className="context-action" onClick={onChangeName}>
Rename
</div>
</div>,
document.getElementById('anchor')
)}
<DockviewReact
onReady={onReady}
components={components}
tabComponents={tabComponents}
onTabContextMenu={onTabContextMenu}
watermarkComponent={Watermark}
showDndOverlay={(ev) => {
return true;
}}
onDidDrop={(ev) => {
console.log('onDidDrop', ev);
}}
/>
</>
);
};
const Watermark = (props: IWatermarkPanelProps) => {
const [groups, setGroups] = React.useState<number>(props.containerApi.size);
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.containerApi.onDidLayoutChange(() => {
console.log(`groups2 ${props.containerApi.size}`);
setGroups(props.containerApi.size);
})
);
return () => {
disposable.dispose();
};
}, []);
const onClick = () => {
props.close();
};
return (
<div
style={{
display: 'flex',
width: '100%',
flexGrow: 1,
height: '100%',
flexDirection: 'column',
}}
>
<div
style={{
height: '35px',
display: 'flex',
width: '100%',
}}
>
<span style={{ flexGrow: 1 }} />
{groups > 1 && (
<span
onClick={onClick}
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<a className="close-action"></a>
</span>
)}
</div>
<div
style={{
flexGrow: 1,
display: 'flex',
justifyContent: 'center',
}}
>
<span>Watermark component</span>
</div>
</div>
);
};

View File

@ -0,0 +1,67 @@
import * as React from 'react';
import { IGridviewPanelProps } from 'dockview';
import { CompositeDisposable } from '../lifecycle';
export const Panel = (props: IGridviewPanelProps) => {
const [active, setActive] = React.useState<boolean>();
const [focused, setFocused] = React.useState<boolean>();
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidActiveChange((event) => {
setActive(event.isActive);
}),
props.api.onDidFocusChange((event) => {
setFocused(event.isFocused);
})
);
return () => {
disposable.dispose();
};
}, []);
const onToggle = () => {
const editorPanel = props.containerApi.getPanel('editor');
editorPanel.api.setVisible(!editorPanel.api.isVisible);
};
const onClose = () => {
const editorPanel = props.containerApi.getPanel('editor');
editorPanel.api.setVisible(true);
props.api.setVisible(false);
};
const onMove = () => {
const thisPanel = props.containerApi.getPanel('panel');
const editor = props.containerApi.getPanel('editor');
props.containerApi.movePanel(thisPanel, {
direction: 'left',
reference: editor.id,
});
};
return (
<div
style={{
borderTop: `1px solid var(--dv-separator-border)`,
boxSizing: 'border-box',
backgroundColor: 'rgb(30,30,30)',
height: '100%',
}}
>
<div style={{ display: 'flex', padding: '5px' }}>
<span>This panel is outside of the dockable layer</span>
<span style={{ flexGrow: 1 }} />
{/* <button onClick={onMove}>Move</button> */}
<button onClick={onToggle}>Resize</button>
<button onClick={onClose}>Close</button>
</div>
<div style={{ padding: '0px 5px' }}>
<div>{`isPanelActive: ${active} isPanelFocused: ${focused}`}</div>
</div>
</div>
);
};

View File

@ -0,0 +1,14 @@
.example-functions-panel {
padding: 5px;
display: flex;
flex-direction: column;
.example-functions-panel-header-bar {
display: flex;
justify-content: flex-end;
}
.example-functions-panel-section {
padding: 8px 0px;
}
}

View File

@ -0,0 +1,102 @@
import { IDockviewPanelProps } from 'dockview';
import * as React from 'react';
import { CompositeDisposable } from '../../lifecycle';
import './exampleFunctions.scss';
export const ExampleFunctions = (
props: IDockviewPanelProps & { [key: string]: any }
) => {
const [panelState, setPanelState] = React.useState<{
isGroupActive: boolean;
isPanelVisible: boolean;
}>({
isGroupActive: props.api.isActive,
isPanelVisible: props.api.isVisible,
});
const [panelName, setPanelName] = React.useState<string>();
const input = React.useRef<HTMLInputElement>();
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidActiveChange((event) => {
setPanelState((_) => ({
..._,
isGroupActive: event.isActive,
}));
}),
props.api.onDidVisibilityChange((x) => {
setPanelState((_) => ({
..._,
isPanelVisible: x.isVisible,
}));
}),
props.api.onDidFocusChange(({ isFocused }) => {
if (isFocused) {
input.current.focus();
}
})
);
return () => {
disposable.dispose();
};
}, []);
const onRename = () => {
props.api.setTitle(panelName);
};
const onClose = () => {
props.api.close();
};
return (
<div className="example-functions-panel">
<div className="example-functions-panel-header-bar">
<span style={{ padding: '0px 8px' }}>
<span>{'isGroupActive: '}</span>
<span
style={{
color: panelState.isGroupActive
? '#23d16f'
: '#cd312b',
}}
>
{`${panelState.isGroupActive}`}
</span>
</span>
<span style={{ padding: '0px 8px' }}>
<span>{'isPanelVisible: '}</span>
<span
style={{
color: panelState.isPanelVisible
? '#23d16f'
: '#cd312b',
}}
>
{`${panelState.isPanelVisible}`}
</span>
</span>
</div>
<div className="example-functions-panel-section">
<input
value={panelName}
placeholder="New Panel Name"
type="text"
onChange={(event) => setPanelName(event.target.value)}
/>
<button onClick={onRename}>Rename</button>
</div>
<input
style={{ width: '175px' }}
ref={input}
placeholder="This is focused by the panel"
/>
<input
style={{ width: '175px' }}
placeholder="This is also focusable"
/>
</div>
);
};

View File

@ -0,0 +1,25 @@
import * as React from 'react';
export interface IRegistry {
register<T>(name: string, value: T): void;
get<T>(name: string): T;
}
class Registry implements IRegistry {
private cache = new Map<string, any>();
register<T>(name: string, value: T) {
this.cache.set(name, value);
}
get<T>(name: string) {
return this.cache.get(name) as T;
}
}
const RegistryInstance = new Registry();
export const useLayoutRegistry = (): IRegistry => {
const ref = React.useRef<IRegistry>(RegistryInstance);
return ref.current;
};

View File

@ -0,0 +1,38 @@
import * as React from 'react';
import { IDockviewPanelProps } from 'dockview';
export const Settings = (props: IDockviewPanelProps) => {
const [tabHeight, setTabHeight] = React.useState<number>(
props.containerApi.getTabHeight()
);
const onTabHeightChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value);
if (!Number.isNaN(value)) {
setTabHeight(value);
}
};
const onClick = () => {
props.containerApi.setTabHeight(tabHeight);
};
const onRemove = () => {
props.containerApi.setTabHeight(undefined);
};
return (
<div style={{ height: '100%', color: 'white' }}>
<label>
Tab height
<input
onChange={onTabHeightChange}
value={tabHeight}
type="number"
/>
<button onClick={onClick}>Apply</button>
<button onClick={onRemove}>Remove</button>
</label>
</div>
);
};

View File

@ -0,0 +1,57 @@
{
"views": [
{
"size": 22,
"data": {
"id": "1",
"component": "controlCenter",
"props": {},
"title": "Control Center",
"state": {}
},
"minimumSize": 120
},
{
"size": 508,
"data": {
"id": "2",
"component": "default",
"props": {},
"title": "Panel 1",
"state": {}
},
"minimumSize": 120,
"expanded": true
},
{
"size": 248,
"data": {
"id": "3",
"component": "default",
"props": {},
"title": "Panel 2",
"state": {}
},
"minimumSize": 120,
"expanded": true
},
{
"size": 22,
"data": {
"id": "4",
"component": "default",
"props": {},
"title": "Panel 3",
"state": {}
},
"minimumSize": 120,
"expanded": false
}
],
"size": 800,
"orientation": "VERTICAL"
}

View File

@ -0,0 +1,18 @@
.pane-header {
.my-header {
.actions {
display: none;
}
&.within {
.actions {
display: flex;
}
}
}
&:focus {
.actions {
display: flex;
}
}
}

View File

@ -0,0 +1,97 @@
import * as React from 'react';
import {
IGridviewPanelProps,
PaneviewReact,
PaneviewReadyEvent,
IPaneviewPanelProps,
PaneviewApi,
PaneviewDropEvent,
} from 'dockview';
import { ControlCenter } from './controlCenter';
import './sidebar.scss';
import { CompositeDisposable } from '../lifecycle';
const components = {
default: (props: IPaneviewPanelProps) => {
return <div style={{ height: '100%' }}>This is an example panel</div>;
},
controlCenter: ControlCenter,
};
export const Sidebar = (props: IGridviewPanelProps) => {
const api = React.useRef<PaneviewApi>();
const onReady = (event: PaneviewReadyEvent) => {
api.current = event.api;
console.log(props.api.width, props.api.height);
event.api.fromJSON(require('./sidebar.layout.json'));
return;
event.api.addPanel({
id: '1',
component: 'controlCenter',
title: 'Control Center',
});
event.api.addPanel({
id: '2',
component: 'default',
title: 'Panel 1',
});
event.api.addPanel({
id: '3',
component: 'default',
title: 'Panel 2',
});
event.api.addPanel({
id: '4',
component: 'default',
title: 'Panel 3',
});
setTimeout(() => {
console.log(JSON.stringify(event.api.toJSON(), null, 4));
}, 10000);
};
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidDimensionsChange((ev) => {
api.current.layout(ev.width, ev.height);
}),
props.api.onDidVisibilityChange((event) => {
console.log(event);
}),
props.api.onDidFocusChange(({ isFocused }) => {
if (isFocused) {
api.current.focus();
}
})
);
return () => {
disposable.dispose();
};
}, []);
const onDidDrop = React.useCallback((event: PaneviewDropEvent) => {
console.log('drop', event);
}, []);
return (
<div
style={{
backgroundColor: 'rgb(37,37,38)',
height: '100%',
}}
>
<PaneviewReact
components={components}
onReady={onReady}
onDidDrop={onDidDrop}
/>
</div>
);
};

View File

@ -0,0 +1,145 @@
import * as React from 'react';
import {
ISplitviewPanelProps,
Orientation,
SplitviewReadyEvent,
SplitviewReact,
SplitviewApi,
IDockviewPanelProps,
} from 'dockview';
import { useLayoutRegistry } from './registry';
import './splitPanel.scss';
import { CompositeDisposable } from '../lifecycle';
const components = {
default1: (props: ISplitviewPanelProps) => {
const ref = React.useRef<HTMLInputElement>();
const [focused, setFocused] = React.useState<boolean>(false);
const [active, setActive] = React.useState<boolean>(false);
const onClick = (ev: React.MouseEvent<HTMLButtonElement>) => {
props.api.setSize({ size: 300 });
};
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidFocusChange((event) => {
setFocused(event.isFocused);
}),
props.api.onDidActiveChange((event) => {
setActive(event.isActive);
}),
props.api.onDidFocusChange(({ isFocused }) => {
if (isFocused) {
ref.current.focus();
}
})
);
return () => {
disposable.dispose();
};
}, []);
return (
<div style={{ height: '100%' }}>
<div style={{ display: 'flex', padding: '5px' }}>
<div>This is a splitview panel inside a dockable panel</div>
<span style={{ flexGrow: 1 }} />
<button onClick={onClick}>resize</button>
</div>
<div style={{ padding: '0px 5px' }}>
<div>{`isPanelActive: ${active} isPanelFocused: ${focused}`}</div>
<input ref={ref} type="text" placeholder="focus test" />
<span>{(props as any).text}</span>
</div>
</div>
);
},
};
export const SplitPanel = (props: IDockviewPanelProps) => {
const api = React.useRef<SplitviewApi>();
const registry = useLayoutRegistry();
React.useEffect(() => {
const disposable = new CompositeDisposable(
props.api.onDidDimensionsChange((event) => {
api.current?.layout(event.width, event.height - 25);
}),
props.api.onDidFocusChange(({ isFocused }) => {
if (isFocused) {
api.current.focus();
}
})
);
return () => {
disposable.dispose();
};
}, []);
const onReady = (event: SplitviewReadyEvent) => {
api.current = event.api;
registry.register('splitview', event.api);
event.api.fromJSON(require('./splitpanel.layout.json'));
return;
event.api.addPanel({
id: '1',
component: 'default1',
snap: true,
params: {
text: 'hiya',
},
});
event.api.addPanel({ id: '2', component: 'default1' });
};
const onUpdateProps = () => {
const panel = api.current.getPanel('1');
panel.update({ params: { text: Date.now().toString() } });
};
const onAdd = () => {
api.current.addPanel({
id: `${Date.now()}`,
component: 'default1',
snap: true,
params: {
text: 'hiya',
},
});
};
const onRemove = () => {
const panels = api.current.panels;
if (panels.length === 0) {
return;
}
api.current.removePanel(panels[panels.length - 1]);
};
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
color: 'white',
}}
>
<div style={{ height: '25px' }}>
<button onClick={onUpdateProps}>Update props</button>
<button onClick={onAdd}>Add</button>
<button onClick={onRemove}>Remove</button>
</div>
<SplitviewReact
components={components}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>
</div>
);
};

Some files were not shown because too many files have changed in this diff Show More