oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// fixtures/strip-reorder.mjs — reordering slides from the strip, three
2// ways to one op (move_slide): HTML5 drag and drop of an item, Alt+Up
3// / Alt+Down on a focused item's link, and the item menu's Move up /
4// Move down (the Slide section's buttons are the same commands). The
5// drop target is the item under the pointer, before or after it by
6// which half was crossed, and said with a data-drop attribute the
7// stylesheet draws as a line.
8//
9// The pure part is dropTarget: given the item ids in order, the
10// dragged id, the item dropped on and the half, the `after` a
11// move_slide wants (null for first) — or undefined when the drop
12// changes nothing.
13
14/** The `after` for dropping `id` before/after `on`: null puts it first; undefined means no move. */
15export function dropTarget (ids, id, on, half) {
16 if (id === on) return undefined
17 const rest = ids.filter(x => x !== id)
18 const i = rest.indexOf(on)
19 if (i < 0) return undefined
20 const at = half === 'before' ? i : i + 1
21 const after = at === 0 ? null : rest[at - 1]
22 const was = ids.indexOf(id)
23 const now = rest.slice(0, at).length
24 if (was === now) return undefined
25 return after
26}
27
28/** move_slide from a drop, or null. */
29export function dropOp (ids, id, on, half) {
30 const after = dropTarget(ids, id, on, half)
31 if (after === undefined) return null
32 return after === null ? { op: 'move_slide', id } : { op: 'move_slide', id, after }
33}
34
35/**
36 * Wires drag and drop and the keyboard onto the strip's <ol>.
37 * getIDs() is the slide order; onMove(op) applies a move_slide. The
38 * item is what is dragged (draggable="true" in the markup); the link
39 * inside it is what takes the keys.
40 */
41export function mountReorder ({ root, getIDs, onMove, onNudge }) {
42 let dragging = null
43
44 const itemOf = el => el && el.closest('[slides-strip-item][data-slide-id]')
45 function clearMarks () {
46 for (const li of root.querySelectorAll('[data-drop]')) delete li.dataset.drop
47 }
48
49 root.addEventListener('dragstart', e => {
50 const li = itemOf(e.target)
51 if (!li) return
52 dragging = li.dataset.slideId
53 li.dataset.dragging = 'true'
54 e.dataTransfer.effectAllowed = 'move'
55 e.dataTransfer.setData('text/plain', dragging)
56 })
57 root.addEventListener('dragover', e => {
58 const li = itemOf(e.target)
59 if (!li || !dragging) return
60 e.preventDefault()
61 e.dataTransfer.dropEffect = 'move'
62 const r = li.getBoundingClientRect()
63 const vertical = getComputedStyle(root).flexDirection === 'column'
64 const before = vertical ? e.clientY < r.top + r.height / 2 : e.clientX < r.left + r.width / 2
65 clearMarks()
66 li.dataset.drop = before ? 'before' : 'after'
67 })
68 root.addEventListener('dragleave', e => {
69 if (!root.contains(e.relatedTarget)) clearMarks()
70 })
71 root.addEventListener('drop', e => {
72 const li = itemOf(e.target)
73 if (!li || !dragging) return
74 e.preventDefault()
75 const op = dropOp(getIDs(), dragging, li.dataset.slideId, li.dataset.drop || 'after')
76 clearMarks()
77 if (op) onMove(op)
78 })
79 root.addEventListener('dragend', () => {
80 for (const li of root.querySelectorAll('[data-dragging]')) delete li.dataset.dragging
81 clearMarks()
82 dragging = null
83 })
84
85 root.addEventListener('keydown', e => {
86 if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return
87 const li = itemOf(e.target)
88 if (!li) return
89 e.preventDefault()
90 onNudge(li.dataset.slideId, e.key === 'ArrowUp' ? 'up' : 'down')
91 })
92}
93