fix: 修复灯带顺序控件不能很好地被控制。

This commit is contained in:
Ivan Li 2023-04-15 11:26:05 +08:00
parent a905c98823
commit 09799cb2d5
2 changed files with 160 additions and 63 deletions

View File

@ -1,14 +1,16 @@
import { import {
batch, batch,
Component, Component,
createContext,
createEffect, createEffect,
createMemo, createMemo,
createSignal, createSignal,
For, For,
Index, Index,
JSX, JSX,
on, Match,
onCleanup,
onMount,
Switch,
untrack, untrack,
useContext, useContext,
} from 'solid-js'; } from 'solid-js';
@ -21,18 +23,24 @@ import background from '../assets/transparent-grid-background.svg?url';
const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }> = ( const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }> = (
props, props,
) => { ) => {
const [fullLeds, setFullLeds] = createSignal<Array<string | null>>([]); const [leds, setLeds] = createSignal<Array<string | null>>([]);
const [dragging, setDragging] = createSignal<boolean>(false); const [dragging, setDragging] = createSignal<boolean>(false);
const [dragStart, setDragStart] = createSignal<{ x: number; y: number } | null>(null); const [dragStart, setDragStart] = createSignal<{ x: number; y: number } | null>(null);
const [dragCurr, setDragCurr] = createSignal<{ x: number; y: number } | null>(null); const [dragCurr, setDragCurr] = createSignal<{ x: number; y: number } | null>(null);
const [dragStartIndex, setDragStartIndex] = createSignal<number>(0); const [dragStartIndex, setDragStartIndex] = createSignal<number>(0);
const [cellWidth, setCellWidth] = createSignal<number>(0); const [cellWidth, setCellWidth] = createSignal<number>(0);
const [, { setSelectedStripPart }] = useContext(LedStripConfigurationContext); const [, { setSelectedStripPart }] = useContext(LedStripConfigurationContext);
const [rootWidth, setRootWidth] = createSignal<number>(0);
let root: HTMLDivElement;
const move = (targetStart: number) => { const move = (targetStart: number) => {
if (targetStart === props.mapper.start) { if (targetStart === props.mapper.start) {
return; return;
} }
console.log(
`moving strip part ${props.strip.display_id} ${props.strip.border} from ${props.mapper.start} to ${targetStart}`,
);
invoke('move_strip_part', { invoke('move_strip_part', {
displayId: props.strip.display_id, displayId: props.strip.display_id,
border: props.strip.border, border: props.strip.border,
@ -43,40 +51,67 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
// reset translateX on config updated // reset translateX on config updated
createEffect(() => { createEffect(() => {
const indexDiff = props.mapper.start - dragStartIndex(); const indexDiff = props.mapper.start - dragStartIndex();
untrack(() => { const start = untrack(dragStart);
if (!dragStart() || !dragCurr()) { const curr = untrack(dragCurr);
return; const _dragging = untrack(dragging);
}
if (start === null || curr === null) {
return;
}
if (_dragging && indexDiff !== 0) {
const compensation = indexDiff * cellWidth(); const compensation = indexDiff * cellWidth();
batch(() => { batch(() => {
setDragStartIndex(props.mapper.start); setDragStartIndex(props.mapper.start);
setDragStart({ setDragStart({
x: dragStart()!.x + compensation, x: start.x + compensation,
y: dragCurr()!.y, y: curr.y,
}); });
}); });
}); } else {
batch(() => {
setDragStartIndex(props.mapper.start);
setDragStart(null);
setDragCurr(null);
});
}
}); });
const onPointerDown = (ev: PointerEvent) => { const onPointerDown = (ev: PointerEvent) => {
if (ev.button !== 0) { if (ev.button !== 0) {
return; return;
} }
setDragging(true); batch(() => {
setDragStart({ x: ev.clientX, y: ev.clientY }); setDragging(true);
setDragCurr({ x: ev.clientX, y: ev.clientY }); if (dragStart() === null) {
setDragStartIndex(props.mapper.start); setDragStart({ x: ev.clientX, y: ev.clientY });
}
setDragCurr({ x: ev.clientX, y: ev.clientY });
setDragStartIndex(props.mapper.start);
});
}; };
const onPointerUp = () => (ev: PointerEvent) => { const onPointerUp = (ev: PointerEvent) => {
if (ev.button !== 0) { if (ev.button !== 0) {
return; return;
} }
if (dragging() === false) {
return;
}
setDragging(false); setDragging(false);
const diff = ev.clientX - dragStart()!.x;
const moved = Math.round(diff / cellWidth());
if (moved === 0) {
return;
}
move(props.mapper.start + moved);
}; };
const onPointerMove = (ev: PointerEvent) => { const onPointerMove = (ev: PointerEvent) => {
if (dragging() === false) {
return;
}
setSelectedStripPart({ setSelectedStripPart({
displayId: props.strip.display_id, displayId: props.strip.display_id,
border: props.strip.border, border: props.strip.border,
@ -89,22 +124,26 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
return; return;
} }
setDragCurr({ x: ev.clientX, y: ev.clientY }); setDragCurr({ x: ev.clientX, y: ev.clientY });
const cellWidth =
(ev.currentTarget as HTMLDivElement).clientWidth / ledStripStore.totalLedCount;
const diff = ev.clientX - dragStart()!.x;
const moved = Math.round(diff / cellWidth);
if (moved === 0) {
return;
}
setCellWidth(cellWidth);
move(props.mapper.start + moved);
}; };
const onPointerLeave = () => { const onPointerLeave = () => {
setSelectedStripPart(null); setSelectedStripPart(null);
}; };
createEffect(() => {
onMount(() => {
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerleave', onPointerLeave);
window.addEventListener('pointerup', onPointerUp);
});
onCleanup(() => {
window.removeEventListener('pointermove', onPointerMove);
window.removeEventListener('pointerleave', onPointerLeave);
window.removeEventListener('pointerup', onPointerUp);
});
});
const reverse = () => { const reverse = () => {
invoke('reverse_led_strip_part', { invoke('reverse_led_strip_part', {
displayId: props.strip.display_id, displayId: props.strip.display_id,
@ -112,59 +151,104 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper
}).catch((err) => console.error(err)); }).catch((err) => console.error(err));
}; };
// update fullLeds const setColor = (fullIndex: number, colorsIndex: number, fullLeds: string[]) => {
createEffect(() => {
const fullLeds = new Array(ledStripStore.totalLedCount).fill(null);
const colors = ledStripStore.colors; const colors = ledStripStore.colors;
let c1 = `rgb(${Math.floor(colors[colorsIndex * 3] * 0.8)}, ${Math.floor(
colors[colorsIndex * 3 + 1] * 0.8,
)}, ${Math.floor(colors[colorsIndex * 3 + 2] * 0.8)})`;
let c2 = `rgb(${Math.min(Math.floor(colors[colorsIndex * 3] * 1.2), 255)}, ${Math.min(
Math.floor(colors[colorsIndex * 3 + 1] * 1.2),
255,
)}, ${Math.min(Math.floor(colors[colorsIndex * 3 + 2] * 1.2), 255)})`;
const { start, end, pos } = props.mapper; if (fullLeds.length <= fullIndex) {
const isForward = start < end; console.error('out of range', fullIndex, fullLeds.length);
const step = isForward ? 1 : -1; return;
for (let i = start, j = pos; i !== end; i += step, j++) {
let c1 = `rgb(${Math.floor(colors[j * 3] * 0.8)}, ${Math.floor(
colors[j * 3 + 1] * 0.8,
)}, ${Math.floor(colors[j * 3 + 2] * 0.8)})`;
let c2 = `rgb(${Math.min(Math.floor(colors[j * 3] * 1.2), 255)}, ${Math.min(
Math.floor(colors[j * 3 + 1] * 1.2),
255,
)}, ${Math.min(Math.floor(colors[j * 3 + 2] * 1.2), 255)})`;
fullLeds[i] = `linear-gradient(70deg, ${c1} 10%, ${c2})`;
} }
setFullLeds(fullLeds); fullLeds[fullIndex] = `linear-gradient(70deg, ${c1} 10%, ${c2})`;
};
// update fullLeds
createEffect(() => {
const { start, end, pos } = props.mapper;
const leds = new Array(Math.abs(start - end)).fill(null);
if (start < end) {
for (let i = 0, j = pos; i < leds.length; i++, j++) {
setColor(i, j, leds);
}
} else {
for (let i = leds.length - 1, j = pos; i >= 0; i--, j++) {
setColor(i, j, leds);
}
}
setLeds(leds);
});
// update rootWidth
createEffect(() => {
let observer: ResizeObserver;
onMount(() => {
observer = new ResizeObserver(() => {
setRootWidth(root.clientWidth);
});
observer.observe(root);
});
onCleanup(() => {
observer?.unobserve(root);
});
});
// update cellWidth
createEffect(() => {
const cellWidth = rootWidth() / ledStripStore.totalLedCount;
setCellWidth(cellWidth);
}); });
const style = createMemo<JSX.CSSProperties>(() => { const style = createMemo<JSX.CSSProperties>(() => {
return { return {
transform: `translateX(${(dragCurr()?.x ?? 0) - (dragStart()?.x ?? 0)}px)`, transform: `translateX(${
(dragCurr()?.x ?? 0) -
(dragStart()?.x ?? 0) +
cellWidth() * Math.min(props.mapper.start, props.mapper.end)
}px)`,
width: `${cellWidth() * leds().length}px`,
}; };
}); });
return ( return (
<div <div
class="flex h-2 m-2 select-none cursor-ew-resize focus:cursor-ew-resize" class="flex mx-2 select-none cursor-ew-resize focus:cursor-ew-resize"
style={style()}
onPointerMove={onPointerMove}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
onPointerLeave={onPointerLeave}
ondblclick={reverse} ondblclick={reverse}
ref={root!}
> >
<For each={fullLeds()}> <div
{(it) => ( style={style()}
<div class="rounded-full border border-white flex h-3"
class="flex-auto flex h-full w-full justify-center items-center relative" classList={{
title={it ?? ''} 'bg-gradient-to-b from-yellow-500/60 to-orange-300/60': dragging(),
> 'bg-gradient-to-b from-white/50 to-stone-500/40': !dragging(),
}}
>
<For each={leds()}>
{(it) => (
<div <div
class="absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-full ring-1 ring-stone-100" class="flex-auto flex h-full w-full justify-center items-center relative"
classList={{ 'ring-stone-300/50': !it }} title={it ?? ''}
style={{ background: it ?? 'transparent' }} >
/> <div
</div> class="absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-full ring-1 ring-stone-100"
)} classList={{ 'ring-stone-300/50': !it }}
</For> style={{ background: it ?? 'transparent' }}
/>
</div>
)}
</For>
</div>
</div> </div>
); );
}; };
@ -220,7 +304,11 @@ export const LedStripPartsSorter: Component = () => {
<SorterResult /> <SorterResult />
<Index each={ledStripStore.strips}> <Index each={ledStripStore.strips}>
{(strip, index) => ( {(strip, index) => (
<SorterItem strip={strip()} mapper={ledStripStore.mappers[index]} /> <Switch>
<Match when={strip().len > 0}>
<SorterItem strip={strip()} mapper={ledStripStore.mappers[index]} />
</Match>
</Switch>
)} )}
</Index> </Index>
</div> </div>

View File

@ -7,6 +7,15 @@ export const [ledStripStore, setLedStripStore] = createStore({
colors: new Uint8ClampedArray(), colors: new Uint8ClampedArray(),
sortedColors: new Uint8ClampedArray(), sortedColors: new Uint8ClampedArray(),
get totalLedCount() { get totalLedCount() {
return Math.max(0, ...ledStripStore.mappers.map((m) => Math.max(m.start, m.end))); return Math.max(
0,
...ledStripStore.mappers.map((m) => {
if (m.start === m.end) {
return 0;
} else {
return Math.max(m.start, m.end);
}
}),
);
}, },
}); });