dead simple temporary toolbar

This commit is contained in:
Orion Reed 2024-12-03 03:40:24 -05:00
parent e9a5edf787
commit 23d3d54e33
2 changed files with 194 additions and 0 deletions

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Toolbar Demo</title>
<style>
html {
height: 100%;
}
body {
min-height: 100%;
position: relative;
margin: 0;
overscroll-behavior: none;
}
folk-shape {
background: transparent;
position: absolute;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 2px;
border: 2px solid rgba(0, 0, 0, 0.5);
}
distance-field {
position: absolute;
inset: 0;
}
folk-event-propagator {
display: block;
position: absolute;
inset: 0 0 0 0;
pointer-events: none;
z-index: 1000;
}
</style>
</head>
<body>
<distance-field>
<folk-shape id="box1" x="100" y="100" width="50" height="50"></folk-shape>
<folk-shape id="box2" x="100" y="200" width="50" height="50"></folk-shape>
<folk-shape id="box3" x="100" y="300" width="50" height="50"></folk-shape>
<folk-shape id="box4" x="300" y="150" width="80" height="40"></folk-shape>
<folk-shape id="box5" x="400" y="250" width="60" height="90"></folk-shape>
<folk-shape id="box6" x="200" y="400" width="100" height="100"></folk-shape>
<folk-shape id="box7" x="500" y="100" width="30" height="70"></folk-shape>
</distance-field>
<folk-toolbar></folk-toolbar>
<script type="module">
import { FolkShape } from '../src/folk-shape.ts';
import { DistanceField } from '../src/distance-field.ts';
import { FolkToolbar } from '../src/folk-toolbar.ts';
FolkShape.define();
DistanceField.define();
FolkToolbar.define();
</script>
</body>
</html>

130
src/folk-toolbar.ts Normal file
View File

@ -0,0 +1,130 @@
import { FolkEventPropagator } from './folk-event-propagator.ts';
import { css } from './common/tags.ts';
const styles = new CSSStyleSheet();
styles.replaceSync(css`
:host {
position: fixed;
bottom: 16px;
left: 50%;
transform: translateX(-50%);
background: white;
padding: 8px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
gap: 8px;
}
button {
padding: 8px 16px;
border-radius: 4px;
border: 1px solid #ccc;
background: white;
cursor: pointer;
}
button.active {
background: #eee;
}
`);
export class FolkToolbar extends HTMLElement {
static tagName = 'folk-toolbar';
static define() {
customElements.define(this.tagName, this);
}
#mode: 'idle' | 'connecting' = 'idle';
#sourceElement: Element | null = null;
#connectBtn: HTMLButtonElement;
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [styles];
const connectBtn = document.createElement('button');
connectBtn.textContent = 'Connect Elements';
connectBtn.addEventListener('click', () => this.toggleConnectionMode());
this.#connectBtn = connectBtn;
shadow.appendChild(connectBtn);
document.addEventListener('click', this.handleDocumentClick.bind(this));
}
toggleConnectionMode() {
if (this.#mode === 'idle') {
this.#mode = 'connecting';
this.#sourceElement = null;
document.body.style.cursor = 'crosshair';
this.#connectBtn.classList.add('active');
this.#connectBtn.textContent = 'Select Source Element...';
} else {
this.#mode = 'idle';
this.#sourceElement = null;
document.body.style.cursor = '';
this.#connectBtn.classList.remove('active');
this.#connectBtn.textContent = 'Connect Elements';
}
}
handleDocumentClick(event: MouseEvent) {
if (this.#mode !== 'connecting') return;
// Prevent clicking toolbar itself
if (event.composedPath().includes(this)) return;
event.preventDefault();
const target = event.target as Element;
if (!this.#sourceElement) {
// First click - select source
this.#sourceElement = target;
this.#connectBtn.textContent = 'Select Target Element...';
} else {
// Second click - create connection
this.createConnection(this.#sourceElement, target);
this.#sourceElement = null;
this.#mode = 'idle';
document.body.style.cursor = '';
this.#connectBtn.classList.remove('active');
this.#connectBtn.textContent = 'Connect Elements';
}
}
createConnection(source: Element, target: Element) {
const sourceId = source.id || this.ensureElementId(source);
const targetId = target.id || this.ensureElementId(target);
// hack because we gotta sort out usage of constructor vs connectedCallback
const propagator = new DOMParser().parseFromString(
`
<folk-event-propagator
source="#${sourceId}"
target="#${targetId}"
triggers="click"
expression="rotation: Math.random() * 360"
></folk-event-propagator>
`,
'text/html'
).body.firstElementChild;
if (!customElements.get('folk-event-propagator')) {
FolkEventPropagator.define();
}
if (propagator) {
document.body.appendChild(propagator);
}
}
ensureElementId(element: Element): string {
if (!element.id) {
element.id = `folk-element-${Math.random().toString(36).slice(2)}`;
}
return element.id;
}
}