jupyterlab_advanced_image_viewer_extension 1.1.4 → 1.1.6

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 CHANGED
@@ -23,7 +23,7 @@ The viewer toolbar adds zoom in / out / fit controls and a `help` link that list
23
23
  - **Arrow-key navigation** - Left and Right open the previous and next image in the same folder, in the order the file browser lists them, advancing within one viewer instead of opening new tabs
24
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
25
25
  - **Refresh** - a toolbar refresh button reloads the image from disk, the same control the HTML viewer provides
26
- - **WebP support** - `.webp` files open in the image viewer with every feature above; stock JupyterLab opens them in the text editor
26
+ - **WebP and AVIF support** - `.webp` and `.avif` files open in the image viewer with every feature above; stock JupyterLab does not open them and shows a File Load Error
27
27
 
28
28
  ## Requirements
29
29
 
package/lib/index.js CHANGED
@@ -3,8 +3,10 @@ import { PathExt } from '@jupyterlab/coreutils';
3
3
  import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
4
4
  import { IImageTracker } from '@jupyterlab/imageviewer';
5
5
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
6
- import { refreshIcon } from '@jupyterlab/ui-components';
6
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
7
+ import { imageIcon, refreshIcon } from '@jupyterlab/ui-components';
7
8
  import { Widget } from '@lumino/widgets';
9
+ // eslint-disable-next-line jupyter/prefer-lazy-imports -- activation attaches the controller to restored image tabs at once
8
10
  import { ViewerController } from './controller';
9
11
  const PLUGIN_ID = 'jupyterlab_advanced_image_viewer_extension:plugin';
