Connection Typeahead Arrow Keys (#352)

This is a first pass to get arrow keys to work with the connection
typeahead.
This commit is contained in:
Sylvie Crowe
2024-09-09 16:26:24 -07:00
committed by GitHub
parent 65a10ffb36
commit 663cb2cce7
3 changed files with 98 additions and 63 deletions
+35 -19
View File
@@ -492,6 +492,7 @@ const ChangeConnectionBlockModal = React.memo(
const connStatus = jotai.useAtomValue(connStatusAtom);
const [connList, setConnList] = React.useState<Array<string>>([]);
const allConnStatus = jotai.useAtomValue(atoms.allConnStatus);
const [rowIndex, setRowIndex] = React.useState(0);
const connStatusMap = new Map<string, ConnStatus>();
let maxActiveConnNum = 1;
for (const conn of allConnStatus) {
@@ -506,14 +507,10 @@ const ChangeConnectionBlockModal = React.memo(
return;
}
const prtn = WshServer.ConnListCommand({ timeout: 2000 });
prtn.then((connList) => {
setConnList(connList ?? []);
prtn.then((newConnList) => {
setConnList(newConnList ?? []);
}).catch((e) => console.log("unable to load conn list from backend. using blank list: ", e));
}, [changeConnModalOpen]);
React.useEffect(() => {
console.log("connSelected is: ", connSelected);
}, [connSelected]);
}, [changeConnModalOpen, setConnList]);
const changeConnection = React.useCallback(
async (connName: string) => {
@@ -534,7 +531,6 @@ const ChangeConnectionBlockModal = React.memo(
oref: WOS.makeORef("block", blockId),
meta: { connection: connName, file: newCwd },
});
const tabId = globalStore.get(atoms.activeTabId);
try {
await WshServer.ConnEnsureCommand(connName, { timeout: 60000 });
} catch (e) {
@@ -588,7 +584,6 @@ const ChangeConnectionBlockModal = React.memo(
};
const priorityItems: Array<SuggestionConnectionItem> = [];
if (createNew) {
console.log("added to priority items");
priorityItems.push(newConnectionSuggestion);
}
if (showReconnect && (connStatus.status == "disconnected" || connStatus.status == "error")) {
@@ -610,10 +605,6 @@ const ChangeConnectionBlockModal = React.memo(
iconColor: "var(--grey-text-color)",
value: "",
label: localName,
onSelect: (_: string) => {
changeConnection("");
globalStore.set(changeConnModalAtom, false);
},
});
}
const remoteItems = filteredList.map((connName) => {
@@ -647,14 +638,30 @@ const ChangeConnectionBlockModal = React.memo(
suggestions.push(remoteSuggestions);
}
let selectionList: Array<SuggestionConnectionItem> = [
...prioritySuggestions.items,
...localSuggestion.items,
...remoteSuggestions.items,
];
// quick way to change icon color when highlighted
selectionList = selectionList.map((item, index) => {
if (index == rowIndex && item.iconColor == "var(--grey-text-color)") {
item.iconColor = "var(--main-text-color)";
}
return item;
});
const handleTypeAheadKeyDown = React.useCallback(
(waveEvent: WaveKeyboardEvent): boolean => {
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
changeConnection(connSelected);
globalStore.set(changeConnModalAtom, false);
setConnSelected("");
refocusNode(blockId);
return true;
const rowItem = selectionList[rowIndex];
if ("onSelect" in rowItem && rowItem.onSelect) {
rowItem.onSelect(rowItem.value);
} else {
changeConnection(rowItem.value);
globalStore.set(changeConnModalAtom, false);
}
}
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
globalStore.set(changeConnModalAtom, false);
@@ -662,8 +669,16 @@ const ChangeConnectionBlockModal = React.memo(
refocusNode(blockId);
return true;
}
if (keyutil.checkKeyPressed(waveEvent, "ArrowUp")) {
setRowIndex((idx) => Math.max(idx - 1, 0));
return true;
}
if (keyutil.checkKeyPressed(waveEvent, "ArrowDown")) {
setRowIndex((idx) => Math.min(idx + 1, filteredList.length));
return true;
}
},
[changeConnModalAtom, viewModel, blockId, connSelected]
[changeConnModalAtom, viewModel, blockId, connSelected, selectionList]
);
// this check was also moved to BlockFrame to prevent all the above code from running unnecessarily
if (!changeConnModalOpen) {
@@ -678,6 +693,7 @@ const ChangeConnectionBlockModal = React.memo(
changeConnection(selected);
globalStore.set(changeConnModalAtom, false);
}}
selectIndex={rowIndex}
autoFocus={isNodeFocused}
onKeyDown={(e) => keyutil.keydownWrapper(handleTypeAheadKeyDown)(e)}
onChange={(current: string) => setConnSelected(current)}
+7 -2
View File
@@ -85,10 +85,15 @@
align-items: center;
gap: 8px;
align-self: stretch;
border-radius: 4px;
&:hover {
&.selected {
background-color: rgb(from var(--accent-color) r g b / 0.5);
color: var(--main-text-color);
}
&:hover:not(.selected) {
background-color: var(--highlight-bg-color);
border-radius: 4px;
}
.typeahead-item-name {
+56 -42
View File
@@ -11,51 +11,59 @@ import "./typeaheadmodal.less";
interface SuggestionsProps {
suggestions?: SuggestionsType[];
onSelect?: (_: string) => void;
selectIndex: number;
}
const Suggestions = forwardRef<HTMLDivElement, SuggestionsProps>(({ suggestions, onSelect }: SuggestionsProps, ref) => {
const renderIcon = (icon: string | React.ReactNode, color: string) => {
if (typeof icon === "string") {
return <i className={makeIconClass(icon, false)} style={{ color: color }}></i>;
}
return icon;
};
const Suggestions = forwardRef<HTMLDivElement, SuggestionsProps>(
({ suggestions, onSelect, selectIndex }: SuggestionsProps, ref) => {
const renderIcon = (icon: string | React.ReactNode, color: string) => {
if (typeof icon === "string") {
return <i className={makeIconClass(icon, false)} style={{ color: color }}></i>;
}
return icon;
};
const renderItem = (item: SuggestionBaseItem | SuggestionConnectionItem, index: number) => (
<div
key={index}
onClick={() => {
if ("onSelect" in item && item.onSelect) {
item.onSelect(item.label);
} else {
onSelect(item.label);
}
}}
className="suggestion-item"
>
<div className="typeahead-item-name">
{item.icon && renderIcon(item.icon, "iconColor" in item && item.iconColor ? item.iconColor : "inherit")}
{item.label}
const renderItem = (item: SuggestionBaseItem | SuggestionConnectionItem, index: number) => (
<div
key={index}
onClick={() => {
if ("onSelect" in item && item.onSelect) {
item.onSelect(item.value);
} else {
onSelect(item.value);
}
}}
className={clsx("suggestion-item", { selected: selectIndex === index })}
>
<div className="typeahead-item-name">
{item.icon &&
renderIcon(item.icon, "iconColor" in item && item.iconColor ? item.iconColor : "inherit")}
{item.label}
</div>
</div>
</div>
);
);
return (
<div ref={ref} className="suggestions">
{suggestions.map((item, index) => {
if ("headerText" in item) {
return (
<div key={index}>
{item.headerText && <div className="suggestion-header">{item.headerText}</div>}
{item.items.map((subItem, subIndex) => renderItem(subItem, subIndex))}
</div>
);
}
return renderItem(item as SuggestionBaseItem, index);
})}
</div>
);
});
let fullIndex = -1;
return (
<div ref={ref} className="suggestions">
{suggestions.map((item, index) => {
if ("headerText" in item) {
return (
<div key={index}>
{item.headerText && <div className="suggestion-header">{item.headerText}</div>}
{item.items.map((subItem, subIndex) => {
fullIndex += 1;
return renderItem(subItem, fullIndex);
})}
</div>
);
}
return renderItem(item as SuggestionBaseItem, index);
})}
</div>
);
}
);
interface TypeAheadModalProps {
anchorRef: React.RefObject<HTMLDivElement>;
@@ -70,6 +78,7 @@ interface TypeAheadModalProps {
onKeyDown?: (_) => void;
giveFocusRef?: React.MutableRefObject<() => boolean>;
autoFocus?: boolean;
selectIndex?: number;
}
const TypeAheadModal = ({
@@ -85,6 +94,7 @@ const TypeAheadModal = ({
onClickBackdrop,
giveFocusRef,
autoFocus,
selectIndex,
}: TypeAheadModalProps) => {
const { width, height } = useDimensions(blockRef);
const modalRef = useRef<HTMLDivElement>(null);
@@ -139,7 +149,6 @@ const TypeAheadModal = ({
let modalWidth = 300;
if (modalWidth > availableWidth) {
console.log("got here!!!!!");
modalWidth = availableWidth;
}
@@ -226,7 +235,12 @@ const TypeAheadModal = ({
}}
>
{suggestions?.length > 0 && (
<Suggestions ref={suggestionsRef} suggestions={suggestions} onSelect={handleSelect} />
<Suggestions
ref={suggestionsRef}
suggestions={suggestions}
onSelect={handleSelect}
selectIndex={selectIndex}
/>
)}
</div>
</div>