mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
Connection Typeahead/Suggestions (#332)
Adds a list of potential remotes to add and filters it as you type. It also provides options for reconnecting on a disconnection and specifically connecting to a local connection
This commit is contained in:
@@ -448,6 +448,10 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
const changeConnModalOpen = jotai.useAtomValue(changeConnModalAtom);
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const isNodeFocused = jotai.useAtomValue(nodeModel.isFocused);
|
||||
const connection = blockData?.meta?.connection ?? "local";
|
||||
const connStatusAtom = getConnStatusAtom(connection);
|
||||
const connStatus = jotai.useAtomValue(connStatusAtom);
|
||||
const [suggestions, setSuggestions] = React.useState([]);
|
||||
const changeConnection = React.useCallback(
|
||||
async (connName: string) => {
|
||||
if (connName == "") {
|
||||
@@ -476,6 +480,109 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
},
|
||||
[blockId, blockData]
|
||||
);
|
||||
React.useEffect(() => {
|
||||
const loadFromBackend = async () => {
|
||||
let connList: Array<string>;
|
||||
|
||||
try {
|
||||
connList = await WshServer.ConnListCommand({ timeout: 2000 });
|
||||
} catch (e) {
|
||||
console.log("unable to load conn list from backend. using blank list: ", e);
|
||||
}
|
||||
let createNew: boolean = true;
|
||||
if (connSelected == "") {
|
||||
createNew = false;
|
||||
}
|
||||
const filteredList: Array<string> = [];
|
||||
for (const conn of connList) {
|
||||
if (conn === connSelected) {
|
||||
createNew = false;
|
||||
}
|
||||
if (conn.includes(connSelected)) {
|
||||
filteredList.push(conn);
|
||||
}
|
||||
}
|
||||
// priority handles special suggestions when necessary
|
||||
// for instance, when reconnecting
|
||||
const newConnectionSuggestion: SuggestionConnectionItem = {
|
||||
status: "connected",
|
||||
icon: "arrow-right-arrow-left",
|
||||
iconColor: "var(--conn-icon-color)",
|
||||
label: `(+) ${connSelected}`,
|
||||
value: "",
|
||||
onSelect: (_: string) => {
|
||||
changeConnection(connSelected);
|
||||
globalStore.set(changeConnModalAtom, false);
|
||||
},
|
||||
};
|
||||
const reconnectSuggestion: SuggestionConnectionItem = {
|
||||
status: "connected",
|
||||
icon: "arrow-right-arrow-left",
|
||||
iconColor: "var(--conn-icon-color)",
|
||||
label: `Reconnect to ${connStatus.connection}`,
|
||||
value: "",
|
||||
onSelect: async (_: string) => {
|
||||
console.log("unimplemented: reconnect");
|
||||
},
|
||||
};
|
||||
const priorityItems: Array<SuggestionConnectionItem> = [];
|
||||
if (createNew) {
|
||||
console.log("added to priority items");
|
||||
priorityItems.push(newConnectionSuggestion);
|
||||
}
|
||||
if (connStatus.status == "disconnected" || connStatus.status == "error") {
|
||||
priorityItems.push(reconnectSuggestion);
|
||||
}
|
||||
const prioritySuggestions: SuggestionConnectionScope = {
|
||||
headerText: "",
|
||||
items: priorityItems,
|
||||
};
|
||||
const localSuggestion: SuggestionConnectionScope = {
|
||||
headerText: "Local",
|
||||
items: [
|
||||
{
|
||||
status: "connected",
|
||||
icon: "laptop",
|
||||
iconColor: "var(--grey-text-color)",
|
||||
value: "",
|
||||
label: "Switch to Local Connection",
|
||||
// TODO: need to specify user name and host name
|
||||
onSelect: (_: string) => {
|
||||
changeConnection("");
|
||||
globalStore.set(changeConnModalAtom, false);
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const remoteItems = filteredList.map((connName) => {
|
||||
const item: SuggestionConnectionItem = {
|
||||
status: "connected",
|
||||
icon: "arrow-right-arrow-left",
|
||||
iconColor: "var(--conn-icon-color)",
|
||||
value: connName,
|
||||
label: connName,
|
||||
};
|
||||
return item;
|
||||
});
|
||||
const remoteSuggestions: SuggestionConnectionScope = {
|
||||
headerText: "Remote",
|
||||
items: remoteItems,
|
||||
};
|
||||
|
||||
let out: Array<SuggestionsType> = [];
|
||||
if (prioritySuggestions.items.length > 0) {
|
||||
out.push(prioritySuggestions);
|
||||
}
|
||||
if (localSuggestion.items.length > 0) {
|
||||
out.push(localSuggestion);
|
||||
}
|
||||
if (remoteSuggestions.items.length > 0) {
|
||||
out.push(remoteSuggestions);
|
||||
}
|
||||
setSuggestions(out);
|
||||
};
|
||||
loadFromBackend();
|
||||
}, [connStatus, setSuggestions, connSelected, changeConnection]);
|
||||
const handleTypeAheadKeyDown = React.useCallback(
|
||||
(waveEvent: WaveKeyboardEvent): boolean => {
|
||||
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||
@@ -493,6 +600,9 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
},
|
||||
[changeConnModalAtom, viewModel, blockId, connSelected]
|
||||
);
|
||||
React.useEffect(() => {
|
||||
console.log("connSelected is: ", connSelected);
|
||||
}, [connSelected]);
|
||||
if (!changeConnModalOpen) {
|
||||
return null;
|
||||
}
|
||||
@@ -500,7 +610,7 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
<TypeAheadModal
|
||||
blockRef={blockRef}
|
||||
anchorRef={connBtnRef}
|
||||
// suggestions={[]}
|
||||
suggestions={suggestions}
|
||||
onSelect={(selected: string) => {
|
||||
changeConnection(selected);
|
||||
globalStore.set(changeConnModalAtom, false);
|
||||
@@ -509,7 +619,7 @@ const ChangeConnectionBlockModal = React.memo(
|
||||
onKeyDown={(e) => keyutil.keydownWrapper(handleTypeAheadKeyDown)(e)}
|
||||
onChange={(current: string) => setConnSelected(current)}
|
||||
value={connSelected}
|
||||
label="Switch connection"
|
||||
label="username@host"
|
||||
onClickBackdrop={() => globalStore.set(changeConnModalAtom, false)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -208,7 +208,7 @@ export const ConnectionButton = React.memo(
|
||||
const connStatus = jotai.useAtomValue(connStatusAtom);
|
||||
let showDisconnectedSlash = false;
|
||||
let connIconElem: React.ReactNode = null;
|
||||
let color = "#53b4ea";
|
||||
let color = "var(--conn-icon-color)";
|
||||
const clickHandler = function () {
|
||||
setConnModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -8,45 +8,35 @@ import ReactDOM from "react-dom";
|
||||
|
||||
import "./typeaheadmodal.less";
|
||||
|
||||
type ConnStatus = "connected" | "connecting" | "disconnected" | "error";
|
||||
|
||||
interface BaseItem {
|
||||
label: string;
|
||||
value: string;
|
||||
icon?: string | React.ReactNode;
|
||||
}
|
||||
interface ConnectionItem extends BaseItem {
|
||||
status: ConnStatus;
|
||||
iconColor: string;
|
||||
}
|
||||
|
||||
interface ConnectionScope {
|
||||
headerText?: string;
|
||||
items: ConnectionItem[];
|
||||
}
|
||||
|
||||
type SuggestionsType = ConnectionItem | ConnectionScope;
|
||||
|
||||
interface SuggestionsProps {
|
||||
suggestions?: SuggestionsType[];
|
||||
onSelect?: (_: string) => void;
|
||||
}
|
||||
|
||||
const Suggestions = forwardRef<HTMLDivElement, SuggestionsProps>(({ suggestions, onSelect }: SuggestionsProps, ref) => {
|
||||
const renderIcon = (icon: string | React.ReactNode) => {
|
||||
const renderIcon = (icon: string | React.ReactNode, color: string) => {
|
||||
if (typeof icon === "string") {
|
||||
return <i className={makeIconClass(icon, false)}></i>;
|
||||
return <i className={makeIconClass(icon, false)} style={{ color: color }}></i>;
|
||||
}
|
||||
return icon;
|
||||
};
|
||||
|
||||
const renderItem = (item: BaseItem | ConnectionItem, index: number) => (
|
||||
<div key={index} onClick={() => onSelect(item.label)} className="suggestion-item">
|
||||
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="name">
|
||||
{item.icon && renderIcon(item.icon)}
|
||||
{item.icon && renderIcon(item.icon, "iconColor" in item && item.iconColor ? item.iconColor : "inherit")}
|
||||
{item.label}
|
||||
</div>
|
||||
{"status" in item && item.status == "connected" && <i className={makeIconClass("fa-check", false)}></i>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -61,7 +51,7 @@ const Suggestions = forwardRef<HTMLDivElement, SuggestionsProps>(({ suggestions,
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return renderItem(item as BaseItem, index);
|
||||
return renderItem(item as SuggestionBaseItem, index);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@@ -249,4 +239,3 @@ const TypeAheadModal = ({
|
||||
};
|
||||
|
||||
export { TypeAheadModal };
|
||||
export type { SuggestionsType };
|
||||
|
||||
@@ -32,6 +32,11 @@ class WshServerType {
|
||||
return wshServerRpcHelper_call("connensure", data, opts);
|
||||
}
|
||||
|
||||
// command "connlist" [call]
|
||||
ConnListCommand(opts?: RpcOpts): Promise<string[]> {
|
||||
return wshServerRpcHelper_call("connlist", null, opts);
|
||||
}
|
||||
|
||||
// command "connreinstallwsh" [call]
|
||||
ConnReinstallWshCommand(data: string, opts?: RpcOpts): Promise<void> {
|
||||
return wshServerRpcHelper_call("connreinstallwsh", data, opts);
|
||||
|
||||
@@ -91,4 +91,7 @@
|
||||
--form-element-primary-color: var(--accent-color);
|
||||
--form-element-secondary-color: rgba(255, 255, 255, 0.2);
|
||||
--form-element-error-color: var(--error-color);
|
||||
|
||||
/* temporary conn icon color - will be replaced with individual colors */
|
||||
--conn-icon-color: #53b4ea;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user