2023-02-03 15:33:03 -06:00
|
|
|
import { Fragment, JSXElementConstructor, ReactElement, useEffect, useState } from "react";
|
2023-02-01 17:24:17 -06:00
|
|
|
import { Field, FieldProps, Focusable, GamepadButton } from "../deck-components";
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
export type ReorderableEntry<T> = {
|
2023-01-28 16:53:22 -06:00
|
|
|
label: string,
|
2023-02-01 08:18:55 -06:00
|
|
|
data?:T,
|
|
|
|
|
position:number
|
2022-11-25 18:32:55 -05:00
|
|
|
}
|
|
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
type ListProps<T> = {
|
|
|
|
|
entries: ReorderableEntry<T>[],
|
|
|
|
|
onSave: (entries: ReorderableEntry<T>[]) => void,
|
2023-02-01 17:24:17 -06:00
|
|
|
interactables?: JSXElementConstructor<{entry:ReorderableEntry<T>}>,
|
|
|
|
|
fieldProps?: FieldProps
|
2022-11-25 18:32:55 -05:00
|
|
|
}
|
|
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
/**
|
|
|
|
|
* A component for creating reorderable lists.
|
|
|
|
|
*
|
|
|
|
|
* Implementation example can be found {@link https://github.com/Tormak9970/Component-Testing-Plugin/blob/main/src/testing-window/ReorderableListTest.tsx here}.
|
|
|
|
|
*/
|
|
|
|
|
export function ReorderableList<T>(props: ListProps<T>) {
|
|
|
|
|
const [entryList, setEntryList] = useState<ReorderableEntry<T>[]>(props.entries.sort((a:ReorderableEntry<T>, b:ReorderableEntry<T>) => a.position - b.position));
|
|
|
|
|
const [reorderEnabled, setReorderEnabled] = useState<boolean>(false);
|
|
|
|
|
|
2023-02-03 15:33:03 -06:00
|
|
|
useEffect(() => {
|
|
|
|
|
setEntryList(props.entries.sort((a: ReorderableEntry<T>, b: ReorderableEntry<T>) => a.position - b.position));
|
|
|
|
|
}, [props.entries]);
|
|
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
function toggleReorderEnabled(): void {
|
|
|
|
|
let newReorderValue = !reorderEnabled;
|
|
|
|
|
setReorderEnabled(newReorderValue);
|
|
|
|
|
|
|
|
|
|
if (!newReorderValue){
|
|
|
|
|
props.onSave(entryList);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
return (
|
|
|
|
|
<Fragment>
|
|
|
|
|
<style>{`
|
|
|
|
|
.reorderable-list {
|
|
|
|
|
width: inherit;
|
|
|
|
|
height: inherit;
|
|
|
|
|
|
|
|
|
|
flex: 1 1 1px;
|
|
|
|
|
scroll-padding: 48px 0px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
align-content: stretch;
|
|
|
|
|
}
|
|
|
|
|
`}</style>
|
|
|
|
|
<div className="reorderable-list">
|
|
|
|
|
<Focusable
|
|
|
|
|
onSecondaryButton={toggleReorderEnabled}
|
|
|
|
|
onSecondaryActionDescription={reorderEnabled ? "Save Order" : "Reorder"}
|
|
|
|
|
onClick={toggleReorderEnabled}>
|
|
|
|
|
{
|
|
|
|
|
entryList.map((entry: ReorderableEntry<T>) => (
|
2023-02-01 17:24:17 -06:00
|
|
|
<ReorderableItem listData={entryList} entryData={entry} reorderEntryFunc={setEntryList} reorderEnabled={reorderEnabled} fieldProps={props.fieldProps}>
|
|
|
|
|
{props.interactables ? <props.interactables entry={entry} /> : null}
|
2023-02-01 08:18:55 -06:00
|
|
|
</ReorderableItem>
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
</Focusable>
|
|
|
|
|
</div>
|
|
|
|
|
</Fragment>
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
type ListEntryProps<T> = {
|
2023-02-01 17:24:17 -06:00
|
|
|
fieldProps?: FieldProps,
|
2023-02-01 08:18:55 -06:00
|
|
|
listData: ReorderableEntry<T>[],
|
|
|
|
|
entryData: ReorderableEntry<T>,
|
|
|
|
|
reorderEntryFunc: CallableFunction,
|
|
|
|
|
reorderEnabled: boolean,
|
2023-02-01 17:42:03 -06:00
|
|
|
children: ReactElement | null
|
2023-02-01 08:18:55 -06:00
|
|
|
}
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
function ReorderableItem<T>(props: ListEntryProps<T>) {
|
|
|
|
|
const listEntries = props.listData;
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
function onReorder(e: Event): void {
|
|
|
|
|
if (!props.reorderEnabled) return;
|
2022-11-25 18:32:55 -05:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
const event = e as CustomEvent;
|
|
|
|
|
const currentIdx = listEntries.findIndex((entryData: ReorderableEntry<T>) => entryData === props.entryData);
|
|
|
|
|
const currentIdxValue = listEntries[currentIdx];
|
|
|
|
|
if (currentIdx < 0) return;
|
|
|
|
|
|
|
|
|
|
let targetPosition: number = -1;
|
|
|
|
|
if (event.detail.button == GamepadButton.DIR_DOWN) {
|
|
|
|
|
targetPosition = currentIdxValue.position+1;
|
|
|
|
|
} else if (event.detail.button == GamepadButton.DIR_UP) {
|
|
|
|
|
targetPosition = currentIdxValue.position-1;
|
|
|
|
|
}
|
2023-01-28 16:53:22 -06:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
if (targetPosition >= listEntries.length || targetPosition < 0) return;
|
2023-01-28 16:53:22 -06:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
let otherToUpdate = listEntries.find((entryData: ReorderableEntry<T>) => entryData.position === targetPosition);
|
|
|
|
|
if (!otherToUpdate) return;
|
2023-01-28 16:53:22 -06:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
let currentPosition = currentIdxValue.position;
|
2023-01-28 17:19:24 -06:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
currentIdxValue.position = otherToUpdate.position;
|
|
|
|
|
otherToUpdate.position = currentPosition;
|
2023-01-28 17:19:24 -06:00
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
props.reorderEntryFunc([...listEntries].sort((a:ReorderableEntry<T>, b:ReorderableEntry<T>) => a.position - b.position));
|
2023-01-28 16:53:22 -06:00
|
|
|
}
|
|
|
|
|
|
2023-02-01 08:18:55 -06:00
|
|
|
const baseCssProps = {
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
justifyContent: "space-between",
|
|
|
|
|
width: "100%"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return(
|
|
|
|
|
// @ts-ignore
|
2023-02-01 17:24:17 -06:00
|
|
|
<Field label={props.entryData.label} style={props.reorderEnabled ? {...baseCssProps, background: "#678BA670"} : {...baseCssProps}} {...props.fieldProps} focusable={!props.children} onButtonDown={onReorder}>
|
|
|
|
|
<Focusable style={{ display: "flex", width: "100%", position: "relative" }}>
|
2023-02-01 08:18:55 -06:00
|
|
|
{props.children}
|
|
|
|
|
</Focusable>
|
|
|
|
|
</Field>
|
2023-01-28 16:53:22 -06:00
|
|
|
);
|
2022-11-25 18:32:55 -05:00
|
|
|
}
|