feat(18.1-01): task 4 flat reorder in the admin SPA
- ReorderView reads flat from the reorder data; ReorderBranch takes a flat prop and shows only move up and move down - ArrowRight and ArrowLeft do nothing, an inside drop lands after the row (tree.ts flatZone, flatMove) and flatten sends parent_id null - vitest flat cases; docs describe the flat page; rebuilt dist
This commit is contained in:
@@ -14,6 +14,8 @@ import {
|
||||
canDrop,
|
||||
canMove,
|
||||
cloneTree,
|
||||
flatMove,
|
||||
flatZone,
|
||||
flatten,
|
||||
locate,
|
||||
buildTree,
|
||||
@@ -36,7 +38,10 @@ import { showToast } from '../state/useToasts'
|
||||
// than the screen. A failed save restores the last saved tree unless a later
|
||||
// save (which carries the whole tree) is waiting; a 409 (the records changed
|
||||
// meanwhile) reloads it. A response that arrives after the page reloaded or
|
||||
// switched partition leaves the screen alone.
|
||||
// switched partition leaves the screen alone. A flat reorder (D-17, the
|
||||
// data's `flat`) is a plain ordered list: no indent, outdent or inside drop,
|
||||
// ArrowRight and ArrowLeft do nothing, and every node is sent with
|
||||
// parent_id null.
|
||||
const SAVE_DEBOUNCE_MS = 400
|
||||
const SAVED_SHOWN_MS = 2000
|
||||
|
||||
@@ -56,6 +61,7 @@ type Status = 'loading' | 'ready' | 'failed' | 'forbidden'
|
||||
const status = ref<Status>('loading')
|
||||
const title = ref('')
|
||||
const partition = ref<AdminReorderPartition | null>(null)
|
||||
const flat = ref(false)
|
||||
const tree = ref<TreeNode[]>([])
|
||||
// The tree as last saved (or loaded): a failed save goes back to it.
|
||||
let savedTree: TreeNode[] = []
|
||||
@@ -99,6 +105,7 @@ async function load(): Promise<void> {
|
||||
}
|
||||
title.value = data.data.title
|
||||
partition.value = (data.data.partition as AdminReorderPartition | null) ?? null
|
||||
flat.value = data.data.flat === true
|
||||
tree.value = buildTree(data.data.nodes ?? [])
|
||||
savedTree = cloneTree(tree.value)
|
||||
status.value = 'ready'
|
||||
@@ -149,7 +156,7 @@ function failed(job: SaveJob): void {
|
||||
|
||||
async function send(job: SaveJob): Promise<void> {
|
||||
inFlight = true
|
||||
const body = { nodes: flatten(job.sent), partition: job.partition } as unknown as AdminReorderRequest
|
||||
const body = { nodes: flatten(job.sent, flat.value), partition: job.partition } as unknown as AdminReorderRequest
|
||||
savedShown.value = false
|
||||
saving.value++
|
||||
try {
|
||||
@@ -239,6 +246,9 @@ async function moved(id: number, kind: 'moved' | 'nested' | 'unnested'): Promise
|
||||
}
|
||||
|
||||
function move(id: number, kind: Move): void {
|
||||
if (!flatMove(kind, flat.value)) {
|
||||
return
|
||||
}
|
||||
const next = cloneTree(tree.value)
|
||||
if (!applyMove(next, id, kind)) {
|
||||
return
|
||||
@@ -251,7 +261,7 @@ const KEY_MOVES: Record<string, Move> = { ArrowUp: 'up', ArrowDown: 'down', Arro
|
||||
|
||||
function onHandleKeydown(event: KeyboardEvent, id: number): void {
|
||||
const kind = KEY_MOVES[event.key]
|
||||
if (kind) {
|
||||
if (kind && flatMove(kind, flat.value)) {
|
||||
event.preventDefault()
|
||||
move(id, kind)
|
||||
}
|
||||
@@ -288,7 +298,7 @@ function onDragOver(event: DragEvent, id: number): void {
|
||||
const box = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
||||
const offset = event.clientY - box.top
|
||||
const zone: Zone = offset < box.height / 3 ? 'before' : offset > (box.height * 2) / 3 ? 'after' : 'inside'
|
||||
dropTarget.value = { id, zone }
|
||||
dropTarget.value = { id, zone: flatZone(zone, flat.value) }
|
||||
}
|
||||
|
||||
function onDragLeave(id: number): void {
|
||||
@@ -324,7 +334,7 @@ provide(REORDER_PAGE, {
|
||||
dropTarget,
|
||||
blocked,
|
||||
nameOf,
|
||||
canMove: (id: number, kind: Move) => canMove(tree.value, id, kind),
|
||||
canMove: (id: number, kind: Move) => flatMove(kind, flat.value) && canMove(tree.value, id, kind),
|
||||
move,
|
||||
onHandleKeydown,
|
||||
onDragStart,
|
||||
@@ -437,7 +447,14 @@ void load()
|
||||
<p class="font-semibold">{{ t('backend::lang.reorder.empty') }}</p>
|
||||
<p class="max-w-[420px] text-[13px] text-muted">{{ t('backend::lang.reorder.empty_hint') }}</p>
|
||||
</div>
|
||||
<ReorderBranch v-else :nodes="tree" :depth="0" :aria-label="t('backend::lang.reorder.tree')" data-reorder-tree />
|
||||
<ReorderBranch
|
||||
v-else
|
||||
:nodes="tree"
|
||||
:depth="0"
|
||||
:flat="flat"
|
||||
:aria-label="t('backend::lang.reorder.tree')"
|
||||
data-reorder-tree
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="sr-only" aria-live="polite" data-reorder-live>{{ announcement }}</p>
|
||||
|
||||
Reference in New Issue
Block a user