xtralab 0.15.1 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -33
- package/lib/fileBrowser/commands.d.ts +1 -0
- package/lib/fileBrowser/commands.js +46 -1
- package/lib/fileBrowser/fileBrowser.js +4 -8
- package/lib/fileBrowser/gitStatus.d.ts +16 -5
- package/lib/fileBrowser/gitStatus.js +28 -17
- package/lib/fileBrowser/widget.d.ts +15 -0
- package/lib/fileBrowser/widget.js +13 -0
- package/lib/git/commands.d.ts +5 -0
- package/lib/git/commands.js +4 -3
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +34 -19
- package/lib/git/diffSurface.js +49 -115
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +7 -30
- package/lib/git/diffWidget.js +56 -100
- package/lib/git/imageDiff.d.ts +9 -0
- package/lib/git/imageDiff.js +2 -28
- package/lib/git/index.js +41 -2
- package/lib/git/notebookDiff.js +3 -2
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/fileBrowser/commands.ts +64 -0
- package/src/fileBrowser/fileBrowser.tsx +4 -9
- package/src/fileBrowser/gitStatus.ts +33 -20
- package/src/fileBrowser/widget.tsx +22 -0
- package/src/git/commands.ts +13 -1
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +113 -146
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +108 -138
- package/src/git/imageDiff.tsx +12 -34
- package/src/git/index.ts +51 -2
- package/src/git/notebookDiff.tsx +3 -2
- package/style/git.css +4 -2
package/lib/git/diffWidget.js
CHANGED
|
@@ -5,7 +5,8 @@ import { ReactWidget } from '@jupyterlab/ui-components';
|
|
|
5
5
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
6
6
|
import { Signal } from '@lumino/signaling';
|
|
7
7
|
import { buildDiffAskRequest } from './askRequest';
|
|
8
|
-
import {
|
|
8
|
+
import { useDiffPreferences } from './diffPreferences';
|
|
9
|
+
import { DIFF_WIDGET_CSS_CLASS, DiffStyleControl, DiffSurface, LineWrapControl, NotebookViewModeControl, isDarkTheme, isPierreTheme } from './diffSurface';
|
|
9
10
|
import { imageDataType } from './imageDiff';
|
|
10
11
|
export { DIFF_WIDGET_CSS_CLASS };
|
|
11
12
|
/**
|
|
@@ -16,17 +17,13 @@ export class XtralabDiffWidget extends ReactWidget {
|
|
|
16
17
|
super();
|
|
17
18
|
this._reloadNonce = 0;
|
|
18
19
|
this._pendingRefresh = null;
|
|
19
|
-
this._notebookViewModeChanged = new Signal(this);
|
|
20
20
|
this._hasNotebookView = false;
|
|
21
21
|
this._hasNotebookViewChanged = new Signal(this);
|
|
22
|
-
this._diffStyleChanged = new Signal(this);
|
|
23
22
|
this._fileDiffActive = false;
|
|
24
23
|
this._fileDiffActiveChanged = new Signal(this);
|
|
25
24
|
this._emptied = new Signal(this);
|
|
26
25
|
this._model = model;
|
|
27
26
|
this._context = context;
|
|
28
|
-
this._notebookViewMode = readStoredNotebookViewMode();
|
|
29
|
-
this._diffStyle = readStoredDiffStyle();
|
|
30
27
|
// Reuse jupyterlab-git's host sizing while applying xtralab styling.
|
|
31
28
|
this.addClass('jp-git-diff-root');
|
|
32
29
|
this.addClass(DIFF_WIDGET_CSS_CLASS);
|
|
@@ -77,29 +74,6 @@ export class XtralabDiffWidget extends ReactWidget {
|
|
|
77
74
|
this.update();
|
|
78
75
|
await done.promise;
|
|
79
76
|
}
|
|
80
|
-
/**
|
|
81
|
-
* The current rendered-vs-JSON choice for notebook diffs.
|
|
82
|
-
*/
|
|
83
|
-
get notebookViewMode() {
|
|
84
|
-
return this._notebookViewMode;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Set the notebook view mode and persist it.
|
|
88
|
-
*/
|
|
89
|
-
setNotebookViewMode(mode) {
|
|
90
|
-
if (mode === this._notebookViewMode) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
this._notebookViewMode = mode;
|
|
94
|
-
writeStoredNotebookViewMode(mode);
|
|
95
|
-
this._notebookViewModeChanged.emit(mode);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* A signal emitted when the notebook view mode changes.
|
|
99
|
-
*/
|
|
100
|
-
get notebookViewModeChanged() {
|
|
101
|
-
return this._notebookViewModeChanged;
|
|
102
|
-
}
|
|
103
77
|
/**
|
|
104
78
|
* Whether a rendered notebook view is currently available.
|
|
105
79
|
*/
|
|
@@ -123,30 +97,7 @@ export class XtralabDiffWidget extends ReactWidget {
|
|
|
123
97
|
return this._hasNotebookViewChanged;
|
|
124
98
|
}
|
|
125
99
|
/**
|
|
126
|
-
*
|
|
127
|
-
*/
|
|
128
|
-
get diffStyle() {
|
|
129
|
-
return this._diffStyle;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Set the diff style and persist it.
|
|
133
|
-
*/
|
|
134
|
-
setDiffStyle(style) {
|
|
135
|
-
if (style === this._diffStyle) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
this._diffStyle = style;
|
|
139
|
-
writeStoredDiffStyle(style);
|
|
140
|
-
this._diffStyleChanged.emit(style);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* A signal emitted when the diff style changes.
|
|
144
|
-
*/
|
|
145
|
-
get diffStyleChanged() {
|
|
146
|
-
return this._diffStyleChanged;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Whether the Split/Unified toolbar selector currently applies.
|
|
100
|
+
* Whether the Split/Unified and line-wrap toolbar controls currently apply.
|
|
150
101
|
*/
|
|
151
102
|
get fileDiffActive() {
|
|
152
103
|
return this._fileDiffActive;
|
|
@@ -219,7 +170,8 @@ function ModelDiffView(props) {
|
|
|
219
170
|
var _a, _b;
|
|
220
171
|
const { widget } = props;
|
|
221
172
|
const model = widget.model;
|
|
222
|
-
const { contentsManager, rendermime, themeManager, askAgent, trans } = widget.context;
|
|
173
|
+
const { contentsManager, rendermime, themeManager, askAgent, preferences, trans } = widget.context;
|
|
174
|
+
const values = useDiffPreferences(preferences);
|
|
223
175
|
const nonce = widget.reloadNonce;
|
|
224
176
|
const [state, setState] = React.useState({
|
|
225
177
|
loading: true,
|
|
@@ -227,31 +179,15 @@ function ModelDiffView(props) {
|
|
|
227
179
|
newText: '',
|
|
228
180
|
error: null
|
|
229
181
|
});
|
|
230
|
-
const [notebookViewMode, setNotebookViewMode] = React.useState(() => widget.notebookViewMode);
|
|
231
|
-
React.useEffect(() => {
|
|
232
|
-
const handler = (sender, mode) => {
|
|
233
|
-
setNotebookViewMode(mode);
|
|
234
|
-
};
|
|
235
|
-
widget.notebookViewModeChanged.connect(handler);
|
|
236
|
-
setNotebookViewMode(widget.notebookViewMode);
|
|
237
|
-
return () => {
|
|
238
|
-
widget.notebookViewModeChanged.disconnect(handler);
|
|
239
|
-
};
|
|
240
|
-
}, [widget]);
|
|
241
182
|
const handleNotebookAvailabilityChange = React.useCallback((available) => {
|
|
242
183
|
widget.setHasNotebookView(available);
|
|
243
184
|
}, [widget]);
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
setDiffStyle(widget.diffStyle);
|
|
251
|
-
return () => {
|
|
252
|
-
widget.diffStyleChanged.disconnect(handler);
|
|
253
|
-
};
|
|
254
|
-
}, [widget]);
|
|
185
|
+
const handleSplitRatioChange = React.useCallback((splitRatio) => {
|
|
186
|
+
preferences.update({ splitRatio });
|
|
187
|
+
}, [preferences]);
|
|
188
|
+
const handleImageViewModeChange = React.useCallback((imageViewMode) => {
|
|
189
|
+
preferences.update({ imageViewMode });
|
|
190
|
+
}, [preferences]);
|
|
255
191
|
const handleFileDiffActiveChange = React.useCallback((active) => {
|
|
256
192
|
widget.setFileDiffActive(active);
|
|
257
193
|
}, [widget]);
|
|
@@ -372,7 +308,7 @@ function ModelDiffView(props) {
|
|
|
372
308
|
}));
|
|
373
309
|
};
|
|
374
310
|
}, [askAgent, model, state.oldText, state.newText, trans]);
|
|
375
|
-
return (React.createElement(DiffSurface, { loading: state.loading, error: state.error, isBinary: isBinary, oldText: state.oldText, newText: state.newText, newName: model.filename, oldName: (_b = model.oldFilename) !== null && _b !== void 0 ? _b : model.filename, dark: dark, pierreTheme: pierre, rendermime: rendermime, notebookViewMode: notebookViewMode, diffStyle: diffStyle, onNotebookAvailabilityChange: handleNotebookAvailabilityChange, onFileDiffActiveChange: handleFileDiffActiveChange, onMetadataChange: handleMetadataChange, hunkDiscard: hunkDiscard, onLineAsk: handleLineAsk, trans: trans }));
|
|
311
|
+
return (React.createElement(DiffSurface, { loading: state.loading, error: state.error, isBinary: isBinary, oldText: state.oldText, newText: state.newText, newName: model.filename, oldName: (_b = model.oldFilename) !== null && _b !== void 0 ? _b : model.filename, dark: dark, pierreTheme: pierre, rendermime: rendermime, notebookViewMode: values.notebookViewMode, diffStyle: values.diffStyle, lineWrap: values.lineWrap, splitRatio: values.splitRatio, onSplitRatioChange: handleSplitRatioChange, imageViewMode: values.imageViewMode, onImageViewModeChange: handleImageViewModeChange, onNotebookAvailabilityChange: handleNotebookAvailabilityChange, onFileDiffActiveChange: handleFileDiffActiveChange, onMetadataChange: handleMetadataChange, hunkDiscard: hunkDiscard, onLineAsk: handleLineAsk, trans: trans }));
|
|
376
312
|
}
|
|
377
313
|
/**
|
|
378
314
|
* Notebook/JSON toggle mounted into a host-owned toolbar.
|
|
@@ -392,26 +328,20 @@ class NotebookViewModeToolbarItem extends ReactWidget {
|
|
|
392
328
|
}
|
|
393
329
|
function NotebookViewModeToolbarControl(props) {
|
|
394
330
|
const { widget } = props;
|
|
395
|
-
const
|
|
331
|
+
const { preferences, trans } = widget.context;
|
|
332
|
+
const { notebookViewMode } = useDiffPreferences(preferences);
|
|
396
333
|
const [available, setAvailable] = React.useState(() => widget.hasNotebookView);
|
|
397
334
|
React.useEffect(() => {
|
|
398
|
-
const onMode = (sender, next) => {
|
|
399
|
-
setMode(next);
|
|
400
|
-
};
|
|
401
335
|
const onAvailable = (sender, next) => {
|
|
402
336
|
setAvailable(next);
|
|
403
337
|
};
|
|
404
|
-
widget.notebookViewModeChanged.connect(onMode);
|
|
405
338
|
widget.hasNotebookViewChanged.connect(onAvailable);
|
|
406
|
-
setMode(widget.notebookViewMode);
|
|
407
339
|
setAvailable(widget.hasNotebookView);
|
|
408
340
|
return () => {
|
|
409
|
-
widget.notebookViewModeChanged.disconnect(onMode);
|
|
410
341
|
widget.hasNotebookViewChanged.disconnect(onAvailable);
|
|
411
342
|
};
|
|
412
343
|
}, [widget]);
|
|
413
|
-
|
|
414
|
-
return (React.createElement(NotebookViewModeControl, { mode: mode, available: available, onChange: next => widget.setNotebookViewMode(next), trans: trans }));
|
|
344
|
+
return (React.createElement(NotebookViewModeControl, { mode: notebookViewMode, available: available, onChange: next => preferences.update({ notebookViewMode: next }), trans: trans }));
|
|
415
345
|
}
|
|
416
346
|
/**
|
|
417
347
|
* Split/Unified toggle mounted into a host-owned toolbar.
|
|
@@ -431,26 +361,51 @@ class DiffStyleToolbarItem extends ReactWidget {
|
|
|
431
361
|
}
|
|
432
362
|
function DiffStyleToolbarControl(props) {
|
|
433
363
|
const { widget } = props;
|
|
434
|
-
const
|
|
435
|
-
const
|
|
364
|
+
const { preferences, trans } = widget.context;
|
|
365
|
+
const { diffStyle, diffStyleControl } = useDiffPreferences(preferences);
|
|
366
|
+
const available = useFileDiffActive(widget);
|
|
367
|
+
return (React.createElement(DiffStyleControl, { diffStyle: diffStyle, control: diffStyleControl, available: available, onChange: next => preferences.update({ diffStyle: next }), trans: trans }));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Line-wrap toggle mounted into a host-owned toolbar.
|
|
371
|
+
*/
|
|
372
|
+
class LineWrapToolbarItem extends ReactWidget {
|
|
373
|
+
constructor(widget) {
|
|
374
|
+
super();
|
|
375
|
+
this._widget = widget;
|
|
376
|
+
this.addClass('jp-xtralab-DiffWidget-lineWrapToolbarItem');
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Render the line-wrap toggle.
|
|
380
|
+
*/
|
|
381
|
+
render() {
|
|
382
|
+
return React.createElement(LineWrapToolbarControl, { widget: this._widget });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function LineWrapToolbarControl(props) {
|
|
386
|
+
const { widget } = props;
|
|
387
|
+
const { preferences, trans } = widget.context;
|
|
388
|
+
const { lineWrap } = useDiffPreferences(preferences);
|
|
389
|
+
const available = useFileDiffActive(widget);
|
|
390
|
+
return (React.createElement(LineWrapControl, { lineWrap: lineWrap, available: available, onChange: next => preferences.update({ lineWrap: next }), trans: trans }));
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Track {@link XtralabDiffWidget.fileDiffActive} for the split/unified and
|
|
394
|
+
* line-wrap controls.
|
|
395
|
+
*/
|
|
396
|
+
function useFileDiffActive(widget) {
|
|
397
|
+
const [active, setActive] = React.useState(() => widget.fileDiffActive);
|
|
436
398
|
React.useEffect(() => {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
};
|
|
440
|
-
const onAvailable = (sender, next) => {
|
|
441
|
-
setAvailable(next);
|
|
399
|
+
const onActive = (sender, next) => {
|
|
400
|
+
setActive(next);
|
|
442
401
|
};
|
|
443
|
-
widget.
|
|
444
|
-
widget.
|
|
445
|
-
setStyle(widget.diffStyle);
|
|
446
|
-
setAvailable(widget.fileDiffActive);
|
|
402
|
+
widget.fileDiffActiveChanged.connect(onActive);
|
|
403
|
+
setActive(widget.fileDiffActive);
|
|
447
404
|
return () => {
|
|
448
|
-
widget.
|
|
449
|
-
widget.fileDiffActiveChanged.disconnect(onAvailable);
|
|
405
|
+
widget.fileDiffActiveChanged.disconnect(onActive);
|
|
450
406
|
};
|
|
451
407
|
}, [widget]);
|
|
452
|
-
|
|
453
|
-
return (React.createElement(DiffStyleControl, { diffStyle: style, available: available, onChange: next => widget.setDiffStyle(next), trans: trans }));
|
|
408
|
+
return active;
|
|
454
409
|
}
|
|
455
410
|
/**
|
|
456
411
|
* Mount the shared diff toolbar controls into a host-owned toolbar.
|
|
@@ -458,4 +413,5 @@ function DiffStyleToolbarControl(props) {
|
|
|
458
413
|
export function addDiffToolbarItems(toolbar, widget) {
|
|
459
414
|
toolbar.addItem('xtralab-notebook-view-mode', new NotebookViewModeToolbarItem(widget));
|
|
460
415
|
toolbar.addItem('xtralab-diff-style', new DiffStyleToolbarItem(widget));
|
|
416
|
+
toolbar.addItem('xtralab-diff-line-wrap', new LineWrapToolbarItem(widget));
|
|
461
417
|
}
|
package/lib/git/imageDiff.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
+
import type { ImageDiffViewMode } from './diffPreferences';
|
|
3
4
|
export declare const IMAGE_DIFF_EXTENSIONS: string[];
|
|
4
5
|
/**
|
|
5
6
|
* The `data:` MIME subtype for a path xtralab renders as an image, or
|
|
@@ -22,6 +23,14 @@ interface IImageDiffViewProps {
|
|
|
22
23
|
* MIME subtype from {@link imageDataType}.
|
|
23
24
|
*/
|
|
24
25
|
fileType: string;
|
|
26
|
+
/**
|
|
27
|
+
* The selected comparison mode (host-controlled).
|
|
28
|
+
*/
|
|
29
|
+
mode: ImageDiffViewMode;
|
|
30
|
+
/**
|
|
31
|
+
* Called when the user picks another comparison mode.
|
|
32
|
+
*/
|
|
33
|
+
onModeChange: (mode: ImageDiffViewMode) => void;
|
|
25
34
|
/**
|
|
26
35
|
* Translation bundle for user-facing strings.
|
|
27
36
|
*/
|
package/lib/git/imageDiff.js
CHANGED
|
@@ -33,27 +33,6 @@ export function imageDataType(path) {
|
|
|
33
33
|
}
|
|
34
34
|
return (_a = IMAGE_DATA_TYPES[lower.slice(dot)]) !== null && _a !== void 0 ? _a : null;
|
|
35
35
|
}
|
|
36
|
-
const IMAGE_DIFF_VIEW_MODE_STORAGE_KEY = 'xtralab:image-diff-view-mode';
|
|
37
|
-
function readStoredImageViewMode() {
|
|
38
|
-
try {
|
|
39
|
-
const raw = window.localStorage.getItem(IMAGE_DIFF_VIEW_MODE_STORAGE_KEY);
|
|
40
|
-
if (raw === '2-up' || raw === 'swipe' || raw === 'onion') {
|
|
41
|
-
return raw;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch (_a) {
|
|
45
|
-
// localStorage can throw in privacy/sandboxed contexts.
|
|
46
|
-
}
|
|
47
|
-
return '2-up';
|
|
48
|
-
}
|
|
49
|
-
function writeStoredImageViewMode(mode) {
|
|
50
|
-
try {
|
|
51
|
-
window.localStorage.setItem(IMAGE_DIFF_VIEW_MODE_STORAGE_KEY, mode);
|
|
52
|
-
}
|
|
53
|
-
catch (_a) {
|
|
54
|
-
// Best-effort.
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
36
|
/**
|
|
58
37
|
* Strip whitespace from a base64 payload: the server's `base64.encodebytes`
|
|
59
38
|
* inserts newlines some browsers reject in `data:` URLs, and the cleaned
|
|
@@ -97,21 +76,16 @@ function toSide(raw, fileType) {
|
|
|
97
76
|
* segmented-control styling) plus the selected 2-up / swipe / onion view.
|
|
98
77
|
*/
|
|
99
78
|
export function ImageDiffView(props) {
|
|
100
|
-
const { reference, challenger, fileType, trans } = props;
|
|
79
|
+
const { reference, challenger, fileType, mode, onModeChange, trans } = props;
|
|
101
80
|
const ref = React.useMemo(() => toSide(reference, fileType), [reference, fileType]);
|
|
102
81
|
const chall = React.useMemo(() => toSide(challenger, fileType), [challenger, fileType]);
|
|
103
|
-
const [mode, setMode] = React.useState(() => readStoredImageViewMode());
|
|
104
|
-
const changeMode = React.useCallback((next) => {
|
|
105
|
-
setMode(next);
|
|
106
|
-
writeStoredImageViewMode(next);
|
|
107
|
-
}, []);
|
|
108
82
|
return (React.createElement("div", { className: "jp-xtralab-ImageDiff" },
|
|
109
83
|
React.createElement("div", { className: "jp-xtralab-ImageDiff-header" },
|
|
110
84
|
React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": trans.__('Image diff view mode') }, [
|
|
111
85
|
['2-up', trans.__('2-up')],
|
|
112
86
|
['swipe', trans.__('Swipe')],
|
|
113
87
|
['onion', trans.__('Onion Skin')]
|
|
114
|
-
].map(([value, label]) => (React.createElement("button", { key: value, type: "button", role: "tab", "aria-selected": mode === value, "data-active": mode === value, className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () =>
|
|
88
|
+
].map(([value, label]) => (React.createElement("button", { key: value, type: "button", role: "tab", "aria-selected": mode === value, "data-active": mode === value, className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onModeChange(value) }, label))))),
|
|
115
89
|
React.createElement("div", { className: "jp-xtralab-ImageDiff-body" }, mode === '2-up' ? (React.createElement(TwoUp, { reference: ref, challenger: chall, trans: trans })) : mode === 'swipe' ? (React.createElement(Swipe, { reference: ref, challenger: chall, trans: trans })) : (React.createElement(OnionSkin, { reference: ref, challenger: chall, trans: trans })))));
|
|
116
90
|
}
|
|
117
91
|
function useNaturalSize() {
|
package/lib/git/index.js
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import { IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
|
|
2
2
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
3
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
4
|
+
import { IStateDB } from '@jupyterlab/statedb';
|
|
3
5
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
4
6
|
import { IAskAgent } from '../askAgent/tokens';
|
|
5
7
|
import { CommandIDs, PREVIEW_DIFF_WIDGET_ID, pinnedDiffWidgetId, registerGitCommands } from './commands';
|
|
8
|
+
import { DiffPreferences, IDiffPreferences } from './diffPreferences';
|
|
6
9
|
import diffProviderPlugin from './diffProvider';
|
|
7
10
|
const GIT_DIFF_COMMAND_PLUGIN_ID = 'xtralab:git-diff-command';
|
|
11
|
+
const GIT_DIFF_PREFERENCES_PLUGIN_ID = 'xtralab:git-diff-preferences';
|
|
8
12
|
const GIT_DIFF_TRACKER_NAMESPACE = 'xtralab-git-diff';
|
|
13
|
+
/**
|
|
14
|
+
* The display choices shared by the launcher diff and the Git panel diff.
|
|
15
|
+
*/
|
|
16
|
+
const diffPreferencesPlugin = {
|
|
17
|
+
id: GIT_DIFF_PREFERENCES_PLUGIN_ID,
|
|
18
|
+
description: 'Keeps the git diff display choices in the settings and the state database.',
|
|
19
|
+
autoStart: true,
|
|
20
|
+
provides: IDiffPreferences,
|
|
21
|
+
optional: [ISettingRegistry, IStateDB],
|
|
22
|
+
activate: (app, settingRegistry, state) => {
|
|
23
|
+
const preferences = new DiffPreferences();
|
|
24
|
+
if (settingRegistry !== null) {
|
|
25
|
+
settingRegistry
|
|
26
|
+
.load(GIT_DIFF_PREFERENCES_PLUGIN_ID)
|
|
27
|
+
.then(settings => preferences.connectSettings(settings))
|
|
28
|
+
.catch(reason => {
|
|
29
|
+
console.error(`xtralab: failed to load settings for ${GIT_DIFF_PREFERENCES_PLUGIN_ID}`, reason);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (state !== null) {
|
|
33
|
+
preferences.connectState(state).catch(reason => {
|
|
34
|
+
console.error('xtralab: failed to restore the diff split', reason);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return preferences;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
9
40
|
/**
|
|
10
41
|
* The launcher's git diff command plugin. The upstream git panel stays
|
|
11
42
|
* enabled; this only adds `xtralab:git:open-diff` and preview/pinned-tab
|
|
@@ -15,8 +46,14 @@ const diffCommandPlugin = {
|
|
|
15
46
|
id: GIT_DIFF_COMMAND_PLUGIN_ID,
|
|
16
47
|
description: "The launcher dashboard's side-by-side git diff command, powered by @pierre/diffs.",
|
|
17
48
|
autoStart: true,
|
|
18
|
-
optional: [
|
|
19
|
-
|
|
49
|
+
optional: [
|
|
50
|
+
IThemeManager,
|
|
51
|
+
IRenderMimeRegistry,
|
|
52
|
+
ITranslator,
|
|
53
|
+
IAskAgent,
|
|
54
|
+
IDiffPreferences
|
|
55
|
+
],
|
|
56
|
+
activate: (app, themeManager, rendermime, translator, askAgent, preferences) => {
|
|
20
57
|
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
21
58
|
const tracker = new WidgetTracker({
|
|
22
59
|
namespace: GIT_DIFF_TRACKER_NAMESPACE
|
|
@@ -32,6 +69,7 @@ const diffCommandPlugin = {
|
|
|
32
69
|
contentsManager: app.serviceManager.contents,
|
|
33
70
|
rendermime,
|
|
34
71
|
askAgent,
|
|
72
|
+
preferences: preferences !== null && preferences !== void 0 ? preferences : new DiffPreferences(),
|
|
35
73
|
trans,
|
|
36
74
|
trackDiff: widget => tracker.add(widget),
|
|
37
75
|
onPinned: current => {
|
|
@@ -52,6 +90,7 @@ const diffCommandPlugin = {
|
|
|
52
90
|
}
|
|
53
91
|
};
|
|
54
92
|
const plugins = [
|
|
93
|
+
diffPreferencesPlugin,
|
|
55
94
|
diffCommandPlugin,
|
|
56
95
|
diffProviderPlugin
|
|
57
96
|
];
|
package/lib/git/notebookDiff.js
CHANGED
|
@@ -5,7 +5,7 @@ import { MessageLoop } from '@lumino/messaging';
|
|
|
5
5
|
import { Widget } from '@lumino/widgets';
|
|
6
6
|
import { FileDiff } from '@pierre/diffs/react';
|
|
7
7
|
import { parseDiffFromFile } from '@pierre/diffs';
|
|
8
|
-
import { resolveDiffTheme } from './diffTheme';
|
|
8
|
+
import { DIFF_SCROLLBAR_CSS, resolveDiffTheme } from './diffTheme';
|
|
9
9
|
const NOTEBOOK_DIFF_CSS_CLASS = 'jp-xtralab-NotebookDiff';
|
|
10
10
|
/**
|
|
11
11
|
* Parse notebook JSON. Returns `null` when the text is not a notebook so
|
|
@@ -317,7 +317,8 @@ function diffLibraryOptions(theme, dark) {
|
|
|
317
317
|
diffStyle: 'split',
|
|
318
318
|
disableFileHeader: true,
|
|
319
319
|
theme,
|
|
320
|
-
themeType: dark ? 'dark' : 'light'
|
|
320
|
+
themeType: dark ? 'dark' : 'light',
|
|
321
|
+
unsafeCSS: DIFF_SCROLLBAR_CSS
|
|
321
322
|
};
|
|
322
323
|
}
|
|
323
324
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xtralab",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "An opinionated JupyterLab meta-package that bundles a curated set of extensions, ships a path-first file browser, renders jupyterlab-git's text, notebook and image diffs with its own viewers, and applies a quieter default workspace configuration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
"@lumino/polling": "^2.1.5",
|
|
91
91
|
"@lumino/signaling": "^2.1.5",
|
|
92
92
|
"@lumino/widgets": "^2.8.0",
|
|
93
|
-
"@pierre/diffs": "^1.
|
|
93
|
+
"@pierre/diffs": "^1.5.1",
|
|
94
|
+
"@pierre/icons": "^0.9.0",
|
|
94
95
|
"@pierre/trees": "1.0.0-beta.4",
|
|
95
96
|
"detect-indent": "^7.0.2",
|
|
96
97
|
"ignore": "^5.3.2",
|
|
@@ -136,7 +137,8 @@
|
|
|
136
137
|
"@jupyterlab/git": {
|
|
137
138
|
"bundled": false,
|
|
138
139
|
"singleton": true
|
|
139
|
-
}
|
|
140
|
+
},
|
|
141
|
+
"@pierre/icons": false
|
|
140
142
|
},
|
|
141
143
|
"disabledExtensions": [
|
|
142
144
|
"@jupyterlab/git:notebook-diff",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "xtralab Git Diff",
|
|
3
|
+
"description": "Display options for the git diff views in the launcher and the Git panel.",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"diffStyle": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"enum": ["split", "unified"],
|
|
9
|
+
"title": "Layout",
|
|
10
|
+
"description": "Show text diffs side by side (split) or in one column (unified).",
|
|
11
|
+
"default": "split"
|
|
12
|
+
},
|
|
13
|
+
"diffStyleControl": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["buttons", "toggle"],
|
|
16
|
+
"title": "Layout buttons",
|
|
17
|
+
"description": "Show two toolbar buttons for the split and unified layouts (buttons), or one button that switches between them (toggle).",
|
|
18
|
+
"default": "buttons"
|
|
19
|
+
},
|
|
20
|
+
"lineWrap": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"title": "Wrap long lines",
|
|
23
|
+
"description": "When true, long lines in text diffs wrap to the width of the view. When false, they scroll horizontally.",
|
|
24
|
+
"default": false
|
|
25
|
+
},
|
|
26
|
+
"notebookViewMode": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["notebook", "json"],
|
|
29
|
+
"title": "Notebook diff view",
|
|
30
|
+
"description": "Show notebook diffs cell by cell (notebook) or as the diff of the notebook JSON (json).",
|
|
31
|
+
"default": "notebook"
|
|
32
|
+
},
|
|
33
|
+
"imageViewMode": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["2-up", "swipe", "onion"],
|
|
36
|
+
"title": "Image diff view",
|
|
37
|
+
"description": "Compare images side by side (2-up), with a slider (swipe), or overlaid (onion).",
|
|
38
|
+
"default": "2-up"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"additionalProperties": false
|
|
42
|
+
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
fileIcon,
|
|
22
22
|
filterIcon,
|
|
23
23
|
IDisposableMenuItem,
|
|
24
|
+
LabIcon,
|
|
24
25
|
newFolderIcon,
|
|
25
26
|
RankedMenu,
|
|
26
27
|
refreshIcon
|
|
@@ -28,7 +29,13 @@ import {
|
|
|
28
29
|
import { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
29
30
|
import { ContextMenu, Widget } from '@lumino/widgets';
|
|
30
31
|
|
|
32
|
+
import {
|
|
33
|
+
CommandArguments as GitCommandArguments,
|
|
34
|
+
CommandIDs as GitCommandIDs
|
|
35
|
+
} from '../git/commands';
|
|
36
|
+
import type { IFileChange } from '../git/tokens';
|
|
31
37
|
import { toCanonicalPath, toServerPath } from './contents';
|
|
38
|
+
import { GIT_REPO_PATH } from './gitStatus';
|
|
32
39
|
import { FILE_BROWSER_ID, IXtralabFileBrowser } from './widget';
|
|
33
40
|
|
|
34
41
|
/**
|
|
@@ -39,6 +46,7 @@ import { FILE_BROWSER_ID, IXtralabFileBrowser } from './widget';
|
|
|
39
46
|
export namespace CommandIDs {
|
|
40
47
|
export const open = 'xtralab:open';
|
|
41
48
|
export const openBrowserTab = 'xtralab:open-browser-tab';
|
|
49
|
+
export const openGitDiff = 'xtralab:open-git-diff';
|
|
42
50
|
export const rename = 'xtralab:rename';
|
|
43
51
|
export const del = 'xtralab:delete';
|
|
44
52
|
export const duplicate = 'xtralab:duplicate';
|
|
@@ -135,6 +143,23 @@ function getOpenPaths(
|
|
|
135
143
|
return candidates.filter(path => !path.endsWith('/'));
|
|
136
144
|
}
|
|
137
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Git changes of the target file, at most one staged and one unstaged.
|
|
148
|
+
* Status paths are repo-relative; like the tree's git badges, this assumes
|
|
149
|
+
* the repo root is the server root.
|
|
150
|
+
*/
|
|
151
|
+
function getTargetChanges(
|
|
152
|
+
app: JupyterFrontEnd,
|
|
153
|
+
browser: IXtralabFileBrowser
|
|
154
|
+
): IFileChange[] {
|
|
155
|
+
const targetPath = getTargetPath(app, browser);
|
|
156
|
+
if (targetPath === undefined) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const serverPath = toServerPath(targetPath);
|
|
160
|
+
return browser.gitChanges.filter(change => change.path === serverPath);
|
|
161
|
+
}
|
|
162
|
+
|
|
138
163
|
function hasTarget(
|
|
139
164
|
app: JupyterFrontEnd,
|
|
140
165
|
browser: IXtralabFileBrowser
|
|
@@ -242,6 +267,19 @@ function makeOpenWithUpdater(
|
|
|
242
267
|
command: CommandIDs.open
|
|
243
268
|
})
|
|
244
269
|
);
|
|
270
|
+
|
|
271
|
+
const changes = getTargetChanges(app, browser);
|
|
272
|
+
if (changes.length > 0) {
|
|
273
|
+
items.push(submenu.addItem({ type: 'separator' }));
|
|
274
|
+
for (const change of changes) {
|
|
275
|
+
items.push(
|
|
276
|
+
submenu.addItem({
|
|
277
|
+
args: { group: change.group },
|
|
278
|
+
command: CommandIDs.openGitDiff
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
245
283
|
};
|
|
246
284
|
}
|
|
247
285
|
|
|
@@ -303,6 +341,32 @@ export function registerCommands(opts: IRegisterCommandsOptions): () => void {
|
|
|
303
341
|
}
|
|
304
342
|
});
|
|
305
343
|
|
|
344
|
+
commands.addCommand(CommandIDs.openGitDiff, {
|
|
345
|
+
label: args =>
|
|
346
|
+
args.group === 'staged'
|
|
347
|
+
? trans.__('Git Diff (Staged)')
|
|
348
|
+
: trans.__('Git Diff (Working)'),
|
|
349
|
+
icon: LabIcon.resolve({ icon: 'git:diff' }).bindprops({
|
|
350
|
+
stylesheet: 'menuItem'
|
|
351
|
+
}),
|
|
352
|
+
execute: async (args: ReadonlyPartialJSONObject) => {
|
|
353
|
+
const change = getTargetChanges(app, browser).find(
|
|
354
|
+
candidate => candidate.group === args.group
|
|
355
|
+
);
|
|
356
|
+
if (change === undefined) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const diffArgs: GitCommandArguments.IOpenDiff = {
|
|
360
|
+
repoPath: GIT_REPO_PATH,
|
|
361
|
+
change
|
|
362
|
+
};
|
|
363
|
+
return commands.execute(
|
|
364
|
+
GitCommandIDs.openDiff,
|
|
365
|
+
diffArgs as unknown as ReadonlyPartialJSONObject
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
306
370
|
commands.addCommand(CommandIDs.rename, {
|
|
307
371
|
label: trans.__('Rename…'),
|
|
308
372
|
icon: editIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
useRootDropZone
|
|
33
33
|
} from './dragAndDrop';
|
|
34
34
|
import { buildIgnoredEntries, loadGitignoreMatcher } from './gitignore';
|
|
35
|
-
import {
|
|
35
|
+
import { GIT_REPO_PATH, loadGitChanges, toGitStatusEntries } from './gitStatus';
|
|
36
36
|
import { FILE_BROWSER_ICONS } from './icons';
|
|
37
37
|
import type { XtralabFileBrowser } from './widget';
|
|
38
38
|
|
|
@@ -54,12 +54,6 @@ const FILE_LISTING_REFRESH_INTERVAL_MS = 10000;
|
|
|
54
54
|
|
|
55
55
|
const FILE_LISTING_REFRESH_MAX_MS = 300_000;
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
* Repo path for `/git/*` calls; empty means the server root, letting git
|
|
59
|
-
* resolve the enclosing repo (same convention as the git panel).
|
|
60
|
-
*/
|
|
61
|
-
const GIT_REPO_PATH = '';
|
|
62
|
-
|
|
63
57
|
const FILE_TREE_TAG = 'file-tree-container';
|
|
64
58
|
|
|
65
59
|
/**
|
|
@@ -180,11 +174,12 @@ export function FileBrowserComponent(
|
|
|
180
174
|
};
|
|
181
175
|
|
|
182
176
|
const refreshGitStatus = async (): Promise<void> => {
|
|
183
|
-
const
|
|
177
|
+
const changes = await loadGitChanges(GIT_REPO_PATH);
|
|
184
178
|
if (cancelled) {
|
|
185
179
|
return;
|
|
186
180
|
}
|
|
187
|
-
gitStatusEntries =
|
|
181
|
+
gitStatusEntries = toGitStatusEntries(changes);
|
|
182
|
+
widget?.updateGitChanges(changes);
|
|
188
183
|
syncGitStatus();
|
|
189
184
|
};
|
|
190
185
|
|