10
12
  const CommandIDs = {
@@ -22,11 +24,20 @@ const IMAGE_EXTS = new Set([
22
24
  '.gif',
23
25
  '.bmp',
24
26
  '.svg',
25
- '.webp'
27
+ '.webp',
28
+ '.avif'
26
29
  ]);
27
30
  // Raster formats that can be drawn to a canvas and copied as PNG. SVG is
28
31
  // excluded - the drawio/SVG extension already adds its own "Copy as PNG".
29
- const RASTER_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
32
+ const RASTER_EXTS = new Set([
33
+ '.png',
34
+ '.jpg',
35
+ '.jpeg',
36
+ '.gif',
37
+ '.bmp',
38
+ '.webp',
39
+ '.avif'
40
+ ]);
30
41
  const defaults = {
31
42
  navEnabled: true,
32
43
  zoomStep: 0.1
@@ -36,9 +47,10 @@ const plugin = {
36
47
  description: 'Advanced image viewer: cursor-anchored wheel zoom, drag-to-pan, fit-to-screen reset, and arrow-key folder navigation.',
37
48
  autoStart: true,
38
49
  requires: [IImageTracker],
39
- optional: [ISettingRegistry, IDefaultFileBrowser],
40
- activate: (app, tracker, settingRegistry, fileBrowser) => {
50
+ optional: [ISettingRegistry, IDefaultFileBrowser, ITranslator],
51
+ activate: (app, tracker, settingRegistry, fileBrowser, translator) => {
41
52
  console.log('JupyterLab extension jupyterlab_advanced_image_viewer_extension is activated!');
53
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab_advanced_image_viewer_extension');
42
54
  const controllers = new WeakMap();
43
55
  const state = { ...defaults };
44
56
  let keyBindings = [];
@@ -71,9 +83,9 @@ const plugin = {
71
83
  '</ul>'
72
84
  ].join('');
73
85
  void showDialog({
74
- title: 'Advanced Image Viewer',
86
+ title: trans.__('Advanced Image Viewer'),
75
87
  body,
76
- buttons: [Dialog.okButton({ label: 'Close' })]
88
+ buttons: [Dialog.okButton({ label: trans.__('Close') })]
77
89
  });
78
90
  };
79
91
  const attach = (widget) => {
@@ -97,7 +109,7 @@ const plugin = {
97
109
  // edited, so there is nothing here to discard.
98
110
  widget.toolbar.addItem('advanced-refresh', new ToolbarButton({
99
111
  icon: refreshIcon,
100
- tooltip: 'Reload image from disk',
112
+ tooltip: trans.__('Reload image from disk'),
101
113
  noFocusOnClick: true,
102
114
  onClick: async () => {
103
115
  await widget.context.revert();
@@ -106,28 +118,28 @@ const plugin = {
106
118
  }));
107
119
  widget.toolbar.addItem('advanced-zoom-out', new ToolbarButton({
108
120
  label: '-',
109
- tooltip: 'Zoom out',
121
+ tooltip: trans.__('Zoom out'),
110
122
  noFocusOnClick: true,
111
123
  onClick: () => app.commands.execute(CommandIDs.zoomOut)
112
124
  }));
113
125
  widget.toolbar.addItem('advanced-zoom-in', new ToolbarButton({
114
126
  label: '+',
115
- tooltip: 'Zoom in',
127
+ tooltip: trans.__('Zoom in'),
116
128
  noFocusOnClick: true,
117
129
  onClick: () => app.commands.execute(CommandIDs.zoomIn)
118
130
  }));
119
131
  widget.toolbar.addItem('advanced-reset-fit', new ToolbarButton({
120
- label: 'Fit',
121
- tooltip: 'Reset to fit',
132
+ label: trans.__('Fit'),
133
+ tooltip: trans.__('Reset to fit'),
122
134
  noFocusOnClick: true,
123
135
  onClick: () => app.commands.execute(CommandIDs.resetFit)
124
136
  }));
125
137
  widget.toolbar.addItem('advanced-spacer', Toolbar.createSpacerItem());
126
138
  const helpLink = new Widget({ node: document.createElement('a') });
127
139
  const helpAnchor = helpLink.node;
128
- helpAnchor.textContent = 'help';
140
+ helpAnchor.textContent = trans.__('help');
129
141
  helpAnchor.href = '#';
130
- helpAnchor.title = 'Keybindings and help';
142
+ helpAnchor.title = trans.__('Keybindings and help');
131
143
  helpAnchor.className = 'jp-AdvancedImageViewer-help-link';
132
144
  helpAnchor.addEventListener('click', event => {
133
145
  event.preventDefault();
@@ -148,18 +160,31 @@ const plugin = {
148
160
  if (webp) {
149
161
  app.docRegistry.addFileType(webp, ['Image']);
150
162
  }
163
+ // JupyterLab registers no 'avif' file type at all, so add one with the
164
+ // same shape as its other image types, bound to the same factory.
165
+ app.docRegistry.addFileType({
166
+ name: 'avif',
167
+ displayName: 'Image',
168
+ mimeTypes: ['image/avif'],
169
+ extensions: ['.avif'],
170
+ icon: imageIcon,
171
+ fileFormat: 'base64'
172
+ }, ['Image']);
151
173
  app.commands.addCommand(CommandIDs.zoomIn, {
152
- label: 'Zoom In (Advanced Image Viewer)',
174
+ label: trans.__('Zoom In (Advanced Image Viewer)'),
175
+ describedBy: { args: { type: 'object', properties: {} } },
153
176
  isEnabled: () => currentController() !== null,
154
177
  execute: () => { var _a; return (_a = currentController()) === null || _a === void 0 ? void 0 : _a.zoomIn(); }
155
178
  });
156
179
  app.commands.addCommand(CommandIDs.zoomOut, {
157
- label: 'Zoom Out (Advanced Image Viewer)',
180
+ label: trans.__('Zoom Out (Advanced Image Viewer)'),
181
+ describedBy: { args: { type: 'object', properties: {} } },
158
182
  isEnabled: () => currentController() !== null,
159
183
  execute: () => { var _a; return (_a = currentController()) === null || _a === void 0 ? void 0 : _a.zoomOut(); }
160
184
  });
161
185
  app.commands.addCommand(CommandIDs.resetFit, {
162
- label: 'Reset to Fit (Advanced Image Viewer)',
186
+ label: trans.__('Reset to Fit (Advanced Image Viewer)'),
187
+ describedBy: { args: { type: 'object', properties: {} } },
163
188
  isEnabled: () => currentController() !== null,
164
189
  execute: () => { var _a; return (_a = currentController()) === null || _a === void 0 ? void 0 : _a.reset(); }
165
190
  });
@@ -175,7 +200,8 @@ const plugin = {
175
200
  return (_a = tracker.find(widget => widget.content.node === node)) !== null && _a !== void 0 ? _a : null;
176
201
  };
177
202
  app.commands.addCommand(CommandIDs.copyClipboard, {
178
- label: 'Copy to Clipboard',
203
+ label: trans.__('Copy to Clipboard'),
204
+ describedBy: { args: { type: 'object', properties: {} } },
179
205
  // Raster only - SVG already has "Copy as PNG" from the drawio extension.
180
206
  isVisible: () => {
181
207
  const widget = contextImageWidget();
@@ -281,12 +307,14 @@ const plugin = {
281
307
  }
282
308
  };
283
309
  app.commands.addCommand(CommandIDs.previous, {
284
- label: 'Previous Image In Folder',
310
+ label: trans.__('Previous Image In Folder'),
311
+ describedBy: { args: { type: 'object', properties: {} } },
285
312
  isEnabled: () => state.navEnabled && tracker.currentWidget !== null,
286
313
  execute: () => navigate(-1)
287
314
  });
288
315
  app.commands.addCommand(CommandIDs.next, {
289
- label: 'Next Image In Folder',
316
+ label: trans.__('Next Image In Folder'),
317
+ describedBy: { args: { type: 'object', properties: {} } },
290
318
  isEnabled: () => state.navEnabled && tracker.currentWidget !== null,
291
319
  execute: () => navigate(1)
292
320
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_advanced_image_viewer_extension",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
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",
@@ -63,6 +63,7 @@
63
63
  "@jupyterlab/filebrowser": "^4.6.0",
64
64
  "@jupyterlab/imageviewer": "^4.6.0",
65
65
  "@jupyterlab/settingregistry": "^4.6.0",
66
+ "@jupyterlab/translation": "^4.6.0",
66
67
  "@jupyterlab/ui-components": "^4.6.0",
67
68
  "@lumino/disposable": "^2.1.0",
68
69
  "@lumino/widgets": "^2.9.0"
package/src/index.ts CHANGED
@@ -20,12 +20,15 @@ import { IImageTracker, ImageViewer } from '@jupyterlab/imageviewer';
20
20
 
21
21
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
22
22
 
23
- import { refreshIcon } from '@jupyterlab/ui-components';
23
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
24
+
25
+ import { imageIcon, refreshIcon } from '@jupyterlab/ui-components';
24
26
 
25
27
  import { IDisposable } from '@lumino/disposable';
26
28
 
27
29
  import { Widget } from '@lumino/widgets';
28
30
 
31
+ // eslint-disable-next-line jupyter/prefer-lazy-imports -- activation attaches the controller to restored image tabs at once
29
32
  import { ViewerController } from './controller';
30
33
 
31
34
  const PLUGIN_ID = 'jupyterlab_advanced_image_viewer_extension:plugin';
@@ -46,12 +49,21 @@ const IMAGE_EXTS = new Set([
46
49
  '.gif',
47
50
  '.bmp',
48
51
  '.svg',
49
- '.webp'
52
+ '.webp',
53
+ '.avif'
50
54
  ]);
51
55
 
52
56
  // Raster formats that can be drawn to a canvas and copied as PNG. SVG is
53
57
  // excluded - the drawio/SVG extension already adds its own "Copy as PNG".
54
- const RASTER_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']);
58
+ const RASTER_EXTS = new Set([
59
+ '.png',
60
+ '.jpg',
61
+ '.jpeg',
62
+ '.gif',
63
+ '.bmp',
64
+ '.webp',
65
+ '.avif'
66
+ ]);
55
67
 
56
68
  interface ISettingsState {
57
69
  navEnabled: boolean;
@@ -69,16 +81,20 @@ const plugin: JupyterFrontEndPlugin<void> = {
69
81
  'Advanced image viewer: cursor-anchored wheel zoom, drag-to-pan, fit-to-screen reset, and arrow-key folder navigation.',
70
82
  autoStart: true,
71
83
  requires: [IImageTracker],
72
- optional: [ISettingRegistry, IDefaultFileBrowser],
84
+ optional: [ISettingRegistry, IDefaultFileBrowser, ITranslator],
73
85
  activate: (
74
86
  app: JupyterFrontEnd,
75
87
  tracker: IImageTracker,
76
88
  settingRegistry: ISettingRegistry | null,
77
- fileBrowser: IDefaultFileBrowser | null
89
+ fileBrowser: IDefaultFileBrowser | null,
90
+ translator: ITranslator | null
78
91
  ): void => {
79
92
  console.log(
80
93
  'JupyterLab extension jupyterlab_advanced_image_viewer_extension is activated!'
81
94
  );
95
+ const trans = (translator ?? nullTranslator).load(
96
+ 'jupyterlab_advanced_image_viewer_extension'
97
+ );
82
98
  const controllers = new WeakMap<ImageViewer, ViewerController>();
83
99
  const state: ISettingsState = { ...defaults };
84
100
  let keyBindings: IDisposable[] = [];
@@ -112,9 +128,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
112
128
  '</ul>'
113
129
  ].join('');
114
130
  void showDialog({
115
- title: 'Advanced Image Viewer',
131
+ title: trans.__('Advanced Image Viewer'),
116
132
  body,
117
- buttons: [Dialog.okButton({ label: 'Close' })]
133
+ buttons: [Dialog.okButton({ label: trans.__('Close') })]
118
134
  });
119
135
  };
120
136
 
@@ -146,7 +162,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
146
162
  'advanced-refresh',
147
163
  new ToolbarButton({
148
164
  icon: refreshIcon,
149
- tooltip: 'Reload image from disk',
165
+ tooltip: trans.__('Reload image from disk'),
150
166
  noFocusOnClick: true,
151
167
  onClick: async () => {
152
168
  await widget.context.revert();
@@ -158,7 +174,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
158
174
  'advanced-zoom-out',
159
175
  new ToolbarButton({
160
176
  label: '-',
161
- tooltip: 'Zoom out',
177
+ tooltip: trans.__('Zoom out'),
162
178
  noFocusOnClick: true,
163
179
  onClick: () => app.commands.execute(CommandIDs.zoomOut)
164
180
  })
@@ -167,7 +183,7 @@ const plugin: JupyterFrontEndPlugin<void> = {
167
183
  'advanced-zoom-in',
168
184
  new ToolbarButton({
169
185
  label: '+',
170
- tooltip: 'Zoom in',
186
+ tooltip: trans.__('Zoom in'),
171
187
  noFocusOnClick: true,
172
188
  onClick: () => app.commands.execute(CommandIDs.zoomIn)
173
189
  })
@@ -175,8 +191,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
175
191
  widget.toolbar.addItem(
176
192
  'advanced-reset-fit',
177
193
  new ToolbarButton({
178
- label: 'Fit',
179
- tooltip: 'Reset to fit',
194
+ label: trans.__('Fit'),
195
+ tooltip: trans.__('Reset to fit'),
180
196
  noFocusOnClick: true,
181
197
  onClick: () => app.commands.execute(CommandIDs.resetFit)
182
198
  })
@@ -184,9 +200,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
184
200
  widget.toolbar.addItem('advanced-spacer', Toolbar.createSpacerItem());
185
201
  const helpLink = new Widget({ node: document.createElement('a') });
186
202
  const helpAnchor = helpLink.node as HTMLAnchorElement;
187
- helpAnchor.textContent = 'help';
203
+ helpAnchor.textContent = trans.__('help');
188
204
  helpAnchor.href = '#';
189
- helpAnchor.title = 'Keybindings and help';
205
+ helpAnchor.title = trans.__('Keybindings and help');
190
206
  helpAnchor.className = 'jp-AdvancedImageViewer-help-link';
191
207
  helpAnchor.addEventListener('click', event => {
192
208
  event.preventDefault();
@@ -209,19 +225,35 @@ const plugin: JupyterFrontEndPlugin<void> = {
209
225
  if (webp) {
210
226
  app.docRegistry.addFileType(webp, ['Image']);
211
227
  }
228
+ // JupyterLab registers no 'avif' file type at all, so add one with the
229
+ // same shape as its other image types, bound to the same factory.
230
+ app.docRegistry.addFileType(
231
+ {
232
+ name: 'avif',
233
+ displayName: 'Image',
234
+ mimeTypes: ['image/avif'],
235
+ extensions: ['.avif'],
236
+ icon: imageIcon,
237
+ fileFormat: 'base64'
238
+ },
239
+ ['Image']
240
+ );
212
241
 
213
242
  app.commands.addCommand(CommandIDs.zoomIn, {
214
- label: 'Zoom In (Advanced Image Viewer)',
243
+ label: trans.__('Zoom In (Advanced Image Viewer)'),
244
+ describedBy: { args: { type: 'object', properties: {} } },
215
245
  isEnabled: () => currentController() !== null,
216
246
  execute: () => currentController()?.zoomIn()
217
247
  });
218
248
  app.commands.addCommand(CommandIDs.zoomOut, {
219
- label: 'Zoom Out (Advanced Image Viewer)',
249
+ label: trans.__('Zoom Out (Advanced Image Viewer)'),
250
+ describedBy: { args: { type: 'object', properties: {} } },
220
251
  isEnabled: () => currentController() !== null,
221
252
  execute: () => currentController()?.zoomOut()
222
253
  });
223
254
  app.commands.addCommand(CommandIDs.resetFit, {
224
- label: 'Reset to Fit (Advanced Image Viewer)',
255
+ label: trans.__('Reset to Fit (Advanced Image Viewer)'),
256
+ describedBy: { args: { type: 'object', properties: {} } },
225
257
  isEnabled: () => currentController() !== null,
226
258
  execute: () => currentController()?.reset()
227
259
  });
@@ -240,7 +272,8 @@ const plugin: JupyterFrontEndPlugin<void> = {
240
272
  };
241
273
 
242
274
  app.commands.addCommand(CommandIDs.copyClipboard, {
243
- label: 'Copy to Clipboard',
275
+ label: trans.__('Copy to Clipboard'),
276
+ describedBy: { args: { type: 'object', properties: {} } },
244
277
  // Raster only - SVG already has "Copy as PNG" from the drawio extension.
245
278
  isVisible: () => {
246
279
  const widget = contextImageWidget();
@@ -359,12 +392,14 @@ const plugin: JupyterFrontEndPlugin<void> = {
359
392
  };
360
393
 
361
394
  app.commands.addCommand(CommandIDs.previous, {
362
- label: 'Previous Image In Folder',
395
+ label: trans.__('Previous Image In Folder'),
396
+ describedBy: { args: { type: 'object', properties: {} } },
363
397
  isEnabled: () => state.navEnabled && tracker.currentWidget !== null,
364
398
  execute: () => navigate(-1)
365
399
  });
366
400
  app.commands.addCommand(CommandIDs.next, {
367
- label: 'Next Image In Folder',
401
+ label: trans.__('Next Image In Folder'),
402
+ describedBy: { args: { type: 'object', properties: {} } },
368
403
  isEnabled: () => state.navEnabled && tracker.currentWidget !== null,
369
404
  execute: () => navigate(1)
370
405
  });