refactor event propagator to lit

This commit is contained in:
“chrisshank” 2024-12-10 00:48:01 -08:00
parent 24b9442a43
commit fb3895e0d4
10 changed files with 103 additions and 133 deletions

View File

@ -41,14 +41,14 @@
<folk-event-propagator
source="folk-metronome"
target="#kick"
triggers="beat"
trigger="beat"
expression="play(): from.beat === 1"
></folk-event-propagator>
<folk-event-propagator
source="folk-metronome"
target="#hat"
triggers="beat"
trigger="beat"
expression="play(): from.beat % 3"
></folk-event-propagator>

View File

@ -90,14 +90,14 @@
<folk-event-propagator
source="#recipe"
target="folk-llm"
triggers="click"
trigger="click"
expression="prompt: `double this list of ingredients '${from.innerHTML}'`"
></folk-event-propagator>
<folk-event-propagator
source="folk-llm"
target="folk-timer"
triggers="started"
trigger="started"
expression="reset(): true
start(): true"
>
@ -106,7 +106,7 @@ start(): true"
<folk-event-propagator
source="folk-llm"
target="folk-timer"
triggers="finished"
trigger="finished"
expression="stop(): true"
></folk-event-propagator>

View File

@ -39,7 +39,7 @@
<folk-event-propagator
source="#box1"
target="#box2"
triggers="click"
trigger="click"
expression="textContent: to.textContent + 'r'"
></folk-event-propagator>
@ -48,13 +48,12 @@
<folk-event-propagator
source="#box3"
target="#box4"
triggers="transform"
trigger="transform"
expression="y: from.x,
rotation: from.x"
></folk-event-propagator>
<script type="module">
import 'https://cdn.jsdelivr.net/npm/@zachleat/snow-fall';
import '../src/standalone/folk-shape.ts';
import '../src/standalone/folk-event-propagator.ts';
@ -80,6 +79,8 @@ rotation: from.x"
}
isBlowing = !isBlowing;
});
requestIdleCallback(() => import('https://cdn.jsdelivr.net/npm/@zachleat/snow-fall'));
</script>
</body>
</html>

View File

@ -30,7 +30,7 @@
<folk-event-propagator
source="#box1"
target="#box2"
triggers="click"
trigger="click"
expression="textContent: to.textContent + '!'"
></folk-event-propagator>
@ -39,7 +39,7 @@
<folk-event-propagator
source="#box3"
target="#box4"
triggers="transform"
trigger="transform"
expression="y: from.x,
rotation: from.x"
></folk-event-propagator>

View File

@ -52,21 +52,21 @@
<folk-event-propagator
source="textarea"
target="sl-qr-code"
triggers="input"
trigger="input"
expression="value: from.value"
></folk-event-propagator>
<folk-event-propagator
source="input[type='range']"
target="sl-qr-code"
triggers="input"
trigger="input"
expression="radius: from.value"
></folk-event-propagator>
<folk-event-propagator
source="input[type='color']"
target="sl-qr-code"
triggers="input"
trigger="input"
expression="fill: from.value"
></folk-event-propagator>

View File

@ -76,14 +76,14 @@
<folk-event-propagator
source="input[type='range']"
target="folk-map"
triggers="input"
trigger="input"
expression="lat: from.value"
></folk-event-propagator>
<folk-event-propagator
source="folk-map"
target="input[type='range']"
triggers="recenter"
trigger="recenter"
expression="value: from.lat"
></folk-event-propagator>

View File

@ -143,21 +143,21 @@
<folk-event-propagator
source="#map1"
target="folk-cell[column='A'][row='1']"
triggers="recenter"
trigger="recenter"
expression="expression: from.coordinates.lat"
></folk-event-propagator>
<folk-event-propagator
source="#map1"
target="folk-cell[column='A'][row='2']"
triggers="recenter"
trigger="recenter"
expression="expression: from.coordinates.lng"
></folk-event-propagator>
<folk-event-propagator
source="folk-cell[column='A'][row='3']"
target="#map2"
triggers="propagate"
trigger="propagate"
expression="coordinates: from.value"
></folk-event-propagator>

