@jlab-enhanced/favorites 3.4.0 → 3.5.0
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/lib/components.d.ts +9 -1
- package/lib/components.js +69 -16
- package/lib/index.js +5 -3
- package/package.json +2 -1
- package/style/base.css +4 -0
package/lib/components.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ReactWidget } from '@jupyterlab/ui-components';
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { FavoritesManager } from './manager';
|
|
5
5
|
import { IFavorites } from './token';
|
|
6
|
+
import { IStateDB } from '@jupyterlab/statedb';
|
|
6
7
|
declare namespace types {
|
|
7
8
|
type FavoriteComponentProps = {
|
|
8
9
|
favorite: IFavorites.Favorite;
|
|
@@ -19,7 +20,14 @@ export declare class FavoritesWidget extends ReactWidget {
|
|
|
19
20
|
private manager;
|
|
20
21
|
private filebrowser;
|
|
21
22
|
private pathChanged;
|
|
22
|
-
|
|
23
|
+
private _stateDB?;
|
|
24
|
+
constructor(manager: FavoritesManager, filebrowser: FileBrowser, stateDB: IStateDB | null);
|
|
23
25
|
render(): JSX.Element;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Information persisted in StateDB.
|
|
29
|
+
*/
|
|
30
|
+
export interface IPersistedState {
|
|
31
|
+
height?: number;
|
|
32
|
+
}
|
|
25
33
|
export {};
|
package/lib/components.js
CHANGED
|
@@ -43,6 +43,11 @@ const FAVORITE_BREADCRUMB_ICON_CLASS = 'jp-Favorites-BreadCrumbs-Icon';
|
|
|
43
43
|
* The spacing from the bottom of the FileBrowser to leave when resizing the Favorites container.
|
|
44
44
|
*/
|
|
45
45
|
const BOTTOM_SPACING = 100;
|
|
46
|
+
/**
|
|
47
|
+
* The key in State DB.
|
|
48
|
+
* In JupyterLab the value will be stored per workspace.
|
|
49
|
+
*/
|
|
50
|
+
const STATE_DB_KEY = 'favorites';
|
|
46
51
|
const FavoriteComponent = (props) => {
|
|
47
52
|
const { favorite, handleClick } = props;
|
|
48
53
|
let [displayName, dirname] = getName(favorite.path);
|
|
@@ -90,10 +95,34 @@ export const FavoritesBreadCrumbs = (props) => {
|
|
|
90
95
|
React.createElement(icon.react, { className: FAVORITE_BREADCRUMB_ICON_CLASS, tag: "span" })));
|
|
91
96
|
}));
|
|
92
97
|
};
|
|
93
|
-
function FavoritesContainer({ visibleFavorites, manager }) {
|
|
98
|
+
function FavoritesContainer({ visibleFavorites, manager, stateDB }) {
|
|
94
99
|
const containerRef = React.useRef(null);
|
|
95
100
|
const [isResizing, setIsResizing] = React.useState(false);
|
|
96
101
|
const cursorDisposableRef = React.useRef(null);
|
|
102
|
+
const [persistedHeight, setPersistedHeight] = usePersistedHeight(stateDB);
|
|
103
|
+
const applyHeight = React.useCallback((height) => {
|
|
104
|
+
const container = containerRef.current;
|
|
105
|
+
if (!container) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Height of filebrowser widget
|
|
109
|
+
const parentElement = container.closest('.jp-FileBrowser');
|
|
110
|
+
const parentRect = parentElement === null || parentElement === void 0 ? void 0 : parentElement.getBoundingClientRect();
|
|
111
|
+
if (!parentRect) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const maxHeight = parentRect.height - BOTTOM_SPACING;
|
|
115
|
+
if (height > 24 && height < maxHeight) {
|
|
116
|
+
container.style.maxHeight = maxHeight + 'px'; // To ensure default max-height of css is overridden
|
|
117
|
+
container.style.height = height + 'px';
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
// Apply persisted height when it's loaded (before paint to avoid flicker)
|
|
121
|
+
React.useLayoutEffect(() => {
|
|
122
|
+
if (persistedHeight !== undefined) {
|
|
123
|
+
applyHeight(persistedHeight);
|
|
124
|
+
}
|
|
125
|
+
}, [persistedHeight, applyHeight]);
|
|
97
126
|
const handleMouseDown = () => {
|
|
98
127
|
setIsResizing(true);
|
|
99
128
|
cursorDisposableRef.current = Drag.overrideCursor('ns-resize');
|
|
@@ -106,20 +135,13 @@ function FavoritesContainer({ visibleFavorites, manager }) {
|
|
|
106
135
|
if (!container) {
|
|
107
136
|
return;
|
|
108
137
|
}
|
|
109
|
-
// Height of filebrowser widget
|
|
110
|
-
const parentElement = container.closest('.jp-FileBrowser');
|
|
111
|
-
const parentRect = parentElement === null || parentElement === void 0 ? void 0 : parentElement.getBoundingClientRect();
|
|
112
|
-
if (!parentRect) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
138
|
const rect = container.getBoundingClientRect();
|
|
116
139
|
const newHeight = e.clientY - rect.top;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}, [isResizing]);
|
|
140
|
+
// Apply height immediately for visual feedback
|
|
141
|
+
applyHeight(newHeight);
|
|
142
|
+
// Persist to stateDB
|
|
143
|
+
setPersistedHeight(newHeight);
|
|
144
|
+
}, [isResizing, applyHeight, setPersistedHeight]);
|
|
123
145
|
const handleMouseUp = React.useCallback(() => {
|
|
124
146
|
var _a;
|
|
125
147
|
setIsResizing(false);
|
|
@@ -144,14 +166,15 @@ function FavoritesContainer({ visibleFavorites, manager }) {
|
|
|
144
166
|
}, []);
|
|
145
167
|
return (React.createElement(React.Fragment, null,
|
|
146
168
|
React.createElement("div", { ref: containerRef, className: FAVORITE_CONTAINER_CLASS }, (visibleFavorites !== null && visibleFavorites !== void 0 ? visibleFavorites : []).map(f => (React.createElement(FavoriteComponent, { key: `favorites-item-${f.path}`, favorite: f, handleClick: manager.handleClick.bind(manager) })))),
|
|
147
|
-
React.createElement("div", { className:
|
|
169
|
+
React.createElement("div", { className: 'jp-Favorites-resize-handle' + (isResizing ? ' jp-mod-active' : ''), onMouseDown: handleMouseDown })));
|
|
148
170
|
}
|
|
149
171
|
export class FavoritesWidget extends ReactWidget {
|
|
150
|
-
constructor(manager, filebrowser) {
|
|
172
|
+
constructor(manager, filebrowser, stateDB) {
|
|
151
173
|
super();
|
|
152
174
|
this.pathChanged = new Signal(this);
|
|
153
175
|
this.manager = manager;
|
|
154
176
|
this.filebrowser = filebrowser;
|
|
177
|
+
this._stateDB = stateDB !== null && stateDB !== void 0 ? stateDB : undefined;
|
|
155
178
|
this.addClass(FAVORITE_MAIN_CLASS);
|
|
156
179
|
this.filebrowser.model.pathChanged.connect((_, changedArgs) => {
|
|
157
180
|
const path = changedArgs.newValue;
|
|
@@ -162,7 +185,37 @@ export class FavoritesWidget extends ReactWidget {
|
|
|
162
185
|
return (React.createElement(UseSignal, { signal: this.manager.favoritesChanged, initialSender: this.manager, initialArgs: this.manager.visibleFavorites() }, (manager, visibleFavorites) => (React.createElement("div", null,
|
|
163
186
|
React.createElement(UseSignal, { signal: manager.visibilityChanged, initialSender: manager, initialArgs: manager.isVisible() }, (manager, isVisible) => isVisible && (React.createElement(React.Fragment, null,
|
|
164
187
|
React.createElement("div", { className: FAVORITE_HEADER_CLASS }, "Favorites"),
|
|
165
|
-
React.createElement(FavoritesContainer, { visibleFavorites: visibleFavorites, manager: manager }),
|
|
188
|
+
React.createElement(FavoritesContainer, { visibleFavorites: visibleFavorites, manager: manager, stateDB: this._stateDB }),
|
|
166
189
|
React.createElement("div", { className: FILEBROWSER_HEADER_CLASS }, "File Browser"))))))));
|
|
167
190
|
}
|
|
168
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Custom hook to manage persisted state in stateDB.
|
|
194
|
+
* Similar to useState but automatically persists to stateDB.
|
|
195
|
+
*/
|
|
196
|
+
function usePersistedHeight(stateDB) {
|
|
197
|
+
const [height, setHeightState] = React.useState(undefined);
|
|
198
|
+
const initializedRef = React.useRef(false);
|
|
199
|
+
// Load initial value from stateDB before first paint
|
|
200
|
+
React.useLayoutEffect(() => {
|
|
201
|
+
if (!stateDB || initializedRef.current) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
initializedRef.current = true;
|
|
205
|
+
stateDB.fetch(STATE_DB_KEY).then(result => {
|
|
206
|
+
const persistedHeight = result === null || result === void 0 ? void 0 : result.height;
|
|
207
|
+
if (typeof persistedHeight === 'number') {
|
|
208
|
+
setHeightState(persistedHeight);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}, [stateDB]);
|
|
212
|
+
// Setter that updates both state and stateDB
|
|
213
|
+
const setPersistedHeight = React.useCallback((newHeight) => {
|
|
214
|
+
setHeightState(newHeight);
|
|
215
|
+
if (stateDB) {
|
|
216
|
+
const state = { height: newHeight };
|
|
217
|
+
stateDB.save(STATE_DB_KEY, state);
|
|
218
|
+
}
|
|
219
|
+
}, [stateDB]);
|
|
220
|
+
return [height, setPersistedHeight];
|
|
221
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { ICommandPalette, InputDialog, IToolbarWidgetRegistry } from '@jupyterla
|
|
|
15
15
|
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
16
16
|
import { IEditorServices } from '@jupyterlab/codeeditor';
|
|
17
17
|
import { StarredNotebookContentFactory } from './starPrompt';
|
|
18
|
+
import { IStateDB } from '@jupyterlab/statedb';
|
|
18
19
|
export { IFavorites, FAVORITE_TAG } from './token';
|
|
19
20
|
const BREADCRUMBS_CLASS = 'jp-FileBrowser-crumbs';
|
|
20
21
|
/**
|
|
@@ -46,9 +47,10 @@ const favorites = {
|
|
|
46
47
|
IDefaultFileBrowser,
|
|
47
48
|
IMainMenu,
|
|
48
49
|
IToolbarWidgetRegistry,
|
|
49
|
-
ICommandPalette
|
|
50
|
+
ICommandPalette,
|
|
51
|
+
IStateDB
|
|
50
52
|
],
|
|
51
|
-
activate: async (app, factory, settingsRegistry, notebookTracker, translator, filebrowser, mainMenu, toolbarRegistry, palette) => {
|
|
53
|
+
activate: async (app, factory, settingsRegistry, notebookTracker, translator, filebrowser, mainMenu, toolbarRegistry, palette, stateDB) => {
|
|
52
54
|
console.log('JupyterLab extension jupyterlab-favorites is activated!');
|
|
53
55
|
const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
|
|
54
56
|
const docRegistry = app.docRegistry;
|
|
@@ -57,7 +59,7 @@ const favorites = {
|
|
|
57
59
|
favoritesManager.init();
|
|
58
60
|
const favoriteSettings = await settingsRegistry.load(SettingIDs.favorites);
|
|
59
61
|
if (filebrowser) {
|
|
60
|
-
const favoritesWidget = new FavoritesWidget(favoritesManager, filebrowser);
|
|
62
|
+
const favoritesWidget = new FavoritesWidget(favoritesManager, filebrowser, stateDB);
|
|
61
63
|
const layout = filebrowser.layout;
|
|
62
64
|
layout.insertWidget(0, favoritesWidget);
|
|
63
65
|
const breadcrumbs = filebrowser.node.querySelector(`.${BREADCRUMBS_CLASS}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jlab-enhanced/favorites",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Add the ability to save favorite folders to JupyterLab for quicker browsing",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"@jupyterlab/notebook": "^4.0.5",
|
|
81
81
|
"@jupyterlab/services": "^7.0.5",
|
|
82
82
|
"@jupyterlab/settingregistry": "^4.0.5",
|
|
83
|
+
"@jupyterlab/statedb": "^4.0.5",
|
|
83
84
|
"@jupyterlab/ui-components": "^4.0.5",
|
|
84
85
|
"@lumino/commands": "^2.0.1",
|
|
85
86
|
"@lumino/coreutils": "^2.0.1",
|