import React, { useState, createRef, useEffect } from "react";
import { fromEvent, interval, animationFrameScheduler } from "rxjs";
import { flatMap, scan, withLatestFrom, map, takeUntil, last } from "rxjs/operators";
import { lerp } from "../../utils";
import "./style.css";
function Draggable({
id,
dispatch,
pos,
label,
size
}) {
const draggableRef = createRef();
const [coords, setCoords] = useState(pos);
const [labelPos, setLabelPos] = useState({
x: pos.x + size.w / 2,
y: pos.y + size.h / 2,
});
useEffect(() => {
setLabelPos({
x: coords.x + size.w/2,
y: coords.y + size.h/2
})
dispatch({
type: "UPDATE_BOX_POS",
id: id,
x: coords.x,
y: coords.y,
});
}, [dispatch, coords, size, id]);
const handleMouseDown = (evt) => {
const animationFrame$ = interval(0, animationFrameScheduler);
const dragTarget = draggableRef.current;
const mousedown$ = fromEvent(dragTarget, "mousedown");
const mousemove$ = fromEvent(document, "mousemove");
const mouseup$ = fromEvent(dragTarget, "mouseup");
console.log("----- drag start -----");
const mousedrag$ = mousedown$.pipe(
flatMap((md) => {
const dx = md.offsetX - coords.x;
const dy = md.offsetY - coords.y;
return animationFrame$.pipe(
withLatestFrom(mousemove$, (_, p) => {
p.preventDefault();
return p
}),
scan(lerp(0.5)),
map((p) => ({
x: p.x - dx,
y: p.y - dy
})
),
takeUntil(mouseup$.pipe(last()))
);
}),
takeUntil(mouseup$)
);
mousedown$.subscribe(() => {
dispatch({type: 'SELECT_BOX', id: id, selected: true });
});
mousedrag$.subscribe(({x, y}) => {
setCoords({ x, y });
});
mouseup$.subscribe(() => {
// if (!selected) { return; }
console.log("----- drag end -----");
dispatch({ type: "SELECT_BOX", id: id, selected: false });
});
}
return (
<g
className="ele"
onMouseDown={(e) => handleMouseDown(e)}
ref={draggableRef}
>
<rect
width={size.w}
height={size.h}
x={coords.x}
y={coords.y}
rx="5"
ry="5"
fill="#fff"
stroke={"#111" /*isSelected ? "#666" : "#111"*/}
></rect>
<text
x={labelPos.x}
y={labelPos.y}
textAnchor="middle"
alignmentBaseline="central">
Hello
</text>
</g>
);
}
export default Draggable;
function Draggable({ id, dispatch, pos, label, size }) {
const draggableRef = createRef();
const [coords, setCoords] = useState(pos);
const [labelPos, setLabelPos] = useState({
x: pos.x + size.w / 2,
y: pos.y + size.h / 2
});
useEffect(
() => {
setLabelPos({
x: coords.x + size.w / 2,
y: coords.y + size.h / 2
});
dispatch({
type: 'UPDATE_BOX_POS',
id: id,
x: coords.x,
y: coords.y
});
},
[dispatch, coords, size, id]
);
// NOTE - Using useMemo here for simplicity's sake, but you may want to use a
// combination of useEffect and useRef instead, as useMemo can recalculate
// values at will.
const drag$ = useMemo(
() => {
if (!draggableRef.current) return EMPTY;
const animationFrame$ = interval(0, animationFrameScheduler);
const dragTarget = draggableRef.current;
// Using tap means the side effects are handled automatically as part of
// a single subscription to the "mousedrag$" stream.
const mousedown$ = fromEvent(dragTarget, 'mousedown').pipe(
tap(() => dispatch({ type: 'SELECT_BOX', id: id, selected: true }))
);
const mouseup$ = fromEvent(dragTarget, 'mouseup').pipe(
tap(() => {
// if (!selected) { return; }
console.log('----- drag end -----');
dispatch({ type: 'SELECT_BOX', id: id, selected: false });
}),
share()
);
const mousemove$ = fromEvent(document, 'mousemove');
const mousedrag$ = mousedown$.pipe(
flatMap((md) => {
const dx = md.offsetX - coords.x;
const dy = md.offsetY - coords.y;
return animationFrame$.pipe(
withLatestFrom(mousemove$, (_, p) => {
p.preventDefault();
return p;
}),
scan(lerp(0.5)),
map((p) => ({
x: p.x - dx,
y: p.y - dy
})),
// What does this do?
takeUntil(mouseup$.pipe(last()))
);
}),
takeUntil(mouseup$),
tap(({ x, y }) => {
setCoords({ x, y });
})
);
return mousedrag$;
},
[draggableRef.current, dispatch, setCoords]
);
useEffect(
() => {
const subscription = drag$.subscribe();
return () => subscription.unsubscribe();
},
[drag$]
);
return (
<g className="ele" ref={draggableRef}>
<rect
width={size.w}
height={size.h}
x={coords.x}
y={coords.y}
rx="5"
ry="5"
fill="#fff"
stroke={'#111' /*isSelected ? "#666" : "#111"*/}
/>
<text
x={labelPos.x}
y={labelPos.y}
textAnchor="middle"
alignmentBaseline="central"
>
Hello
</text>
</g>
);
}
export default Draggable;
I often find myself writing Observable chains with empty success
handlers like this:
concat(this.tripExpenseAccessor.destroyExpense(expense), this.hydrate()).subscribe(
(success) => {},
(error) => {
this.rollbar.error(error);
}
);
This feels like a code smell. Is the best practice to use catchError instead like below?
concat(this.tripExpenseAccessor.destroyExpense(expense), this.hydrate()).pipe(
catchError((error) => {
this.rollbar.error(error);
return of({});
})
).subscribe();
this.onChangeHourChanged.pipe(
distinctUntilChanged(),
takeUntil(this.destroyed$),
).subscribe((hour) => {
const type = DateTimeType[this.dateTimeType];
console.log('HourChanged');
this.currentDateTime.set({
hour,
});
this.time = this.setTime(this.dateFilterValidatorService.setDate(type, this.currentDateTime));
this.writeValue(this.time);
});
this.onDispatchChanges.next(changes);
this.onDispatchChanges.pipe(
debounceTime(600),
distinctUntilChanged(),
).subscribe(dateChanges => {
this.dispatchChanges(dateChanges);
});
next
on it later