|
|
@@ -1,5 +1,5 @@
|
|
|
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
|
|
-import { memo, useEffect, useReducer, useRef } from "react"
|
|
|
+import { memo, useEffect, useRef, useState } from "react"
|
|
|
import { ApiConfigMeta } from "../../../../src/shared/ExtensionMessage"
|
|
|
import { Dropdown } from "vscrui"
|
|
|
import type { DropdownOption } from "vscrui"
|
|
|
@@ -14,86 +14,6 @@ interface ApiConfigManagerProps {
|
|
|
onUpsertConfig: (configName: string) => void
|
|
|
}
|
|
|
|
|
|
-type State = {
|
|
|
- isRenaming: boolean
|
|
|
- isCreating: boolean
|
|
|
- inputValue: string
|
|
|
- newProfileName: string
|
|
|
- error: string | null
|
|
|
-}
|
|
|
-
|
|
|
-type Action =
|
|
|
- | { type: "START_RENAME"; payload: string }
|
|
|
- | { type: "CANCEL_EDIT" }
|
|
|
- | { type: "SET_INPUT"; payload: string }
|
|
|
- | { type: "SET_NEW_NAME"; payload: string }
|
|
|
- | { type: "START_CREATE" }
|
|
|
- | { type: "CANCEL_CREATE" }
|
|
|
- | { type: "SET_ERROR"; payload: string | null }
|
|
|
- | { type: "RESET_STATE" }
|
|
|
-
|
|
|
-const initialState: State = {
|
|
|
- isRenaming: false,
|
|
|
- isCreating: false,
|
|
|
- inputValue: "",
|
|
|
- newProfileName: "",
|
|
|
- error: null,
|
|
|
-}
|
|
|
-
|
|
|
-const reducer = (state: State, action: Action): State => {
|
|
|
- switch (action.type) {
|
|
|
- case "START_RENAME":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- isRenaming: true,
|
|
|
- inputValue: action.payload,
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "CANCEL_EDIT":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- isRenaming: false,
|
|
|
- inputValue: "",
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "SET_INPUT":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- inputValue: action.payload,
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "SET_NEW_NAME":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- newProfileName: action.payload,
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "START_CREATE":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- isCreating: true,
|
|
|
- newProfileName: "",
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "CANCEL_CREATE":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- isCreating: false,
|
|
|
- newProfileName: "",
|
|
|
- error: null,
|
|
|
- }
|
|
|
- case "SET_ERROR":
|
|
|
- return {
|
|
|
- ...state,
|
|
|
- error: action.payload,
|
|
|
- }
|
|
|
- case "RESET_STATE":
|
|
|
- return initialState
|
|
|
- default:
|
|
|
- return state
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
const ApiConfigManager = ({
|
|
|
currentApiConfigName = "",
|
|
|
listApiConfigMeta = [],
|
|
|
@@ -102,7 +22,11 @@ const ApiConfigManager = ({
|
|
|
onRenameConfig,
|
|
|
onUpsertConfig,
|
|
|
}: ApiConfigManagerProps) => {
|
|
|
- const [state, dispatch] = useReducer(reducer, initialState)
|
|
|
+ const [isRenaming, setIsRenaming] = useState(false)
|
|
|
+ const [isCreating, setIsCreating] = useState(false)
|
|
|
+ const [inputValue, setInputValue] = useState("")
|
|
|
+ const [newProfileName, setNewProfileName] = useState("")
|
|
|
+ const [error, setError] = useState<string | null>(null)
|
|
|
const inputRef = useRef<any>(null)
|
|
|
const newProfileInputRef = useRef<any>(null)
|
|
|
|
|
|
@@ -127,68 +51,84 @@ const ApiConfigManager = ({
|
|
|
|
|
|
// Focus input when entering rename mode
|
|
|
useEffect(() => {
|
|
|
- if (state.isRenaming) {
|
|
|
+ if (isRenaming) {
|
|
|
const timeoutId = setTimeout(() => inputRef.current?.focus(), 0)
|
|
|
return () => clearTimeout(timeoutId)
|
|
|
}
|
|
|
- }, [state.isRenaming])
|
|
|
+ }, [isRenaming])
|
|
|
|
|
|
// Focus input when opening new dialog
|
|
|
useEffect(() => {
|
|
|
- if (state.isCreating) {
|
|
|
+ if (isCreating) {
|
|
|
const timeoutId = setTimeout(() => newProfileInputRef.current?.focus(), 0)
|
|
|
return () => clearTimeout(timeoutId)
|
|
|
}
|
|
|
- }, [state.isCreating])
|
|
|
+ }, [isCreating])
|
|
|
|
|
|
// Reset state when current profile changes
|
|
|
useEffect(() => {
|
|
|
- dispatch({ type: "RESET_STATE" })
|
|
|
+ setIsRenaming(false)
|
|
|
+ setIsCreating(false)
|
|
|
+ setInputValue("")
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
}, [currentApiConfigName])
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
- dispatch({ type: "START_CREATE" })
|
|
|
+ setIsCreating(true)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
|
|
|
const handleStartRename = () => {
|
|
|
- dispatch({ type: "START_RENAME", payload: currentApiConfigName || "" })
|
|
|
+ setIsRenaming(true)
|
|
|
+ setInputValue(currentApiConfigName || "")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
- dispatch({ type: "CANCEL_EDIT" })
|
|
|
+ setIsRenaming(false)
|
|
|
+ setInputValue("")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
|
|
|
const handleSave = () => {
|
|
|
- const trimmedValue = state.inputValue.trim()
|
|
|
+ const trimmedValue = inputValue.trim()
|
|
|
const error = validateName(trimmedValue, false)
|
|
|
|
|
|
if (error) {
|
|
|
- dispatch({ type: "SET_ERROR", payload: error })
|
|
|
+ setError(error)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if (state.isRenaming && currentApiConfigName) {
|
|
|
+ if (isRenaming && currentApiConfigName) {
|
|
|
if (currentApiConfigName === trimmedValue) {
|
|
|
- dispatch({ type: "CANCEL_EDIT" })
|
|
|
+ setIsRenaming(false)
|
|
|
+ setInputValue("")
|
|
|
+ setError(null)
|
|
|
return
|
|
|
}
|
|
|
onRenameConfig(currentApiConfigName, trimmedValue)
|
|
|
}
|
|
|
|
|
|
- dispatch({ type: "CANCEL_EDIT" })
|
|
|
+ setIsRenaming(false)
|
|
|
+ setInputValue("")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
|
|
|
const handleNewProfileSave = () => {
|
|
|
- const trimmedValue = state.newProfileName.trim()
|
|
|
+ const trimmedValue = newProfileName.trim()
|
|
|
const error = validateName(trimmedValue, true)
|
|
|
|
|
|
if (error) {
|
|
|
- dispatch({ type: "SET_ERROR", payload: error })
|
|
|
+ setError(error)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
onUpsertConfig(trimmedValue)
|
|
|
- dispatch({ type: "CANCEL_CREATE" })
|
|
|
+ setIsCreating(false)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
|
|
|
const handleDelete = () => {
|
|
|
@@ -212,23 +152,24 @@ const ApiConfigManager = ({
|
|
|
<span style={{ fontWeight: "500" }}>Configuration Profile</span>
|
|
|
</label>
|
|
|
|
|
|
- {state.isRenaming ? (
|
|
|
+ {isRenaming ? (
|
|
|
<div
|
|
|
data-testid="rename-form"
|
|
|
style={{ display: "flex", gap: "4px", alignItems: "center", flexDirection: "column" }}>
|
|
|
<div style={{ display: "flex", gap: "4px", alignItems: "center", width: "100%" }}>
|
|
|
<VSCodeTextField
|
|
|
ref={inputRef}
|
|
|
- value={state.inputValue}
|
|
|
+ value={inputValue}
|
|
|
onInput={(e: unknown) => {
|
|
|
const target = e as { target: { value: string } }
|
|
|
- dispatch({ type: "SET_INPUT", payload: target.target.value })
|
|
|
+ setInputValue(target.target.value)
|
|
|
+ setError(null)
|
|
|
}}
|
|
|
placeholder="Enter new name"
|
|
|
style={{ flexGrow: 1 }}
|
|
|
onKeyDown={(e: unknown) => {
|
|
|
const event = e as { key: string }
|
|
|
- if (event.key === "Enter" && state.inputValue.trim()) {
|
|
|
+ if (event.key === "Enter" && inputValue.trim()) {
|
|
|
handleSave()
|
|
|
} else if (event.key === "Escape") {
|
|
|
handleCancel()
|
|
|
@@ -237,7 +178,7 @@ const ApiConfigManager = ({
|
|
|
/>
|
|
|
<VSCodeButton
|
|
|
appearance="icon"
|
|
|
- disabled={!state.inputValue.trim()}
|
|
|
+ disabled={!inputValue.trim()}
|
|
|
onClick={handleSave}
|
|
|
title="Save"
|
|
|
style={{
|
|
|
@@ -263,9 +204,9 @@ const ApiConfigManager = ({
|
|
|
<span className="codicon codicon-close" />
|
|
|
</VSCodeButton>
|
|
|
</div>
|
|
|
- {state.error && (
|
|
|
+ {error && (
|
|
|
<p className="text-red-500 text-sm mt-2" data-testid="error-message">
|
|
|
- {state.error}
|
|
|
+ {error}
|
|
|
</p>
|
|
|
)}
|
|
|
</div>
|
|
|
@@ -345,8 +286,18 @@ const ApiConfigManager = ({
|
|
|
)}
|
|
|
|
|
|
<Dialog
|
|
|
- open={state.isCreating}
|
|
|
- onOpenChange={(open: boolean) => dispatch({ type: open ? "START_CREATE" : "CANCEL_CREATE" })}
|
|
|
+ open={isCreating}
|
|
|
+ onOpenChange={(open: boolean) => {
|
|
|
+ if (open) {
|
|
|
+ setIsCreating(true)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
+ } else {
|
|
|
+ setIsCreating(false)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
+ }
|
|
|
+ }}
|
|
|
aria-labelledby="new-profile-title">
|
|
|
<DialogContent className="p-4 max-w-sm">
|
|
|
<h2 id="new-profile-title" className="text-lg font-semibold mb-4">
|
|
|
@@ -355,39 +306,52 @@ const ApiConfigManager = ({
|
|
|
<button
|
|
|
className="absolute right-4 top-4"
|
|
|
aria-label="Close dialog"
|
|
|
- onClick={() => dispatch({ type: "CANCEL_CREATE" })}>
|
|
|
+ onClick={() => {
|
|
|
+ setIsCreating(false)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
+ }}>
|
|
|
<span className="codicon codicon-close" />
|
|
|
</button>
|
|
|
<VSCodeTextField
|
|
|
ref={newProfileInputRef}
|
|
|
- value={state.newProfileName}
|
|
|
+ value={newProfileName}
|
|
|
onInput={(e: unknown) => {
|
|
|
const target = e as { target: { value: string } }
|
|
|
- dispatch({ type: "SET_NEW_NAME", payload: target.target.value })
|
|
|
+ setNewProfileName(target.target.value)
|
|
|
+ setError(null)
|
|
|
}}
|
|
|
placeholder="Enter profile name"
|
|
|
style={{ width: "100%" }}
|
|
|
onKeyDown={(e: unknown) => {
|
|
|
const event = e as { key: string }
|
|
|
- if (event.key === "Enter" && state.newProfileName.trim()) {
|
|
|
+ if (event.key === "Enter" && newProfileName.trim()) {
|
|
|
handleNewProfileSave()
|
|
|
} else if (event.key === "Escape") {
|
|
|
- dispatch({ type: "CANCEL_CREATE" })
|
|
|
+ setIsCreating(false)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
}
|
|
|
}}
|
|
|
/>
|
|
|
- {state.error && (
|
|
|
+ {error && (
|
|
|
<p className="text-red-500 text-sm mt-2" data-testid="error-message">
|
|
|
- {state.error}
|
|
|
+ {error}
|
|
|
</p>
|
|
|
)}
|
|
|
<div className="flex justify-end gap-2 mt-4">
|
|
|
- <VSCodeButton appearance="secondary" onClick={() => dispatch({ type: "CANCEL_CREATE" })}>
|
|
|
+ <VSCodeButton
|
|
|
+ appearance="secondary"
|
|
|
+ onClick={() => {
|
|
|
+ setIsCreating(false)
|
|
|
+ setNewProfileName("")
|
|
|
+ setError(null)
|
|
|
+ }}>
|
|
|
Cancel
|
|
|
</VSCodeButton>
|
|
|
<VSCodeButton
|
|
|
appearance="primary"
|
|
|
- disabled={!state.newProfileName.trim()}
|
|
|
+ disabled={!newProfileName.trim()}
|
|
|
onClick={handleNewProfileSave}>
|
|
|
Create Profile
|
|
|
</VSCodeButton>
|