Files
router-studio/web/src/apps/view/components/DataItemForm.tsx
abearxiong d8acdaf97d feat: enhance studio app with left panel toggle and loading state
- Added a left panel toggle button in the header to show/hide the ViewList component.
- Integrated loading state management in the studio store for better user feedback during data fetching.
- Updated the ViewList component to utilize a dropdown menu for editing and deleting views.
- Improved UI elements with consistent border styles and loading indicators.
- Refactored the DataItemForm and ViewFormItem components for better user experience and added clipboard copy functionality.
- Introduced a new DropdownMenu component for better dropdown handling across the application.
2026-01-01 23:56:56 +08:00

201 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { Query } from "@kevisual/query"
import { QueryRouterServer } from "@kevisual/router"
import { nanoid } from "nanoid"
export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker;
type RouteViewBase = {
id: string;
title: string;
description: string;
enabled?: boolean;
}
export type RouterViewApi = {
type: 'api',
api: {
url: string,
// 已初始化的query实例不需要编辑配置
query?: Query
}
} & RouteViewBase;
export type RouterViewContext = {
type: 'context',
context: {
key: string,
// 从context中获取router不需要编辑配置
router?: QueryRouterServer
}
} & RouteViewBase;
export type RouterViewWorker = {
type: 'worker',
worker: {
type: 'Worker' | 'SharedWorker' | 'serviceWorker',
url: string,
// 已初始化的worker实例不需要编辑配置
worker?: Worker | SharedWorker | ServiceWorker,
/**
* worker选项
* default: { type: 'module' }
*/
workerOptions?: {
type: 'module' | 'classic'
}
}
} & RouteViewBase;
interface DataItemFormProps {
item: RouterViewItem
onChange: (item: any) => void
onRemove: () => void
}
export const DataItemForm = ({ item, onChange, onRemove }: DataItemFormProps) => {
const handleChange = (field: string, value: any) => {
if (field === 'type') {
const newItem: RouterViewItem = { ...item, type: value }
if (value === 'api' && !('api' in item)) {
(newItem as RouterViewApi).api = { url: '' }
} else if (value === 'context' && !('context' in item)) {
(newItem as RouterViewContext).context = { key: '' }
} else if (value === 'worker' && !('worker' in item)) {
(newItem as RouterViewWorker).worker = { type: 'Worker', url: '', workerOptions: { type: 'module' } }
}
if (!newItem.id) {
newItem.id = nanoid(16)
}
onChange(newItem)
} else {
onChange({ ...item, [field]: value })
}
}
const handleNestedChange = (parent: string, field: string, value: any) => {
const parentValue = item[parent as keyof RouterViewItem] as Record<string, any> | undefined
const newParentValue: Record<string, any> = {
...(parentValue || {}),
[field]: value
}
onChange({ ...item, [parent]: newParentValue })
}
const handleNestedDeepChange = (parent: string, nestedParent: string, field: string, value: any) => {
const parentValue = item[parent as keyof RouterViewItem] as Record<string, any> | undefined
const nestedValue = parentValue?.[nestedParent] as Record<string, any> | undefined
const newNestedValue: Record<string, any> = {
...(nestedValue || {}),
[field]: value
}
const newParentValue: Record<string, any> = {
...(parentValue || {}),
[nestedParent]: newNestedValue
}
onChange({ ...item, [parent]: newParentValue })
}
return (
<div className="border border-gray-300 rounded-lg p-4 mb-4 space-y-4">
<div className="flex justify-between items-center">
<h3 className="font-medium"></h3>
<button
type="button"
onClick={onRemove}
className="text-sm text-red-500 hover:text-red-700"
>
</button>
</div>
<div className="space-y-2">
<Label></Label>
<Input
value={item.title || ''}
onChange={(e) => handleChange('title', e.target.value)}
placeholder="输入标题"
/>
</div>
<div className="space-y-2">
<Label></Label>
<select
value={item.type}
onChange={(e) => handleChange('type', e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="api">API</option>
<option value="context">Context</option>
<option value="worker">Worker</option>
</select>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="enabled"
checked={item.enabled !== false}
onCheckedChange={(checked) => handleChange('enabled', checked)}
/>
<Label htmlFor="enabled" className="cursor-pointer"></Label>
</div>
{(item.type === 'api') && (
<div className="space-y-2">
<Label>API URL</Label>
<Input
value={item.api?.url || ''}
onChange={(e) => handleNestedChange('api', 'url', e.target.value)}
placeholder="输入 API 地址"
/>
</div>
)}
{item.type === 'context' && (
<div className="space-y-2">
<Label>Context Key</Label>
<Input
value={item.context?.key || ''}
onChange={(e) => handleNestedChange('context', 'key', e.target.value)}
placeholder="输入 Context Key"
/>
</div>
)}
{item.type === 'worker' && (
<div className="space-y-4">
<div className="space-y-2">
<Label>Worker Type</Label>
<select
value={item.worker?.type || 'Worker'}
onChange={(e) => handleNestedChange('worker', 'type', e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="Worker">Worker</option>
<option value="SharedWorker">SharedWorker</option>
<option value="serviceWorker">ServiceWorker</option>
</select>
</div>
<div className="space-y-2">
<Label>Worker URL</Label>
<Input
value={item.worker?.url || ''}
onChange={(e) => handleNestedChange('worker', 'url', e.target.value)}
placeholder="输入 Worker URL"
/>
</div>
<div className="space-y-2">
<Label>Worker Options Type</Label>
<select
value={item.worker?.workerOptions?.type || 'module'}
onChange={(e) => handleNestedDeepChange('worker', 'workerOptions', 'type', e.target.value)}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="module">Module</option>
<option value="classic">Classic</option>
</select>
</div>
</div>
)}
</div>
)
}