|
15 | 15 | export let selectable = false;
|
16 | 16 | export let height: number | undefined = undefined;
|
17 | 17 | export let i18n: I18nFormatter;
|
| 18 | + export let allow_reordering = false; |
| 19 | +
|
| 20 | + let dragging_index: number | null = null; |
| 21 | + let drop_target_index: number | null = null; |
| 22 | +
|
| 23 | + function handle_drag_start(event: DragEvent, index: number): void { |
| 24 | + dragging_index = index; |
| 25 | + if (event.dataTransfer) { |
| 26 | + event.dataTransfer.effectAllowed = "move"; |
| 27 | + event.dataTransfer.setData("text/plain", index.toString()); |
| 28 | + } |
| 29 | + } |
| 30 | +
|
| 31 | + function handle_drag_over(event: DragEvent, index: number): void { |
| 32 | + event.preventDefault(); |
| 33 | + if (index === normalized_files.length - 1) { |
| 34 | + const rect = (event.currentTarget as HTMLElement).getBoundingClientRect(); |
| 35 | + const midY = rect.top + rect.height / 2; |
| 36 | + drop_target_index = |
| 37 | + event.clientY > midY ? normalized_files.length : index; |
| 38 | + } else { |
| 39 | + drop_target_index = index; |
| 40 | + } |
| 41 | + if (event.dataTransfer) { |
| 42 | + event.dataTransfer.dropEffect = "move"; |
| 43 | + } |
| 44 | + } |
| 45 | +
|
| 46 | + function handle_drag_end(event: DragEvent): void { |
| 47 | + if ( |
| 48 | + !event.dataTransfer?.dropEffect || |
| 49 | + event.dataTransfer.dropEffect === "none" |
| 50 | + ) { |
| 51 | + dragging_index = null; |
| 52 | + drop_target_index = null; |
| 53 | + } |
| 54 | + } |
| 55 | +
|
| 56 | + function handle_drop(event: DragEvent, index: number): void { |
| 57 | + event.preventDefault(); |
| 58 | + if (dragging_index === null || dragging_index === index) return; |
| 59 | +
|
| 60 | + const files = Array.isArray(value) ? [...value] : [value]; |
| 61 | + const [removed] = files.splice(dragging_index, 1); |
| 62 | + files.splice( |
| 63 | + drop_target_index === normalized_files.length |
| 64 | + ? normalized_files.length |
| 65 | + : index, |
| 66 | + 0, |
| 67 | + removed |
| 68 | + ); |
| 69 | +
|
| 70 | + const new_value = Array.isArray(value) ? files : files[0]; |
| 71 | + dispatch("change", new_value); |
| 72 | +
|
| 73 | + dragging_index = null; |
| 74 | + drop_target_index = null; |
| 75 | + } |
18 | 76 |
|
19 | 77 | function split_filename(filename: string): [string, string] {
|
20 | 78 | const last_dot = filename.lastIndexOf(".");
|
|
70 | 128 | >
|
71 | 129 | <table class="file-preview">
|
72 | 130 | <tbody>
|
73 |
| - {#each normalized_files as file, i (file)} |
| 131 | + {#each normalized_files as file, i (file.url)} |
74 | 132 | <tr
|
75 | 133 | class="file"
|
76 | 134 | class:selectable
|
| 135 | + class:dragging={dragging_index === i} |
| 136 | + class:drop-target={drop_target_index === i || |
| 137 | + (i === normalized_files.length - 1 && |
| 138 | + drop_target_index === normalized_files.length)} |
| 139 | + data-drop-target={drop_target_index === normalized_files.length && |
| 140 | + i === normalized_files.length - 1 |
| 141 | + ? "after" |
| 142 | + : drop_target_index === i + 1 |
| 143 | + ? "after" |
| 144 | + : "before"} |
| 145 | + draggable={allow_reordering && normalized_files.length > 1} |
77 | 146 | on:click={(event) => {
|
78 | 147 | handle_row_click(event, i);
|
79 | 148 | }}
|
| 149 | + on:dragstart={(event) => handle_drag_start(event, i)} |
| 150 | + on:dragenter|preventDefault |
| 151 | + on:dragover={(event) => handle_drag_over(event, i)} |
| 152 | + on:drop={(event) => handle_drop(event, i)} |
| 153 | + on:dragend={handle_drag_end} |
80 | 154 | >
|
81 | 155 | <td class="filename" aria-label={file.orig_name}>
|
| 156 | + {#if allow_reordering && normalized_files.length > 1} |
| 157 | + <span class="drag-handle">⋮⋮</span> |
| 158 | + {/if} |
82 | 159 | <span class="stem">{file.filename_stem}</span>
|
83 | 160 | <span class="ext">{file.filename_ext}</span>
|
84 | 161 | </td>
|
|
204 | 281 | tbody > tr:nth-child(odd) {
|
205 | 282 | background: var(--table-odd-background-fill);
|
206 | 283 | }
|
| 284 | +
|
| 285 | + .drag-handle { |
| 286 | + cursor: grab; |
| 287 | + color: var(--body-text-color-subdued); |
| 288 | + padding-right: var(--size-2); |
| 289 | + user-select: none; |
| 290 | + } |
| 291 | +
|
| 292 | + .dragging { |
| 293 | + opacity: 0.5; |
| 294 | + cursor: grabbing; |
| 295 | + } |
| 296 | +
|
| 297 | + .drop-target { |
| 298 | + border-top: 2px solid var(--color-accent); |
| 299 | + } |
| 300 | +
|
| 301 | + tr:last-child.drop-target[data-drop-target="before"] { |
| 302 | + border-top: 2px solid var(--color-accent); |
| 303 | + border-bottom: none; |
| 304 | + } |
| 305 | +
|
| 306 | + tr:last-child.drop-target[data-drop-target="after"] { |
| 307 | + border-top: none; |
| 308 | + border-bottom: 2px solid var(--color-accent); |
| 309 | + } |
207 | 310 | </style>
|
0 commit comments