jupyterlab_advanced_image_viewer_extension 1.1.2 → 1.1.3

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
@@ -20,14 +20,14 @@ The viewer toolbar adds zoom in / out / fit controls and a `help` link that list
20
20
  - **Wheel zoom** - scroll to zoom in and out, anchored at the cursor; zoom out as far as you like
21
21
  - **Drag to pan** - hold and drag to move the image at any zoom; the image can touch a viewport border but never leave it, 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
- - **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
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
26
  - **WebP support** - `.webp` files open in the image viewer with every feature above; stock JupyterLab opens them in the text editor
27
27
 
28
28
  ## Requirements
29
29
 
30
- - JupyterLab >= 4.0.0
30
+ - JupyterLab >= 4.6
31
31
 
32
32
  ## Install
33
33
 
package/lib/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Dialog, showDialog, Toolbar, ToolbarButton } from '@jupyterlab/apputils';
2
2
  import { PathExt } from '@jupyterlab/coreutils';
3
+ import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
3
4
  import { IImageTracker } from '@jupyterlab/imageviewer';
4
5
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
5
6
  import { refreshIcon } from '@jupyterlab/ui-components';
@@ -35,8 +36,8 @@ const plugin = {
35
36
  description: 'Advanced image viewer: cursor-anchored wheel zoom, drag-to-pan, fit-to-screen reset, and arrow-key folder navigation.',
36
37
  autoStart: true,
37
38
  requires: [IImageTracker],
38
- optional: [ISettingRegistry],
39
- activate: (app, tracker, settingRegistry) => {
39
+ optional: [ISettingRegistry, IDefaultFileBrowser],
40
+ activate: (app, tracker, settingRegistry, fileBrowser) => {
40
41
  console.log('JupyterLab extension jupyterlab_advanced_image_viewer_extension is activated!');
41
42
  const controllers = new WeakMap();
42
43
  const state = { ...defaults };
@@ -218,20 +219,34 @@ const plugin = {
218
219
  }
219
220
  const path = widget.context.path;
220
221
  const dirPath = PathExt.dirname(path);
221
- const listing = await app.serviceManager.contents.get(dirPath, {
222
- content: true
223
- });
224
- const content = listing.content;
225
- if (!content) {
226
- return;
222
+ const isImage = (item) => item.type === 'file' &&
223
+ IMAGE_EXTS.has(PathExt.extname(item.name).toLowerCase());
224
+ // Step through the images in the order the file browser shows them
225
+ // when it lists this folder, so its sort key, its direction and any
226
+ // sort plugin decide what comes next: a C-locale sort puts '.' before
227
+ // '_' where localeCompare puts '_' first, and a re-sort here skipped
228
+ // files. FileBrowser keeps `listing` protected, but it is the only
229
+ // source of the displayed order. Otherwise fall back to natural name
230
+ // order, the stock file browser default.
231
+ let images = [];
232
+ if (fileBrowser && fileBrowser.model.path === dirPath) {
233
+ const shown = fileBrowser
234
+ .listing;
235
+ images = Array.from(shown.sortedItems()).filter(isImage);
236
+ }
237
+ if (!images.some(item => item.path === path)) {
238
+ const listing = await app.serviceManager.contents.get(dirPath, {
239
+ content: true
240
+ });
241
+ const content = listing.content;
242
+ if (!content) {
243
+ return;
244
+ }
245
+ images = content.filter(isImage).sort((a, b) => a.name.localeCompare(b.name, undefined, {
246
+ numeric: true,
247
+ sensitivity: 'base'
248
+ }));
227
249
  }
228
- const images = content
229
- .filter(item => item.type === 'file' &&
230
- IMAGE_EXTS.has(PathExt.extname(item.name).toLowerCase()))
231
- .sort((a, b) => a.name.localeCompare(b.name, undefined, {
232
- numeric: true,
233
- sensitivity: 'base'
234
- }));
235
250
  if (images.length <= 1) {
236
251
  return;
237
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab_advanced_image_viewer_extension",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
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",
@@ -31,8 +31,8 @@
31
31
  "scripts": {
32
32
  "build": "jlpm build:lib && jlpm build:labextension:dev",
33
33
  "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
34
- "build:labextension": "jupyter labextension build .",
35
- "build:labextension:dev": "jupyter labextension build --development True .",
34
+ "build:labextension": "jupyter-builder build .",
35
+ "build:labextension:dev": "jupyter-builder build --development True .",
36
36
  "build:lib": "tsc --sourceMap",
37
37
  "build:lib:prod": "tsc",
38
38
  "clean": "jlpm clean:lib",
@@ -41,7 +41,7 @@
41
41
  "clean:labextension": "rimraf jupyterlab_advanced_image_viewer_extension/labextension jupyterlab_advanced_image_viewer_extension/_version.py",
42
42
  "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
43
43
  "eslint": "jlpm eslint:check --fix",
44
- "eslint:check": "eslint . --cache --ext .ts,.tsx",
44
+ "eslint:check": "eslint . --cache",
45
45
  "install:extension": "jlpm build",
46
46
  "lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
47
47
  "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
@@ -53,46 +53,46 @@
53
53
  "test": "jest --coverage",
54
54
  "watch": "run-p watch:src watch:labextension",
55
55
  "watch:src": "tsc -w --sourceMap",
56
- "watch:labextension": "jupyter labextension watch ."
56
+ "watch:labextension": "jupyter-builder watch ."
57
57
  },
58
58
  "dependencies": {
59
- "@jupyterlab/application": "^4.0.0",
60
- "@jupyterlab/apputils": "^4.0.0",
61
- "@jupyterlab/coreutils": "^6.0.0",
62
- "@jupyterlab/docregistry": "^4.0.0",
63
- "@jupyterlab/imageviewer": "^4.0.0",
64
- "@jupyterlab/settingregistry": "^4.0.0",
65
- "@jupyterlab/ui-components": "^4.0.0",
66
- "@lumino/disposable": "^2.0.0",
67
- "@lumino/widgets": "^2.0.0"
59
+ "@jupyterlab/application": "^4.6.0",
60
+ "@jupyterlab/apputils": "^4.7.0",
61
+ "@jupyterlab/coreutils": "^6.6.0",
62
+ "@jupyterlab/docregistry": "^4.6.0",
63
+ "@jupyterlab/filebrowser": "^4.6.0",
64
+ "@jupyterlab/imageviewer": "^4.6.0",
65
+ "@jupyterlab/settingregistry": "^4.6.0",
66
+ "@jupyterlab/ui-components": "^4.6.0",
67
+ "@lumino/disposable": "^2.1.0",
68
+ "@lumino/widgets": "^2.9.0"
68
69
  },
69
70
  "devDependencies": {
70
- "@jupyterlab/builder": "^4.0.0",
71
- "@jupyterlab/testutils": "^4.0.0",
72
- "@types/jest": "^29.2.0",
71
+ "@eslint/js": "^9.39.0",
72
+ "@jupyter/builder": "^1.2.0",
73
+ "@jupyter/eslint-plugin": "^1.3.0",
74
+ "@jupyterlab/testutils": "^4.6.0",
75
+ "@types/jest": "^29.5.0",
73
76
  "@types/json-schema": "^7.0.11",
74
- "@types/react": "^18.0.26",
77
+ "@types/react": "^18.3.0",
75
78
  "@types/react-addons-linked-state-mixin": "^0.14.22",
76
- "@typescript-eslint/eslint-plugin": "^6.1.0",
77
- "@typescript-eslint/parser": "^6.1.0",
78
- "css-loader": "^6.7.1",
79
- "eslint": "^8.36.0",
80
- "eslint-config-prettier": "^8.8.0",
81
- "eslint-plugin-prettier": "^5.0.0",
82
- "jest": "^29.2.0",
79
+ "eslint": "^9.39.0",
80
+ "eslint-config-prettier": "^9.1.0",
81
+ "eslint-plugin-prettier": "^5.5.0",
82
+ "globals": "^15.15.0",
83
+ "jest": "^29.7.0",
83
84
  "mkdirp": "^1.0.3",
84
85
  "npm-run-all2": "^7.0.1",
85
- "prettier": "^3.0.0",
86
+ "prettier": "^3.9.0",
86
87
  "rimraf": "^5.0.1",
87
- "source-map-loader": "^1.0.2",
88
- "style-loader": "^3.3.1",
89
- "stylelint": "^15.10.1",
88
+ "stylelint": "^15.11.0",
90
89
  "stylelint-config-recommended": "^13.0.0",
91
90
  "stylelint-config-standard": "^34.0.0",
92
- "stylelint-csstree-validator": "^3.0.0",
93
- "stylelint-prettier": "^4.0.0",
91
+ "stylelint-csstree-validator": "^3.1.0",
92
+ "stylelint-prettier": "^4.1.0",
94
93
  "typescript": "~5.8.0",
95
- "yjs": "^13.5.0"
94
+ "typescript-eslint": "^8.71.0",
95
+ "yjs": "^13.6.0"
96
96
  },
97
97
  "resolutions": {
98
98
  "lib0": "0.2.111",
@@ -117,69 +117,6 @@
117
117
  "outputDir": "jupyterlab_advanced_image_viewer_extension/labextension",
118
118
  "schemaDir": "schema"
119
119
  },
120
- "eslintIgnore": [
121
- "node_modules",
122
- "dist",
123
- "coverage",
124
- "**/*.d.ts",
125
- "tests",
126
- "**/__tests__",
127
- "ui-tests"
128
- ],
129
- "eslintConfig": {
130
- "extends": [
131
- "eslint:recommended",
132
- "plugin:@typescript-eslint/eslint-recommended",
133
- "plugin:@typescript-eslint/recommended",
134
- "plugin:prettier/recommended"
135
- ],
136
- "parser": "@typescript-eslint/parser",
137
- "parserOptions": {
138
- "project": "tsconfig.json",
139
- "sourceType": "module"
140
- },
141
- "plugins": [
142
- "@typescript-eslint"
143
- ],
144
- "rules": {
145
- "@typescript-eslint/naming-convention": [
146
- "error",
147
- {
148
- "selector": "interface",
149
- "format": [
150
- "PascalCase"
151
- ],
152
- "custom": {
153
- "regex": "^I[A-Z]",
154
- "match": true
155
- }
156
- }
157
- ],
158
- "@typescript-eslint/no-unused-vars": [
159
- "warn",
160
- {
161
- "args": "none"
162
- }
163
- ],
164
- "@typescript-eslint/no-explicit-any": "off",
165
- "@typescript-eslint/no-namespace": "off",
166
- "@typescript-eslint/no-use-before-define": "off",
167
- "@typescript-eslint/quotes": [
168
- "error",
169
- "single",
170
- {
171
- "avoidEscape": true,
172
- "allowTemplateLiterals": false
173
- }
174
- ],
175
- "curly": [
176
- "error",
177
- "all"
178
- ],
179
- "eqeqeq": "error",
180
- "prefer-arrow-callback": "error"
181
- }
182
- },
183
120
  "prettier": {
184
121
  "singleQuote": true,
185
122
  "trailingComma": "none",
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ import { PathExt } from '@jupyterlab/coreutils';
14
14
 
15
15
  import { IDocumentWidget } from '@jupyterlab/docregistry';
16
16
 
17
+ import { DirListing, IDefaultFileBrowser } from '@jupyterlab/filebrowser';
18
+
17
19
  import { IImageTracker, ImageViewer } from '@jupyterlab/imageviewer';
18
20
 
19
21
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
@@ -67,11 +69,12 @@ const plugin: JupyterFrontEndPlugin<void> = {
67
69
  'Advanced image viewer: cursor-anchored wheel zoom, drag-to-pan, fit-to-screen reset, and arrow-key folder navigation.',
68
70
  autoStart: true,
69
71
  requires: [IImageTracker],
70
- optional: [ISettingRegistry],
72
+ optional: [ISettingRegistry, IDefaultFileBrowser],
71
73
  activate: (
72
74
  app: JupyterFrontEnd,
73
75
  tracker: IImageTracker,
74
- settingRegistry: ISettingRegistry | null
76
+ settingRegistry: ISettingRegistry | null,
77
+ fileBrowser: IDefaultFileBrowser | null
75
78
  ): void => {
76
79
  console.log(
77
80
  'JupyterLab extension jupyterlab_advanced_image_viewer_extension is activated!'
@@ -285,29 +288,41 @@ const plugin: JupyterFrontEndPlugin<void> = {
285
288
  }
286
289
  const path = widget.context.path;
287
290
  const dirPath = PathExt.dirname(path);
288
- const listing = await app.serviceManager.contents.get(dirPath, {
289
- content: true
290
- });
291
- const content = listing.content as Array<{
292
- name: string;
293
- path: string;
294
- type: string;
295
- }> | null;
296
- if (!content) {
297
- return;
291
+ const isImage = (item: { name: string; type: string }): boolean =>
292
+ item.type === 'file' &&
293
+ IMAGE_EXTS.has(PathExt.extname(item.name).toLowerCase());
294
+ // Step through the images in the order the file browser shows them
295
+ // when it lists this folder, so its sort key, its direction and any
296
+ // sort plugin decide what comes next: a C-locale sort puts '.' before
297
+ // '_' where localeCompare puts '_' first, and a re-sort here skipped
298
+ // files. FileBrowser keeps `listing` protected, but it is the only
299
+ // source of the displayed order. Otherwise fall back to natural name
300
+ // order, the stock file browser default.
301
+ let images: Array<{ name: string; path: string; type: string }> = [];
302
+ if (fileBrowser && fileBrowser.model.path === dirPath) {
303
+ const shown = (fileBrowser as unknown as { listing: DirListing })
304
+ .listing;
305
+ images = Array.from(shown.sortedItems()).filter(isImage);
298
306
  }
299
- const images = content
300
- .filter(
301
- item =>
302
- item.type === 'file' &&
303
- IMAGE_EXTS.has(PathExt.extname(item.name).toLowerCase())
304
- )
305
- .sort((a, b) =>
307
+ if (!images.some(item => item.path === path)) {
308
+ const listing = await app.serviceManager.contents.get(dirPath, {
309
+ content: true
310
+ });
311
+ const content = listing.content as Array<{
312
+ name: string;
313
+ path: string;
314
+ type: string;
315
+ }> | null;
316
+ if (!content) {
317
+ return;
318
+ }
319
+ images = content.filter(isImage).sort((a, b) =>
306
320
  a.name.localeCompare(b.name, undefined, {
307
321
  numeric: true,
308
322
  sensitivity: 'base'
309
323
  })
310
324
  );
325
+ }
311
326
  if (images.length <= 1) {
312
327
  return;
313
328
  }