1 |
|
2 |
|
3 | import { DOMUtils, showErrorMessage } from '@jupyterlab/apputils';
|
4 | import { PageConfig, PathExt } from '@jupyterlab/coreutils';
|
5 | import { renameFile } from '@jupyterlab/docmanager';
|
6 | import { nullTranslator } from '@jupyterlab/translation';
|
7 | import { ellipsesIcon, homeIcon as preferredIcon, folderIcon as rootIcon } from '@jupyterlab/ui-components';
|
8 | import { ArrayExt } from '@lumino/algorithm';
|
9 | import { ElementExt } from '@lumino/domutils';
|
10 | import { Widget } from '@lumino/widgets';
|
11 |
|
12 |
|
13 |
|
14 | const BREADCRUMB_CLASS = 'jp-BreadCrumbs';
|
15 |
|
16 |
|
17 |
|
18 | const BREADCRUMB_ROOT_CLASS = 'jp-BreadCrumbs-home';
|
19 |
|
20 |
|
21 |
|
22 | const BREADCRUMB_PREFERRED_CLASS = 'jp-BreadCrumbs-preferred';
|
23 |
|
24 |
|
25 |
|
26 | const BREADCRUMB_ITEM_CLASS = 'jp-BreadCrumbs-item';
|
27 |
|
28 |
|
29 |
|
30 | const BREAD_CRUMB_PATHS = ['/', '../../', '../', ''];
|
31 |
|
32 |
|
33 |
|
34 | const CONTENTS_MIME = 'application/x-jupyter-icontents';
|
35 |
|
36 |
|
37 |
|
38 | const DROP_TARGET_CLASS = 'jp-mod-dropTarget';
|
39 |
|
40 |
|
41 |
|
42 | export class BreadCrumbs extends Widget {
|
43 | |
44 |
|
45 |
|
46 |
|
47 |
|
48 | constructor(options) {
|
49 | super();
|
50 | this.translator = options.translator || nullTranslator;
|
51 | this._trans = this.translator.load('jupyterlab');
|
52 | this._model = options.model;
|
53 | this.addClass(BREADCRUMB_CLASS);
|
54 | this._crumbs = Private.createCrumbs();
|
55 | this._crumbSeps = Private.createCrumbSeparators();
|
56 | const hasPreferred = PageConfig.getOption('preferredPath');
|
57 | this._hasPreferred = hasPreferred && hasPreferred !== '/' ? true : false;
|
58 | if (this._hasPreferred) {
|
59 | this.node.appendChild(this._crumbs[Private.Crumb.Preferred]);
|
60 | }
|
61 | this.node.appendChild(this._crumbs[Private.Crumb.Home]);
|
62 | this._model.refreshed.connect(this.update, this);
|
63 | }
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | handleEvent(event) {
|
75 | switch (event.type) {
|
76 | case 'click':
|
77 | this._evtClick(event);
|
78 | break;
|
79 | case 'lm-dragenter':
|
80 | this._evtDragEnter(event);
|
81 | break;
|
82 | case 'lm-dragleave':
|
83 | this._evtDragLeave(event);
|
84 | break;
|
85 | case 'lm-dragover':
|
86 | this._evtDragOver(event);
|
87 | break;
|
88 | case 'lm-drop':
|
89 | this._evtDrop(event);
|
90 | break;
|
91 | default:
|
92 | return;
|
93 | }
|
94 | }
|
95 | |
96 |
|
97 |
|
98 | onAfterAttach(msg) {
|
99 | super.onAfterAttach(msg);
|
100 | this.update();
|
101 | const node = this.node;
|
102 | node.addEventListener('click', this);
|
103 | node.addEventListener('lm-dragenter', this);
|
104 | node.addEventListener('lm-dragleave', this);
|
105 | node.addEventListener('lm-dragover', this);
|
106 | node.addEventListener('lm-drop', this);
|
107 | }
|
108 | |
109 |
|
110 |
|
111 | onBeforeDetach(msg) {
|
112 | super.onBeforeDetach(msg);
|
113 | const node = this.node;
|
114 | node.removeEventListener('click', this);
|
115 | node.removeEventListener('lm-dragenter', this);
|
116 | node.removeEventListener('lm-dragleave', this);
|
117 | node.removeEventListener('lm-dragover', this);
|
118 | node.removeEventListener('lm-drop', this);
|
119 | }
|
120 | |
121 |
|
122 |
|
123 | onUpdateRequest(msg) {
|
124 |
|
125 | const contents = this._model.manager.services.contents;
|
126 | const localPath = contents.localPath(this._model.path);
|
127 | Private.updateCrumbs(this._crumbs, this._crumbSeps, localPath, this._hasPreferred);
|
128 | }
|
129 | |
130 |
|
131 |
|
132 | _evtClick(event) {
|
133 |
|
134 | if (event.button !== 0) {
|
135 | return;
|
136 | }
|
137 |
|
138 | let node = event.target;
|
139 | while (node && node !== this.node) {
|
140 | if (node.classList.contains(BREADCRUMB_PREFERRED_CLASS)) {
|
141 | this._model
|
142 | .cd(PageConfig.getOption('preferredPath'))
|
143 | .catch(error => showErrorMessage(this._trans.__('Open Error'), error));
|
144 |
|
145 | event.preventDefault();
|
146 | event.stopPropagation();
|
147 | return;
|
148 | }
|
149 | if (node.classList.contains(BREADCRUMB_ITEM_CLASS) ||
|
150 | node.classList.contains(BREADCRUMB_ROOT_CLASS)) {
|
151 | const index = ArrayExt.findFirstIndex(this._crumbs, value => value === node);
|
152 | this._model
|
153 | .cd(BREAD_CRUMB_PATHS[index])
|
154 | .catch(error => showErrorMessage(this._trans.__('Open Error'), error));
|
155 |
|
156 | event.preventDefault();
|
157 | event.stopPropagation();
|
158 | return;
|
159 | }
|
160 | node = node.parentElement;
|
161 | }
|
162 | }
|
163 | |
164 |
|
165 |
|
166 | _evtDragEnter(event) {
|
167 | if (event.mimeData.hasData(CONTENTS_MIME)) {
|
168 | const index = ArrayExt.findFirstIndex(this._crumbs, node => ElementExt.hitTest(node, event.clientX, event.clientY));
|
169 | if (index !== -1) {
|
170 | if (index !== Private.Crumb.Current) {
|
171 | this._crumbs[index].classList.add(DROP_TARGET_CLASS);
|
172 | event.preventDefault();
|
173 | event.stopPropagation();
|
174 | }
|
175 | }
|
176 | }
|
177 | }
|
178 | |
179 |
|
180 |
|
181 | _evtDragLeave(event) {
|
182 | event.preventDefault();
|
183 | event.stopPropagation();
|
184 | const dropTarget = DOMUtils.findElement(this.node, DROP_TARGET_CLASS);
|
185 | if (dropTarget) {
|
186 | dropTarget.classList.remove(DROP_TARGET_CLASS);
|
187 | }
|
188 | }
|
189 | |
190 |
|
191 |
|
192 | _evtDragOver(event) {
|
193 | event.preventDefault();
|
194 | event.stopPropagation();
|
195 | event.dropAction = event.proposedAction;
|
196 | const dropTarget = DOMUtils.findElement(this.node, DROP_TARGET_CLASS);
|
197 | if (dropTarget) {
|
198 | dropTarget.classList.remove(DROP_TARGET_CLASS);
|
199 | }
|
200 | const index = ArrayExt.findFirstIndex(this._crumbs, node => ElementExt.hitTest(node, event.clientX, event.clientY));
|
201 | if (index !== -1) {
|
202 | this._crumbs[index].classList.add(DROP_TARGET_CLASS);
|
203 | }
|
204 | }
|
205 | |
206 |
|
207 |
|
208 | _evtDrop(event) {
|
209 | event.preventDefault();
|
210 | event.stopPropagation();
|
211 | if (event.proposedAction === 'none') {
|
212 | event.dropAction = 'none';
|
213 | return;
|
214 | }
|
215 | if (!event.mimeData.hasData(CONTENTS_MIME)) {
|
216 | return;
|
217 | }
|
218 | event.dropAction = event.proposedAction;
|
219 | let target = event.target;
|
220 | while (target && target.parentElement) {
|
221 | if (target.classList.contains(DROP_TARGET_CLASS)) {
|
222 | target.classList.remove(DROP_TARGET_CLASS);
|
223 | break;
|
224 | }
|
225 | target = target.parentElement;
|
226 | }
|
227 |
|
228 | const index = ArrayExt.findFirstIndex(this._crumbs, node => node === target);
|
229 | if (index === -1) {
|
230 | return;
|
231 | }
|
232 | const model = this._model;
|
233 | const path = PathExt.resolve(model.path, BREAD_CRUMB_PATHS[index]);
|
234 | const manager = model.manager;
|
235 |
|
236 | const promises = [];
|
237 | const oldPaths = event.mimeData.getData(CONTENTS_MIME);
|
238 | for (const oldPath of oldPaths) {
|
239 | const localOldPath = manager.services.contents.localPath(oldPath);
|
240 | const name = PathExt.basename(localOldPath);
|
241 | const newPath = PathExt.join(path, name);
|
242 | promises.push(renameFile(manager, oldPath, newPath));
|
243 | }
|
244 | void Promise.all(promises).catch(err => {
|
245 | return showErrorMessage(this._trans.__('Move Error'), err);
|
246 | });
|
247 | }
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 | var Private;
|
253 | (function (Private) {
|
254 | |
255 |
|
256 |
|
257 | let Crumb;
|
258 | (function (Crumb) {
|
259 | Crumb[Crumb["Home"] = 0] = "Home";
|
260 | Crumb[Crumb["Ellipsis"] = 1] = "Ellipsis";
|
261 | Crumb[Crumb["Parent"] = 2] = "Parent";
|
262 | Crumb[Crumb["Current"] = 3] = "Current";
|
263 | Crumb[Crumb["Preferred"] = 4] = "Preferred";
|
264 | })(Crumb = Private.Crumb || (Private.Crumb = {}));
|
265 | |
266 |
|
267 |
|
268 | function updateCrumbs(breadcrumbs, separators, path, hasPreferred) {
|
269 | const node = breadcrumbs[0].parentNode;
|
270 |
|
271 | const firstChild = node.firstChild;
|
272 | while (firstChild && firstChild.nextSibling) {
|
273 | node.removeChild(firstChild.nextSibling);
|
274 | }
|
275 | if (hasPreferred) {
|
276 | node.appendChild(breadcrumbs[Crumb.Home]);
|
277 | node.appendChild(separators[0]);
|
278 | }
|
279 | else {
|
280 | node.appendChild(separators[0]);
|
281 | }
|
282 | const parts = path.split('/');
|
283 | if (parts.length > 2) {
|
284 | node.appendChild(breadcrumbs[Crumb.Ellipsis]);
|
285 | const grandParent = parts.slice(0, parts.length - 2).join('/');
|
286 | breadcrumbs[Crumb.Ellipsis].title = grandParent;
|
287 | node.appendChild(separators[1]);
|
288 | }
|
289 | if (path) {
|
290 | if (parts.length >= 2) {
|
291 | breadcrumbs[Crumb.Parent].textContent = parts[parts.length - 2];
|
292 | node.appendChild(breadcrumbs[Crumb.Parent]);
|
293 | const parent = parts.slice(0, parts.length - 1).join('/');
|
294 | breadcrumbs[Crumb.Parent].title = parent;
|
295 | node.appendChild(separators[2]);
|
296 | }
|
297 | breadcrumbs[Crumb.Current].textContent = parts[parts.length - 1];
|
298 | node.appendChild(breadcrumbs[Crumb.Current]);
|
299 | breadcrumbs[Crumb.Current].title = path;
|
300 | node.appendChild(separators[3]);
|
301 | }
|
302 | }
|
303 | Private.updateCrumbs = updateCrumbs;
|
304 | |
305 |
|
306 |
|
307 | function createCrumbs() {
|
308 | const home = rootIcon.element({
|
309 | className: BREADCRUMB_ROOT_CLASS,
|
310 | tag: 'span',
|
311 | title: PageConfig.getOption('serverRoot') || 'Jupyter Server Root',
|
312 | stylesheet: 'breadCrumb'
|
313 | });
|
314 | const ellipsis = ellipsesIcon.element({
|
315 | className: BREADCRUMB_ITEM_CLASS,
|
316 | tag: 'span',
|
317 | stylesheet: 'breadCrumb'
|
318 | });
|
319 | const parent = document.createElement('span');
|
320 | parent.className = BREADCRUMB_ITEM_CLASS;
|
321 | const current = document.createElement('span');
|
322 | current.className = BREADCRUMB_ITEM_CLASS;
|
323 | const preferred = preferredIcon.element({
|
324 | className: BREADCRUMB_PREFERRED_CLASS,
|
325 | tag: 'span',
|
326 | title: PageConfig.getOption('preferredPath') || 'Jupyter Preferred Path',
|
327 | stylesheet: 'breadCrumb'
|
328 | });
|
329 | return [home, ellipsis, parent, current, preferred];
|
330 | }
|
331 | Private.createCrumbs = createCrumbs;
|
332 | |
333 |
|
334 |
|
335 | function createCrumbSeparators() {
|
336 | const items = [];
|
337 |
|
338 | const MAX_DIRECTORIES = 2;
|
339 |
|
340 |
|
341 | for (let i = 0; i < MAX_DIRECTORIES + 2; i++) {
|
342 | const item = document.createElement('span');
|
343 | item.textContent = '/';
|
344 | items.push(item);
|
345 | }
|
346 | return items;
|
347 | }
|
348 | Private.createCrumbSeparators = createCrumbSeparators;
|
349 | })(Private || (Private = {}));
|
350 |
|
\ | No newline at end of file |