• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import m from 'mithril';
18
19import {DropDirection} from '../common/dragndrop_logic';
20
21import {globals} from './globals';
22
23export interface ReorderableCell {
24  content: m.Children;
25  extraClass?: string;
26}
27
28export interface ReorderableCellGroupAttrs {
29  cells: ReorderableCell[];
30  onReorder: (from: number, to: number, side: DropDirection) => void;
31}
32
33const placeholderElement = document.createElement('span');
34
35// A component that renders a group of cells on the same row that can be
36// reordered between each other by using drag'n'drop.
37//
38// On completed reorder, a callback is fired.
39export class ReorderableCellGroup implements
40    m.ClassComponent<ReorderableCellGroupAttrs> {
41  // Index of a cell being dragged.
42  draggingFrom: number = -1;
43
44  // Index of a cell cursor is hovering over.
45  draggingTo: number = -1;
46
47  // Whether the cursor hovering on the left or right side of the element: used
48  // to add the dragged element either before or after the drop target.
49  dropDirection: DropDirection = 'left';
50
51  // Auxillary array used to count entrances into `dragenter` event: these are
52  // incremented not only when hovering over a cell, but also for any child of
53  // the tree.
54  enterCounters: number[] = [];
55
56  getClassForIndex(index: number): string {
57    if (this.draggingFrom === index) {
58      return 'dragged';
59    }
60    if (this.draggingTo === index) {
61      return this.dropDirection === 'left' ? 'highlight-left' :
62                                             'highlight-right';
63    }
64    return '';
65  }
66
67  view(vnode: m.Vnode<ReorderableCellGroupAttrs>): m.Children {
68    return vnode.attrs.cells.map(
69        (cell, index) => m(
70            `td.reorderable-cell${cell.extraClass ?? ''}`,
71            {
72              draggable: 'draggable',
73              class: this.getClassForIndex(index),
74              ondragstart: (e: DragEvent) => {
75                this.draggingFrom = index;
76                if (e.dataTransfer !== null) {
77                  e.dataTransfer.setDragImage(placeholderElement, 0, 0);
78                }
79
80                globals.rafScheduler.scheduleFullRedraw();
81              },
82              ondragover: (e: DragEvent) => {
83                let target = e.target as HTMLElement;
84                if (this.draggingFrom === index || this.draggingFrom === -1) {
85                  // Don't do anything when hovering on the same cell that's
86                  // been dragged, or when dragging something other than the
87                  // cell from the same group
88                  return;
89                }
90
91                while (target.tagName.toLowerCase() !== 'td' &&
92                       target.parentElement !== null) {
93                  target = target.parentElement;
94                }
95
96                // When hovering over cell on the right half, the cell will be
97                // moved to the right of it, vice versa for the left side. This
98                // is done such that it's possible to put dragged cell to every
99                // possible position.
100                const offset = e.clientX - target.getBoundingClientRect().x;
101                const newDropDirection =
102                    (offset > target.clientWidth / 2) ? 'right' : 'left';
103                const redraw = (newDropDirection !== this.dropDirection) ||
104                    (index !== this.draggingTo);
105                this.dropDirection = newDropDirection;
106                this.draggingTo = index;
107
108
109                if (redraw) {
110                  globals.rafScheduler.scheduleFullRedraw();
111                }
112              },
113              ondragenter: (e: DragEvent) => {
114                this.enterCounters[index]++;
115
116                if (this.enterCounters[index] === 1 &&
117                    e.dataTransfer !== null) {
118                  e.dataTransfer.dropEffect = 'move';
119                }
120              },
121              ondragleave: (e: DragEvent) => {
122                this.enterCounters[index]--;
123                if (this.draggingFrom === -1 || this.enterCounters[index] > 0) {
124                  return;
125                }
126
127                if (e.dataTransfer !== null) {
128                  e.dataTransfer.dropEffect = 'none';
129                }
130
131                this.draggingTo = -1;
132                globals.rafScheduler.scheduleFullRedraw();
133              },
134              ondragend: () => {
135                if (this.draggingTo !== this.draggingFrom &&
136                    this.draggingTo !== -1) {
137                  vnode.attrs.onReorder(
138                      this.draggingFrom, this.draggingTo, this.dropDirection);
139                }
140
141                this.draggingFrom = -1;
142                this.draggingTo = -1;
143                globals.rafScheduler.scheduleFullRedraw();
144              },
145            },
146            cell.content));
147  }
148
149  oncreate(vnode: m.VnodeDOM<ReorderableCellGroupAttrs, this>) {
150    this.enterCounters = Array(vnode.attrs.cells.length).fill(0);
151  }
152
153  onupdate(vnode: m.VnodeDOM<ReorderableCellGroupAttrs, this>) {
154    if (this.enterCounters.length !== vnode.attrs.cells.length) {
155      this.enterCounters = Array(vnode.attrs.cells.length).fill(0);
156    }
157  }
158}
159