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 +2 -2
- package/lib/index.js +30 -15
- package/package.json +32 -95
- package/src/index.ts +34 -19
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.
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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.
|
|
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
|
|
35
|
-
"build:labextension:dev": "jupyter
|
|
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
|
|
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
|
|
56
|
+
"watch:labextension": "jupyter-builder watch ."
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@jupyterlab/application": "^4.
|
|
60
|
-
"@jupyterlab/apputils": "^4.
|
|
61
|
-
"@jupyterlab/coreutils": "^6.
|
|
62
|
-
"@jupyterlab/docregistry": "^4.
|
|
63
|
-
"@jupyterlab/
|
|
64
|
-
"@jupyterlab/
|
|
65
|
-
"@jupyterlab/
|
|
66
|
-
"@
|
|
67
|
-
"@lumino/
|
|
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
|
-
"@
|
|
71
|
-
"@
|
|
72
|
-
"@
|
|
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
|
|
77
|
+
"@types/react": "^18.3.0",
|
|
75
78
|
"@types/react-addons-linked-state-mixin": "^0.14.22",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
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.
|
|
86
|
+
"prettier": "^3.9.0",
|
|
86
87
|
"rimraf": "^5.0.1",
|
|
87
|
-
"
|
|
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.
|
|
93
|
-
"stylelint-prettier": "^4.
|
|
91
|
+
"stylelint-csstree-validator": "^3.1.0",
|
|
92
|
+
"stylelint-prettier": "^4.1.0",
|
|
94
93
|
"typescript": "~5.8.0",
|
|
95
|
-
"
|
|
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
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
300
|
-
.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
}
|