From 136c1c4d501b1cfc76a100c9ac8e280a41e76f53 Mon Sep 17 00:00:00 2001 From: Hamed Date: Sat, 24 Oct 2026 08:57:47 +0330 Subject: [PATCH] feat(hosts): add drag-and-drop and global subscription ordering (#6895) Hosts already had one saved order on the Hosts page, but subscriptions honoured it only inside each inbound, so a host of a later inbound could never come before one of an earlier inbound (#6894). A new opt-in setting makes raw/base64, Clash/Mihomo and JSON subscriptions follow the Hosts page order across inbounds; the default keeps today's inbound-first output unchanged. The Hosts table gains drag-and-drop reordering next to the Up/Down buttons, on a pointer-reorder hook the Routing Rules table now shares. --- .github/workflows/ci.yml | 10 + docs/content/docs/en/config/subscription.mdx | 38 ++- docs/content/docs/en/reference/api/hosts.mdx | 8 +- docs/public/images/hosts-order-after.png | Bin 0 -> 33929 bytes docs/public/images/hosts-order-before.png | Bin 0 -> 32926 bytes docs/public/openapi.json | 12 +- frontend/public/openapi.json | 12 +- frontend/src/api/queries/useHostMutations.ts | 2 +- frontend/src/generated/examples.ts | 2 + frontend/src/generated/schemas.ts | 10 + frontend/src/generated/types.ts | 2 + frontend/src/generated/zod.ts | 2 + frontend/src/hooks/usePointerRowReorder.ts | 109 ++++++++ frontend/src/models/setting.ts | 3 +- frontend/src/pages/api-docs/endpoints.ts | 3 +- frontend/src/pages/hosts/HostList.css | 18 ++ frontend/src/pages/hosts/HostList.tsx | 57 +++- frontend/src/pages/hosts/HostsPage.tsx | 39 ++- .../pages/settings/SubscriptionGeneralTab.tsx | 18 +- .../src/pages/xray/routing/RoutingTab.tsx | 142 ++++------ frontend/src/schemas/setting.ts | 4 + frontend/src/test/hosts-reorder.test.tsx | 183 +++++++++++++ frontend/src/test/pointer-events.ts | 23 ++ frontend/src/test/routing-reorder.test.tsx | 101 +++++++ .../test/subscription-general-tab.test.tsx | 21 ++ frontend/src/test/use-all-settings.test.tsx | 24 ++ .../src/test/use-pointer-row-reorder.test.tsx | 238 ++++++++++++++++ internal/sub/clash_service.go | 21 +- internal/sub/host_order.go | 116 ++++++++ internal/sub/host_order_test.go | 257 ++++++++++++++++++ internal/sub/host_sub.go | 6 +- internal/sub/json_service.go | 45 ++- internal/sub/service.go | 37 ++- internal/web/entity/entity.go | 1 + internal/web/service/host.go | 33 ++- internal/web/service/host_test.go | 38 +++ internal/web/service/setting.go | 12 + .../web/service/setting_sub_info_node_test.go | 28 ++ internal/web/translation/ar-EG.json | 9 +- internal/web/translation/en-US.json | 9 +- internal/web/translation/es-ES.json | 9 +- internal/web/translation/fa-IR.json | 9 +- internal/web/translation/id-ID.json | 9 +- internal/web/translation/ja-JP.json | 9 +- internal/web/translation/pt-BR.json | 9 +- internal/web/translation/ru-RU.json | 9 +- internal/web/translation/tr-TR.json | 9 +- internal/web/translation/uk-UA.json | 9 +- internal/web/translation/vi-VN.json | 9 +- internal/web/translation/zh-CN.json | 9 +- internal/web/translation/zh-TW.json | 9 +- 51 files changed, 1643 insertions(+), 149 deletions(-) create mode 100644 docs/public/images/hosts-order-after.png create mode 100644 docs/public/images/hosts-order-before.png create mode 100644 frontend/src/hooks/usePointerRowReorder.ts create mode 100644 frontend/src/test/hosts-reorder.test.tsx create mode 100644 frontend/src/test/pointer-events.ts create mode 100644 frontend/src/test/routing-reorder.test.tsx create mode 100644 frontend/src/test/use-pointer-row-reorder.test.tsx create mode 100644 internal/sub/host_order.go create mode 100644 internal/sub/host_order_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c7148544..e4d41a204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,6 +92,16 @@ jobs: exit 1 fi + - name: PostgreSQL subscription Host ordering tests + run: | + set -o pipefail + go test ./internal/sub -run '^TestSubHostOrder_Postgres$' -count=1 -v | tee /tmp/postgres-host-order.log + passed=$(grep -c -- '^--- PASS' /tmp/postgres-host-order.log || true) + if [ "$passed" -ne 1 ]; then + echo "expected passing PostgreSQL Host ordering coverage, got $passed" >&2 + exit 1 + fi + - name: PostgreSQL Telegram lookup and legacy migration tests run: | set -o pipefail diff --git a/docs/content/docs/en/config/subscription.mdx b/docs/content/docs/en/config/subscription.mdx index fd7cfda30..f22d74653 100644 --- a/docs/content/docs/en/config/subscription.mdx +++ b/docs/content/docs/en/config/subscription.mdx @@ -51,7 +51,7 @@ The **format is chosen by path**, each with its own enable toggle: | **Clash for Windows (legacy)** | `/clash-legacy/` | `subClashEnable` | YAML limited to proxy types, transports, and ciphers supported by the legacy Clash core. | Only enabled inbounds using **VLESS, VMess, Trojan, Shadowsocks, WireGuard, AmneziaWG, MTProto, TUIC, or Hysteria2** -appear in a subscription, ordered by their sub-sort index (TUIC and AmneziaWG are included in raw links and Clash/Mihomo profiles, but omitted from JSON endpoints; MTProto is included in raw links). Requesting `subPath` +appear in a subscription, ordered by their sub-sort index by default (TUIC and AmneziaWG are included in raw links and Clash/Mihomo profiles, but omitted from JSON endpoints; MTProto is included in raw links). Requesting `subPath` with an `Accept: text/html` header (or `?html=1`) returns a human-readable info page instead of the raw body. @@ -74,6 +74,42 @@ default matcher so existing subscription URLs continue returning YAML. It does not distinguish legacy clients from Mihomo-based clients; Clash for Windows users must use `/clash-legacy/` for a compatible profile. +### Subscription node ordering + +In **Settings → Subscription → Panel settings**, select **Subscription Node Ordering** +(`subHostSortMode`). **Inbound order (default)** preserves the existing subscription +sort index, then inbound ID, with each inbound's Hosts ordered inside that block. + +**Hosts order** (`hosts-first`) uses the global order on **Hosts**, including Host +groups attached to different inbounds. Drag a group's grip above or below another +row to save its position, or use the existing Up/Down buttons. A failed save restores +the previous displayed order. The next subscription fetch uses the saved order; +no inbound edit or Xray restart is needed. + +For example, these rows start with Host A (Inbound #5) above Host B (Inbound #20): + +![Hosts before reordering, with Host A above Host B](/images/hosts-order-before.png) + +Dragging Host B above Host A saves the order across both inbounds: + +![Hosts after reordering, with Host B above Host A](/images/hosts-order-after.png) + +These screenshots use example Hosts and synthetic API responses. + +Host groups sort by their persisted sort order. Ties use the group remark and group +ID, matching the Hosts table. Within a group, inbound subscription sort index, +inbound ID and Host row ID provide deterministic ordering for attachments and +addresses. Inbounds without applicable Hosts and legacy externalProxy entries +follow all Host entries in their previous relative order. External client links +remain appended, and the subscription info node remains first. + +This applies to Raw/Base64, Clash/Mihomo and JSON subscriptions. JSON balancer +documents keep their slots from the existing numeric balancer/inbound sort; only +the endpoint slots are rearranged, preserving the relative order of Host entries. +Eligibility, exclusions, credentials and protocol settings are unchanged. +Standalone share-link and QR previews retain their existing behavior. Client apps +may apply their own sorting after importing a subscription. + ### Base64 vs JSON The **Base64** body is just the newline-joined share links, standard-base64 diff --git a/docs/content/docs/en/reference/api/hosts.mdx b/docs/content/docs/en/reference/api/hosts.mdx index 0ff41553f..4f14d7b5e 100644 --- a/docs/content/docs/en/reference/api/hosts.mdx +++ b/docs/content/docs/en/reference/api/hosts.mdx @@ -35,7 +35,9 @@ _openapi: url: '#enable-or-disable-a-host-group' - depth: 2 title: Set host group sort order by the position of each groupId in the array. - url: '#set-host-group-sort-order-by-the-position-of-each-groupid-in-the-array' + IDs must be unique and refer to existing groups; omitted groups follow + in their previous order. + url: '#set-host-group-sort-order-by-the-position-of-each-groupid-in-the-array-ids-must-be-unique-and-refer-to-existing-groups-omitted-groups-follow-in-their-previous-order' - depth: 2 title: Add a host group to inbounds (same as /add). url: '#add-a-host-group-to-inbounds-same-as-add' @@ -65,7 +67,9 @@ _openapi: - content: Enable or disable a host group. id: enable-or-disable-a-host-group - content: Set host group sort order by the position of each groupId in the array. - id: set-host-group-sort-order-by-the-position-of-each-groupid-in-the-array + IDs must be unique and refer to existing groups; omitted groups follow + in their previous order. + id: set-host-group-sort-order-by-the-position-of-each-groupid-in-the-array-ids-must-be-unique-and-refer-to-existing-groups-omitted-groups-follow-in-their-previous-order - content: Add a host group to inbounds (same as /add). id: add-a-host-group-to-inbounds-same-as-add - content: Enable or disable many host groups in one call. diff --git a/docs/public/images/hosts-order-after.png b/docs/public/images/hosts-order-after.png new file mode 100644 index 0000000000000000000000000000000000000000..da213347c5009c3830344a2a77136e8351f579ee GIT binary patch literal 33929 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 literal 0 HcmV?d00001 diff --git a/docs/public/images/hosts-order-before.png b/docs/public/images/hosts-order-before.png new file mode 100644 index 0000000000000000000000000000000000000000..8cc5d0ce7f0ccf74bf49486fc10a38d574759a2d GIT binary patch literal 32926 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 literal 0 HcmV?d00001 diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 0ea084f34..2d2022b87 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -350,6 +350,10 @@ "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -705,6 +709,7 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", @@ -1136,6 +1141,10 @@ "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -1499,6 +1508,7 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", @@ -12619,7 +12629,7 @@ "tags": [ "Hosts" ], - "summary": "Set host group sort order by the position of each groupId in the array.", + "summary": "Set host group sort order by the position of each groupId in the array. IDs must be unique and refer to existing groups; omitted groups follow in their previous order.", "operationId": "post_panel_api_hosts_reorder", "requestBody": { "required": true, diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 0ea084f34..2d2022b87 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -350,6 +350,10 @@ "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -705,6 +709,7 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", @@ -1136,6 +1141,10 @@ "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -1499,6 +1508,7 @@ "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", @@ -12619,7 +12629,7 @@ "tags": [ "Hosts" ], - "summary": "Set host group sort order by the position of each groupId in the array.", + "summary": "Set host group sort order by the position of each groupId in the array. IDs must be unique and refer to existing groups; omitted groups follow in their previous order.", "operationId": "post_panel_api_hosts_reorder", "requestBody": { "required": true, diff --git a/frontend/src/api/queries/useHostMutations.ts b/frontend/src/api/queries/useHostMutations.ts index 9a842d031..dfac21944 100644 --- a/frontend/src/api/queries/useHostMutations.ts +++ b/frontend/src/api/queries/useHostMutations.ts @@ -45,7 +45,7 @@ export function useHostMutations() { mutationFn: (groupIds: string[]) => HttpUtil.post('/panel/api/hosts/reorder', { ids: groupIds }, JSON_HEADERS), onSuccess: (msg) => { - if (msg?.success) invalidate(); + if (msg?.success) return invalidate(); }, }); diff --git a/frontend/src/generated/examples.ts b/frontend/src/generated/examples.ts index 85faa648b..ea20e92d8 100644 --- a/frontend/src/generated/examples.ts +++ b/frontend/src/generated/examples.ts @@ -100,6 +100,7 @@ export const EXAMPLES: Record = { "subHappTunMode": "", "subHappTunType": "", "subHideSettings": false, + "subHostSortMode": "inbound-first", "subIncyAnnounceUrl": "", "subIncyAppAutoDetect": false, "subIncyBannerBgColor": "", @@ -289,6 +290,7 @@ export const EXAMPLES: Record = { "subHappTunMode": "", "subHappTunType": "", "subHideSettings": false, + "subHostSortMode": "inbound-first", "subIncyAnnounceUrl": "", "subIncyAppAutoDetect": false, "subIncyBannerBgColor": "", diff --git a/frontend/src/generated/schemas.ts b/frontend/src/generated/schemas.ts index 0d684072c..ab2119314 100644 --- a/frontend/src/generated/schemas.ts +++ b/frontend/src/generated/schemas.ts @@ -324,6 +324,10 @@ export const SCHEMAS: Record = { "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -679,6 +683,7 @@ export const SCHEMAS: Record = { "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", @@ -1110,6 +1115,10 @@ export const SCHEMAS: Record = { "subHideSettings": { "type": "boolean" }, + "subHostSortMode": { + "example": "inbound-first", + "type": "string" + }, "subIncyAnnounceUrl": { "type": "string" }, @@ -1473,6 +1482,7 @@ export const SCHEMAS: Record = { "subHappTunMode", "subHappTunType", "subHideSettings", + "subHostSortMode", "subIncyAnnounceUrl", "subIncyAppAutoDetect", "subIncyBannerBgColor", diff --git a/frontend/src/generated/types.ts b/frontend/src/generated/types.ts index e23ad9c37..845096c9d 100644 --- a/frontend/src/generated/types.ts +++ b/frontend/src/generated/types.ts @@ -108,6 +108,7 @@ export interface AllSetting { subHappTunMode: string; subHappTunType: string; subHideSettings: boolean; + subHostSortMode: string; subIncyAnnounceUrl: string; subIncyAppAutoDetect: boolean; subIncyBannerBgColor: string; @@ -298,6 +299,7 @@ export interface AllSettingView { subHappTunMode: string; subHappTunType: string; subHideSettings: boolean; + subHostSortMode: string; subIncyAnnounceUrl: string; subIncyAppAutoDetect: boolean; subIncyBannerBgColor: string; diff --git a/frontend/src/generated/zod.ts b/frontend/src/generated/zod.ts index a9c4296ec..aeb8a3c53 100644 --- a/frontend/src/generated/zod.ts +++ b/frontend/src/generated/zod.ts @@ -124,6 +124,7 @@ export const AllSettingSchema = z.object({ subHappTunMode: z.string(), subHappTunType: z.string(), subHideSettings: z.boolean(), + subHostSortMode: z.string(), subIncyAnnounceUrl: z.string(), subIncyAppAutoDetect: z.boolean(), subIncyBannerBgColor: z.string(), @@ -315,6 +316,7 @@ export const AllSettingViewSchema = z.object({ subHappTunMode: z.string(), subHappTunType: z.string(), subHideSettings: z.boolean(), + subHostSortMode: z.string(), subIncyAnnounceUrl: z.string(), subIncyAppAutoDetect: z.boolean(), subIncyBannerBgColor: z.string(), diff --git a/frontend/src/hooks/usePointerRowReorder.ts b/frontend/src/hooks/usePointerRowReorder.ts new file mode 100644 index 000000000..03206ae52 --- /dev/null +++ b/frontend/src/hooks/usePointerRowReorder.ts @@ -0,0 +1,109 @@ +import { useCallback, useLayoutEffect, useRef, useState } from 'react'; +import type { PointerEvent as ReactPointerEvent, RefObject } from 'react'; + +export interface PointerRowDrop { + from: TKey; + to: TKey; + after: boolean; +} + +interface PointerRowDrag { + from: TKey; + to: TKey | null; + after: boolean; +} + +interface PointerRowReorderOptions { + containerRef: RefObject; + getRowKey: (row: HTMLElement) => TKey | null; + disabled?: boolean; + isNoop?: (drop: PointerRowDrop) => boolean; + onDrop: (drop: PointerRowDrop) => void; +} + +// Rows expose data-row-key; consumers retain their own insertion and persistence rules. +export function usePointerRowReorder( + options: PointerRowReorderOptions, +) { + const [drag, setDrag] = useState | null>(null); + const latest = useRef(options); + const cleanupRef = useRef<(() => void) | null>(null); + + useLayoutEffect(() => { + latest.current = options; + if (cleanupRef.current && options.disabled) { + cleanupRef.current(); + setDrag(null); + } + }, [options]); + useLayoutEffect(() => () => cleanupRef.current?.(), []); + + const onPointerDown = useCallback((from: TKey, event: ReactPointerEvent) => { + const current = latest.current; + if (current.disabled || cleanupRef.current || event.isPrimary === false || event.button !== 0) + return; + event.preventDefault(); + const handle = event.currentTarget; + const doc = handle.ownerDocument; + const pointerId = event.pointerId; + const startY = event.clientY; + let active = false; + + const targetAt = (e: PointerEvent): PointerRowDrop | null => { + const row = doc + .elementFromPoint(e.clientX, e.clientY) + ?.closest('[data-row-key]'); + const config = latest.current; + if (!row || !config.containerRef.current?.contains(row)) return null; + const to = config.getRowKey(row); + if (to === null) return null; + const rect = row.getBoundingClientRect(); + return { from, to, after: e.clientY > rect.top + rect.height / 2 }; + }; + const move = (e: PointerEvent) => { + if (e.pointerId !== pointerId || (!active && Math.abs(e.clientY - startY) < 5)) return; + active = true; + setDrag(targetAt(e) ?? { from, to: null, after: false }); + }; + const cleanup = () => { + doc.removeEventListener('pointermove', move); + doc.removeEventListener('pointerup', up); + doc.removeEventListener('pointercancel', cancel); + handle.removeEventListener('lostpointercapture', cancel); + cleanupRef.current = null; + try { + if (handle.hasPointerCapture?.(pointerId)) handle.releasePointerCapture(pointerId); + } catch { + // Capture may already be released when the handle is detached. + } + }; + const cancel = (e: Event) => { + if (!('pointerId' in e) || e.pointerId !== pointerId) return; + cleanup(); + setDrag(null); + }; + const up = (e: PointerEvent) => { + if (e.pointerId !== pointerId) return; + const drop = + active && latest.current.containerRef.current?.contains(handle) ? targetAt(e) : null; + cleanup(); + setDrag(null); + const config = latest.current; + if (!drop || config.disabled || drop.from === drop.to || config.isNoop?.(drop)) return; + config.onDrop(drop); + }; + + cleanupRef.current = cleanup; + doc.addEventListener('pointermove', move); + doc.addEventListener('pointerup', up); + doc.addEventListener('pointercancel', cancel); + handle.addEventListener('lostpointercapture', cancel); + try { + handle.setPointerCapture?.(pointerId); + } catch { + // Detached handles and synthetic pointers may not support capture. + } + }, []); + + return { drag, onPointerDown }; +} diff --git a/frontend/src/models/setting.ts b/frontend/src/models/setting.ts index f69f8a624..1dcf9abaf 100644 --- a/frontend/src/models/setting.ts +++ b/frontend/src/models/setting.ts @@ -1,5 +1,5 @@ import { ObjectUtil } from '@/utils'; -import type { SubProfileMode } from '@/schemas/setting'; +import type { SubProfileMode, SubHostSortMode } from '@/schemas/setting'; export class AllSetting { webListen = ''; @@ -20,6 +20,7 @@ export class AllSetting { remarkTemplate = '{{INBOUND}}-{{EMAIL}}|📊{{TRAFFIC_LEFT}}|⏳{{DAYS_LEFT}}D'; subShowIdentityOnAllLinks = false; subInfoNodeEnable = false; + subHostSortMode: SubHostSortMode = 'inbound-first'; subCalendarExpireInclusive = false; subExpiredTemplate = '⛔ {{EMAIL}} | Expired: {{EXPIRE_DATE}}'; subTrafficDepletedTemplate = diff --git a/frontend/src/pages/api-docs/endpoints.ts b/frontend/src/pages/api-docs/endpoints.ts index b69a99f36..9765ed52b 100644 --- a/frontend/src/pages/api-docs/endpoints.ts +++ b/frontend/src/pages/api-docs/endpoints.ts @@ -1893,7 +1893,8 @@ export const sections: readonly Section[] = [ { method: 'POST', path: '/panel/api/hosts/reorder', - summary: 'Set host group sort order by the position of each groupId in the array.', + summary: + 'Set host group sort order by the position of each groupId in the array. IDs must be unique and refer to existing groups; omitted groups follow in their previous order.', body: '{\n "ids": ["abc-123", "def-456"]\n}', }, { diff --git a/frontend/src/pages/hosts/HostList.css b/frontend/src/pages/hosts/HostList.css index aa52a50f0..50ebd5182 100644 --- a/frontend/src/pages/hosts/HostList.css +++ b/frontend/src/pages/hosts/HostList.css @@ -2,6 +2,24 @@ width: 100%; } +.host-drag-handle { + cursor: grab; + touch-action: none; +} +.host-drag-handle:focus-visible { + outline: 2px solid var(--ant-color-primary); + outline-offset: 2px; +} +.host-dragging { + opacity: 0.5; +} +.host-drop-before > td { + border-top: 2px solid var(--ant-color-primary) !important; +} +.host-drop-after > td { + border-bottom: 2px solid var(--ant-color-primary) !important; +} + .host-remark-cell { display: flex; flex-direction: column; diff --git a/frontend/src/pages/hosts/HostList.tsx b/frontend/src/pages/hosts/HostList.tsx index b10576b10..fff010bd0 100644 --- a/frontend/src/pages/hosts/HostList.tsx +++ b/frontend/src/pages/hosts/HostList.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Card, Popover, Space, Switch, Table, Tag, Tooltip } from 'antd'; import type { ColumnsType } from 'antd/es/table'; @@ -8,11 +8,13 @@ import { DeleteOutlined, EditOutlined, GlobalOutlined, + HolderOutlined, PlusOutlined, } from '@ant-design/icons'; import type { HostRecord } from '@/api/queries/useHostsQuery'; import type { InboundOption } from '@/schemas/client'; +import { usePointerRowReorder, type PointerRowDrop } from '@/hooks/usePointerRowReorder'; import './HostList.css'; interface HostListProps { @@ -27,6 +29,8 @@ interface HostListProps { onDelete: (host: HostRecord) => void; onToggleEnable: (host: HostRecord, next: boolean) => void; onMove: (host: HostRecord, dir: 'up' | 'down') => void; + onReorder: (groupIds: string[]) => void; + reordering?: boolean; onBulkEnable: (enable: boolean) => void; onBulkDelete: () => void; } @@ -52,10 +56,19 @@ export function sortHosts(hosts: HostRecord[]): HostRecord[] { const sa = a.sortOrder ?? 0; const sb = b.sortOrder ?? 0; if (sa !== sb) return sa - sb; - return (a.remark || '').localeCompare(b.remark || ''); + const ar = a.remark || ''; + const br = b.remark || ''; + if (ar !== br) return ar < br ? -1 : 1; + return a.groupId < b.groupId ? -1 : a.groupId > b.groupId ? 1 : 0; }); } +function hostDropDestination(keys: string[], { from, to, after }: PointerRowDrop) { + const source = keys.indexOf(from); + const slot = keys.indexOf(to) + (after ? 1 : 0); + return slot - (source < slot ? 1 : 0); +} + export default function HostList(props: HostListProps) { const { t } = useTranslation(); const { @@ -70,6 +83,8 @@ export default function HostList(props: HostListProps) { onDelete, onToggleEnable, onMove, + onReorder, + reordering, onBulkEnable, onBulkDelete, } = props; @@ -81,23 +96,47 @@ export default function HostList(props: HostListProps) { }, [inboundOptions]); const sorted = useMemo(() => sortHosts(hosts), [hosts]); + const tableRef = useRef(null); + const keys = sorted.map((h) => h.groupId); + const { drag, onPointerDown: onHandlePointerDown } = usePointerRowReorder({ + containerRef: tableRef, + getRowKey: (row) => keys.find((key) => key === row.dataset.rowKey) ?? null, + disabled: reordering || loading, + isNoop: (drop) => keys.indexOf(drop.from) === hostDropDestination(keys, drop), + onDrop: (drop) => { + const ids = [...keys]; + const destination = hostDropDestination(keys, drop); + ids.splice(ids.indexOf(drop.from), 1); + ids.splice(destination, 0, drop.from); + onReorder(ids); + }, + }); const columns: ColumnsType = [ { title: t('pages.hosts.fields.actions'), key: 'actions', - width: 168, + width: 196, render: (_, h, idx) => { const count = sorted.length; return ( +