[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
550
components/scripts/ScriptDialogHost.tsx
Normal file
550
components/scripts/ScriptDialogHost.tsx
Normal file
@@ -0,0 +1,550 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useI18n } from '@/application/i18n/I18nProvider';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { netcattyBridge } from '@/infrastructure/services/netcattyBridge.ts';
|
||||
import type {
|
||||
ScriptDialogCondition,
|
||||
ScriptDialogField,
|
||||
ScriptDialogForm,
|
||||
ScriptDialogFormValue,
|
||||
ScriptDialogRequest,
|
||||
} from '@/types/global/netcatty-bridge-script.d.ts';
|
||||
|
||||
type FormValues = Record<string, ScriptDialogFormValue>;
|
||||
type FormErrors = Record<string, string>;
|
||||
type FormValidationMessages = string | {
|
||||
required: string;
|
||||
numberInvalid: string;
|
||||
numberMin: (min: number) => string;
|
||||
numberMax: (max: number) => string;
|
||||
numberStep: (step: number) => string;
|
||||
};
|
||||
|
||||
export function getInitialFormValues(request: ScriptDialogRequest): FormValues {
|
||||
if (request.type !== 'form' || !request.form) return {};
|
||||
return Object.fromEntries(
|
||||
request.form.fields.map((field) => [field.name, field.defaultValue]),
|
||||
);
|
||||
}
|
||||
|
||||
export function applyFormValue(values: FormValues, name: string, value: ScriptDialogFormValue): FormValues {
|
||||
return { ...values, [name]: value };
|
||||
}
|
||||
|
||||
export function getDialogFieldDomId(name: string): string {
|
||||
return `script-dialog-${encodeURIComponent(name || 'field')}`;
|
||||
}
|
||||
|
||||
function resolveValidationMessages(messages: FormValidationMessages = 'Required') {
|
||||
if (typeof messages === 'string') {
|
||||
return {
|
||||
required: messages,
|
||||
numberInvalid: messages,
|
||||
numberMin: () => messages,
|
||||
numberMax: () => messages,
|
||||
numberStep: () => messages,
|
||||
};
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
function matchesNumberStep(value: number, step: number, base = 0) {
|
||||
const quotient = (value - base) / step;
|
||||
return Math.abs(quotient - Math.round(quotient)) < 1e-9;
|
||||
}
|
||||
|
||||
function getNumberStepBase(field: Extract<ScriptDialogField, { type: 'number' }>) {
|
||||
return field.min ?? field.defaultValue ?? 0;
|
||||
}
|
||||
|
||||
function getConditionFieldValue(
|
||||
form: ScriptDialogForm,
|
||||
values: FormValues,
|
||||
fieldName: string,
|
||||
): ScriptDialogFormValue {
|
||||
const field = form.fields.find((candidate) => candidate.name === fieldName);
|
||||
const value = values[fieldName];
|
||||
if (field?.type === 'number') {
|
||||
if (value === undefined || value === '') return undefined;
|
||||
const numberValue = typeof value === 'number' ? value : Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : value;
|
||||
}
|
||||
if (field?.type === 'checkbox') {
|
||||
return Boolean(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function evaluateDialogCondition(
|
||||
form: ScriptDialogForm,
|
||||
values: FormValues,
|
||||
condition: ScriptDialogCondition,
|
||||
): boolean {
|
||||
const value = getConditionFieldValue(form, values, condition.field);
|
||||
if ('equals' in condition) {
|
||||
return value === condition.equals;
|
||||
}
|
||||
if ('notEquals' in condition) {
|
||||
return value !== condition.notEquals;
|
||||
}
|
||||
if ('truthy' in condition) {
|
||||
return Boolean(value);
|
||||
}
|
||||
return !value;
|
||||
}
|
||||
|
||||
export function isDialogFieldVisible(
|
||||
form: ScriptDialogForm,
|
||||
field: ScriptDialogField,
|
||||
values: FormValues,
|
||||
): boolean {
|
||||
return getVisibleDialogFormFields(form, values).some((visibleField) => visibleField.name === field.name);
|
||||
}
|
||||
|
||||
export function getVisibleDialogFormFields(form: ScriptDialogForm, values: FormValues): ScriptDialogField[] {
|
||||
const visibleFields: ScriptDialogField[] = [];
|
||||
const visibleNames = new Set<string>();
|
||||
for (const field of form.fields) {
|
||||
if (!field.visibleWhen) {
|
||||
visibleFields.push(field);
|
||||
visibleNames.add(field.name);
|
||||
continue;
|
||||
}
|
||||
if (!visibleNames.has(field.visibleWhen.field)) {
|
||||
continue;
|
||||
}
|
||||
if (evaluateDialogCondition(form, values, field.visibleWhen)) {
|
||||
visibleFields.push(field);
|
||||
visibleNames.add(field.name);
|
||||
}
|
||||
}
|
||||
return visibleFields;
|
||||
}
|
||||
|
||||
export function validateDialogFormValues(
|
||||
form: ScriptDialogForm,
|
||||
values: FormValues,
|
||||
messages: FormValidationMessages = 'Required',
|
||||
): FormErrors {
|
||||
const validationMessages = resolveValidationMessages(messages);
|
||||
const errors: FormErrors = {};
|
||||
for (const field of getVisibleDialogFormFields(form, values)) {
|
||||
const value = values[field.name];
|
||||
if (field.type === 'checkbox') {
|
||||
if (field.required === true && !value) {
|
||||
errors[field.name] = validationMessages.required;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (field.type === 'number') {
|
||||
const isEmpty = value === undefined || value === '';
|
||||
if (isEmpty) {
|
||||
if (field.required !== false) {
|
||||
errors[field.name] = validationMessages.required;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const numberValue = typeof value === 'number' ? value : Number(value);
|
||||
if (!Number.isFinite(numberValue)) {
|
||||
errors[field.name] = validationMessages.numberInvalid;
|
||||
continue;
|
||||
}
|
||||
if (field.min !== undefined && numberValue < field.min) {
|
||||
errors[field.name] = validationMessages.numberMin(field.min);
|
||||
continue;
|
||||
}
|
||||
if (field.max !== undefined && numberValue > field.max) {
|
||||
errors[field.name] = validationMessages.numberMax(field.max);
|
||||
continue;
|
||||
}
|
||||
if (field.step !== undefined && !matchesNumberStep(numberValue, field.step, getNumberStepBase(field))) {
|
||||
errors[field.name] = validationMessages.numberStep(field.step);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (field.required === false) continue;
|
||||
if (value === undefined || String(value).trim() === '') {
|
||||
errors[field.name] = validationMessages.required;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function normalizeDialogFormSubmitValues(form: ScriptDialogForm, values: FormValues): FormValues {
|
||||
const next: FormValues = {};
|
||||
for (const field of getVisibleDialogFormFields(form, values)) {
|
||||
const value = values[field.name];
|
||||
if (field.type === 'number') {
|
||||
if (value === undefined || value === '') {
|
||||
next[field.name] = undefined;
|
||||
continue;
|
||||
}
|
||||
const numberValue = typeof value === 'number' ? value : Number(value);
|
||||
next[field.name] = Number.isFinite(numberValue) ? numberValue : undefined;
|
||||
continue;
|
||||
}
|
||||
next[field.name] = value;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export function ScriptDialogFormFields({
|
||||
form,
|
||||
formValues,
|
||||
formErrors = {},
|
||||
onValueChange,
|
||||
}: {
|
||||
form: ScriptDialogForm;
|
||||
formValues: FormValues;
|
||||
formErrors?: FormErrors;
|
||||
onValueChange: (name: string, value: ScriptDialogFormValue) => void;
|
||||
}) {
|
||||
const renderFormField = (field: ScriptDialogField) => {
|
||||
const inputId = getDialogFieldDomId(field.name);
|
||||
const fieldError = formErrors[field.name];
|
||||
const descriptionId = field.description ? `${inputId}-description` : undefined;
|
||||
const errorId = fieldError ? `${inputId}-error` : undefined;
|
||||
const describedBy = [descriptionId, errorId].filter(Boolean).join(' ') || undefined;
|
||||
const fieldDescription = field.description ? (
|
||||
<p id={descriptionId} className="text-xs text-muted-foreground">{field.description}</p>
|
||||
) : null;
|
||||
const inlineDescription = field.description ? (
|
||||
<span id={descriptionId} className="mt-1 block text-xs text-muted-foreground">{field.description}</span>
|
||||
) : null;
|
||||
const errorMessage = fieldError ? (
|
||||
<p id={errorId} className="text-xs text-destructive">{fieldError}</p>
|
||||
) : null;
|
||||
const inlineErrorMessage = fieldError ? (
|
||||
<span id={errorId} className="mt-1 block text-xs text-destructive">{fieldError}</span>
|
||||
) : null;
|
||||
|
||||
if (field.type === 'select') {
|
||||
const labelId = `${inputId}-label`;
|
||||
const selectedValue = String(formValues[field.name] ?? field.defaultValue);
|
||||
const selectedOption = field.options.find((option) => option.value === selectedValue);
|
||||
const selectedDescriptionId = selectedOption?.description ? `${inputId}-selected-description` : undefined;
|
||||
const selectDescribedBy = [describedBy, selectedDescriptionId].filter(Boolean).join(' ') || undefined;
|
||||
return (
|
||||
<div key={field.name} className="space-y-2">
|
||||
<Label id={labelId} htmlFor={inputId}>{field.label}</Label>
|
||||
<Select
|
||||
value={selectedValue}
|
||||
onValueChange={(value) => onValueChange(field.name, value)}
|
||||
>
|
||||
<SelectTrigger
|
||||
id={inputId}
|
||||
aria-labelledby={labelId}
|
||||
aria-describedby={selectDescribedBy}
|
||||
aria-invalid={fieldError ? true : undefined}
|
||||
className="w-full"
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{field.options.map((option) => (
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
disabled={option.disabled}
|
||||
textValue={option.label}
|
||||
>
|
||||
<span className="flex min-w-0 flex-col">
|
||||
<span className="truncate">{option.label}</span>
|
||||
{option.description ? (
|
||||
<span className="truncate text-xs text-muted-foreground">{option.description}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedOption?.description ? (
|
||||
<p id={selectedDescriptionId} className="text-xs text-muted-foreground">{selectedOption.description}</p>
|
||||
) : null}
|
||||
{fieldDescription}
|
||||
{errorMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'radio') {
|
||||
const selectedValue = String(formValues[field.name] ?? field.defaultValue);
|
||||
return (
|
||||
<fieldset
|
||||
key={field.name}
|
||||
className="space-y-2"
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={fieldError ? true : undefined}
|
||||
>
|
||||
<legend className="text-sm font-medium leading-5">{field.label}</legend>
|
||||
{fieldDescription}
|
||||
<div className="space-y-2">
|
||||
{field.options.map((option, index) => {
|
||||
const optionInputId = `${inputId}-${index}`;
|
||||
return (
|
||||
<label
|
||||
key={option.value}
|
||||
htmlFor={optionInputId}
|
||||
className="flex items-start gap-2 rounded-md border border-border/60 px-3 py-2 text-sm"
|
||||
>
|
||||
<input
|
||||
id={optionInputId}
|
||||
type="radio"
|
||||
name={inputId}
|
||||
value={option.value}
|
||||
checked={selectedValue === option.value}
|
||||
disabled={option.disabled}
|
||||
onChange={(event) => onValueChange(field.name, event.target.value)}
|
||||
className="mt-0.5 h-4 w-4 accent-primary"
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block">{option.label}</span>
|
||||
{option.description ? (
|
||||
<span className="block text-xs text-muted-foreground">{option.description}</span>
|
||||
) : null}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{errorMessage}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'textarea') {
|
||||
return (
|
||||
<div key={field.name} className="space-y-2">
|
||||
<Label htmlFor={inputId}>{field.label}</Label>
|
||||
<Textarea
|
||||
id={inputId}
|
||||
value={String(formValues[field.name] ?? field.defaultValue)}
|
||||
placeholder={field.placeholder}
|
||||
onChange={(event) => onValueChange(field.name, event.target.value)}
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={fieldError ? true : undefined}
|
||||
/>
|
||||
{fieldDescription}
|
||||
{errorMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'number') {
|
||||
return (
|
||||
<div key={field.name} className="space-y-2">
|
||||
<Label htmlFor={inputId}>{field.label}</Label>
|
||||
<Input
|
||||
id={inputId}
|
||||
type="number"
|
||||
value={formValues[field.name] ?? field.defaultValue ?? ''}
|
||||
placeholder={field.placeholder}
|
||||
min={field.min}
|
||||
max={field.max}
|
||||
step={field.step}
|
||||
onChange={(event) => onValueChange(field.name, event.target.value)}
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={fieldError ? true : undefined}
|
||||
/>
|
||||
{fieldDescription}
|
||||
{errorMessage}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={field.name} className="space-y-2">
|
||||
<label htmlFor={inputId} className="flex items-start gap-2 text-sm">
|
||||
<input
|
||||
id={inputId}
|
||||
type="checkbox"
|
||||
checked={Boolean(formValues[field.name] ?? field.defaultValue)}
|
||||
onChange={(event) => onValueChange(field.name, event.target.checked)}
|
||||
aria-describedby={describedBy}
|
||||
aria-invalid={fieldError ? true : undefined}
|
||||
className="mt-0.5 h-4 w-4 accent-primary"
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block font-medium leading-5">{field.label}</span>
|
||||
{inlineDescription}
|
||||
{inlineErrorMessage}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{getVisibleDialogFormFields(form, formValues).map(renderFormField)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ScriptDialogFormBody({
|
||||
form,
|
||||
formValues,
|
||||
formErrors,
|
||||
onValueChange,
|
||||
}: {
|
||||
form: ScriptDialogForm;
|
||||
formValues: FormValues;
|
||||
formErrors?: FormErrors;
|
||||
onValueChange: (name: string, value: ScriptDialogFormValue) => void;
|
||||
}) {
|
||||
return (
|
||||
<ScrollArea className="min-h-0 pr-3">
|
||||
<ScriptDialogFormFields
|
||||
form={form}
|
||||
formValues={formValues}
|
||||
formErrors={formErrors}
|
||||
onValueChange={onValueChange}
|
||||
/>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
|
||||
export function ScriptDialogHost() {
|
||||
const { t } = useI18n();
|
||||
const [request, setRequest] = useState<ScriptDialogRequest | null>(null);
|
||||
const [promptValue, setPromptValue] = useState('');
|
||||
const [formValues, setFormValues] = useState<FormValues>({});
|
||||
const [formErrors, setFormErrors] = useState<FormErrors>({});
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = netcattyBridge.get()?.onScriptDialogRequest?.((payload) => {
|
||||
setRequest(payload);
|
||||
setPromptValue(payload.defaultValue ?? '');
|
||||
setFormValues(getInitialFormValues(payload));
|
||||
setFormErrors({});
|
||||
});
|
||||
return dispose;
|
||||
}, []);
|
||||
|
||||
const respond = useCallback(async (value?: unknown, cancelled = false) => {
|
||||
if (!request) return;
|
||||
await netcattyBridge.get()?.scriptDialogResponse?.(request.requestId, value, cancelled);
|
||||
setRequest(null);
|
||||
}, [request]);
|
||||
|
||||
if (!request) return null;
|
||||
|
||||
const form = request.type === 'form' ? request.form : undefined;
|
||||
const dialogTitle = request.type === 'waitForTimeout'
|
||||
? t('scripts.dialog.waitForTimeoutTitle')
|
||||
: form?.title || t('scripts.dialog.title');
|
||||
const message = form?.message ?? request.message;
|
||||
|
||||
const setFormValue = (name: string, value: ScriptDialogFormValue) => {
|
||||
setFormValues((current) => applyFormValue(current, name, value));
|
||||
setFormErrors((current) => {
|
||||
if (!current[name]) return current;
|
||||
const { [name]: _removed, ...rest } = current;
|
||||
return rest;
|
||||
});
|
||||
};
|
||||
|
||||
const submitForm = () => {
|
||||
if (!form) return;
|
||||
const errors = validateDialogFormValues(form, formValues, {
|
||||
required: t('scripts.dialog.required'),
|
||||
numberInvalid: t('scripts.dialog.numberInvalid'),
|
||||
numberMin: (min) => t('scripts.dialog.numberMin', { min }),
|
||||
numberMax: (max) => t('scripts.dialog.numberMax', { max }),
|
||||
numberStep: (step) => t('scripts.dialog.numberStep', { step }),
|
||||
});
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFormErrors(errors);
|
||||
return;
|
||||
}
|
||||
const submitValues = normalizeDialogFormSubmitValues(form, formValues);
|
||||
void respond(submitValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
void respond(request.type === 'waitForTimeout' ? 'abort' : undefined, true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className={form ? 'max-h-[85vh] grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden' : undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||
{message ? <DialogDescription>{message}</DialogDescription> : null}
|
||||
</DialogHeader>
|
||||
{request.type === 'prompt' ? (
|
||||
<Input
|
||||
type={request.sensitive ? 'password' : 'text'}
|
||||
value={promptValue}
|
||||
onChange={(event) => setPromptValue(event.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
) : null}
|
||||
{form ? (
|
||||
<ScriptDialogFormBody
|
||||
form={form}
|
||||
formValues={formValues}
|
||||
formErrors={formErrors}
|
||||
onValueChange={setFormValue}
|
||||
/>
|
||||
) : null}
|
||||
<DialogFooter>
|
||||
{request.type === 'waitForTimeout' ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void respond('abort')}>
|
||||
{t('scripts.dialog.abort')}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={() => void respond('skip')}>
|
||||
{t('scripts.dialog.skip')}
|
||||
</Button>
|
||||
<Button onClick={() => void respond('retry')}>
|
||||
{t('scripts.dialog.retry')}
|
||||
</Button>
|
||||
</>
|
||||
) : request.type === 'confirm' ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void respond(false)}>{t('common.cancel')}</Button>
|
||||
<Button onClick={() => void respond(true)}>{t('scripts.dialog.ok')}</Button>
|
||||
</>
|
||||
) : request.type === 'prompt' ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void respond(undefined, true)}>{t('common.cancel')}</Button>
|
||||
<Button onClick={() => void respond(promptValue)}>{t('scripts.dialog.ok')}</Button>
|
||||
</>
|
||||
) : request.type === 'form' ? (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => void respond(undefined, true)}>
|
||||
{form?.cancelLabel || t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={submitForm}>
|
||||
{form?.submitLabel || t('scripts.dialog.ok')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => void respond(undefined)}>{t('scripts.dialog.ok')}</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user