Skip to content
Snippets Groups Projects
Unverified Commit 2f01330c authored by Nick Fitzpatrick's avatar Nick Fitzpatrick Committed by GitHub
Browse files

38092 metadata table columns dnd (#39637)

* Metadata Table columns use Sortable List

* cleanup

* e2e test adjustments
parent 818dde03
No related branches found
No related tags found
No related merge requests found
......@@ -659,10 +659,25 @@ const getFieldSection = fieldName => {
};
const moveField = (fieldIndex, deltaY) => {
cy.get(".Grabber").eq(fieldIndex).trigger("mousedown", 0, 0, { force: true });
cy.get("#ColumnsList")
.trigger("mousemove", 10, deltaY)
.trigger("mouseup", 10, deltaY);
cy.get(".Grabber")
.eq(fieldIndex)
.trigger("pointerdown", 0, 0, { force: true, button: 0, isPrimary: true })
.wait(200)
.trigger("pointermove", 5, 5, { force: true, button: 0, isPrimary: true })
.wait(200)
//cy.get("#ColumnsList")
.trigger("pointermove", 10, deltaY, {
force: true,
button: 0,
isPrimary: true,
})
.wait(200)
.trigger("pointerup", 10, deltaY, {
force: true,
button: 0,
isPrimary: true,
})
.wait(200);
};
const setTableOrder = order => {
......
......@@ -15,6 +15,7 @@ export const ColumnContainer = styled.section`
&:last-child {
margin-bottom: 0;
}
background: ${color("white")};
`;
export const ColumnInput = styled(InputBlurChange)`
......
import type { UniqueIdentifier } from "@dnd-kit/core";
import { useSensor, PointerSensor } from "@dnd-kit/core";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import cx from "classnames";
import type { ReactNode } from "react";
import { useCallback, useMemo } from "react";
import { connect } from "react-redux";
import { t } from "ttag";
......@@ -7,12 +10,8 @@ import _ from "underscore";
import Grabber from "metabase/components/Grabber";
import TippyPopoverWithTrigger from "metabase/components/PopoverWithTrigger/TippyPopoverWithTrigger";
import {
SortableContainer,
SortableElement,
SortableHandle,
} from "metabase/components/sortable";
import AccordionList from "metabase/core/components/AccordionList";
import { SortableList } from "metabase/core/components/Sortable";
import Tables from "metabase/entities/tables";
import { Icon } from "metabase/ui";
import type Field from "metabase-lib/metadata/Field";
......@@ -45,11 +44,6 @@ interface DispatchProps {
onUpdateFieldOrder: (table: Table, fieldOrder: FieldId[]) => void;
}
interface DragProps {
oldIndex: number;
newIndex: number;
}
type MetadataTableColumnListProps = OwnProps & DispatchProps;
const mapDispatchToProps: DispatchProps = {
......@@ -57,6 +51,8 @@ const mapDispatchToProps: DispatchProps = {
onUpdateFieldOrder: Tables.actions.setFieldOrder,
};
const getId = (field: Field) => field.getId();
const MetadataTableColumnList = ({
table,
idFields,
......@@ -67,23 +63,31 @@ const MetadataTableColumnList = ({
const { fields = [], visibility_type } = table;
const isHidden = visibility_type != null;
const pointerSensor = useSensor(PointerSensor, {
activationConstraint: { distance: 0 },
});
const sortedFields = useMemo(
() => _.sortBy(fields, field => field.position),
[fields],
);
const handleSortStart = useCallback(() => {
document.body.classList.add("grabbing");
}, []);
const handleSortEnd = useCallback(
({ oldIndex, newIndex }: DragProps) => {
document.body.classList.remove("grabbing");
const fieldOrder = updateFieldOrder(sortedFields, oldIndex, newIndex);
({ itemIds: fieldOrder }) => {
onUpdateFieldOrder(table, fieldOrder);
},
[table, sortedFields, onUpdateFieldOrder],
[table, onUpdateFieldOrder],
);
const renderItem = ({ item, id }: { item: Field; id: string | number }) => (
<SortableColumn
key={`sortable-${id}`}
id={id}
field={item}
idFields={idFields}
table={table}
selectedSchemaId={selectedSchemaId}
/>
);
return (
......@@ -108,41 +112,20 @@ const MetadataTableColumnList = ({
</SortButtonContainer>
</div>
</div>
<SortableColumnList
helperClass="ColumnSortHelper"
useDragHandle={true}
onSortStart={handleSortStart}
onSortEnd={handleSortEnd}
>
{sortedFields.map((field, index) => (
<SortableColumn
key={field.getId()}
index={index}
field={field}
idFields={idFields}
selectedDatabaseId={table.db_id}
selectedSchemaId={selectedSchemaId}
selectedTableId={table.id}
dragHandle={<SortableColumnHandle />}
/>
))}
</SortableColumnList>
<div>
<SortableList
items={sortedFields}
renderItem={renderItem}
getId={getId}
onSortEnd={handleSortEnd}
sensors={[pointerSensor]}
useDragOverlay={false}
/>
</div>
</div>
);
};
interface ColumnListProps {
children?: ReactNode;
}
const ColumnList = ({ children, ...props }: ColumnListProps) => {
return <div {...props}>{children}</div>;
};
const ColumnGrabber = () => {
return <Grabber style={{ width: 10 }} />;
};
interface TableFieldOrderOption {
name: string;
value: TableFieldOrder;
......@@ -191,31 +174,56 @@ const TableFieldOrderDropdown = ({
);
};
const SortableColumn = SortableElement(MetadataTableColumn);
const SortableColumnList = SortableContainer(ColumnList);
const SortableColumnHandle = SortableHandle(ColumnGrabber);
const updateFieldOrder = (
fields: Field[],
oldIndex: number,
newIndex: number,
) => {
const fieldOrder = new Array<FieldId>(fields.length);
fields.forEach((field, prevIndex) => {
const nextIndex =
newIndex <= prevIndex && prevIndex < oldIndex
? prevIndex + 1 // shift down
: oldIndex < prevIndex && prevIndex <= newIndex
? prevIndex - 1 // shift up
: prevIndex === oldIndex
? newIndex // move dragged column to new location
: prevIndex; // otherwise, leave it where it is
fieldOrder[nextIndex] = Number(field.id);
interface SortableColumnProps {
id: UniqueIdentifier;
field: Field;
idFields: Field[];
table: Table;
selectedSchemaId: SchemaId;
}
const SortableColumn = ({
id,
field,
table,
idFields,
selectedSchemaId,
}: SortableColumnProps) => {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id,
});
return fieldOrder;
const dragHandle = (
<Grabber style={{ width: 10 }} {...attributes} {...listeners} />
);
return (
<div
ref={setNodeRef}
style={{
transform: CSS.Transform.toString(transform),
transition,
position: "relative",
zIndex: isDragging ? 100 : 1,
}}
>
<MetadataTableColumn
field={field}
idFields={idFields}
selectedDatabaseId={table.db_id}
selectedSchemaId={selectedSchemaId}
selectedTableId={table.id}
dragHandle={dragHandle}
/>
</div>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
......
/* eslint-disable react/prop-types */
import cx from "classnames";
export default function Grabber({ className = "", style }) {
return <div className={cx("Grabber cursor-grab", className)} style={style} />;
export default function Grabber({ className = "", style, ...props }) {
return (
<div
className={cx("Grabber cursor-grab", className)}
style={style}
{...props}
/>
);
}
......@@ -15,6 +15,7 @@ type ItemId = number | string;
export type DragEndEvent = {
id: ItemId;
newIndex: number;
itemIds: ItemId[];
};
interface RenderItemProps<T> {
......@@ -30,6 +31,7 @@ interface useSortableListProps<T> {
onSortEnd?: ({ id, newIndex }: DragEndEvent) => void;
sensors?: SensorDescriptor<any>[];
modifiers?: Modifier[];
useDragOverlay?: boolean;
}
export const SortableList = <T,>({
......@@ -40,6 +42,7 @@ export const SortableList = <T,>({
onSortEnd,
sensors = [],
modifiers = [],
useDragOverlay = true,
}: useSortableListProps<T>) => {
const [itemIds, setItemIds] = useState<ItemId[]>([]);
const [indexedItems, setIndexedItems] = useState<Record<ItemId, T>>({});
......@@ -90,6 +93,7 @@ export const SortableList = <T,>({
onSortEnd({
id: getId(activeItem),
newIndex: itemIds.findIndex(id => id === getId(activeItem)),
itemIds,
});
setActiveItem(null);
}
......@@ -104,15 +108,17 @@ export const SortableList = <T,>({
modifiers={modifiers}
>
<SortableContext items={itemIds}>{sortableElements}</SortableContext>
<DragOverlay>
{activeItem
? renderItem({
item: activeItem,
id: getId(activeItem),
isDragOverlay: true,
})
: null}
</DragOverlay>
{useDragOverlay && (
<DragOverlay>
{activeItem
? renderItem({
item: activeItem,
id: getId(activeItem),
isDragOverlay: true,
})
: null}
</DragOverlay>
)}
</DndContext>
);
};
import { useSensor, PointerSensor } from "@dnd-kit/core";
import { useCallback } from "react";
import type { DragEndEvent } from "metabase/core/components/Sortable";
import { Sortable, SortableList } from "metabase/core/components/Sortable";
import type { IconProps } from "metabase/ui";
......@@ -23,13 +24,7 @@ interface SortableColumnFunctions<T> {
}
interface ChartSettingOrderedItemsProps<T extends SortableItem>
extends SortableColumnFunctions<T> {
onSortEnd: ({
id,
newIndex,
}: {
id: number | string;
newIndex: number;
}) => void;
onSortEnd: ({ id, newIndex }: DragEndEvent) => void;
items: T[];
getId: (item: T) => string | number;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment