window-plug
Workspace Window plug
npm i window-plug
|
pnpm add window-plug
|
yarn add window-plug
|
Examples
# web
# view source
example/web.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<WindowItemElement> {}
@$.element()
class WindowItemElement extends $(WorkspaceWindowElement) {
WindowPlug = $.element(WindowPlugElement)
plugScene?: WindowPlugSceneElement
@$.out() inputs = new $.RefSet<WindowPlugElement>([
{ plug: new Plug(Plug.Input, IO.Midi) },
{ plug: new Plug(Plug.Input, IO.Audio) },
])
@$.out() outputs = new $.RefSet<WindowPlugElement>([
{ plug: new Plug(Plug.Output, IO.Midi) },
{ plug: new Plug(Plug.Output, IO.Audio) },
])
mounted($: WindowItemElement['$']) {
$.Controls = $.part(() => <div></div>)
$.ContextMenu = $.part(() => (
<>
<ContextMenuOption keyboard={['Ctrl', 'N']}>New</ContextMenuOption>
<ContextMenuOption keyboard={['Alt', 'R']}>Remove the thing</ContextMenuOption>
<ContextMenuOption>and another</ContextMenuOption>
<hr />
<ContextMenuOption disabled>and another</ContextMenuOption>
<ContextMenuOption>and another</ContextMenuOption>
</>
))
const Plugs = $.part(({ host, WindowPlug, plugScene, inputs, outputs, onContextMenu }) => (
<div part="plugs">
{[
['inputs', inputs] as const,
['outputs', outputs] as const,
].map(([part, plugs]) => (
<div part={part}>
{plugs.map(plug => (
<WindowPlug
{window-plug.plug}
part="plug"
dest={host}
scene={plugScene}
oncontextmenu={onContextMenu(() => (
<>
<ContextMenuOption keyboard={['Alt', 'M']} disabled={!plug.ref.current?.plug?.cables.size}>
Mute All
</ContextMenuOption>
<ContextMenuOption keyboard={['Alt', 'D']} disabled={!plug.ref.current?.plug?.cables.size}>
Disconnect All
</ContextMenuOption>
</>
))}
/>
))}
</div>
))}
</div>
))
$.Item = $.part(({ WindowPlug }) => (
<>
<style>
{/*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; */
}
`}
</style>
<Plugs />
<div>hello this is a window</div>
</>
))
}
}
interface SceneElement extends $.Element<SceneElement> {}
@$.element()
class SceneElement extends HTMLElement {
Workspace = $.element(WorkspaceElement)
WindowItem = $.element(WindowItemElement)
WindowPlugScene = $.element(WindowPlugSceneElement)
workspace?: WorkspaceElement
plugScene?: WindowPlugSceneElement
@$.out() items = new $.RefSet<WindowItemElement>([
{ 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 }) => (
<WindowPlugScene ref={$.ref.plugScene} workspace={workspace} />
))
const PlugArrows = $.part(({ plugScene: { PlugArrows } }) => <PlugArrows />)
const Items = $.part(({ WindowItem, items, plugScene }) =>
items.map(item => <WindowItem {window-plug.item} plugScene={plugScene} />)
)
$.render(({ Workspace, WindowItem }) => (
<>
<style>
{/*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; */
}
`}
</style>
<Workspace ref={$.ref.workspace}>
<Items />
<PlugArrows />
</Workspace>
<PlugScene />
</>
))
}
}
const Scene = $.element(SceneElement)
const Classes = [
$.Rect,
$.RefSet,
Cable,
Plug,
SceneElement,
]
const sceneRef = new $.Ref()
const historyItems = new Set<string>()
const History = () => {
return [window-plug.historyItems].map((x, i) => (
<button
onclick={() => {
sceneRef.current = deserialize(x, Classes)
render()
}}
>
{i}
</button>
))
}
if (localStorage.lastScene) {
sceneRef.current = deserialize(localStorage.lastScene, Classes)
}
const render = () => {
$.render(
<>
<div style="z-index: 999999; position: fixed;">
<History />
</div>
<Scene
ref={sceneRef}
onchange={$.event.debounce(200)(() => {
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
# PlugArrow
src/plug-arrow.ts#L6
# constructor
(cable, dests, options)
src/plug-arrow.ts#L13
# new PlugArrow
()
# dests
Point | WindowPlugElement []
# options
# WindowPlugArrowElement
src/window-plug-arrow.tsx#L48
# $
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> # context
ContextClass<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> (Options) =>
-
EventHandler<any, MouseEvent>
# onarrowhoverstart
EventHandler<WindowPlugArrowElement, CustomEvent<{
}>>
# onmounted
EventHandler<WindowPlugArrowElement, CustomEvent<any>>
# onunmounted
EventHandler<WindowPlugArrowElement, CustomEvent<any>>
# reconnectInput
src/window-plug-arrow.tsx#L80 EventHandler<SVGCircleElement, PointerEvent | TouchEvent>
# reconnectOutput
src/window-plug-arrow.tsx#L79 EventHandler<SVGCircleElement, PointerEvent | TouchEvent>
# state
= ...
src/window-plug-arrow.tsx#L52 State<WindowPlugArrowElement, {
},
"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>
# created
(ctx)
# ctx
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> created(ctx) =>
- void
# mounted
($)
src/window-plug-arrow.tsx#L82
# $
Context<WindowPlugArrowElement & JsxContext<WindowPlugArrowElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> mounted($) =>
- void
# on
(name)
on<K>(name) =>
-
On<Fn<[ EventHandler<WindowPlugArrowElement, LifecycleEvents & WindowPlugArrowEvents [K]> ], Off>>
# WindowPlugElement
src/window-plug.tsx#L23
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> # connectStart
src/window-plug.tsx#L35 # (cable)
(cable) =>
-
EventHandler<WindowPlugElement, PointerEvent>
# context
ContextClass<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> # 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>>
# state
= ...
src/window-plug.tsx#L27 State<WindowPlugElement, {
}, string> & EventMethods<WindowPlugElement, {}> & InlineEventMap<WindowPlugElement, {}>
# created
(ctx)
# ctx
Context<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> created(ctx) =>
- void
# mounted
($)
src/window-plug.tsx#L37
# $
Context<WindowPlugElement & JsxContext<WindowPlugElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> mounted($) =>
- void
# on
(name)
on<K>(name) =>
-
On<Fn<[ EventHandler<WindowPlugElement, LifecycleEvents & WindowPlugEvents [K]> ], Off>>
# WindowPlugSceneElement
src/window-plug-scene.tsx#L26
# $
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> # WindowPlugArrow
= ...
src/window-plug-scene.tsx#L32 Component<WindowPlugArrowElement, HTMLElement>
# context
ContextClass<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> # onhover
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
# onmounted
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
# onunmounted
EventHandler<WindowPlugSceneElement, CustomEvent<any>>
# created
(ctx)
# ctx
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> created(ctx) =>
- void
# mounted
($)
src/window-plug-scene.tsx#L59
# $
Context<WindowPlugSceneElement & JsxContext<WindowPlugSceneElement> & Omit<{
<T>(ctor) =>
-
CleanClass<T>
<T>(ctx) =>
-
Wrapper<T>
},
"transition"
>> mounted($) =>
- void
# on
(name)
on<K>(name) =>
-
On<Fn<[ EventHandler<WindowPlugSceneElement, LifecycleEvents & WindowPlugSceneEvents [K]> ], Off>>
# WindowPlugArrowEvents
src/window-plug-arrow.tsx#L44 {
Credits
- alice-bob by stagas – transport agnostic strongly typed duplex rpc interfaces
- animatrix by stagas – Create DOM Animations.
- everyday-utils by stagas – Everyday utilities
- find-shortest-path by stagas – Find shortest path between two nodes using A-star search.
- geometrik by stagas – Geometry classes and utils.
- immutable-map-set by stagas – Immutable Map and Set objects
- plugs-and-cables by stagas – Plugs and cables.
- serialize-whatever by stagas – Serialize and deserialize whatever.
- sigl by stagas – Web framework
- x-workspace by stagas – Workspace Web Component.
Contributing
All contributions are welcome!