diff --git a/src/custom-components/ReorderableList.tsx b/src/custom-components/ReorderableList.tsx index 625d38a..9a74d52 100644 --- a/src/custom-components/ReorderableList.tsx +++ b/src/custom-components/ReorderableList.tsx @@ -1,301 +1,300 @@ import React, { Fragment, useEffect, useRef, useState } from "react"; import { FaEllipsisH, FaArrowsAltV } from "react-icons/fa"; -import { ButtonItem, DialogButton, Field, Focusable } from "../deck-components"; -import { GamepadButton, GamepadEvent } from "../deck-components/FooterLegend" +import { ButtonItem, DialogButton, Field, Focusable, GamepadButton, GamepadEvent } from "../deck-components"; interface Positioned { - position: number + position: number } export type ReorderableEntry = { - key: string, - label: string, - data: T + key: string, + label: string, + data: T } export type ReorderableEntryProps = { - entry: ReorderableEntry, - index: number, - action: (e:MouseEvent, entry:ReorderableEntry) => void + entry: ReorderableEntry, + index: number, + action: (e: MouseEvent, entry: ReorderableEntry) => void } export type ReorderableListData = { - [key:string]: ReorderableEntry + [key: string]: ReorderableEntry } export type ReloadData = { - showReload: boolean, - reload: () => Promise, - reloadLabel?: string + showReload: boolean, + reload: () => Promise, + reloadLabel?: string } type ReorderableListProps = { - data: ReorderableListData, - action: (e:MouseEvent, entry:ReorderableEntry) => void, - onUpdate: (data: {[key:string]:T}) => void, - reloadData: ReloadData + data: ReorderableListData, + action: (e: MouseEvent, entry: ReorderableEntry) => void, + onUpdate: (data: { [key: string]: T }) => void, + reloadData: ReloadData } const ELEM_HEIGHT = 32; //height of each ReorderableEntry element export function ReorderableList(props: ReorderableListProps) { - let reorderEnabled = useRef(false); - const touchOrigin = useRef({"x": -1, "y": -1}); - const mouseOrigin = useRef({"x": -1, "y": -1}); - let focusedSide = useRef(false); //false = left, true = right - let focusIdx = useRef(0); + let reorderEnabled = useRef(false); + const touchOrigin = useRef({ "x": -1, "y": -1 }); + const mouseOrigin = useRef({ "x": -1, "y": -1 }); + let focusedSide = useRef(false); //false = left, true = right + let focusIdx = useRef(0); - let data = props.data; - let onUpdate = props.onUpdate; - let dataAsList:ReorderableEntry[] = Object.values(props.data).sort((a, b) => a.data.position - b.data.position);; + let data = props.data; + let onUpdate = props.onUpdate; + let dataAsList: ReorderableEntry[] = Object.values(props.data).sort((a, b) => a.data.position - b.data.position);; + + const [update, setUpdate] = useState(0); + + useEffect(() => { + dataAsList = []; + dataAsList = Object.values(props.data).sort((a, b) => a.data.position - b.data.position); + data = props.data; + }); + + function enableReorder() { reorderEnabled.current = true; } + function disabledReorder() { reorderEnabled.current = false; } + function forceUpdate() { setUpdate(update === 0 ? 1 : 0); } + + function ReorderableEntry(props: ReorderableEntryProps) { + const wrapperFocusable = useRef(null); + const reorderBtn = useRef(null); + const optionsBtn = useRef(null); + + let lastEvent = false; - const [update, setUpdate] = useState(0); - useEffect(() => { - dataAsList = []; - dataAsList = Object.values(props.data).sort((a, b) => a.data.position - b.data.position); - data = props.data; + if (focusIdx.current === props.index) { + if (!focusedSide.current) { + optionsBtn.current?.blur(); + reorderBtn.current?.focus(); + } else { + reorderBtn.current?.blur(); + optionsBtn.current?.focus(); + } + } }); - function enableReorder() { reorderEnabled.current = true; } - function disabledReorder() { reorderEnabled.current = false; } - function forceUpdate() { setUpdate(update === 0 ? 1 : 0); } - - function ReorderableEntry(props: ReorderableEntryProps) { - const wrapperFocusable = useRef(null); - const reorderBtn = useRef(null); - const optionsBtn = useRef(null); + function reorder(down: boolean) { + if ((down && props.entry.data.position != dataAsList.length) || (!down && props.entry.data.position != 1)) { + const thisData = props.entry; + const previous = dataAsList[down ? props.index + 1 : props.index - 1]; + const tmp = thisData.data.position; + thisData.data.position = previous.data.position; + previous.data.position = tmp; - let lastEvent = false; + const refs = data; + refs[thisData.key] = thisData; + refs[previous.key] = previous; - useEffect(() => { - if (focusIdx.current === props.index) { - if (!focusedSide.current) { - optionsBtn.current?.blur(); - reorderBtn.current?.focus(); - } else { - reorderBtn.current?.blur(); - optionsBtn.current?.focus(); - } - } - }); + const toSave: { [key: string]: T } = {}; + Object.values(refs).map((val: ReorderableEntry) => { + toSave[val.key] = val.data; + }) + onUpdate(toSave); - function reorder(down:boolean) { - if ((down && props.entry.data.position != dataAsList.length) || (!down && props.entry.data.position != 1)) { - const thisData = props.entry; - const previous = dataAsList[down ? props.index+1 : props.index-1]; - const tmp = thisData.data.position; - thisData.data.position = previous.data.position; - previous.data.position = tmp; - - const refs = data; - refs[thisData.key] = thisData; - refs[previous.key] = previous; - - const toSave:{[key:string]:T} = {}; - Object.values(refs).map((val:ReorderableEntry) => { - toSave[val.key] = val.data; - }) - onUpdate(toSave); - - if (down) { - focusIdx.current++; - } else { - focusIdx.current--; - } - } + if (down) { + focusIdx.current++; + } else { + focusIdx.current--; } - - return ( - -
- { focusIdx.current = props.index; }} ref={wrapperFocusable} style={{ width: "100%" }}> - { - switch(e.detail.button) { - case GamepadButton.DIR_DOWN: { - - if (reorderEnabled.current && props.entry.data.position === dataAsList.length) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - - if (reorderEnabled.current && props.entry.data.position != dataAsList.length) reorder(true); - - if (props.entry.data.position != dataAsList.length) { - focusIdx.current++; - forceUpdate(); - } - break; - } - case GamepadButton.DIR_UP: { - if (reorderEnabled.current && props.entry.data.position === 1) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - - if (reorderEnabled.current && props.entry.data.position != 1) reorder(false); - - if (props.entry.data.position != 1) { - focusIdx.current--; - forceUpdate(); - } - break; - } - case GamepadButton.DIR_LEFT: { - lastEvent = true; - if (focusedSide.current) { - focusedSide.current = false; - } - reorderEnabled.current = false; - } - case GamepadButton.DIR_RIGHT: { - if (!lastEvent) { - if (!focusedSide.current) { - focusedSide.current = true; - } - reorderEnabled.current = false; - } else { - lastEvent = false; - } - } - } - return false; - }} - onMouseMove={(e:React.MouseEvent) => { - // once user has moved height of an entry, swap - if (reorderEnabled.current) { - const dy = e.clientY - mouseOrigin.current.y; - if (Math.abs(dy) >= ELEM_HEIGHT) { - reorder(dy > 0); - mouseOrigin.current = { - "x": e.clientX, - "y": e.clientY, - } - } - } - }} - onTouchMove={(e:React.TouchEvent) => { - if (reorderEnabled.current) { - const dy = e.touches[0].clientY - touchOrigin.current.y; - if (Math.abs(dy) >= ELEM_HEIGHT) { - reorder(dy > 0); - touchOrigin.current = { - "x": e.touches[0].clientX, - "y": e.touches[0].clientY, - } - } - } - }} - > - { - switch(e.detail.button) { - case GamepadButton.OK: { - enableReorder(); - } - } - }} - onButtonUp={(e:GamepadEvent) => { - switch(e.detail.button) { - case GamepadButton.OK: { - disabledReorder(); - } - } - }} - onMouseDown={(e:MouseEvent) => { - mouseOrigin.current = { - "x": e.clientX, - "y": e.clientY, - } - enableReorder(); - }} - onTouchStart={(e:TouchEvent) => { - touchOrigin.current = { - "x": e.touches[0].clientX, - "y": e.touches[0].clientY, - } - enableReorder(); - }} - > - - - {props.action(e, props.entry);}} - ref={optionsBtn} - > - - - - -
-
- ); + } } - + return ( - - -
{ - mouseOrigin.current = { - "x": -1, - "y": -1, + return false; + }} + onMouseMove={(e: React.MouseEvent) => { + // once user has moved height of an entry, swap + if (reorderEnabled.current) { + const dy = e.clientY - mouseOrigin.current.y; + if (Math.abs(dy) >= ELEM_HEIGHT) { + reorder(dy > 0); + mouseOrigin.current = { + "x": e.clientX, + "y": e.clientY, + } + } } - disabledReorder(); - }} - onTouchEnd={() => { - touchOrigin.current = { - "x": -1, - "y": -1, + }} + onTouchMove={(e: React.TouchEvent) => { + if (reorderEnabled.current) { + const dy = e.touches[0].clientY - touchOrigin.current.y; + if (Math.abs(dy) >= ELEM_HEIGHT) { + reorder(dy > 0); + touchOrigin.current = { + "x": e.touches[0].clientX, + "y": e.touches[0].clientY, + } + } } - disabledReorder(); - }} + }} > - {dataAsList.length > 0 ? - dataAsList.map((itm: ReorderableEntry, i:number) => ( - - )) : ( -
- No data to display right now. -
- ) - } - {props.reloadData.showReload ? ( - - Reload {props.reloadData.reloadLabel} - - ) : ""} -
-
+ { + switch (e.detail.button) { + case GamepadButton.OK: { + enableReorder(); + } + } + }} + onButtonUp={(e: GamepadEvent) => { + switch (e.detail.button) { + case GamepadButton.OK: { + disabledReorder(); + } + } + }} + onMouseDown={(e: MouseEvent) => { + mouseOrigin.current = { + "x": e.clientX, + "y": e.clientY, + } + enableReorder(); + }} + onTouchStart={(e: TouchEvent) => { + touchOrigin.current = { + "x": e.touches[0].clientX, + "y": e.touches[0].clientY, + } + enableReorder(); + }} + > + + + { props.action(e, props.entry); }} + ref={optionsBtn} + > + + + + + + ); + } + + return ( + + +
{ + mouseOrigin.current = { + "x": -1, + "y": -1, + } + disabledReorder(); + }} + onTouchEnd={() => { + touchOrigin.current = { + "x": -1, + "y": -1, + } + disabledReorder(); + }} + > + {dataAsList.length > 0 ? + dataAsList.map((itm: ReorderableEntry, i: number) => ( + + )) : ( +
+ No data to display right now. +
+ ) + } + {props.reloadData.showReload ? ( + + Reload {props.reloadData.reloadLabel} + + ) : ""} +
+
+ ); } \ No newline at end of file