mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
feat: add workspace directory picker
This commit is contained in:
parent
c51334ebc3
commit
e7ef0f5d49
9 changed files with 1317 additions and 511 deletions
|
|
@ -1,96 +1,69 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { h } from './i18n.js';
|
||||
import { WorkspaceDirectoryPicker } from './workspace-directory-picker.js';
|
||||
|
||||
function looksLikeAbsolutePath(value) {
|
||||
return value.startsWith('/') || /^[A-Za-z]:[\\/]/.test(value) || /^\\\\/.test(value);
|
||||
}
|
||||
export const WorkspaceDirectoryPickerContext = React.createContext(null);
|
||||
|
||||
export function WorkspaceEditor({ workspace, disabled = false, onSave }) {
|
||||
const [editing, setEditing] = React.useState(false);
|
||||
const [draft, setDraft] = React.useState(workspace ?? '');
|
||||
export function WorkspaceEditor({ workspace, directoryPicker, disabled = false, onSave }) {
|
||||
const sharedDirectoryPicker = React.useContext(WorkspaceDirectoryPickerContext);
|
||||
const activeDirectoryPicker = directoryPicker ?? sharedDirectoryPicker;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [saving, setSaving] = React.useState(false);
|
||||
const [error, setError] = React.useState(null);
|
||||
const inputRef = React.useRef(null);
|
||||
const editButtonRef = React.useRef(null);
|
||||
const restoreFocus = React.useRef(false);
|
||||
const savingRef = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!editing) setDraft(workspace ?? '');
|
||||
}, [workspace, editing]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (editing) inputRef.current?.focus();
|
||||
else if (restoreFocus.current) {
|
||||
restoreFocus.current = false;
|
||||
editButtonRef.current?.focus();
|
||||
}
|
||||
}, [editing]);
|
||||
|
||||
const cancel = () => {
|
||||
setDraft(workspace ?? '');
|
||||
const close = React.useCallback(() => {
|
||||
setOpen(false);
|
||||
setError(null);
|
||||
restoreFocus.current = true;
|
||||
setEditing(false);
|
||||
};
|
||||
queueMicrotask(() => editButtonRef.current?.focus?.());
|
||||
}, []);
|
||||
|
||||
const submit = async (event) => {
|
||||
event.preventDefault();
|
||||
const value = draft.trim();
|
||||
if (!value || saving || disabled) return;
|
||||
if (!looksLikeAbsolutePath(value)) {
|
||||
setError('工作区必须是绝对路径。');
|
||||
const pick = React.useCallback(async (value) => {
|
||||
if (!value || savingRef.current || disabled) return;
|
||||
if (value === workspace) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
savingRef.current = true;
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
await onSave?.(value);
|
||||
restoreFocus.current = true;
|
||||
setEditing(false);
|
||||
close();
|
||||
} catch (cause) {
|
||||
setError(cause?.message ?? '工作区修改失败,请重试。');
|
||||
} finally {
|
||||
savingRef.current = false;
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
}, [close, disabled, onSave, workspace]);
|
||||
|
||||
return h('div', { className: 'dim-workspace' },
|
||||
h('div', { className: 'dim-workspaceHeader' },
|
||||
h('span', null, '当前工作区'),
|
||||
editing ? null : h('button', {
|
||||
h('button', {
|
||||
type: 'button',
|
||||
ref: editButtonRef,
|
||||
className: 'dim-workspaceEdit',
|
||||
onClick: () => { setEditing(true); setError(null); },
|
||||
disabled,
|
||||
}, '修改')),
|
||||
editing
|
||||
? h('form', { className: 'dim-workspaceForm', onSubmit: submit },
|
||||
h('input', {
|
||||
ref: inputRef,
|
||||
value: draft,
|
||||
onChange: (event) => setDraft(event.target.value),
|
||||
placeholder: '/绝对路径/到/工作区',
|
||||
'aria-label': '工作区绝对路径',
|
||||
autoCapitalize: 'none',
|
||||
autoCorrect: 'off',
|
||||
spellCheck: false,
|
||||
maxLength: 4_096,
|
||||
required: true,
|
||||
disabled: saving || disabled,
|
||||
}),
|
||||
h('div', { className: 'dim-workspaceActions' },
|
||||
h('button', {
|
||||
type: 'submit', disabled: saving || disabled || !draft.trim(),
|
||||
}, saving ? '保存中…' : '保存'),
|
||||
h('button', { type: 'button', onClick: cancel, disabled: saving || disabled }, '取消')),
|
||||
error ? h('p', { className: 'dim-workspaceError', role: 'alert' }, error) : null)
|
||||
: workspace
|
||||
? React.createElement('code', {
|
||||
className: 'dim-workspacePath',
|
||||
title: workspace,
|
||||
}, workspace)
|
||||
: h('code', { className: 'dim-workspacePath' }, '未设置'),
|
||||
onClick: () => { setOpen(true); setError(null); },
|
||||
disabled: disabled || !activeDirectoryPicker,
|
||||
}, '选择目录')),
|
||||
workspace
|
||||
? React.createElement('code', {
|
||||
className: 'dim-workspacePath',
|
||||
title: workspace,
|
||||
}, workspace)
|
||||
: h('code', { className: 'dim-workspacePath' }, '未设置'),
|
||||
open ? h(WorkspaceDirectoryPicker, {
|
||||
open,
|
||||
startPath: workspace,
|
||||
picker: activeDirectoryPicker,
|
||||
busy: saving || disabled,
|
||||
saveError: error,
|
||||
onPicked: pick,
|
||||
onCancel: close,
|
||||
}) : null,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue