diff --git a/src/components/led-strip-parts-sorter.tsx b/src/components/led-strip-parts-sorter.tsx index cb01e42..6e2d514 100644 --- a/src/components/led-strip-parts-sorter.tsx +++ b/src/components/led-strip-parts-sorter.tsx @@ -1,14 +1,16 @@ import { batch, Component, - createContext, createEffect, createMemo, createSignal, For, Index, JSX, - on, + Match, + onCleanup, + onMount, + Switch, untrack, useContext, } from 'solid-js'; @@ -21,18 +23,24 @@ import background from '../assets/transparent-grid-background.svg?url'; const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }> = ( props, ) => { - const [fullLeds, setFullLeds] = createSignal>([]); + const [leds, setLeds] = createSignal>([]); const [dragging, setDragging] = createSignal(false); const [dragStart, setDragStart] = createSignal<{ x: number; y: number } | null>(null); const [dragCurr, setDragCurr] = createSignal<{ x: number; y: number } | null>(null); const [dragStartIndex, setDragStartIndex] = createSignal(0); const [cellWidth, setCellWidth] = createSignal(0); const [, { setSelectedStripPart }] = useContext(LedStripConfigurationContext); + const [rootWidth, setRootWidth] = createSignal(0); + + let root: HTMLDivElement; const move = (targetStart: number) => { if (targetStart === props.mapper.start) { return; } + console.log( + `moving strip part ${props.strip.display_id} ${props.strip.border} from ${props.mapper.start} to ${targetStart}`, + ); invoke('move_strip_part', { displayId: props.strip.display_id, border: props.strip.border, @@ -43,40 +51,67 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper // reset translateX on config updated createEffect(() => { const indexDiff = props.mapper.start - dragStartIndex(); - untrack(() => { - if (!dragStart() || !dragCurr()) { - return; - } + const start = untrack(dragStart); + const curr = untrack(dragCurr); + const _dragging = untrack(dragging); + if (start === null || curr === null) { + return; + } + if (_dragging && indexDiff !== 0) { const compensation = indexDiff * cellWidth(); batch(() => { setDragStartIndex(props.mapper.start); setDragStart({ - x: dragStart()!.x + compensation, - y: dragCurr()!.y, + x: start.x + compensation, + y: curr.y, }); }); - }); + } else { + batch(() => { + setDragStartIndex(props.mapper.start); + setDragStart(null); + setDragCurr(null); + }); + } }); const onPointerDown = (ev: PointerEvent) => { if (ev.button !== 0) { return; } - setDragging(true); - setDragStart({ x: ev.clientX, y: ev.clientY }); - setDragCurr({ x: ev.clientX, y: ev.clientY }); - setDragStartIndex(props.mapper.start); + batch(() => { + setDragging(true); + if (dragStart() === null) { + 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) { return; } + + if (dragging() === false) { + return; + } 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) => { + if (dragging() === false) { + return; + } + setSelectedStripPart({ displayId: props.strip.display_id, border: props.strip.border, @@ -89,22 +124,26 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper return; } 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 = () => { 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 = () => { invoke('reverse_led_strip_part', { displayId: props.strip.display_id, @@ -112,59 +151,104 @@ const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }).catch((err) => console.error(err)); }; - // update fullLeds - createEffect(() => { - const fullLeds = new Array(ledStripStore.totalLedCount).fill(null); + const setColor = (fullIndex: number, colorsIndex: number, fullLeds: string[]) => { 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; - const isForward = start < end; - const step = isForward ? 1 : -1; - 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})`; + if (fullLeds.length <= fullIndex) { + console.error('out of range', fullIndex, fullLeds.length); + return; } - 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(() => { 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 (
- - {(it) => ( -
+
+ + {(it) => (
-
- )} -
+ class="flex-auto flex h-full w-full justify-center items-center relative" + title={it ?? ''} + > +
+
+ )} + +
); }; @@ -220,7 +304,11 @@ export const LedStripPartsSorter: Component = () => { {(strip, index) => ( - + + 0}> + + + )}
diff --git a/src/stores/led-strip.store.tsx b/src/stores/led-strip.store.tsx index 754838a..d2e5802 100644 --- a/src/stores/led-strip.store.tsx +++ b/src/stores/led-strip.store.tsx @@ -7,6 +7,15 @@ export const [ledStripStore, setLedStripStore] = createStore({ colors: new Uint8ClampedArray(), sortedColors: new Uint8ClampedArray(), 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); + } + }), + ); }, });