View File

@ -10,11 +10,7 @@ const folkObserver = new FolkObserver();
export class FolkBaseConnection extends FolkElement {
@property({ type: String, reflect: true }) source = '';
#sourceElement: Element | null = null;
get sourceElement() {
return this.#sourceElement;
}
@state() sourceElement: Element | null = null;
@state() sourceRect: DOMRectReadOnly | null = null;
@ -22,11 +18,7 @@ export class FolkBaseConnection extends FolkElement {
@state() targetRect: DOMRectReadOnly | null = null;
#targetElement: Element | null = null;
get targetElement() {
return this.#targetElement;
}
@state() targetElement: Element | null = null;
override disconnectedCallback() {
super.disconnectedCallback();
@ -58,20 +50,20 @@ export class FolkBaseConnection extends FolkElement {
if (vertex) {
this.sourceRect = DOMRectReadOnly.fromRect(vertex);
} else {
this.#sourceElement = document.querySelector(this.source);
this.sourceElement = document.querySelector(this.source);
if (this.#sourceElement === null) {
if (this.sourceElement === null) {
this.sourceRect = null;
} else {
folkObserver.observe(this.#sourceElement, this.#sourceCallback);
folkObserver.observe(this.sourceElement, this.#sourceCallback);
}
}
}
unobserveSource() {
if (this.#sourceElement === null) return;
if (this.sourceElement === null) return;
folkObserver.unobserve(this.#sourceElement, this.#sourceCallback);
folkObserver.unobserve(this.sourceElement, this.#sourceCallback);
}
#targetCallback = (entry: ClientRectObserverEntry) => {
@ -86,18 +78,18 @@ export class FolkBaseConnection extends FolkElement {
if (vertex) {
this.targetRect = DOMRectReadOnly.fromRect(vertex);
} else {
this.#targetElement = document.querySelector(this.target);
this.targetElement = document.querySelector(this.target);
if (this.#targetElement === null) {
if (this.targetElement === null) {
this.targetRect = null;
} else {
folkObserver.observe(this.#targetElement, this.#targetCallback);
folkObserver.observe(this.targetElement, this.#targetCallback);
}
}
}
unobserveTarget() {
if (this.#targetElement === null) return;
folkObserver.unobserve(this.#targetElement, this.#targetCallback);
if (this.targetElement === null) return;
folkObserver.unobserve(this.targetElement, this.#targetCallback);
}
}

View File

@ -1,5 +1,6 @@
import { css, PropertyValues } from '@lit/reactive-element';
import { FolkRope } from './folk-rope.ts';
import { property } from '@lit/reactive-element/decorators.js';
// import * as parser from '@babel/parser';
export class FolkEventPropagator extends FolkRope {
@ -30,30 +31,77 @@ export class FolkEventPropagator extends FolkRope {
}
`;
#triggers: string[] = [];
get triggers() {
return this.#triggers;
}
set triggers(triggers: string | string[]) {
if (typeof triggers === 'string') {
triggers = triggers.split(',');
}
this.#removeEventListenersToSource();
@property({ type: String, reflect: true }) trigger = '';
this.#triggers = triggers;
@property({ type: String, reflect: true }) expression = '';
this.#addEventListenersToSource();
}
#expression = '';
#function: Function | null = null;
get expression() {
return this.#expression;
#triggerTextarea = document.createElement('textarea');
#expressionTextarea = document.createElement('textarea');
override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);
this.#triggerTextarea.addEventListener('change', () => {
this.trigger = this.#triggerTextarea.value;
});
this.#expressionTextarea.addEventListener('input', () => {
this.expression = this.#expressionTextarea.value;
});
this.#triggerTextarea.value = this.trigger;
this.#expressionTextarea.value = this.expression;
this.renderRoot.append(this.#triggerTextarea, this.#expressionTextarea);
}
set expression(expression) {
this.mend();
this.#expression = expression;
const processedExp = expression.trim();
override updated(changedProperties: PropertyValues<this>): void {
super.update(changedProperties);
if (changedProperties.has('trigger')) {
this.sourceElement?.removeEventListener(this.trigger, this.#evaluateExpression);
this.sourceElement?.addEventListener(this.trigger, this.#evaluateExpression);
}
if (changedProperties.has('expression')) {
this.#parseExpression();
}
const previousSourceElement = changedProperties.get('sourceElement');
if (previousSourceElement) {
const trigger = changedProperties.get('trigger') || this.trigger;
previousSourceElement.removeEventListener(trigger, this.#evaluateExpression);
this.sourceElement?.addEventListener(this.trigger, this.#evaluateExpression);
}
}
override disconnectedCallback(): void {
super.disconnectedCallback();
this.sourceElement?.removeEventListener(this.trigger, this.#evaluateExpression);
}
override draw() {
super.draw();
const triggerPoint = this.points[Math.floor(this.points.length / 5)];
if (triggerPoint) {
this.#triggerTextarea.style.left = `${triggerPoint.pos.x}px`;
this.#triggerTextarea.style.top = `${triggerPoint.pos.y}px`;
}
const expressionPoint = this.points[Math.floor(this.points.length / 2)];
if (expressionPoint) {
this.#expressionTextarea.style.left = `${expressionPoint.pos.x}px`;
this.#expressionTextarea.style.top = `${expressionPoint.pos.y}px`;
}
}
#parseExpression() {
const processedExp = this.expression.trim();
const codeLines: string[] = [];
@ -103,6 +151,7 @@ to.${key} = ${value};`);
try {
// parseAst(functionBody);
this.#function = new Function('from', 'to', 'event', functionBody);
this.mend();
} catch (error) {
console.warn('Failed to parse expression:', error, functionBody);
this.cut();
@ -110,80 +159,8 @@ to.${key} = ${value};`);
}
}
#triggerTextarea = document.createElement('textarea');
#expressionTextarea = document.createElement('textarea');
override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);
this.#triggerTextarea.addEventListener('change', () => {
this.triggers = this.#triggerTextarea.value;
});
this.triggers = this.#triggerTextarea.value = this.getAttribute('triggers') || '';
this.renderRoot.appendChild(this.#triggerTextarea);
this.#expressionTextarea.addEventListener('input', () => {
this.expression = this.#expressionTextarea.value;
});
this.renderRoot.appendChild(this.#expressionTextarea);
this.expression = this.#expressionTextarea.value = this.getAttribute('expression') || '';
}
override draw() {
super.draw();
const triggerPoint = this.points[Math.floor(this.points.length / 5)];
if (triggerPoint) {
this.#triggerTextarea.style.left = `${triggerPoint.pos.x}px`;
this.#triggerTextarea.style.top = `${triggerPoint.pos.y}px`;
}
const expressionPoint = this.points[Math.floor(this.points.length / 2)];
if (expressionPoint) {
this.#expressionTextarea.style.left = `${expressionPoint.pos.x}px`;
this.#expressionTextarea.style.top = `${expressionPoint.pos.y}px`;
}
}
override observeSource() {
super.observeSource();
this.#addEventListenersToSource();
}
#addEventListenersToSource() {
for (const trigger of this.#triggers) {
// TODO: add special triggers for intersection, rAF, etc.
this.sourceElement?.addEventListener(trigger, this.evaluateExpression);
}
}
override unobserveSource() {
super.unobserveSource();
this.#removeEventListenersToSource();
}
#removeEventListenersToSource() {
for (const trigger of this.#triggers) {
this.sourceElement?.removeEventListener(trigger, this.evaluateExpression);
}
}
override observeTarget() {
super.observeTarget();
}
override unobserveTarget() {
super.unobserveTarget();
}
evaluateExpression = (event?: Event) => {
#evaluateExpression = (event?: Event) => {
console.log('eval');
if (this.sourceElement === null || this.targetElement === null) return;
this.stroke = 'black';
if (!this.#function) return;

View File

@ -106,7 +106,7 @@ export class FolkToolbar extends HTMLElement {
<folk-event-propagator
source="#${sourceId}"
target="#${targetId}"
triggers="click"
trigger="click"
expression="rotation: Math.random() * 360"
></folk-event-propagator>
`,