Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/stagas/window-plug
Workspace Window plug
https://github.com/stagas/window-plug
Last synced: 14 days ago
JSON representation
Workspace Window plug
- Host: GitHub
- URL: https://github.com/stagas/window-plug
- Owner: stagas
- License: mit
- Created: 2022-06-07T18:09:54.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-08-21T15:20:54.000Z (about 2 years ago)
- Last Synced: 2024-10-12T21:49:52.549Z (about 1 month ago)
- Language: TypeScript
- Size: 63.5 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
window-plugWorkspace Window plug
npm i window-plug
pnpm add window-plug
yarn add window-plug
## Examples
#
web
- #
view source
example/web.tsx
```tsx
/** @jsxImportSource sigl */
import $ from 'sigl'
import { deserialize, serialize } from 'serialize-whatever'
import { ContextMenuOption, WorkspaceElement, WorkspaceWindowElement } from 'x-workspace'
import { Cable, Plug, WindowPlugElement, WindowPlugSceneElement } from 'window-plug'
const IO = {
Midi: 'midi',
Audio: 'audio',
} as const
interface WindowItemElement extends $.Element {}
@$.element()
class WindowItemElement extends $(WorkspaceWindowElement) {
WindowPlug = $.element(WindowPlugElement)
plugScene?: WindowPlugSceneElement
@$.out() inputs = new $.RefSet([
{ plug: new Plug(Plug.Input, IO.Midi) },
{ plug: new Plug(Plug.Input, IO.Audio) },
])
@$.out() outputs = new $.RefSet([
{ plug: new Plug(Plug.Output, IO.Midi) },
{ plug: new Plug(Plug.Output, IO.Audio) },
])
mounted($: WindowItemElement['$']) {
$.Controls = $.part(() =>
$.ContextMenu = $.part(() => (
<>
New
Remove the thing
and another
and another
and another
>
))
const Plugs = $.part(({ host, WindowPlug, plugScene, inputs, outputs, onContextMenu }) => (
{[
['inputs', inputs] as const,
['outputs', outputs] as const,
].map(([part, plugs]) => (
{plugs.map(plug => (
(
<>
Mute All
Disconnect All
>
))}
/>
))}
))}
))
$.Item = $.part(({ WindowPlug }) => (
<>
{/*css*/ `
:host {
--audio: #09f;
--midi: #a80;
--plug-width: 28px;
display: flex;
width: 100%;
height: 100%;
position: relative;
}
[part=plugs] {
position: absolute;
height: 100%;
width: 100%;
}
[part=plugs] > * {
width: var(--plug-width);
height: 100%;
pointer-events: none;
display: flex;
flex-flow: column nowrap;
align-items: center;
justify-content: center;
position: absolute;
gap: 20px;
}
[part=inputs] {
left: calc(-1 * var(--plug-width));
top: 0;
}
[part=outputs] {
right: calc(-1 * var(--plug-width));
top: 0;
}
[part=plug] {
display: inline-flex;
width: var(--plug-width);
pointer-events: all;
cursor: copy;
}
[part=inputs] [part=plug] {
}
[part=outputs] [part=plug] {
}
[data-cable-kind=audio][data-plug-kind=input]::part(plug) {
background: var(--audio);
}
[data-cable-kind=audio][data-plug-kind=output]::part(plug) {
background: var(--audio);
}
[data-cable-kind=midi][data-plug-kind=input]::part(plug) {
background: var(--midi);
}
[data-cable-kind=midi][data-plug-kind=output]::part(plug) {
background: var(--midi);
}
${WindowPlug}::part(plug) {
/* opacity: 0.55; */
/* transition: opacity 78ms cubic-bezier(0, 0.35, .15, 1); */
z-index: 1;
}
${WindowPlug}::part(back) {
background: #000;
z-index: 0;
}
${WindowPlug}:hover::part(plug) {
/* opacity: 0.75; */
}
${WindowPlug}.disabled::part(plug) {
opacity: 0.2;
}
${WindowPlug}.enabled::part(plug) {
opacity: 0.85;
}
${WindowPlug}.active::part(plug) {
/* opacity: 1; */
}
`}
>
))
}
}
interface SceneElement extends $.Element {}
@$.element()
class SceneElement extends HTMLElement {
Workspace = $.element(WorkspaceElement)
WindowItem = $.element(WindowItemElement)
WindowPlugScene = $.element(WindowPlugSceneElement)
workspace?: WorkspaceElement
plugScene?: WindowPlugSceneElement
@$.out() items = new $.RefSet([
{ rect: new $.Rect(0, 0, 200, 200), label: 'one' },
{ rect: new $.Rect(300, 0, 200, 200), label: 'two' },
])
mounted($: SceneElement['$']) {
const PlugScene = $.part(({ WindowPlugScene, workspace }) => (
))
const PlugArrows = $.part(({ plugScene: { PlugArrows } }) => )
const Items = $.part(({ WindowItem, items, plugScene }) =>
items.map(item => )
)
$.render(({ Workspace, WindowItem }) => (
<>
{/*css*/ `
${Workspace} {
position: absolute;
display: flex;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
${WindowItem} {
box-sizing: border-box;
background: #000;
z-index: 1;
/* border: 5px solid pink; */
}
${WindowItem}.connect-hover {
/* border: 5px solid purple; */
}
`}
>
))
}
}
const Scene = $.element(SceneElement)
const Classes = [
$.Rect,
$.RefSet,
Cable,
Plug,
SceneElement,
]
const sceneRef = new $.Ref()
const historyItems = new Set()
const History = () => {
return [window-plug.historyItems].map((x, i) => (
{
sceneRef.current = deserialize(x, Classes)
render()
}}
>
{i}
))
}
if (localStorage.lastScene) {
sceneRef.current = deserialize(localStorage.lastScene, Classes)
}
const render = () => {
$.render(
<>
{
console.time('serialize')
const serialized = serialize(sceneRef.current)
historyItems.add(serialized)
localStorage.lastScene = serialized
console.log('size:', serialized.length)
console.timeEnd('serialize')
render()
})}
/>
>,
document.body
)
}
render()
```
## API
# PlugKind
WindowPlugArrowDragType
src/window-plug-arrow.tsx#L39 # Head
src/window-plug-arrow.tsx#L40
0
Tail
src/window-plug-arrow.tsx#L41 1
Cable
# Plug
# ondisconnect
# plugKind
# Input
# Output
# connect
(this, other, cable) # disconnect
(this, cable) # PlugArrow
src/plug-arrow.ts#L6 # constructor
(cable, dests, options) src/plug-arrow.ts#L13
# new PlugArrow
()
# cable
dests
Point | WindowPlugElement []
options
cable
src/plug-arrow.ts#L14 # dests
src/plug-arrow.ts#L15 Point | WindowPlugElement []
holding
= false
src/plug-arrow.ts#L11 boolean
options
src/plug-arrow.ts#L16 # plugPoints
src/plug-arrow.ts#L9 [ Point, Point, Point, Point ]
polygon
src/plug-arrow.ts#L10 Polygon
map
= ...
src/plug-arrow.ts#L7 # id
src/plug-arrow.ts#L26 plugs
src/plug-arrow.ts#L36 scene
src/plug-arrow.ts#L30 solve
() src/plug-arrow.ts#L81
solve() =>
Promise<void>
updatePoints
() src/plug-arrow.ts#L41
updatePoints() =>
- void
WindowPlugArrowElement
src/window-plug-arrow.tsx#L48 # constructor
() node_modules/typescript/lib/lib.dom.d.ts#L6370
# new WindowPlugArrowElement
()
$
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> anim
src/window-plug-arrow.tsx#L75 ManualAnimation<WindowPlugArrowAnimation>
animValues
src/window-plug-arrow.tsx#L76 WindowPlugArrowAnimation
cableWidth
= 7.5
src/window-plug-arrow.tsx#L53 number
color
= '#66a'
src/window-plug-arrow.tsx#L57 string
context
ContextClass<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> dispatch
Dispatch<# (name, detail, init)
dragType
= WindowPlugArrowDragType.Head
src/window-plug-arrow.tsx#L66 # holding
= false
src/window-plug-arrow.tsx#L63 false
| {
# cable
src/window-plug-arrow.tsx#L63
dest
src/window-plug-arrow.tsx#L63 Point
plug
src/window-plug-arrow.tsx#L63 } host
# onContextMenu
src/window-plug-arrow.tsx#L70 (Options) =>
EventHandler<any, MouseEvent>
onarrowhoverstart
EventHandler<WindowPlugArrowElement, CustomEvent<{
}>>
onmounted
EventHandler<WindowPlugArrowElement, CustomEvent<any>>
onunmounted
EventHandler<WindowPlugArrowElement, CustomEvent<any>>
path
src/window-plug-arrow.tsx#L61 string
plugArrow
src/window-plug-arrow.tsx#L58 # points
src/window-plug-arrow.tsx#L73 Point []
reconnectInput
src/window-plug-arrow.tsx#L80 EventHandler<SVGCircleElement, PointerEvent | TouchEvent>
reconnectOutput
src/window-plug-arrow.tsx#L79 EventHandler<SVGCircleElement, PointerEvent | TouchEvent>
reconnectStart
src/window-plug-arrow.tsx#L78 # rect
src/window-plug-arrow.tsx#L72 Rect
state
= ...
src/window-plug-arrow.tsx#L52 State<WindowPlugArrowElement, {
# Drag
= 'arrowdrag'
src/window-plug-arrow.tsx#L34
"arrowdrag"
Hold
= 'arrowhold'
src/window-plug-arrow.tsx#L35 "arrowhold"
Hover
= 'arrowhover'
src/window-plug-arrow.tsx#L36 "arrowhover"
Idle
= 'arrowidle'
src/window-plug-arrow.tsx#L33 "arrowidle"
"arrowidle"
| "arrowdrag"
| "arrowhold"
| "arrowhover"
> & EventMethods<WindowPlugArrowElement, { # arrowdragcancel
CustomEvent<any>
arrowdragend
CustomEvent<any>
arrowdragpause
CustomEvent<any>
arrowdragresume
CustomEvent<any>
arrowdragstart
CustomEvent<any>
arrowholdcancel
CustomEvent<any>
arrowholdend
CustomEvent<any>
arrowholdpause
CustomEvent<any>
arrowholdresume
CustomEvent<any>
arrowholdstart
CustomEvent<any>
arrowhovercancel
CustomEvent<any>
arrowhoverend
CustomEvent<any>
arrowhoverpause
CustomEvent<any>
arrowhoverresume
CustomEvent<any>
arrowhoverstart
CustomEvent<any>
arrowidlecancel
CustomEvent<any>
arrowidleend
CustomEvent<any>
arrowidlepause
CustomEvent<any>
arrowidleresume
CustomEvent<any>
arrowidlestart
CustomEvent<any>
# arrowdragcancel
CustomEvent<any>
arrowdragend
CustomEvent<any>
arrowdragpause
CustomEvent<any>
arrowdragresume
CustomEvent<any>
arrowdragstart
CustomEvent<any>
arrowholdcancel
CustomEvent<any>
arrowholdend
CustomEvent<any>
arrowholdpause
CustomEvent<any>
arrowholdresume
CustomEvent<any>
arrowholdstart
CustomEvent<any>
arrowhovercancel
CustomEvent<any>
arrowhoverend
CustomEvent<any>
arrowhoverpause
CustomEvent<any>
arrowhoverresume
CustomEvent<any>
arrowhoverstart
CustomEvent<any>
arrowidlecancel
CustomEvent<any>
arrowidleend
CustomEvent<any>
arrowidlepause
CustomEvent<any>
arrowidleresume
CustomEvent<any>
arrowidlestart
CustomEvent<any>
targetState
= WindowPlugArrowState.Idle
src/window-plug-arrow.tsx#L55 ValuesOf<{
# Drag
= 'arrowdrag'
src/window-plug-arrow.tsx#L34
"arrowdrag"
Hold
= 'arrowhold'
src/window-plug-arrow.tsx#L35 "arrowhold"
Hover
= 'arrowhover'
src/window-plug-arrow.tsx#L36 "arrowhover"
Idle
= 'arrowidle'
src/window-plug-arrow.tsx#L33 "arrowidle"
viewFrameNormalRect
src/window-plug-arrow.tsx#L68 Rect
workspace
src/window-plug-arrow.tsx#L59 WorkspaceElement
zIndex
= 0
src/window-plug-arrow.tsx#L64 number
created
(ctx) # ctx
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> created(ctx) =>
- void
mounted
($) src/window-plug-arrow.tsx#L82 # $
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> mounted($) =>
- void
on
(name) # name
on<K>(name) =>
On<Fn<[ EventHandler<WindowPlugArrowElement, LifecycleEvents & WindowPlugArrowEvents [K]> ], Off>>
toJSON
()
toJSON() =>
Pick<WindowPlugArrowElement, keyof WindowPlugArrowElement>
WindowPlugElement
src/window-plug.tsx#L23 # constructor
(args)
$
Context<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> __#2@#offsetHeight
number
__#2@#offsetLeft
number
__#2@#offsetTop
number
__#2@#offsetWidth
number
connectStart
src/window-plug.tsx#L35 # (cable)
# cable
(cable) =>
EventHandler<WindowPlugElement, PointerEvent>
context
ContextClass<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> count
= 1
src/window-plug.tsx#L33 number
dest
src/window-plug.tsx#L30 ChildOf<WorkspaceWindowElement>
dispatch
Dispatch<# (name, detail, init)
host
# onconnectingend
EventHandler<WindowPlugElement, CustomEvent<any>>
onconnectingmove
EventHandler<WindowPlugElement, CustomEvent<any>>
onconnectingstart
EventHandler<WindowPlugElement, CustomEvent<any>>
onmounted
EventHandler<WindowPlugElement, CustomEvent<any>>
onstatechange
EventHandler<WindowPlugElement, CustomEvent<any>>
onunmounted
EventHandler<WindowPlugElement, CustomEvent<any>>
ownRect
Rect
plug
src/window-plug.tsx#L28 Plug<any, any>
pos
Point
rect
Rect
resize
src/window-plug.tsx#L34 # ()
() =>
- void
scene
src/window-plug.tsx#L31 # size
Point
state
= ...
src/window-plug.tsx#L27 State<WindowPlugElement, {
# Connecting
= 'connecting'
src/window-plug.tsx#L20
string
Idle
= 'idle'
src/window-plug.tsx#L19 string
__#2@#updateOffsets
()
__#2@#updateOffsets() =>
- void
created
(ctx) # ctx
Context<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> created(ctx) =>
- void
mounted
($) src/window-plug.tsx#L37 # $
Context<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> mounted($) =>
- void
on
(name) # name
on<K>(name) =>
On<Fn<[ EventHandler<WindowPlugElement, LifecycleEvents & WindowPlugEvents [K]> ], Off>>
toJSON
()
toJSON() =>
Pick<WindowPlugElement, keyof WindowPlugElement>
WindowPlugSceneElement
src/window-plug-scene.tsx#L26 # constructor
() node_modules/typescript/lib/lib.dom.d.ts#L6370
# new WindowPlugSceneElement
()
$
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> PlugArrows
= ...
src/window-plug-scene.tsx#L33 # ()
() =>
- VKid
WindowPlugArrow
= ...
src/window-plug-scene.tsx#L32 Component<WindowPlugArrowElement, HTMLElement>
addPlug
src/window-plug-scene.tsx#L49 # arrows
= ...
src/window-plug-scene.tsx#L44 # context
ContextClass<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> destRects
src/window-plug-scene.tsx#L42 Rect []
dispatch
Dispatch<# (name, detail, init)
draggingWindow
= false
src/window-plug-scene.tsx#L37 false
| WorkspaceWindowElement
enabled
= []
src/window-plug-scene.tsx#L55 # endConnecting
src/window-plug-scene.tsx#L53 # endPointerMove
src/window-plug-scene.tsx#L54 # ()
() =>
- void
holding
= false
src/window-plug-scene.tsx#L57 false
| {
# cable
src/window-plug-scene.tsx#L57
dest
src/window-plug-scene.tsx#L57 Point
plug
src/window-plug-scene.tsx#L57 } host
# hovering
= null
src/window-plug-scene.tsx#L56 null
| WindowPlugElement
onhover
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
onmounted
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
onunmounted
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
options
= ...
src/window-plug-scene.tsx#L40 # plugs
= ...
src/window-plug-scene.tsx#L45 ImmSet<WindowPlugElement>
plugsMap
= ...
src/window-plug-scene.tsx#L46 ImmMap<Plug<any, any>, WindowPlugElement>
removePlug
src/window-plug-scene.tsx#L50 # root
= ...
src/window-plug-scene.tsx#L30 # solve
src/window-plug-scene.tsx#L48 # ()
() =>
Promise<void>
startConnecting
src/window-plug-scene.tsx#L52 # surface
src/window-plug-scene.tsx#L36 SurfaceElement
viewRect
src/window-plug-scene.tsx#L41 Rect
worker
= ...
src/window-plug-scene.tsx#L39 Agent<WindowPlugWorker, WindowPlugCore>
workspace
src/window-plug-scene.tsx#L35 WorkspaceElement
created
(ctx) # ctx
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> created(ctx) =>
- void
mounted
($) src/window-plug-scene.tsx#L59 # $
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
# ctor
Class<T>
<T>(ctor) =>
CleanClass<T>
ctx
<T>(ctx) =>
Wrapper<T>
# Boolean
undefined | boolean
Number
undefined | number
String
undefined | string
"transition"
>> mounted($) =>
- void
on
(name) # name
on<K>(name) =>
On<Fn<[ EventHandler<WindowPlugSceneElement, LifecycleEvents & WindowPlugSceneEvents [K]> ], Off>>
toJSON
()
toJSON() =>
Pick<WindowPlugSceneElement, keyof WindowPlugSceneElement>
WindowPlugSolveData
src/types.ts#L10 # destRects
src/types.ts#L12
Rect []
viewRect
src/types.ts#L11 Rect
WindowPlugSolveOptions
src/types.ts#L3 # distance
src/types.ts#L6
number
heuristic
src/types.ts#L7 number
separation
src/types.ts#L5 number
step
src/types.ts#L4 number
WindowPlugArrowEvents
src/window-plug-arrow.tsx#L44 {
# arrowhoverstart
src/window-plug-arrow.tsx#L45
CustomEvent<{
}>
WindowPlugEvents
src/window-plug.tsx#L11 {
# connectingend
src/window-plug.tsx#L15
CustomEvent
connectingmove
src/window-plug.tsx#L14 CustomEvent
connectingstart
src/window-plug.tsx#L13 CustomEvent
statechange
src/window-plug.tsx#L12 CustomEvent
WindowPlugSceneEvents
src/window-plug-scene.tsx#L22 {
# hover
src/window-plug-scene.tsx#L23
CustomEvent
WindowPlugArrowState
= ...
src/window-plug-arrow.tsx#L32 {
# Drag
= 'arrowdrag'
src/window-plug-arrow.tsx#L34
"arrowdrag"
Hold
= 'arrowhold'
src/window-plug-arrow.tsx#L35 "arrowhold"
Hover
= 'arrowhover'
src/window-plug-arrow.tsx#L36 "arrowhover"
Idle
= 'arrowidle'
src/window-plug-arrow.tsx#L33 "arrowidle"
WindowPlugState
= ...
src/window-plug.tsx#L18 {
# Connecting
= 'connecting'
src/window-plug.tsx#L20
string
Idle
= 'idle'
src/window-plug.tsx#L19 string
## Credits
- [alice-bob](https://npmjs.org/package/alice-bob) by [stagas](https://github.com/stagas) – transport agnostic strongly typed duplex rpc interfaces
- [animatrix](https://npmjs.org/package/animatrix) by [stagas](https://github.com/stagas) – Create DOM Animations.
- [everyday-utils](https://npmjs.org/package/everyday-utils) by [stagas](https://github.com/stagas) – Everyday utilities
- [find-shortest-path](https://npmjs.org/package/find-shortest-path) by [stagas](https://github.com/stagas) – Find shortest path between two nodes using A-star search.
- [geometrik](https://npmjs.org/package/geometrik) by [stagas](https://github.com/stagas) – Geometry classes and utils.
- [immutable-map-set](https://npmjs.org/package/immutable-map-set) by [stagas](https://github.com/stagas) – Immutable Map and Set objects
- [plugs-and-cables](https://npmjs.org/package/plugs-and-cables) by [stagas](https://github.com/stagas) – Plugs and cables.
- [serialize-whatever](https://npmjs.org/package/serialize-whatever) by [stagas](https://github.com/stagas) – Serialize and deserialize whatever.
- [sigl](https://npmjs.org/package/sigl) by [stagas](https://github.com/stagas) – Web framework
- [x-workspace](https://npmjs.org/package/x-workspace) by [stagas](https://github.com/stagas) – Workspace Web Component.
## Contributing
[Fork](https://github.com/stagas/window-plug/fork) or [edit](https://github.dev/stagas/window-plug) and submit a PR.
All contributions are welcome!
## License
MIT © 2022 [stagas](https://github.com/stagas)