mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
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:
@@ -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)}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user