fix: 修复编辑灯条位置时数据错乱的问题。

This commit is contained in:
Ivan Li 2023-01-16 21:02:53 +08:00
parent cb5fb901f9
commit 458cc85db2
4 changed files with 210 additions and 189 deletions

View File

@ -1,19 +1,6 @@
import debug from 'debug'; import debug from 'debug';
import { isNil, lensPath, set, splitEvery, update } from 'ramda'; import { isNil, lensPath, set, splitEvery, update } from 'ramda';
import { import { FC, useEffect, useMemo, useState } from 'react';
createRef,
FC,
Fragment,
MouseEventHandler,
ReactEventHandler,
ReactNode,
RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import tw, { css, styled } from 'twin.macro'; import tw, { css, styled } from 'twin.macro';
import { Borders, borders } from '../../constants/border'; import { Borders, borders } from '../../constants/border';
import { useLedCount } from '../contents/led-count'; import { useLedCount } from '../contents/led-count';
@ -21,9 +8,10 @@ import { DisplayConfig, LedStripConfigOfBorders } from '../models/display-config
import { LedStripConfig } from '../models/led-strip-config'; import { LedStripConfig } from '../models/led-strip-config';
import { PixelRgb } from '../models/pixel-rgb'; import { PixelRgb } from '../models/pixel-rgb';
import { ScreenshotDto } from '../models/screenshot.dto'; import { ScreenshotDto } from '../models/screenshot.dto';
import { DraggableStrip } from './draggable-strip';
import { StyledPixel } from './styled-pixel'; import { StyledPixel } from './styled-pixel';
const logger = debug('app:completed-led-strip'); export const logger = debug('app:completed-led-strip');
interface CompletedLedStripProps { interface CompletedLedStripProps {
screenshots: ScreenshotDto[]; screenshots: ScreenshotDto[];
@ -99,7 +87,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
}, [ledCount, borderLedStrips, overrideBorderLedStrips]); }, [ledCount, borderLedStrips, overrideBorderLedStrips]);
const strips = useMemo(() => { const strips = useMemo(() => {
return (overrideBorderLedStrips ?? borderLedStrips).map(({ config, pixels }, index) => return borderLedStrips.map(({ config, pixels }, index) =>
config ? ( config ? (
<DraggableStrip <DraggableStrip
key={index} key={index}
@ -110,7 +98,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
); );
}} }}
onConfigFinish={(c) => { onConfigFinish={(c) => {
const indexOfDisplay = Math.round(index / borders.length); const indexOfDisplay = Math.floor(index / borders.length);
const xLens = lensPath<LedStripConfigOfBorders, Borders>([ const xLens = lensPath<LedStripConfigOfBorders, Borders>([
borders[index % borders.length], borders[index % borders.length],
]); ]);
@ -122,7 +110,6 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
screenshots[indexOfDisplay].config.led_strip_of_borders, screenshots[indexOfDisplay].config.led_strip_of_borders,
), ),
}; };
logger('Change DisplayConfig. %o', displayConfig);
onDisplayConfigChange?.(displayConfig); onDisplayConfigChange?.(displayConfig);
}} }}
/> />
@ -130,7 +117,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
<div key={index} /> <div key={index} />
), ),
); );
}, [borderLedStrips, overrideBorderLedStrips]); }, [borderLedStrips, screenshots]);
useEffect(() => { useEffect(() => {
setOverrideBorderLedStrips(undefined); setOverrideBorderLedStrips(undefined);
@ -143,172 +130,4 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
); );
}; };
interface DraggableStripProp {
config: LedStripConfig;
pixels: PixelRgb[];
index: number;
onConfigChange?: (config: LedStripConfig) => void;
onConfigFinish?: (config: LedStripConfig) => void;
}
const DraggableStrip: FC<DraggableStripProp> = ({
config,
pixels,
index,
onConfigChange,
onConfigFinish,
}) => {
const ledItems = pixels.map((rgb, i) => (
<StyledPixel
key={i}
rgb={rgb}
css={css`
grid-column: ${(config?.global_start_position ?? 0) + i + 1} / span 1;
grid-row-start: ${index + 1};
pointer-events: none;
`}
/>
));
const { ledCount } = useLedCount();
const startXRef = useRef(0);
const currentXRef = useRef(0);
const configRef = useRef<LedStripConfig>();
// const currentDiffRef = useRef(0);
const [currentDiff, setCurrentDiff] = useState(0);
const isDragRef = useRef(false);
const handleMouseMoveRef = useRef<(ev: MouseEvent) => void>();
const [boxTranslateX, setBoxTranslateX] = useState(0);
const [placeholders, placeholderRefs]: [ReactNode[], RefObject<HTMLSpanElement>[]] =
useMemo(
() =>
new Array(ledCount)
.fill(undefined)
.map((_, i) => {
const ref = createRef<HTMLSpanElement>();
const n = (
<span
ref={ref}
key={i}
tw="opacity-30 bg-red-500 h-full w-full border border-yellow-400"
css={css`
grid-column-start: ${i + 1};
grid-row-start: ${index + 1};
`}
/>
);
return [n, ref] as [ReactNode, RefObject<HTMLSpanElement>];
})
.reduce(
([nList, refList], [n, ref]) => [
[...nList, n],
[...refList, ref],
],
[[], []] as [ReactNode[], RefObject<HTMLSpanElement>[]],
),
[ledCount],
);
const handleMouseDown: MouseEventHandler<HTMLDivElement> = useCallback(
(ev) => {
startXRef.current = ev.pageX;
ev.currentTarget.requestPointerLock();
isDragRef.current = true;
const placeholderPositions = placeholderRefs.map((it) => {
if (!it.current) {
return [0, 0];
}
const viewportOffset = it.current.getBoundingClientRect();
return [viewportOffset.left, viewportOffset.right] as [number, number];
});
logger('placeholderPositions: %o', placeholderPositions);
// set init position
const initPos = placeholderPositions.findIndex(
([l, r]) => l <= ev.pageX && r >= ev.pageX,
);
setCurrentDiff(initPos);
let prevMatch = 0;
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
handleMouseMoveRef.current = (ev) => {
if (!isDragRef.current) {
return;
}
currentXRef.current = ev.pageX;
setBoxTranslateX(currentXRef.current - startXRef.current);
const match = placeholderPositions.findIndex(
([l, r]) => l <= currentXRef.current && r >= currentXRef.current,
);
if (match === -1) {
return;
}
if (match === prevMatch) {
return;
}
prevMatch = match;
const diff = match - initPos;
const newValue: LedStripConfig = {
...config,
global_start_position: config.global_start_position + diff,
global_end_position: config.global_end_position + diff,
};
configRef.current = newValue;
logger('change config. new: $o', newValue);
onConfigChange?.(newValue);
};
document.body.addEventListener('mousemove', handleMouseMoveRef.current);
},
[placeholderRefs],
);
// move event.
useEffect(() => {
const handleMouseUp = (ev: MouseEvent) => {
startXRef.current = 0;
isDragRef.current = false;
if (configRef.current) {
onConfigFinish?.(configRef.current);
}
document.exitPointerLock();
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
};
document.body.addEventListener('mouseup', handleMouseUp);
return () => {
document.body.removeEventListener('mouseup', handleMouseUp);
};
}, []);
// reset translateX when config updated.
useEffect(() => {
startXRef.current = currentXRef.current;
setCurrentDiff(0);
}, [config]);
return (
<Fragment>
{placeholders}
{ledItems}
<div
tw="border border-gray-700 h-3 w-full rounded-full"
css={css`
grid-column-start: ${(config?.global_start_position ?? 0) + 1 - currentDiff};
grid-column-end: ${(config?.global_end_position ?? 0) + 1 - currentDiff};
grid-row-start: ${index + 1};
cursor: ew-resize;
transform: translateX(${boxTranslateX}px);
`}
onMouseDown={handleMouseDown}
></div>
</Fragment>
);
};

