@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.
@@ -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
- constructor(manager: FavoritesManager, filebrowser: FileBrowser);
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
- const maxHeight = parentRect.height - BOTTOM_SPACING;
118
- if (newHeight > 24 && newHeight < maxHeight) {
119
- container.style.maxHeight = maxHeight + 'px'; // To ensure default max-height of css is overridden
120
- container.style.height = newHeight + 'px';
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: "jp-Favorites-resize-handle", onMouseDown: handleMouseDown })));
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.4.0",
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",
package/style/base.css CHANGED
@@ -56,6 +56,10 @@
56
56
  background: var(--jp-border-color1);
57
57
  }
58
58
 
59
+ .jp-Favorites-resize-handle.jp-mod-active {
60
+ background: var(--jp-brand-color1);
61
+ }
62
+
59
63
  .jp-Favorites {
60
64
  flex: 0 0 auto;
61
65
  overflow: visible;