feat(console): 媒体库拖拽排序加 FLIP 平滑动画(160ms 缓动)——行滑动换位不再跳动
This commit is contained in:
@@ -18086,7 +18086,22 @@ function ConsolePane() {
|
|||||||
if (next) void runUpdateCheck(false);
|
if (next) void runUpdateCheck(false);
|
||||||
};
|
};
|
||||||
const dragRowRef = (0, import_react.useRef)(null);
|
const dragRowRef = (0, import_react.useRef)(null);
|
||||||
|
const dragFlipRef = (0, import_react.useRef)(null);
|
||||||
const [dragRowId, setDragRowId] = (0, import_react.useState)(null);
|
const [dragRowId, setDragRowId] = (0, import_react.useState)(null);
|
||||||
|
(0, import_react.useLayoutEffect)(() => {
|
||||||
|
const first = dragFlipRef.current;
|
||||||
|
if (!first) return;
|
||||||
|
dragFlipRef.current = null;
|
||||||
|
document.querySelectorAll(".dsh-wt_photoRow").forEach((el) => {
|
||||||
|
const key = el.dataset.rid ?? "";
|
||||||
|
const f = first.get(key);
|
||||||
|
if (!f) return;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const dx = f.x - r.left;
|
||||||
|
const dy = f.y - r.top;
|
||||||
|
if (dx || dy) el.animate([{ transform: "translate(" + dx + "px," + dy + "px)" }, { transform: "none" }], { duration: 160, easing: "cubic-bezier(.22,.61,.36,1)" });
|
||||||
|
});
|
||||||
|
}, [photoList]);
|
||||||
const onHandleDown = (id, e) => {
|
const onHandleDown = (id, e) => {
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -18096,6 +18111,12 @@ function ConsolePane() {
|
|||||||
const onMove = (ev) => {
|
const onMove = (ev) => {
|
||||||
if (dragRowRef.current !== id) return;
|
if (dragRowRef.current !== id) return;
|
||||||
const rows = Array.from(document.querySelectorAll(".dsh-wt_photoRow"));
|
const rows = Array.from(document.querySelectorAll(".dsh-wt_photoRow"));
|
||||||
|
const rects = /* @__PURE__ */ new Map();
|
||||||
|
rows.forEach((el) => {
|
||||||
|
const k = el.dataset.rid ?? "";
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
rects.set(k, { x: r.left, y: r.top });
|
||||||
|
});
|
||||||
let target = 0;
|
let target = 0;
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const r = rows[i].getBoundingClientRect();
|
const r = rows[i].getBoundingClientRect();
|
||||||
@@ -18107,6 +18128,7 @@ function ConsolePane() {
|
|||||||
const moved = next.splice(from, 1)[0];
|
const moved = next.splice(from, 1)[0];
|
||||||
const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length));
|
const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length));
|
||||||
next.splice(to, 0, moved);
|
next.splice(to, 0, moved);
|
||||||
|
dragFlipRef.current = rects;
|
||||||
setPhotoList(next);
|
setPhotoList(next);
|
||||||
};
|
};
|
||||||
const onUp = () => {
|
const onUp = () => {
|
||||||
@@ -18516,7 +18538,7 @@ function ConsolePane() {
|
|||||||
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-wt_switch" + (photoGrid ? " dsh-wt_switchOn" : ""), "aria-pressed": photoGrid, "aria-label": T("console.bgGridLabel"), onClick: togglePhotoGrid, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_switchKnob" }) })
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dsh-wt_switch" + (photoGrid ? " dsh-wt_switchOn" : ""), "aria-pressed": photoGrid, "aria-label": T("console.bgGridLabel"), onClick: togglePhotoGrid, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_switchKnob" }) })
|
||||||
] })
|
] })
|
||||||
] }),
|
] }),
|
||||||
photoList.length > 0 ? photoList.slice(0, 4).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-wt_dropRow dsh-wt_photoRow" + (photoId === p.id ? " dsh-wt_photoRowOn" : ""), onClick: () => selectPhoto(p), children: [
|
photoList.length > 0 ? photoList.slice(0, 4).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-rid": p.id, className: "dsh-wt_dropRow dsh-wt_photoRow" + (photoId === p.id ? " dsh-wt_photoRowOn" : ""), onClick: () => selectPhoto(p), children: [
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-wt_thumbWrap", children: [
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-wt_thumbWrap", children: [
|
||||||
p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { className: "dsh-wt_photoThumb", src: p.url, muted: true, playsInline: true, preload: "metadata" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { className: "dsh-wt_photoThumb", src: p.url, alt: "" }),
|
p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("video", { className: "dsh-wt_photoThumb", src: p.url, muted: true, playsInline: true, preload: "metadata" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { className: "dsh-wt_photoThumb", src: p.url, alt: "" }),
|
||||||
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_thumbBadge", "aria-hidden": true, children: p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.5 3.8l8 4.2-8 4.2z", fill: "currentColor" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: [
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-wt_thumbBadge", "aria-hidden": true, children: p.kind === "video" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4.5 3.8l8 4.2-8 4.2z", fill: "currentColor" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "8", height: "8", viewBox: "0 0 16 16", children: [
|
||||||
|
|||||||
@@ -1574,7 +1574,23 @@ function ConsolePane() {
|
|||||||
}
|
}
|
||||||
/** 抓手拖拽排序:按住抓手上下移动 → 行实时重排,松手持久化 */
|
/** 抓手拖拽排序:按住抓手上下移动 → 行实时重排,松手持久化 */
|
||||||
const dragRowRef = useRef<string | null>(null)
|
const dragRowRef = useRef<string | null>(null)
|
||||||
|
const dragFlipRef = useRef<Map<string, { x: number; y: number }> | null>(null)
|
||||||
const [dragRowId, setDragRowId] = useState<string | null>(null)
|
const [dragRowId, setDragRowId] = useState<string | null>(null)
|
||||||
|
// FLIP:重排后旧位置 → 新位置的平滑滑动(160ms 缓动)
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const first = dragFlipRef.current
|
||||||
|
if (!first) return
|
||||||
|
dragFlipRef.current = null
|
||||||
|
document.querySelectorAll<HTMLElement>('.dsh-wt_photoRow').forEach((el) => {
|
||||||
|
const key = el.dataset.rid ?? ''
|
||||||
|
const f = first.get(key)
|
||||||
|
if (!f) return
|
||||||
|
const r = el.getBoundingClientRect()
|
||||||
|
const dx = f.x - r.left
|
||||||
|
const dy = f.y - r.top
|
||||||
|
if (dx || dy) el.animate([{ transform: 'translate(' + dx + 'px,' + dy + 'px)' }, { transform: 'none' }], { duration: 160, easing: 'cubic-bezier(.22,.61,.36,1)' })
|
||||||
|
})
|
||||||
|
}, [photoList])
|
||||||
const onHandleDown = (id: string, e: { button: number; preventDefault: () => void; stopPropagation: () => void }) => {
|
const onHandleDown = (id: string, e: { button: number; preventDefault: () => void; stopPropagation: () => void }) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -1584,6 +1600,9 @@ function ConsolePane() {
|
|||||||
const onMove = (ev: PointerEvent) => {
|
const onMove = (ev: PointerEvent) => {
|
||||||
if (dragRowRef.current !== id) return
|
if (dragRowRef.current !== id) return
|
||||||
const rows = Array.from(document.querySelectorAll<HTMLElement>('.dsh-wt_photoRow'))
|
const rows = Array.from(document.querySelectorAll<HTMLElement>('.dsh-wt_photoRow'))
|
||||||
|
// 收集当前(重排前)位置用于 FLIP
|
||||||
|
const rects = new Map<string, { x: number; y: number }>()
|
||||||
|
rows.forEach((el) => { const k = el.dataset.rid ?? ''; const r = el.getBoundingClientRect(); rects.set(k, { x: r.left, y: r.top }) })
|
||||||
let target = 0
|
let target = 0
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const r = rows[i].getBoundingClientRect()
|
const r = rows[i].getBoundingClientRect()
|
||||||
@@ -1595,6 +1614,7 @@ function ConsolePane() {
|
|||||||
const moved = next.splice(from, 1)[0]
|
const moved = next.splice(from, 1)[0]
|
||||||
const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length))
|
const to = Math.max(0, Math.min(target > from ? target - 1 : target, next.length))
|
||||||
next.splice(to, 0, moved)
|
next.splice(to, 0, moved)
|
||||||
|
dragFlipRef.current = rects
|
||||||
setPhotoList(next)
|
setPhotoList(next)
|
||||||
}
|
}
|
||||||
const onUp = () => {
|
const onUp = () => {
|
||||||
@@ -1916,7 +1936,7 @@ function ConsolePane() {
|
|||||||
</div>
|
</div>
|
||||||
{photoList.length > 0 ? (
|
{photoList.length > 0 ? (
|
||||||
photoList.slice(0, 4).map((p) => (
|
photoList.slice(0, 4).map((p) => (
|
||||||
<div key={p.id} className={'dsh-wt_dropRow dsh-wt_photoRow' + (photoId === p.id ? ' dsh-wt_photoRowOn' : '')} onClick={() => selectPhoto(p)}>
|
<div key={p.id} data-rid={p.id} className={'dsh-wt_dropRow dsh-wt_photoRow' + (photoId === p.id ? ' dsh-wt_photoRowOn' : '')} onClick={() => selectPhoto(p)}>
|
||||||
<span className="dsh-wt_thumbWrap">
|
<span className="dsh-wt_thumbWrap">
|
||||||
{p.kind === 'video'
|
{p.kind === 'video'
|
||||||
? <video className="dsh-wt_photoThumb" src={p.url} muted playsInline preload="metadata" />
|
? <video className="dsh-wt_photoThumb" src={p.url} muted playsInline preload="metadata" />
|
||||||
|
|||||||
Reference in New Issue
Block a user