jupyterlab_advanced_image_viewer_extension 1.0.7 → 1.0.9
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 +1 -0
- package/lib/index.js +58 -1
- package/package.json +1 -1
- package/src/index.ts +65 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ The viewer toolbar adds zoom in / out / fit controls and a `help` link that list
|
|
|
21
21
|
- **Drag to pan** - hold and drag to move the image at any zoom (canvas-style); the grab cursor is always available and Fit recenters
|
|
22
22
|
- **Toolbar controls** - zoom in, zoom out, and reset-to-fit buttons on the viewer toolbar
|
|
23
23
|
- **Arrow-key navigation** - Left and Right open the previous and next image in the same folder, advancing within one viewer instead of opening new tabs
|
|
24
|
+
- **Copy to clipboard** - right-click a raster image (PNG/JPG/…) and choose "Copy to Clipboard" to copy it as PNG to the system clipboard
|
|
24
25
|
|
|
25
26
|
## Requirements
|
|
26
27
|
|
package/lib/index.js
CHANGED
|
@@ -10,7 +10,8 @@ const CommandIDs = {
|
|
|
10
10
|
zoomOut: 'advanced-image-viewer:zoom-out',
|
|
11
11
|
resetFit: 'advanced-image-viewer:reset-fit',
|
|
12
12
|
previous: 'advanced-image-viewer:previous-image',
|
|
13
|
-
next: 'advanced-image-viewer:next-image'
|
|
13
|
+
next: 'advanced-image-viewer:next-image',
|
|
14
|
+
copyClipboard: 'advanced-image-viewer:copy-to-clipboard'
|
|
14
15
|
};
|
|
15
16
|
const IMAGE_EXTS = new Set([
|
|
16
17
|
'.png',
|
|
@@ -21,6 +22,9 @@ const IMAGE_EXTS = new Set([
|
|
|
21
22
|
'.svg',
|
|
22
23
|
'.webp'
|
|
23
24
|
]);
|
|
25
|
+
// Raster formats that can be drawn to a canvas and copied as PNG. SVG is
|
|
26
|
+
// excluded - the drawio/SVG extension already adds its own "Copy as PNG".
|
|
27
|
+
const RASTER_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
|
|
24
28
|
const defaults = {
|
|
25
29
|
navEnabled: true,
|
|
26
30
|
zoomStep: 0.1
|
|
@@ -131,6 +135,59 @@ const plugin = {
|
|
|
131
135
|
isEnabled: () => currentController() !== null,
|
|
132
136
|
execute: () => { var _a; return (_a = currentController()) === null || _a === void 0 ? void 0 : _a.reset(); }
|
|
133
137
|
});
|
|
138
|
+
// Resolve the image widget under the context-menu target. Right-click does
|
|
139
|
+
// not activate the widget, so hit-test the clicked node rather than rely on
|
|
140
|
+
// tracker.currentWidget.
|
|
141
|
+
const contextImageWidget = () => {
|
|
142
|
+
var _a;
|
|
143
|
+
const node = app.contextMenuHitTest(n => n.classList.contains('jp-ImageViewer'));
|
|
144
|
+
if (!node) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return (_a = tracker.find(widget => widget.content.node === node)) !== null && _a !== void 0 ? _a : null;
|
|
148
|
+
};
|
|
149
|
+
app.commands.addCommand(CommandIDs.copyClipboard, {
|
|
150
|
+
label: 'Copy to Clipboard',
|
|
151
|
+
// Raster only - SVG already has "Copy as PNG" from the drawio extension.
|
|
152
|
+
isVisible: () => {
|
|
153
|
+
const widget = contextImageWidget();
|
|
154
|
+
return (widget !== null &&
|
|
155
|
+
RASTER_EXTS.has(PathExt.extname(widget.context.path).toLowerCase()));
|
|
156
|
+
},
|
|
157
|
+
execute: async () => {
|
|
158
|
+
var _a;
|
|
159
|
+
const img = (_a = contextImageWidget()) === null || _a === void 0 ? void 0 : _a.content.node.querySelector('img');
|
|
160
|
+
if (!img) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Copy the source pixels, not the on-screen zoom/pan/rotate view.
|
|
164
|
+
const canvas = document.createElement('canvas');
|
|
165
|
+
canvas.width = img.naturalWidth;
|
|
166
|
+
canvas.height = img.naturalHeight;
|
|
167
|
+
const ctx = canvas.getContext('2d');
|
|
168
|
+
if (!ctx) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
ctx.drawImage(img, 0, 0);
|
|
172
|
+
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'));
|
|
173
|
+
if (!blob) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await navigator.clipboard.write([
|
|
178
|
+
new ClipboardItem({ 'image/png': blob })
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
console.error('Advanced image viewer: clipboard copy failed', err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
app.contextMenu.addItem({
|
|
187
|
+
command: CommandIDs.copyClipboard,
|
|
188
|
+
selector: '.jp-ImageViewer',
|
|
189
|
+
rank: 0
|
|
190
|
+
});
|
|
134
191
|
const navigate = async (delta) => {
|
|
135
192
|
const widget = tracker.currentWidget;
|
|
136
193
|
if (!widget) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab_advanced_image_viewer_extension",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "JupyterLab extension for advanced image viewing: fit-to-screen, cursor-anchored wheel zoom, drag-to-pan, and Left/Right arrow keys to move to the previous and next image in a folder",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,8 @@ const CommandIDs = {
|
|
|
31
31
|
zoomOut: 'advanced-image-viewer:zoom-out',
|
|
32
32
|
resetFit: 'advanced-image-viewer:reset-fit',
|
|
33
33
|
previous: 'advanced-image-viewer:previous-image',
|
|
34
|
-
next: 'advanced-image-viewer:next-image'
|
|
34
|
+
next: 'advanced-image-viewer:next-image',
|
|
35
|
+
copyClipboard: 'advanced-image-viewer:copy-to-clipboard'
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const IMAGE_EXTS = new Set([
|
|
@@ -44,6 +45,10 @@ const IMAGE_EXTS = new Set([
|
|
|
44
45
|
'.webp'
|
|
45
46
|
]);
|
|
46
47
|
|
|
48
|
+
// Raster formats that can be drawn to a canvas and copied as PNG. SVG is
|
|
49
|
+
// excluded - the drawio/SVG extension already adds its own "Copy as PNG".
|
|
50
|
+
const RASTER_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
|
|
51
|
+
|
|
47
52
|
interface ISettingsState {
|
|
48
53
|
navEnabled: boolean;
|
|
49
54
|
zoomStep: number;
|
|
@@ -186,6 +191,65 @@ const plugin: JupyterFrontEndPlugin<void> = {
|
|
|
186
191
|
execute: () => currentController()?.reset()
|
|
187
192
|
});
|
|
188
193
|
|
|
194
|
+
// Resolve the image widget under the context-menu target. Right-click does
|
|
195
|
+
// not activate the widget, so hit-test the clicked node rather than rely on
|
|
196
|
+
// tracker.currentWidget.
|
|
197
|
+
const contextImageWidget = (): IDocumentWidget<ImageViewer> | null => {
|
|
198
|
+
const node = app.contextMenuHitTest(n =>
|
|
199
|
+
n.classList.contains('jp-ImageViewer')
|
|
200
|
+
);
|
|
201
|
+
if (!node) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return tracker.find(widget => widget.content.node === node) ?? null;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
app.commands.addCommand(CommandIDs.copyClipboard, {
|
|
208
|
+
label: 'Copy to Clipboard',
|
|
209
|
+
// Raster only - SVG already has "Copy as PNG" from the drawio extension.
|
|
210
|
+
isVisible: () => {
|
|
211
|
+
const widget = contextImageWidget();
|
|
212
|
+
return (
|
|
213
|
+
widget !== null &&
|
|
214
|
+
RASTER_EXTS.has(PathExt.extname(widget.context.path).toLowerCase())
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
execute: async () => {
|
|
218
|
+
const img = contextImageWidget()?.content.node.querySelector('img');
|
|
219
|
+
if (!img) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Copy the source pixels, not the on-screen zoom/pan/rotate view.
|
|
223
|
+
const canvas = document.createElement('canvas');
|
|
224
|
+
canvas.width = img.naturalWidth;
|
|
225
|
+
canvas.height = img.naturalHeight;
|
|
226
|
+
const ctx = canvas.getContext('2d');
|
|
227
|
+
if (!ctx) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
ctx.drawImage(img, 0, 0);
|
|
231
|
+
const blob = await new Promise<Blob | null>(resolve =>
|
|
232
|
+
canvas.toBlob(resolve, 'image/png')
|
|
233
|
+
);
|
|
234
|
+
if (!blob) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
await navigator.clipboard.write([
|
|
239
|
+
new ClipboardItem({ 'image/png': blob })
|
|
240
|
+
]);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error('Advanced image viewer: clipboard copy failed', err);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
app.contextMenu.addItem({
|
|
248
|
+
command: CommandIDs.copyClipboard,
|
|
249
|
+
selector: '.jp-ImageViewer',
|
|
250
|
+
rank: 0
|
|
251
|
+
});
|
|
252
|
+
|
|
189
253
|
const navigate = async (delta: number): Promise<void> => {
|
|
190
254
|
const widget = tracker.currentWidget;
|
|
191
255
|
if (!widget) {
|