mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-07 18:26:37 +08:00
Fix TypeScript compilation errors
- Add isIndeterminate support to Checkbox component - Use showAddressModal in SortableTableRow for multi-address display - Remove unused onClose parameter in search modal - Remove unused infoPopoverPlacement and related code in node.tsx
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||||
import { CheckIcon } from "lucide-react";
|
import { CheckIcon, MinusIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -11,17 +11,18 @@ function Checkbox({
|
|||||||
return (
|
return (
|
||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow transition-transform duration-100 active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow transition-transform duration-100 active:scale-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<CheckboxPrimitive.Indicator
|
<CheckboxPrimitive.Indicator
|
||||||
className="flex items-center justify-center text-current data-[state=checked]:animate-in data-[state=checked]:zoom-in-75 data-[state=checked]:duration-150"
|
className="flex items-center justify-center text-current data-[state=checked]:animate-in data-[state=checked]:zoom-in-75 data-[state=checked]:duration-150 data-[state=indeterminate]:animate-in data-[state=indeterminate]:zoom-in-75 data-[state=indeterminate]:duration-150"
|
||||||
data-slot="checkbox-indicator"
|
data-slot="checkbox-indicator"
|
||||||
>
|
>
|
||||||
<CheckIcon className="h-3.5 w-3.5" />
|
<CheckIcon className="h-3.5 w-3.5 data-[state=indeterminate]:hidden" />
|
||||||
|
<MinusIcon className="h-3.5 w-3.5 hidden data-[state=indeterminate]:block" />
|
||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -784,44 +784,38 @@ const SortableTableRow = ({
|
|||||||
<TableCell
|
<TableCell
|
||||||
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.inbound} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.inbound} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all select-text ${
|
type="button"
|
||||||
hasMultipleAddresses(forward.inIp)
|
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
|
||||||
? "hover:bg-default-200 hover:shadow-sm cursor-pointer"
|
hasMultipleAddresses(forward.inIp) ? "hover:bg-default-200 hover:shadow-sm cursor-pointer" : "cursor-default"
|
||||||
: "cursor-text"
|
|
||||||
}`}
|
}`}
|
||||||
title="点击复制"
|
title={formatInAddress(forward.inIp, forward.inPort)}
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
e.stopPropagation();
|
if (hasMultipleAddresses(forward.inIp)) {
|
||||||
copyToClipboard(
|
showAddressModal(forward.inIp, forward.inPort, "入口端口");
|
||||||
(forward.inIp || "").replace(/:\d+$/, "") || "默认IP",
|
}
|
||||||
"入口地址",
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatInAddress(forward.inIp, forward.inPort)}
|
{formatInAddress(forward.inIp, forward.inPort)}
|
||||||
</div>
|
</button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell
|
<TableCell
|
||||||
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
className={`${FORWARD_GROUPED_TABLE_COLUMN_CLASS.target} max-w-[280px] ${selectedIds.has(forward.id) ? "bg-primary-50/70 dark:bg-primary-900/40" : ""}`}
|
||||||
>
|
>
|
||||||
<div
|
<button
|
||||||
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all select-text ${
|
type="button"
|
||||||
hasMultipleAddresses(forward.remoteAddr)
|
className={`w-full truncate rounded-md bg-default-100/50 px-2.5 py-1.5 text-left font-mono text-xs font-medium text-default-700 transition-all ${
|
||||||
? "hover:bg-default-200 hover:shadow-sm cursor-pointer"
|
hasMultipleAddresses(forward.remoteAddr) ? "hover:bg-default-200 hover:shadow-sm cursor-pointer" : "cursor-default"
|
||||||
: "cursor-text"
|
|
||||||
}`}
|
}`}
|
||||||
title="点击复制"
|
title={formatRemoteAddress(forward.remoteAddr)}
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
e.stopPropagation();
|
if (hasMultipleAddresses(forward.remoteAddr)) {
|
||||||
copyToClipboard(
|
showAddressModal(forward.remoteAddr, null, "目标地址");
|
||||||
forward.remoteAddr.split(",")[0].split(":")[0],
|
}
|
||||||
"落地地址",
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatRemoteAddress(forward.remoteAddr)}
|
{formatRemoteAddress(forward.remoteAddr)}
|
||||||
</div>
|
</button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={FORWARD_GROUPED_TABLE_COLUMN_CLASS.strategy}>
|
<TableCell className={FORWARD_GROUPED_TABLE_COLUMN_CLASS.strategy}>
|
||||||
<Chip
|
<Chip
|
||||||
@@ -5986,7 +5980,7 @@ export default function ForwardPage() {
|
|||||||
onOpenChange={setIsSearchModalOpen}
|
onOpenChange={setIsSearchModalOpen}
|
||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<ModalHeader className="flex flex-col gap-1">
|
<ModalHeader className="flex flex-col gap-1">
|
||||||
搜索筛选用户规则
|
搜索筛选用户规则
|
||||||
|
|||||||
@@ -374,40 +374,6 @@ export default function NodePage() {
|
|||||||
const [upgradeProgress, setUpgradeProgress] = useState<
|
const [upgradeProgress, setUpgradeProgress] = useState<
|
||||||
Record<number, { stage: string; percent: number; message: string }>
|
Record<number, { stage: string; percent: number; message: string }>
|
||||||
>({});
|
>({});
|
||||||
const [infoPopoverPlacement, setInfoPopoverPlacement] = useState<
|
|
||||||
Record<number, "left" | "bottom">
|
|
||||||
>({});
|
|
||||||
|
|
||||||
const updateInfoPopoverPlacement = useCallback(
|
|
||||||
(nodeId: number, triggerElement: HTMLElement | null) => {
|
|
||||||
if (!triggerElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = triggerElement.getBoundingClientRect();
|
|
||||||
const cardElement = triggerElement.closest("[data-node-card='true']");
|
|
||||||
const cardRect =
|
|
||||||
cardElement instanceof HTMLElement
|
|
||||||
? cardElement.getBoundingClientRect()
|
|
||||||
: null;
|
|
||||||
const estimatedPanelWidth = 288;
|
|
||||||
const containerPadding = 16;
|
|
||||||
const availableLeftSpace = cardRect
|
|
||||||
? rect.left - cardRect.left
|
|
||||||
: rect.left;
|
|
||||||
const nextPlacement: "left" | "bottom" =
|
|
||||||
availableLeftSpace >= estimatedPanelWidth + containerPadding
|
|
||||||
? "left"
|
|
||||||
: "bottom";
|
|
||||||
|
|
||||||
setInfoPopoverPlacement((prev) =>
|
|
||||||
prev[nodeId] === nextPlacement
|
|
||||||
? prev
|
|
||||||
: { ...prev, [nodeId]: nextPlacement },
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNodeOffline = useCallback((nodeId: number) => {
|
const handleNodeOffline = useCallback((nodeId: number) => {
|
||||||
setNodeList((prev) =>
|
setNodeList((prev) =>
|
||||||
@@ -1744,8 +1710,6 @@ export default function NodePage() {
|
|||||||
!node.expiryReminderDismissed,
|
!node.expiryReminderDismissed,
|
||||||
);
|
);
|
||||||
const hasInfoTrigger = hasRemark || hasExpiryInfo;
|
const hasInfoTrigger = hasRemark || hasExpiryInfo;
|
||||||
const infoCount = Number(hasExpiryInfo) + Number(hasRemark);
|
|
||||||
const infoPlacement = infoPopoverPlacement[node.id] ?? "left";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableItem key={node.id} id={node.id}>
|
<SortableItem key={node.id} id={node.id}>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface CheckboxProps
|
|||||||
classNames?: Record<string, string>;
|
classNames?: Record<string, string>;
|
||||||
color?: string;
|
color?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
isIndeterminate?: boolean;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
onValueChange?: (value: boolean) => void;
|
onValueChange?: (value: boolean) => void;
|
||||||
size?: string;
|
size?: string;
|
||||||
@@ -20,6 +21,7 @@ export function Checkbox({
|
|||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
|
isIndeterminate,
|
||||||
isSelected,
|
isSelected,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
...props
|
...props
|
||||||
@@ -32,6 +34,10 @@ export function Checkbox({
|
|||||||
onValueChange?.(value === true);
|
onValueChange?.(value === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkedValue: boolean | "indeterminate" = isIndeterminate
|
||||||
|
? "indeterminate"
|
||||||
|
: Boolean(isSelected);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -41,7 +47,7 @@ export function Checkbox({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<BaseCheckbox
|
<BaseCheckbox
|
||||||
checked={Boolean(isSelected)}
|
checked={checkedValue}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
onCheckedChange={handleCheckedChange}
|
onCheckedChange={handleCheckedChange}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
Reference in New Issue
Block a user