refactor: rename channel priority update to channel field update

This commit is contained in:
CaIon
2026-07-25 16:59:05 +08:00
parent a0d0e5049e
commit 18b0b7631a
5 changed files with 92 additions and 82 deletions
@@ -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 <TagPriorityCell channel={channel} />
}
return <ChannelPriorityCell channel={channel} />
return (
<ChannelFieldCell
channelId={channel.id}
value={channel.priority}
field='priority'
min={-999}
/>
)
}
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 (
<NumericSpinnerInput
value={channel.priority ?? 0}
onChange={priorityUpdateScheduler.schedule}
onCommit={priorityUpdateScheduler.flush}
min={-999}
value={value ?? 0}
onChange={fieldUpdateScheduler.schedule}
onCommit={fieldUpdateScheduler.flush}
min={min}
/>
)
}
@@ -253,57 +262,56 @@ function ChannelPriorityCell({ channel }: { channel: Channel }) {
* Weight cell component with inline editing
*/
function WeightCell({ channel }: { channel: Channel }) {
if (isTagAggregateRow(channel)) {
return <TagWeightCell channel={channel} />
}
return (
<ChannelFieldCell
channelId={channel.id}
value={channel.weight}
field='weight'
min={0}
/>
)
}
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<number | null>(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 (
<>
<NumericSpinnerInput
value={weight ?? 0}
onChange={(value) => {
setPendingValue(value)
setConfirmOpen(true)
}}
min={0}
/>
<ConfirmDialog
open={confirmOpen}
onOpenChange={setConfirmOpen}
title={t('Confirm Batch Update')}
desc={t(
'This will update the weight to {{value}} for all {{count}} channel(s) with tag "{{tag}}". Continue?',
{ value: pendingValue, count: channelCount, tag }
)}
confirmText={t('Update')}
handleConfirm={() => {
if (pendingValue !== null) {
handleUpdateTagField(tag, 'weight', pendingValue, queryClient)
}
setConfirmOpen(false)
}}
/>
</>
)
}
// Regular channel row - editable
return (
<NumericSpinnerInput
value={weight ?? 0}
onChange={(value) => {
handleUpdateChannelField(channel.id, 'weight', value, queryClient)
}}
min={0}
/>
<>
<NumericSpinnerInput
value={weight ?? 0}
onChange={(value) => {
setPendingValue(value)
setConfirmOpen(true)
}}
min={0}
/>
<ConfirmDialog
open={confirmOpen}
onOpenChange={setConfirmOpen}
title={t('Confirm Batch Update')}
desc={t(
'This will update the weight to {{value}} for all {{count}} channel(s) with tag "{{tag}}". Continue?',
{ value: pendingValue, count: channelCount, tag }
)}
confirmText={t('Update')}
handleConfirm={() => {
if (pendingValue !== null) {
handleUpdateTagField(tag, 'weight', pendingValue, queryClient)
}
setConfirmOpen(false)
}}
/>
</>
)
}
@@ -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))
@@ -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<number, () => 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
)
@@ -17,21 +17,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
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,
+1 -1
View File
@@ -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'