feat: 工作目录支持页面内目录浏览器
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
type Task,
|
||||
type VenvProbeResponse,
|
||||
} from '../api/client'
|
||||
import { DirectoryPickerDialog } from './DirectoryPickerDialog'
|
||||
import {
|
||||
createEmptyTaskFormValues,
|
||||
mapServerFieldToFormField,
|
||||
@@ -42,8 +43,10 @@ export function TaskEditorModal({
|
||||
}: TaskEditorModalProps) {
|
||||
const [submitError, setSubmitError] = useState<string | null>(null)
|
||||
const [cronPreviewCount, setCronPreviewCount] = useState(5)
|
||||
const [pickerOpen, setPickerOpen] = useState(false)
|
||||
const dialogRef = useRef<HTMLElement>(null)
|
||||
useFocusTrap(open, dialogRef, '[data-dialog-autofocus]')
|
||||
// 目录选择弹窗打开时让出焦点管理,避免两个焦点陷阱互相争夺 Tab。
|
||||
useFocusTrap(open && !pickerOpen, dialogRef, '[data-dialog-autofocus]')
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
@@ -51,6 +54,7 @@ export function TaskEditorModal({
|
||||
reset,
|
||||
watch,
|
||||
setError,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<TaskFormValues>({
|
||||
resolver: zodResolver(taskFormSchema),
|
||||
@@ -119,6 +123,7 @@ export function TaskEditorModal({
|
||||
const previewVirtualEnv =
|
||||
scriptPreview.venv.trim() || linkedVenv?.venv_path || ''
|
||||
const previewInterpreter = linkedVenv?.display_path ?? null
|
||||
|
||||
const scriptArgs = useFieldArray({ control, name: 'script.args' })
|
||||
const scriptEnv = useFieldArray({ control, name: 'script.env' })
|
||||
const httpHeaders = useFieldArray({ control, name: 'http.headers' })
|
||||
@@ -132,6 +137,7 @@ export function TaskEditorModal({
|
||||
reset(task ? taskToFormValues(task) : createEmptyTaskFormValues())
|
||||
setCronPreviewCount(5)
|
||||
setSubmitError(null)
|
||||
setPickerOpen(false)
|
||||
}, [open, reset, task])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -346,16 +352,35 @@ export function TaskEditorModal({
|
||||
|
||||
{actionType === 'script' ? (
|
||||
<div className="dynamic-fields">
|
||||
<Field
|
||||
label="项目工作目录 (Working Directory)"
|
||||
error={errors.script?.working_dir?.message}
|
||||
>
|
||||
<input
|
||||
{...register('script.working_dir')}
|
||||
className="field-input mono"
|
||||
placeholder="D:\Projects\sync"
|
||||
/>
|
||||
</Field>
|
||||
<div className="field">
|
||||
<label className="field-label" htmlFor="task-working-dir">
|
||||
项目工作目录 (Working Directory)
|
||||
</label>
|
||||
<div className="field-with-action">
|
||||
<input
|
||||
id="task-working-dir"
|
||||
{...register('script.working_dir')}
|
||||
className="field-input mono"
|
||||
placeholder="D:\Projects\sync"
|
||||
aria-invalid={Boolean(errors.script?.working_dir)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="button button-secondary pick-directory-button"
|
||||
// 不夺走输入框焦点:否则 blur 校验插入错误行会让表单瞬间下移,
|
||||
// 点击可能落到移位后的其它元素上。
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => setPickerOpen(true)}
|
||||
>
|
||||
选择文件夹…
|
||||
</button>
|
||||
</div>
|
||||
{errors.script?.working_dir?.message ? (
|
||||
<span className="field-error">
|
||||
{errors.script.working_dir.message}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<VenvDetectorBar
|
||||
workingDir={debouncedWorkingDir}
|
||||
probe={venvProbe.data}
|
||||
@@ -643,6 +668,18 @@ export function TaskEditorModal({
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
<DirectoryPickerDialog
|
||||
open={pickerOpen}
|
||||
initialPath={scriptPreview.working_dir}
|
||||
onSelect={(path) => {
|
||||
setValue('script.working_dir', path, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
})
|
||||
}}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user