feat(api-access): improve provider drag-and-drop sorting animations

This commit is contained in:
lzt404 committed 2026-08-14 18:03:01 +08:00
1 parent 813fbf97a8
commit ee41ac4922
5 files changed
+56 -15

No files matched your search

-1
View File
@@ -652,7 +652,6 @@ export const jaOverrides = {
'apiAccess.notice.deleted': '接続を削除しました',
'apiAccess.notice.enabled': '接続を有効にしました',
'apiAccess.notice.disabled': '接続を無効にしました',
'apiAccess.notice.reordered': 'プロバイダーの順序を更新しました',
'apiAccess.title': 'API 接続',
'apiAccess.count': '接続 {count} 件',
'apiAccess.add': '追加',
-1
View File
@@ -652,7 +652,6 @@ export const en: Record<MessageKey, string> = {
'apiAccess.notice.deleted': 'Connection deleted',
'apiAccess.notice.enabled': 'Connection enabled',
'apiAccess.notice.disabled': 'Connection disabled',
'apiAccess.notice.reordered': 'Provider order updated',
'apiAccess.title': 'API Access',
'apiAccess.count': '{count} connections',
'apiAccess.add': 'Add',
-1
View File
@@ -650,7 +650,6 @@ export const zhCN = {
'apiAccess.notice.deleted': '接入已删除',
'apiAccess.notice.enabled': '接入已启用',
'apiAccess.notice.disabled': '接入已停用',
'apiAccess.notice.reordered': '供应商排序已更新',
'apiAccess.title': 'API 接入',
'apiAccess.count': '{count} 个接入',
'apiAccess.add': '新增',
+44 -8
View File
@@ -105,8 +105,17 @@ type ProviderRow = {
remark: string;
};
const providerDragId = (row: Pick<ProviderRow, 'section' | 'index'>) =>
`${row.section}:${row.index}`;
const providerDragId = (
row: Pick<ProviderRow, 'section' | 'name' | 'apiKey' | 'baseUrl'>,
) => {
const identity = `${row.section}\u0000${row.name}\u0000${row.apiKey}\u0000${row.baseUrl}`;
let hash = 2166136261;
for (let index = 0; index < identity.length; index += 1) {
hash ^= identity.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return `${row.section}:${(hash >>> 0).toString(36)}`;
};
function SortableProviderRow({
row,
@@ -128,10 +137,26 @@ function SortableProviderRow({
transform,
transition,
isDragging,
} = useSortable({ id: providerDragId(row), disabled });
} = useSortable({
id: providerDragId(row),
disabled,
transition: {
duration: 220,
easing: 'cubic-bezier(0.2, 0, 0, 1)',
},
});
const renderedTransform = transform
? {
...transform,
scaleX: isDragging ? 1.012 : transform.scaleX,
scaleY: isDragging ? 1.012 : transform.scaleY,
}
: transform;
const style: CSSProperties = {
transform: DndCss.Transform.toString(transform),
transition,
position: 'relative',
zIndex: isDragging ? 2 : undefined,
transform: DndCss.Transform.toString(renderedTransform),
transition: isDragging ? undefined : transition,
};
return (
@@ -1100,9 +1125,9 @@ export function ApiAccessPage() {
const nextRows = reorderProviderRecords(latestRows, rows, source, target);
if (!nextRows) throw new Error(t('apiAccess.error.stale'));
await managementApi.put(`/${source.section}`, nextRows);
setNotice(t('apiAccess.notice.reordered'));
await loadProviders();
} catch (requestError) {
await loadProviders();
setError(requestErrorMessage(requestError));
} finally {
setDragOverId(null);
@@ -1114,7 +1139,18 @@ export function ApiAccessPage() {
setDragOverId(null);
const source = rows.find((row) => providerDragId(row) === String(event.active.id));
const target = rows.find((row) => providerDragId(row) === String(event.over?.id ?? ''));
if (source && target) void reorderProviders(source, target);
if (!source || !target || source.index === target.index) return;
const optimisticRows = reorderProviderRecords(
records[source.section],
rows,
source,
target,
);
if (optimisticRows) {
setRecords((current) => ({ ...current, [source.section]: optimisticRows }));
}
void reorderProviders(source, target);
};
const totalCount = Object.values(records).reduce((sum, items) => sum + items.length, 0);
@@ -1200,7 +1236,7 @@ export function ApiAccessPage() {
<div className="real-provider-list">
{rows.map((row) => (
<SortableProviderRow
key={`${row.section}-${row.index}-${row.authIndex}`}
key={providerDragId(row)}
row={row}
disabled={busy || rows.length < 2}
dragLabel={t('apiAccess.dragHandle', { remark: row.remark || row.name })}
+12 -4
View File
@@ -3690,16 +3690,18 @@ textarea:focus-visible {
min-height: 88px;
padding: 14px 0;
border-bottom: 1px solid var(--theme-eeece6);
transition: background 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
transition: background 180ms ease, box-shadow 180ms ease, opacity 160ms ease;
}
.real-provider-row.dragging {
opacity: 0.45;
border-radius: 10px;
background: var(--theme-faf9f5);
box-shadow: 0 10px 28px rgb(45 42 38 / 14%);
opacity: 0.96;
}
.real-provider-row.drag-over {
background: var(--theme-f4f1ea);
box-shadow: inset 0 2px 0 var(--theme-2d2a26);
background: color-mix(in srgb, var(--theme-f4f1ea) 65%, transparent);
}
.provider-drag-handle {
@@ -3717,6 +3719,12 @@ textarea:focus-visible {
cursor: grabbing;
}
@media (prefers-reduced-motion: reduce) {
.real-provider-row {
transition: none;
}
}
.provider-row-main,
.provider-row-title,
.provider-row-meta,