View File

@ -0,0 +1,196 @@
import {
createRef,
FC,
Fragment,
MouseEventHandler,
ReactNode,
RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { css } from 'twin.macro';
import { useLedCount } from '../contents/led-count';
import { LedStripConfig } from '../models/led-strip-config';
import { PixelRgb } from '../models/pixel-rgb';
import { StyledPixel } from './styled-pixel';
import { logger } from './completed-led-strip';
interface DraggableStripProp {
config: LedStripConfig;
pixels: PixelRgb[];
index: number;
onConfigChange?: (config: LedStripConfig) => void;
onConfigFinish?: (config: LedStripConfig) => void;
}
export const DraggableStrip: FC<DraggableStripProp> = ({
config,
pixels,
index,
onConfigChange,
onConfigFinish,
}) => {
const { ledCount } = useLedCount();
const startXRef = useRef(0);
const currentXRef = useRef(0);
const configRef = useRef<LedStripConfig>();
const [availableConfig, setAvailableConfig] = useState<LedStripConfig>(config);
// const currentDiffRef = useRef(0);
const isDragRef = useRef(false);
const handleMouseMoveRef = useRef<(ev: MouseEvent) => void>();
const [boxTranslateX, setBoxTranslateX] = useState(0);
const ledItems = useMemo(
() =>
pixels.map((rgb, i) => (
<StyledPixel
key={i}
rgb={rgb}
css={css`
grid-column: ${(availableConfig.global_start_position ?? 0) + i + 1} / span 1;
grid-row-start: ${index + 1};
pointer-events: none;
`}
/>
)),
[pixels, availableConfig],
);
const [placeholders, placeholderRefs]: [ReactNode[], RefObject<HTMLSpanElement>[]] =
useMemo(
() =>
new Array(ledCount)
.fill(undefined)
.map((_, i) => {
const ref = createRef<HTMLSpanElement>();
const n = (
<span
ref={ref}
key={i}
tw="opacity-30 bg-red-500 h-full w-full border border-yellow-400"
css={css`
grid-column-start: ${i + 1};
grid-row-start: ${index + 1};
`}
/>
);
return [n, ref] as [ReactNode, RefObject<HTMLSpanElement>];
})
.reduce(
([nList, refList], [n, ref]) => [
[...nList, n],
[...refList, ref],
],
[[], []] as [ReactNode[], RefObject<HTMLSpanElement>[]],
),
[ledCount],
);
// start and moving
const handleMouseDown: MouseEventHandler<HTMLDivElement> = useCallback(
(ev) => {
startXRef.current = ev.pageX;
ev.currentTarget.requestPointerLock();
isDragRef.current = true;
logger('handleMouseDown, config: %o', config);
const placeholderPositions = placeholderRefs.map((it) => {
if (!it.current) {
return [0, 0];
}
const viewportOffset = it.current.getBoundingClientRect();
return [viewportOffset.left, viewportOffset.right] as [number, number];
});
logger('placeholderPositions: %o', placeholderPositions);
// set init position
const initPos = placeholderPositions.findIndex(
([l, r]) => l <= ev.pageX && r >= ev.pageX,
);
let prevMatch = 0;
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
handleMouseMoveRef.current = (ev) => {
if (!isDragRef.current) {
return;
}
currentXRef.current = ev.pageX;
setBoxTranslateX(currentXRef.current - startXRef.current);
const match = placeholderPositions.findIndex(
([l, r]) => l <= currentXRef.current && r >= currentXRef.current,
);
if (match === -1) {
return;
}
if (match === prevMatch) {
return;
}
prevMatch = match;
const diff = match - initPos;
const newValue: LedStripConfig = {
...config,
global_start_position: config.global_start_position + diff,
global_end_position: config.global_end_position + diff,
};
configRef.current = newValue;
setAvailableConfig(newValue);
logger('change config. old: %o, new: %o', config, newValue);
onConfigChange?.(newValue);
};
document.body.addEventListener('mousemove', handleMouseMoveRef.current);
},
[placeholderRefs, availableConfig, setAvailableConfig, config],
);
// move event.
useEffect(() => {
const handleMouseUp = (ev: MouseEvent) => {
if (configRef.current && isDragRef.current) {
onConfigFinish?.(configRef.current);
}
startXRef.current = 0;
isDragRef.current = false;
document.exitPointerLock();
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
};
document.body.addEventListener('mouseup', handleMouseUp);
return () => {
document.body.removeEventListener('mouseup', handleMouseUp);
};
}, [onConfigFinish]);
// reset translateX when config updated.
useEffect(() => {
startXRef.current = currentXRef.current;
setAvailableConfig(config);
setBoxTranslateX(0);
logger('useEffect, config: %o', config);
}, [config]);
return (
<Fragment>
{placeholders}
{ledItems}
<div
tw="border border-gray-700 h-3 w-full rounded-full"
css={css`
grid-column-start: ${(config?.global_start_position ?? 0) + 1};
grid-column-end: ${(config?.global_end_position ?? 0) + 1};
grid-row-start: ${index + 1};
cursor: ew-resize;
transform: translateX(${boxTranslateX}px);
`}
onMouseDown={handleMouseDown}
></div>
</Fragment>
);
};

View File

@ -1,7 +1,7 @@
import { HTMLAttributes, useCallback } from 'react'; import { HTMLAttributes, useCallback } from 'react';
import { FC } from 'react'; import { FC } from 'react';
import { LedStripConfig } from '../models/led-strip-config'; import { LedStripConfig } from '../models/led-strip-config';
import tw, { css, styled, theme } from 'twin.macro'; import tw, { styled } from 'twin.macro';
import { faLeftRight, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'; import { faLeftRight, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -52,6 +52,9 @@ export const LedStripEditor: FC<LedStripEditorProps> = ({
<StyledButton title="Reverse"> <StyledButton title="Reverse">
<FontAwesomeIcon icon={faLeftRight} /> <FontAwesomeIcon icon={faLeftRight} />
</StyledButton> </StyledButton>
{`s: ${config?.global_start_position ?? 'x'}, e: ${
config?.global_end_position ?? 'x'
}`}
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react'; import { FC, useEffect, useMemo, useState } from 'react';
import tw, { styled } from 'twin.macro'; import tw, { styled } from 'twin.macro';
import { useAsync, useAsyncCallback } from 'react-async-hook'; import { useAsync, useAsyncCallback } from 'react-async-hook';
import { DisplayWithLedStrips } from './components/display-with-led-strips'; import { DisplayWithLedStrips } from './components/display-with-led-strips';
@ -12,6 +12,9 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { update } from 'ramda'; import { update } from 'ramda';
import { CompletedLedStrip } from './components/completed-led-strip'; import { CompletedLedStrip } from './components/completed-led-strip';
import { LedCountProvider } from './contents/led-count'; import { LedCountProvider } from './contents/led-count';
import debug from 'debug';
const logger = debug('app:configurator');
const getPickerConfig = () => invoke<PickerConfiguration>('get_picker_config'); const getPickerConfig = () => invoke<PickerConfiguration>('get_picker_config');
const getScreenshotOfDisplays = () => const getScreenshotOfDisplays = () =>