@jupyterlab/imageviewer 4.0.0-alpha.19 → 4.0.0-alpha.20
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/package.json +12 -11
- package/src/index.ts +9 -0
- package/src/tokens.ts +23 -0
- package/src/widget.ts +289 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlab/imageviewer",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.20",
|
|
4
4
|
"description": "JupyterLab - Image Widget",
|
|
5
5
|
"homepage": "https://github.com/jupyterlab/jupyterlab",
|
|
6
6
|
"bugs": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"lib/*.js.map",
|
|
28
28
|
"lib/*.js",
|
|
29
29
|
"style/*.css",
|
|
30
|
-
"style/index.js"
|
|
30
|
+
"style/index.js",
|
|
31
|
+
"src/**/*.{ts,tsx}"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsc -b",
|
|
@@ -41,19 +42,19 @@
|
|
|
41
42
|
"watch": "tsc -b --watch"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@jupyterlab/apputils": "^4.0.0-alpha.
|
|
45
|
-
"@jupyterlab/coreutils": "^6.0.0-alpha.
|
|
46
|
-
"@jupyterlab/docregistry": "^4.0.0-alpha.
|
|
47
|
-
"@lumino/coreutils": "^2.0.0-
|
|
48
|
-
"@lumino/messaging": "^2.0.0-
|
|
49
|
-
"@lumino/widgets": "^2.0.0-
|
|
45
|
+
"@jupyterlab/apputils": "^4.0.0-alpha.20",
|
|
46
|
+
"@jupyterlab/coreutils": "^6.0.0-alpha.20",
|
|
47
|
+
"@jupyterlab/docregistry": "^4.0.0-alpha.20",
|
|
48
|
+
"@lumino/coreutils": "^2.0.0-rc.0",
|
|
49
|
+
"@lumino/messaging": "^2.0.0-rc.0",
|
|
50
|
+
"@lumino/widgets": "^2.0.0-rc.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@jupyterlab/testing": "^4.0.0-alpha.
|
|
53
|
+
"@jupyterlab/testing": "^4.0.0-alpha.20",
|
|
53
54
|
"@types/jest": "^29.2.0",
|
|
54
55
|
"rimraf": "~3.0.0",
|
|
55
|
-
"typedoc": "~0.
|
|
56
|
-
"typescript": "~
|
|
56
|
+
"typedoc": "~0.23.25",
|
|
57
|
+
"typescript": "~5.0.0-beta"
|
|
57
58
|
},
|
|
58
59
|
"publishConfig": {
|
|
59
60
|
"access": "public"
|
package/src/index.ts
ADDED
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { IWidgetTracker } from '@jupyterlab/apputils';
|
|
5
|
+
|
|
6
|
+
import { IDocumentWidget } from '@jupyterlab/docregistry';
|
|
7
|
+
|
|
8
|
+
import { Token } from '@lumino/coreutils';
|
|
9
|
+
|
|
10
|
+
import { ImageViewer } from './widget';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A class that tracks image widgets.
|
|
14
|
+
*/
|
|
15
|
+
export interface IImageTracker
|
|
16
|
+
extends IWidgetTracker<IDocumentWidget<ImageViewer>> {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The image tracker token.
|
|
20
|
+
*/
|
|
21
|
+
export const IImageTracker = new Token<IImageTracker>(
|
|
22
|
+
'@jupyterlab/imageviewer:IImageTracker'
|
|
23
|
+
);
|
package/src/widget.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
5
|
+
|
|
6
|
+
import { Printing } from '@jupyterlab/apputils';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ABCWidgetFactory,
|
|
10
|
+
DocumentRegistry,
|
|
11
|
+
DocumentWidget,
|
|
12
|
+
IDocumentWidget
|
|
13
|
+
} from '@jupyterlab/docregistry';
|
|
14
|
+
|
|
15
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
16
|
+
|
|
17
|
+
import { Message } from '@lumino/messaging';
|
|
18
|
+
|
|
19
|
+
import { Widget } from '@lumino/widgets';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The class name added to a imageviewer.
|
|
23
|
+
*/
|
|
24
|
+
const IMAGE_CLASS = 'jp-ImageViewer';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A widget for images.
|
|
28
|
+
*/
|
|
29
|
+
export class ImageViewer extends Widget implements Printing.IPrintable {
|
|
30
|
+
/**
|
|
31
|
+
* Construct a new image widget.
|
|
32
|
+
*/
|
|
33
|
+
constructor(context: DocumentRegistry.Context) {
|
|
34
|
+
super();
|
|
35
|
+
this.context = context;
|
|
36
|
+
this.node.tabIndex = 0;
|
|
37
|
+
this.addClass(IMAGE_CLASS);
|
|
38
|
+
|
|
39
|
+
this._img = document.createElement('img');
|
|
40
|
+
this.node.appendChild(this._img);
|
|
41
|
+
|
|
42
|
+
this._onTitleChanged();
|
|
43
|
+
context.pathChanged.connect(this._onTitleChanged, this);
|
|
44
|
+
|
|
45
|
+
void context.ready.then(() => {
|
|
46
|
+
if (this.isDisposed) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const contents = context.contentsModel!;
|
|
50
|
+
this._mimeType = contents.mimetype;
|
|
51
|
+
this._render();
|
|
52
|
+
context.model.contentChanged.connect(this.update, this);
|
|
53
|
+
context.fileChanged.connect(this.update, this);
|
|
54
|
+
this._ready.resolve(void 0);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Print in iframe.
|
|
60
|
+
*/
|
|
61
|
+
[Printing.symbol]() {
|
|
62
|
+
return (): Promise<void> => Printing.printWidget(this);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The image widget's context.
|
|
67
|
+
*/
|
|
68
|
+
readonly context: DocumentRegistry.Context;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A promise that resolves when the image viewer is ready.
|
|
72
|
+
*/
|
|
73
|
+
get ready(): Promise<void> {
|
|
74
|
+
return this._ready.promise;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The scale factor for the image.
|
|
79
|
+
*/
|
|
80
|
+
get scale(): number {
|
|
81
|
+
return this._scale;
|
|
82
|
+
}
|
|
83
|
+
set scale(value: number) {
|
|
84
|
+
if (value === this._scale) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this._scale = value;
|
|
88
|
+
this._updateStyle();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The color inversion of the image.
|
|
93
|
+
*/
|
|
94
|
+
get colorinversion(): number {
|
|
95
|
+
return this._colorinversion;
|
|
96
|
+
}
|
|
97
|
+
set colorinversion(value: number) {
|
|
98
|
+
if (value === this._colorinversion) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this._colorinversion = value;
|
|
102
|
+
this._updateStyle();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Dispose of resources held by the image viewer.
|
|
107
|
+
*/
|
|
108
|
+
dispose(): void {
|
|
109
|
+
if (this._img.src) {
|
|
110
|
+
URL.revokeObjectURL(this._img.src || '');
|
|
111
|
+
}
|
|
112
|
+
super.dispose();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Reset rotation and flip transformations.
|
|
117
|
+
*/
|
|
118
|
+
resetRotationFlip(): void {
|
|
119
|
+
this._matrix = [1, 0, 0, 1];
|
|
120
|
+
this._updateStyle();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Rotate the image counter-clockwise (left).
|
|
125
|
+
*/
|
|
126
|
+
rotateCounterclockwise(): void {
|
|
127
|
+
this._matrix = Private.prod(
|
|
128
|
+
this._matrix,
|
|
129
|
+
Private.rotateCounterclockwiseMatrix
|
|
130
|
+
);
|
|
131
|
+
this._updateStyle();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Rotate the image clockwise (right).
|
|
136
|
+
*/
|
|
137
|
+
rotateClockwise(): void {
|
|
138
|
+
this._matrix = Private.prod(this._matrix, Private.rotateClockwiseMatrix);
|
|
139
|
+
this._updateStyle();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Flip the image horizontally.
|
|
144
|
+
*/
|
|
145
|
+
flipHorizontal(): void {
|
|
146
|
+
this._matrix = Private.prod(this._matrix, Private.flipHMatrix);
|
|
147
|
+
this._updateStyle();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Flip the image vertically.
|
|
152
|
+
*/
|
|
153
|
+
flipVertical(): void {
|
|
154
|
+
this._matrix = Private.prod(this._matrix, Private.flipVMatrix);
|
|
155
|
+
this._updateStyle();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handle `update-request` messages for the widget.
|
|
160
|
+
*/
|
|
161
|
+
protected onUpdateRequest(msg: Message): void {
|
|
162
|
+
if (this.isDisposed || !this.context.isReady) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this._render();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Handle `'activate-request'` messages.
|
|
170
|
+
*/
|
|
171
|
+
protected onActivateRequest(msg: Message): void {
|
|
172
|
+
this.node.focus();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Handle a change to the title.
|
|
177
|
+
*/
|
|
178
|
+
private _onTitleChanged(): void {
|
|
179
|
+
this.title.label = PathExt.basename(this.context.localPath);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Render the widget content.
|
|
184
|
+
*/
|
|
185
|
+
private _render(): void {
|
|
186
|
+
const context = this.context;
|
|
187
|
+
const cm = context.contentsModel;
|
|
188
|
+
if (!cm) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const oldurl = this._img.src || '';
|
|
192
|
+
let content = context.model.toString();
|
|
193
|
+
if (cm.format === 'base64') {
|
|
194
|
+
this._img.src = `data:${this._mimeType};base64,${content}`;
|
|
195
|
+
} else {
|
|
196
|
+
const a = new Blob([content], { type: this._mimeType });
|
|
197
|
+
this._img.src = URL.createObjectURL(a);
|
|
198
|
+
}
|
|
199
|
+
URL.revokeObjectURL(oldurl);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Update the image CSS style, including the transform and filter.
|
|
204
|
+
*/
|
|
205
|
+
private _updateStyle(): void {
|
|
206
|
+
const [a, b, c, d] = this._matrix;
|
|
207
|
+
const [tX, tY] = Private.prodVec(this._matrix, [1, 1]);
|
|
208
|
+
const transform = `matrix(${a}, ${b}, ${c}, ${d}, 0, 0) translate(${
|
|
209
|
+
tX < 0 ? -100 : 0
|
|
210
|
+
}%, ${tY < 0 ? -100 : 0}%) `;
|
|
211
|
+
this._img.style.transform = `scale(${this._scale}) ${transform}`;
|
|
212
|
+
this._img.style.filter = `invert(${this._colorinversion})`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private _mimeType: string;
|
|
216
|
+
private _scale = 1;
|
|
217
|
+
private _matrix = [1, 0, 0, 1];
|
|
218
|
+
private _colorinversion = 0;
|
|
219
|
+
private _ready = new PromiseDelegate<void>();
|
|
220
|
+
private _img: HTMLImageElement;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A widget factory for images.
|
|
225
|
+
*/
|
|
226
|
+
export class ImageViewerFactory extends ABCWidgetFactory<
|
|
227
|
+
IDocumentWidget<ImageViewer>
|
|
228
|
+
> {
|
|
229
|
+
/**
|
|
230
|
+
* Create a new widget given a context.
|
|
231
|
+
*/
|
|
232
|
+
protected createNewWidget(
|
|
233
|
+
context: DocumentRegistry.IContext<DocumentRegistry.IModel>
|
|
234
|
+
): IDocumentWidget<ImageViewer> {
|
|
235
|
+
const content = new ImageViewer(context);
|
|
236
|
+
const widget = new DocumentWidget({ content, context });
|
|
237
|
+
return widget;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A namespace for image widget private data.
|
|
243
|
+
*/
|
|
244
|
+
namespace Private {
|
|
245
|
+
/**
|
|
246
|
+
* Multiply 2x2 matrices.
|
|
247
|
+
*/
|
|
248
|
+
export function prod(
|
|
249
|
+
[a11, a12, a21, a22]: number[],
|
|
250
|
+
[b11, b12, b21, b22]: number[]
|
|
251
|
+
): number[] {
|
|
252
|
+
return [
|
|
253
|
+
a11 * b11 + a12 * b21,
|
|
254
|
+
a11 * b12 + a12 * b22,
|
|
255
|
+
a21 * b11 + a22 * b21,
|
|
256
|
+
a21 * b12 + a22 * b22
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Multiply a 2x2 matrix and a 2x1 vector.
|
|
262
|
+
*/
|
|
263
|
+
export function prodVec(
|
|
264
|
+
[a11, a12, a21, a22]: number[],
|
|
265
|
+
[b1, b2]: number[]
|
|
266
|
+
): number[] {
|
|
267
|
+
return [a11 * b1 + a12 * b2, a21 * b1 + a22 * b2];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Clockwise rotation transformation matrix.
|
|
272
|
+
*/
|
|
273
|
+
export const rotateClockwiseMatrix = [0, 1, -1, 0];
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Counter-clockwise rotation transformation matrix.
|
|
277
|
+
*/
|
|
278
|
+
export const rotateCounterclockwiseMatrix = [0, -1, 1, 0];
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Horizontal flip transformation matrix.
|
|
282
|
+
*/
|
|
283
|
+
export const flipHMatrix = [-1, 0, 0, 1];
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Vertical flip transformation matrix.
|
|
287
|
+
*/
|
|
288
|
+
export const flipVMatrix = [1, 0, 0, -1];
|
|
289
|
+
}
|