feat(api-access): improve provider drag-and-drop sorting animations
This commit is contained in:
1 parent
813fbf97a8
commit
ee41ac4922
5 files changed
+56
-15
No files matched your search
@@ -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': '追加',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '新增',
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user