Skip to content

Commit

Permalink
feat(playground): re-ordering of messages using dnd (#4936)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeldking committed Oct 11, 2024
1 parent ed6e05e commit ad27394
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 105 deletions.
2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"@codemirror/lang-python": "6.1.3",
"@codemirror/lint": "^6.8.1",
"@codemirror/view": "^6.28.5",
"@dnd-kit/abstract": "^0.0.5",
"@dnd-kit/react": "^0.0.5",
"@react-three/drei": "^9.108.4",
"@react-three/fiber": "8.0.12",
"@tanstack/react-table": "^8.19.3",
Expand Down
74 changes: 68 additions & 6 deletions app/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions app/src/components/dnd/DragHandle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";
import { css } from "@emotion/react";

function DragHandle() {
return (
<button
data-cypress="draggable-handle"
aria-roledescription="draggable"
aria-pressed="false"
aria-disabled="false"
className="button--reset"
css={css`
cursor: grab;
background-color: var(--ac-global-color-grey-200);
border: 1px solid var(--ac-global-color-grey-500);
color: var(--ac-global-text-color-900);
display: flex;
align-items: center;
justify-content: center;
padding: var(--ac-global-dimension-size-100)
var(--ac-global-dimension-size-50);
border-radius: var(--ac-global-rounding-small);
overflow: hidden;
`}
>
<svg viewBox="0 0 20 20" width="12" fill="currentColor">
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"></path>
</svg>
</button>
);
}

// Use Ref forwarding for DragHandle
const _DragHandle = React.forwardRef(DragHandle);
export { _DragHandle as DragHandle };
208 changes: 208 additions & 0 deletions app/src/components/dnd/helpers/move.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Copy of @dnd-kit/utils for a needed fix
* TODO: switch to @dnd-kit/helpers once the fix is released
* @src https://github.com/clauderic/dnd-kit/blob/experimental/packages/helpers/src/move.ts
*/
import type {
DragDropEvents,
DragDropManager,
Draggable,
Droppable,
UniqueIdentifier,
} from "@dnd-kit/abstract";

/**
* Move an array item to a different position. Returns a new array with the item moved to the new position.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function arrayMove<T extends any[]>(
array: T,
from: number,
to: number
): T {
if (from === to) {
return array;
}

const newArray = array.slice() as T;
newArray.splice(to, 0, newArray.splice(from, 1)[0]);

return newArray;
}

/**
* Move an array item to a different position. Returns a new array with the item moved to the new position.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function arraySwap<T extends any[]>(
array: T,
from: number,
to: number
): T {
if (from === to) {
return array;
}

const newArray = array.slice() as T;
const item = newArray[from];

newArray[from] = newArray[to];
newArray[to] = item;

return newArray;
}

type Items = UniqueIdentifier[] | { id: UniqueIdentifier }[];

function mutate<
T extends Items | Record<UniqueIdentifier, Items>,
U extends Draggable,
V extends Droppable,
W extends DragDropManager<U, V>,
>(
items: T,
event: Parameters<
DragDropEvents<U, V, W>["dragover"] | DragDropEvents<U, V, W>["dragend"]
>[0],
mutation: typeof arrayMove | typeof arraySwap
): T {
const { source, target, canceled } = event.operation;

if (!source || !target || canceled || source.id === target.id) {
if ("preventDefault" in event) event.preventDefault();
return items;
}

const findIndex = (item: Items[0], id: UniqueIdentifier) =>
item === id || (typeof item === "object" && "id" in item && item.id === id);

if (Array.isArray(items)) {
const sourceIndex = items.findIndex((item) => findIndex(item, source.id));
const targetIndex = items.findIndex((item) => findIndex(item, target.id));

if (sourceIndex === -1 || targetIndex === -1) {
return items;
}

// Reconcile optimistic updates
if (!canceled && "index" in source && typeof source.index === "number") {
const projectedSourceIndex = source.index;

if (projectedSourceIndex !== sourceIndex) {
return mutation(items, sourceIndex, projectedSourceIndex);
}
}

return mutation(items, sourceIndex, targetIndex);
}

const entries = Object.entries(items);

let sourceIndex = -1;
let sourceParent: UniqueIdentifier | undefined;
let targetIndex = -1;
let targetParent: UniqueIdentifier | undefined;

for (const [id, children] of entries) {
if (sourceIndex === -1) {
sourceIndex = children.findIndex((item) => findIndex(item, source.id));

if (sourceIndex !== -1) {
sourceParent = id;
}
}

if (targetIndex === -1) {
targetIndex = children.findIndex((item) => findIndex(item, target.id));

if (targetIndex !== -1) {
targetParent = id;
}
}

if (sourceIndex !== -1 && targetIndex !== -1) {
break;
}
}

if (!source.manager) return items;

const { dragOperation } = source.manager;
const position =
dragOperation.shape?.current.center ?? dragOperation.position.current;

if (targetParent == null) {
if (target.id in items) {
const insertionIndex =
target.shape && position.y > target.shape.center.y
? items[target.id].length
: 0;

// The target does not have any matching children, but appears to be a valid target
targetParent = target.id;
targetIndex = insertionIndex;
}
}

if (
sourceParent == null ||
targetParent == null ||
(sourceParent === targetParent && sourceIndex === targetIndex)
) {
if ("preventDefault" in event) event.preventDefault();

return items;
}

if (sourceParent === targetParent) {
return {
...items,
[sourceParent]: mutation(items[sourceParent], sourceIndex, targetIndex),
};
}

const isBelowTarget = target.shape && position.y > target.shape.center.y;
const modifier = isBelowTarget ? 1 : 0;
const sourceItem = items[sourceParent][sourceIndex];

return {
...items,
[sourceParent]: [
...items[sourceParent].slice(0, sourceIndex),
...items[sourceParent].slice(sourceIndex + 1),
],
[targetParent]: [
...items[targetParent].slice(0, targetIndex + modifier),
sourceItem,
...items[targetParent].slice(targetIndex + modifier),
],
};
}

export function move<
T extends Items | Record<UniqueIdentifier, Items>,
U extends Draggable,
V extends Droppable,
W extends DragDropManager<U, V>,
>(
items: T,
event: Parameters<
DragDropEvents<U, V, W>["dragover"] | DragDropEvents<U, V, W>["dragend"]
>[0]
) {
return mutate(items, event, arrayMove);
}

export function swap<
T extends Items | Record<UniqueIdentifier, Items>,
U extends Draggable,
V extends Droppable,
W extends DragDropManager<U, V>,
>(
items: T,
event: Parameters<
DragDropEvents<U, V, W>["dragover"] | DragDropEvents<U, V, W>["dragend"]
>[0]
) {
return mutate(items, event, arraySwap);
}
Loading

0 comments on commit ad27394

Please sign in to comment.