-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathpage.js
More file actions
262 lines (230 loc) · 9.13 KB
/
page.js
File metadata and controls
262 lines (230 loc) · 9.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
import { h } from '/js/src/index.js';
import { draw } from '../../common/object/draw.js';
import { iconArrowLeft, iconArrowTop } from '/js/src/icons.js';
import { minimalObjectInfo } from './panels/minimalObjectInfo.js';
import { objectInfoResizePanel } from './panels/objectInfoResizePanel.js';
/**
* Exposes the page that shows one layout and its tabs (one at a time), this page can be in edit mode
* LayoutShow is composed of:
* - 1 main view function (the page itself)
* - 1 subcanvasView to bind listeners and set a fixed height of page to allow dragging inside free space
* - N chartView, one per chart with jsroot inside
* @param {Model} model - root model of the application
* @returns {vnode} - virtual node element
*/
export default (model) => h('.scroll-y.absolute-fill.bg-gray-light', { id: 'canvas' }, subcanvasView(model));
/**
* Simple placeholder when the layout is empty
* @returns {vnode} - virtual node element
*/
const emptyListViewMode = () => h('.m4', [
h('h1', 'Empty list'),
h('p', 'Owner can edit this tab to add objects to see.'),
]);
/**
* Placeholder for empty layout in edit mode
* @returns {vnode} - virtual node element
*/
const emptyListEditMode = () => h('.m4', [
h('h1', 'Empty list'),
h('p', [iconArrowLeft(), ' Add new objects from the sidebar tree.']),
h('p', ['You can also add/remove tabs or save/delete this layout on the navbar. ', iconArrowTop()]),
]);
/**
* Container of the different charts, height is fixed and listeners allow dragging
* @param {Model} model - root model of the application
* @returns {undefined} - virtual node element
*/
function subcanvasView(model) {
const { layout } = model;
if (!layout.tab) {
return;
}
if (!layout.tab.objects.length) {
if (layout.editEnabled) {
return emptyListEditMode();
} else {
return emptyListViewMode();
}
}
/*
* Sort the list by id to help template engine. It will only update style's positions and not DOM order
* which could force recreate some charts and then have an unfriendly blink. The source array can be shuffle
* because of the GridList algo, the sort below avoid this.
*/
const tabObjects = cloneSortById(layout.tab.objects);
const subcanvasAttributes = {
style: {
height: '100%',
position: 'relative',
},
id: 'subcanvas',
/**
* Listens to dragover event to update model of moving chart position and compute grid state
* @param {DragEvent} e - https://developer.mozilla.org/fr/docs/Web/Events/dragover
* @returns {undefined}
*/
ondragover(e) {
/*
* Warning CPU heavy function: getBoundingClientRect and offsetHeight re-compute layout
* it is ok to use them on user interactions like clicks or drags
*/
// Avoid events from other draggings things (files, etc.)
if (!layout.tabObjectMoving) {
return;
}
// Mouse position according to the viewport (scroll has no effect)
const { pageX, pageY } = e;
// Canvas is the div containing the subcanvas with screen dependent height (100% - navbar)
const canvas = e.currentTarget.parentElement;
// Subcanvas is the div contaning all graphs' divs, height is independent of the screen
const subcanvas = e.currentTarget;
const canvasDimensions = subcanvas.getBoundingClientRect();
const canvasX = pageX - canvasDimensions.x;
const canvasY = pageY - canvasDimensions.y;
const cellWidth2 = canvasDimensions.width / layout.gridListSize;
// Position in the gridList
const x = Math.floor(canvasX / cellWidth2);
const y = Math.floor(canvasY / (canvas.offsetHeight * 0.95 / layout.gridListSize));
layout.moveTabObjectToPosition(x, y);
},
/**
* Listens to dragend event to end any transparent moving chart from UI and other computing inside model
* @returns {undefined}
*/
ondragend() {
layout.moveTabObjectStop();
},
};
return h('.flex-column.absolute-fill', h('.p2', subcanvasAttributes, tabObjects.map((tabObject) =>
chartView(model, tabObject))));
}
/**
* Shows a jsroot plot, with an overlay on edit mode to allow dragging events instead of dragging jsroot
* content with the mouse. Dragging to desktop is forbidden, but could be added.
* Position of chart is absolute to allow smooth movements when arrangement changes.
* @param {Model} model - root model of the application
* @param {object} tabObject - to be drawn with jsroot
* @returns {vnode} - virtual node element
*/
function chartView(model, tabObject) {
// Changing the key will force redraw of the whole component including jsroot
// This is currently a patch workaround for ensuring a change of drawing option also
// redraw the jsroot plot, because sometimes jsroot do not redraw well on option changes only.
const key = `key${tabObject.id + tabObject.options.length}`;
// Position and size are produced by GridList in the model
const style = {
height: `${model.layout.cellHeight * tabObject.h}%`,
width: `${model.layout.cellWidth * tabObject.w}%`,
top: `${model.layout.cellHeight * tabObject.y}%`,
left: `${model.layout.cellWidth * tabObject.x}%`,
opacity: model.layout.tabObjectMoving && tabObject.id === model.layout.tabObjectMoving.id ? '0' : '1',
};
// Interactions with user
const draggable = model.layout.editEnabled;
const ondragstart = model.layout.editEnabled ? (e) => {
e.dataTransfer.setData('application/qcg', null); // Custom type forbids to drag on desktop
e.dataTransfer.effectAllowed = 'move';
model.layout.moveTabObjectStart(tabObject);
} : null;
const onclick = model.layout.editEnabled ? () => model.layout.editTabObject(tabObject) : null;
const attrs = {
alt: key,
key,
style,
draggable,
ondragstart,
onclick,
onremove: () => 1, // Fix strange bug with unlimited redraws when layout contains only one chart (!!!)
};
let className = '';
className += model.layout.editingTabObject && model.layout.editingTabObject.id === tabObject.id
? 'layout-selected layout-selectable '
: 'layout-selectable ';
const attrsInternal = {
class: className,
};
return h('.absolute.animate-dimensions-position', attrs, [
// Super-container of jsroot data
h('.bg-white.m1.absolute-fill.br3', attrsInternal, drawComponent(model, tabObject)),
// Transparent layer to drag&drop in edit mode, avoid interaction with jsroot
model.layout.editEnabled && h('.object-edit-layer.absolute-fill.m1.br3'),
]);
}
/**
* Method to generate a component containing a header with actions and a jsroot plot
* @param {Model} model - root model of the application
* @param {TabObject} tabObject - object with information form QCG own storage
* @returns {vnode} - virtual node element
*/
const drawComponent = (model, tabObject) => {
const { displayTimestamp = false } = model.layout.item;
const { name, options: drawingOptions = [], ignoreDefaults } = tabObject;
const objectFromQcdbAsRemoteData = model?.object?.objects?.[name] ?? {};
const { displayHints = [], drawOptions = [] } = objectFromQcdbAsRemoteData?.payload ?? {};
let toUseDrawingOptions = [];
if (ignoreDefaults) {
toUseDrawingOptions = Array.from(new Set(drawingOptions));
} else {
toUseDrawingOptions = Array.from(new Set([...drawingOptions, ...displayHints, ...drawOptions]));
}
const lastModified = model.object.getLastModifiedByName(name);
const runNumber = model.object.getRunNumberByName(name);
return h('', {
key: `key-chart-component-${name}-${toUseDrawingOptions.join('-')}`,
style: 'height:100%; display: flex; flex-direction: column',
}, [
h('.jsrootdiv', {
style: {
'z-index': 90,
overflow: 'hidden',
height: '100%',
display: 'flex',
'flex-direction': 'column',
},
}, objectFromQcdbAsRemoteData && draw(
objectFromQcdbAsRemoteData,
{},
toUseDrawingOptions,
(error) => model.object.invalidObject(tabObject.name, error.message),
)),
objectInfoResizePanel(model, tabObject),
displayTimestamp && minimalObjectInfo(runNumber, lastModified),
]);
};
/**
* Predicate to sort objects by id
* @param {object} a - first object to compare
* @param {object} b - second object to compare with
* @returns {number} - as to which element is in front
*/
function compareById(a, b) {
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
return 0;
}
/**
* Creates a copy of array and sort it by id's object
* @param {Array.<object>} array - list of elements to sort by id
* @returns {Array.<object>} copy
*/
function cloneSortById(array) {
return array.concat().sort(compareById);
}