From 18b0b7631a9942bde4e4f40cb5c5c8347a24c6b0 Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 25 Jul 2026 16:59:05 +0800 Subject: [PATCH] refactor: rename channel priority update to channel field update --- .../channels/components/channels-columns.tsx | 136 +++++++++--------- .../components/numeric-spinner-input.tsx | 6 +- .../channel-field-update.test.ts} | 18 +-- ...rity-update.ts => channel-field-update.ts} | 12 +- web/src/features/channels/lib/index.ts | 2 +- 5 files changed, 92 insertions(+), 82 deletions(-) rename web/src/features/channels/lib/{channel-priority-update.test.ts => __tests__/channel-field-update.test.ts} (85%) rename web/src/features/channels/lib/{channel-priority-update.ts => channel-field-update.ts} (84%) diff --git a/web/src/features/channels/components/channels-columns.tsx b/web/src/features/channels/components/channels-columns.tsx index 8d91485c..36dc8f67 100644 --- a/web/src/features/channels/components/channels-columns.tsx +++ b/web/src/features/channels/components/channels-columns.tsx @@ -71,7 +71,7 @@ import { handleUpdateChannelField, handleUpdateTagField, handleUpdateChannelBalance, - createChannelPriorityUpdateScheduler, + createChannelFieldUpdateScheduler, isTagAggregateRow, type TagRow, } from '../lib' @@ -177,7 +177,14 @@ function PriorityCell({ channel }: { channel: Channel }) { return } - return + return ( + + ) } function TagPriorityCell({ channel }: { channel: TagRow }) { @@ -219,32 +226,34 @@ function TagPriorityCell({ channel }: { channel: TagRow }) { ) } -function ChannelPriorityCell({ channel }: { channel: Channel }) { +function ChannelFieldCell({ + channelId, + value, + field, + min, +}: { + channelId: number + value: number | null | undefined + field: 'priority' | 'weight' + min: number +}) { const queryClient = useQueryClient() - const priorityUpdateScheduler = useMemo( + const fieldUpdateScheduler = useMemo( () => - createChannelPriorityUpdateScheduler((value) => { - void handleUpdateChannelField( - channel.id, - 'priority', - value, - queryClient - ) + createChannelFieldUpdateScheduler((nextValue) => { + void handleUpdateChannelField(channelId, field, nextValue, queryClient) }), - [channel.id, queryClient] + [channelId, field, queryClient] ) - useEffect( - () => () => priorityUpdateScheduler.flush(), - [priorityUpdateScheduler] - ) + useEffect(() => () => fieldUpdateScheduler.flush(), [fieldUpdateScheduler]) return ( ) } @@ -253,57 +262,56 @@ function ChannelPriorityCell({ channel }: { channel: Channel }) { * Weight cell component with inline editing */ function WeightCell({ channel }: { channel: Channel }) { + if (isTagAggregateRow(channel)) { + return + } + + return ( + + ) +} + +function TagWeightCell({ channel }: { channel: TagRow }) { const { t } = useTranslation() const queryClient = useQueryClient() - const isTagRow = isTagAggregateRow(channel) const weight = channel.weight const [confirmOpen, setConfirmOpen] = useState(false) const [pendingValue, setPendingValue] = useState(null) + const tag = channel.tag || '' + const channelCount = channel.children?.length || 0 - // Tag row - editable with confirmation for all tag channels - if (isTagRow) { - const tag = channel.tag || '' - const channelCount = channel.children?.length || 0 - - return ( - <> - { - setPendingValue(value) - setConfirmOpen(true) - }} - min={0} - /> - { - if (pendingValue !== null) { - handleUpdateTagField(tag, 'weight', pendingValue, queryClient) - } - setConfirmOpen(false) - }} - /> - - ) - } - - // Regular channel row - editable return ( - { - handleUpdateChannelField(channel.id, 'weight', value, queryClient) - }} - min={0} - /> + <> + { + setPendingValue(value) + setConfirmOpen(true) + }} + min={0} + /> + { + if (pendingValue !== null) { + handleUpdateTagField(tag, 'weight', pendingValue, queryClient) + } + setConfirmOpen(false) + }} + /> + ) } diff --git a/web/src/features/channels/components/numeric-spinner-input.tsx b/web/src/features/channels/components/numeric-spinner-input.tsx index cd53f45a..6890d3f3 100644 --- a/web/src/features/channels/components/numeric-spinner-input.tsx +++ b/web/src/features/channels/components/numeric-spinner-input.tsx @@ -122,8 +122,10 @@ export function NumericSpinnerInput({ const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault() - commitValue() - onCommit?.() + // Blurring routes Enter through the same focusout path as clicking + // away (input onBlur -> commitValue, container onBlur -> onCommit), + // so commit and onCommit each fire exactly once. + inputRef.current?.blur() } else if (e.key === 'Escape') { setEditing(false) setLocalValue(String(value ?? 0)) diff --git a/web/src/features/channels/lib/channel-priority-update.test.ts b/web/src/features/channels/lib/__tests__/channel-field-update.test.ts similarity index 85% rename from web/src/features/channels/lib/channel-priority-update.test.ts rename to web/src/features/channels/lib/__tests__/channel-field-update.test.ts index f77efd7c..d984f656 100644 --- a/web/src/features/channels/lib/channel-priority-update.test.ts +++ b/web/src/features/channels/lib/__tests__/channel-field-update.test.ts @@ -20,9 +20,9 @@ import assert from 'node:assert/strict' import { describe, test } from 'node:test' import { - CHANNEL_PRIORITY_UPDATE_DELAY_MS, - createChannelPriorityUpdateScheduler, -} from './channel-priority-update' + CHANNEL_FIELD_UPDATE_DELAY_MS, + createChannelFieldUpdateScheduler, +} from '../channel-field-update' function createFakeTimers() { const pending = new Map void>() @@ -31,7 +31,7 @@ function createFakeTimers() { return { timers: { setTimeout: (callback: () => void, delay: number) => { - assert.equal(delay, CHANNEL_PRIORITY_UPDATE_DELAY_MS) + assert.equal(delay, CHANNEL_FIELD_UPDATE_DELAY_MS) const id = nextId++ pending.set(id, callback) return id @@ -51,11 +51,11 @@ function createFakeTimers() { } } -describe('channel priority update scheduler', () => { +describe('channel field update scheduler', () => { test('coalesces rapid schedules into one update with the latest value', () => { const fake = createFakeTimers() const updates: number[] = [] - const scheduler = createChannelPriorityUpdateScheduler( + const scheduler = createChannelFieldUpdateScheduler( (value) => updates.push(value), fake.timers ) @@ -73,7 +73,7 @@ describe('channel priority update scheduler', () => { test('flush commits the pending value immediately and cancels the timer', () => { const fake = createFakeTimers() const updates: number[] = [] - const scheduler = createChannelPriorityUpdateScheduler( + const scheduler = createChannelFieldUpdateScheduler( (value) => updates.push(value), fake.timers ) @@ -90,7 +90,7 @@ describe('channel priority update scheduler', () => { test('flush without a pending value does nothing', () => { const fake = createFakeTimers() const updates: number[] = [] - const scheduler = createChannelPriorityUpdateScheduler( + const scheduler = createChannelFieldUpdateScheduler( (value) => updates.push(value), fake.timers ) @@ -105,7 +105,7 @@ describe('channel priority update scheduler', () => { test('preserves a pending value of 0', () => { const fake = createFakeTimers() const updates: number[] = [] - const scheduler = createChannelPriorityUpdateScheduler( + const scheduler = createChannelFieldUpdateScheduler( (value) => updates.push(value), fake.timers ) diff --git a/web/src/features/channels/lib/channel-priority-update.ts b/web/src/features/channels/lib/channel-field-update.ts similarity index 84% rename from web/src/features/channels/lib/channel-priority-update.ts rename to web/src/features/channels/lib/channel-field-update.ts index 746de26b..c93df937 100644 --- a/web/src/features/channels/lib/channel-priority-update.ts +++ b/web/src/features/channels/lib/channel-field-update.ts @@ -17,21 +17,21 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -export const CHANNEL_PRIORITY_UPDATE_DELAY_MS = 800 +export const CHANNEL_FIELD_UPDATE_DELAY_MS = 800 -interface ChannelPriorityUpdateTimers { +interface ChannelFieldUpdateTimers { setTimeout: (callback: () => void, delay: number) => number clearTimeout: (id: number) => void } -const browserTimers: ChannelPriorityUpdateTimers = { +const browserTimers: ChannelFieldUpdateTimers = { setTimeout: (callback, delay) => window.setTimeout(callback, delay), clearTimeout: (id) => window.clearTimeout(id), } -export function createChannelPriorityUpdateScheduler( +export function createChannelFieldUpdateScheduler( onUpdate: (value: number) => void, - timers: ChannelPriorityUpdateTimers = browserTimers + timers: ChannelFieldUpdateTimers = browserTimers ) { let timeoutId: number | undefined let pendingValue: number | undefined @@ -57,7 +57,7 @@ export function createChannelPriorityUpdateScheduler( pendingValue = value timeoutId = timers.setTimeout( commitPendingValue, - CHANNEL_PRIORITY_UPDATE_DELAY_MS + CHANNEL_FIELD_UPDATE_DELAY_MS ) }, flush: commitPendingValue, diff --git a/web/src/features/channels/lib/index.ts b/web/src/features/channels/lib/index.ts index 71059a81..43eb7773 100644 --- a/web/src/features/channels/lib/index.ts +++ b/web/src/features/channels/lib/index.ts @@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com */ // Re-export all library functions export * from './channel-actions' -export * from './channel-priority-update' +export * from './channel-field-update' export * from './advanced-custom' export * from './channel-form-errors' export * from './channel-form'