Skip to content

Commit 4a2ba2b

Browse files
committed
improve: welcome page list of recent files
work in progress, welcome page to refresh dynamically
1 parent c2cb457 commit 4a2ba2b

File tree

6 files changed

+94
-22
lines changed

6 files changed

+94
-22
lines changed

src/app/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2021-2023 The Pybricks Authors
2+
// Copyright (c) 2021-2024 The Pybricks Authors
33
import docsPackage from '@pybricks/ide-docs/package.json';
44

55
// Definitions for compile-time UI settings.
@@ -87,3 +87,6 @@ export const zipFileExtension = '.zip';
8787

8888
/** The ZIP file MIME type ('application/zip') */
8989
export const zipFileMimeType = 'application/zip';
90+
91+
/** maximum number of recent file displayed */
92+
export const recentFileCount = 3;

src/editor/Welcome.tsx

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { Document, Plus } from '@blueprintjs/icons';
88
import React, { useCallback, useEffect, useRef } from 'react';
99
import { useDispatch } from 'react-redux';
1010
import Two from 'two.js';
11-
import { useTernaryDarkMode } from 'usehooks-ts';
11+
import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
1212
import { Activity, useActivitiesSelectedActivity } from '../activities/hooks';
13+
import { recentFileCount } from '../app/constants';
1314
import { explorerCreateNewFile } from '../explorer/actions';
15+
import { UUID } from '../fileStorage';
16+
import { editorActivateFile } from './actions';
1417
import { useI18n } from './i18n';
1518
import logoSvg from './logo.svg';
19+
import { RecentFileMetadata } from '.';
1620

1721
const defaultRotation = -Math.PI / 9; // radians
1822
const rotationSpeedIncrement = 0.1; // radians per second
@@ -100,6 +104,7 @@ const Welcome: React.FunctionComponent<WelcomeProps> = ({ isVisible }) => {
100104

101105
const logo = two.load(logoSvg, (g) => {
102106
g.center();
107+
103108
two.add(logo);
104109
two.play();
105110
});
@@ -111,7 +116,7 @@ const Welcome: React.FunctionComponent<WelcomeProps> = ({ isVisible }) => {
111116
});
112117

113118
logo.fill = fillColorRef.current;
114-
logo.scale = Math.min(two.width, two.height) / 90;
119+
logo.scale = Math.min(two.width, two.height) / 80;
115120
logo.rotation = stateRef.current.rotation;
116121

117122
two.scene.position.x = two.width / 2;
@@ -169,9 +174,34 @@ const Welcome: React.FunctionComponent<WelcomeProps> = ({ isVisible }) => {
169174
dispatch(explorerCreateNewFile());
170175
}, [dispatch, setSelectedActivity]);
171176

172-
const handleOpenExplorer = useCallback(() => {
177+
//useCallback
178+
const handleOpenExplorer = (uuid: UUID) => {
173179
setSelectedActivity(Activity.Explorer);
174-
}, [setSelectedActivity]);
180+
dispatch(editorActivateFile(uuid));
181+
};
182+
183+
const [editorRecentFiles] = useLocalStorage('editor.recentFiles', []);
184+
const getRecentFileShortCuts = () => (
185+
<>
186+
{editorRecentFiles
187+
.slice(0, recentFileCount)
188+
.map((fitem: RecentFileMetadata) => (
189+
<dl key={fitem.uuid}>
190+
<dt>
191+
{i18n.translate('welcome.openProject', {
192+
fileName: fitem.path,
193+
})}
194+
</dt>
195+
<dd>
196+
<Button
197+
icon={<Document />}
198+
onClick={() => handleOpenExplorer(fitem.uuid)}
199+
/>
200+
</dd>
201+
</dl>
202+
))}
203+
</>
204+
);
175205

176206
return (
177207
<div
@@ -183,12 +213,7 @@ const Welcome: React.FunctionComponent<WelcomeProps> = ({ isVisible }) => {
183213
>
184214
<div className="logo" ref={elementRef}></div>
185215
<div className="shortcuts">
186-
<dl>
187-
<dt>{i18n.translate('welcome.openProject')}</dt>
188-
<dd>
189-
<Button icon={<Document />} onClick={handleOpenExplorer} />
190-
</dd>
191-
</dl>
216+
{getRecentFileShortCuts()}
192217
<dl>
193218
<dt>{i18n.translate('welcome.newProject')}</dt>
194219
<dd>

src/editor/editor.scss

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2020-2023 The Pybricks Authors
2+
// Copyright (c) 2020-2024 The Pybricks Authors
33

44
// Custom styling for the Editor control.
55

@@ -76,24 +76,26 @@
7676
justify-content: center;
7777
align-items: center;
7878

79-
// display: block;
8079
width: 100%;
8180
height: 100%;
82-
// max-width: 290px;
8381

8482
.logo {
83+
flex: 1;
84+
display: flex;
85+
min-height: 0;
8586
width: 100%;
86-
height: 100%;
87-
position: absolute;
88-
top: -2%;
8987

9088
svg {
91-
overflow: visible !important;
89+
width: 100%;
90+
height: 100%;
91+
object-fit: contain;
92+
min-height: 10;
93+
flex: 1;
9294
}
9395
}
9496
.shortcuts {
95-
position: relative;
96-
top: +6%;
97+
padding: 20px;
98+
text-align: center;
9799

98100
border-collapse: separate;
99101
border-spacing: 11px 17px;

src/editor/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2024 The Pybricks Authors
3+
4+
import { UUID } from '../fileStorage';
5+
6+
/**
7+
* LocalStorage recent files data type.
8+
*/
9+
export type RecentFileMetadata = Readonly<{
10+
/** A globally unique identifier that serves a a file handle. */
11+
uuid: UUID;
12+
/** The path of the file in storage. */
13+
path: string;
14+
}>;

src/editor/sagas.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2022-2023 The Pybricks Authors
2+
// Copyright (c) 2022-2024 The Pybricks Authors
33

44
import type { DatabaseChangeType, IDatabaseChange } from 'dexie-observable/api';
55
import * as monaco from 'monaco-editor';
@@ -17,6 +17,7 @@ import {
1717
takeEvery,
1818
} from 'typed-redux-saga/macro';
1919
import { alertsShowAlert } from '../alerts/actions';
20+
import { recentFileCount } from '../app/constants';
2021
import { FileStorageDb, UUID } from '../fileStorage';
2122
import {
2223
fileStorageDidFailToLoadTextFile,
@@ -67,6 +68,7 @@ import {
6768
import { EditorError } from './error';
6869
import { ActiveFileHistoryManager, OpenFileManager } from './lib';
6970
import { pybricksMicroPythonId } from './pybricksMicroPython';
71+
import { RecentFileMetadata } from '.';
7072

7173
function* handleEditorGetValueRequest(
7274
editor: monaco.editor.ICodeEditor,
@@ -273,6 +275,32 @@ function* handleEditorActivateFile(
273275

274276
editor.focus();
275277

278+
// store the activated uuid in the recent files queue
279+
let recentFiles = (() => {
280+
try {
281+
return JSON.parse(
282+
localStorage.getItem('editor.recentFiles') ?? '',
283+
) as RecentFileMetadata[];
284+
} catch {
285+
return [];
286+
}
287+
})();
288+
289+
// Check if the file already exists
290+
const fileIndex = recentFiles.findIndex((fitem: RecentFileMetadata) => {
291+
return fitem.uuid === action.uuid;
292+
});
293+
if (fileIndex !== -1) {
294+
recentFiles.splice(fileIndex, 1);
295+
}
296+
297+
const db = yield* getContext<FileStorageDb>('fileStorage');
298+
const metadata = yield* call(() => db.metadata.get(action.uuid));
299+
recentFiles.unshift({ uuid: action.uuid, path: metadata?.path ?? '' }); // Add new (or existing) file to the beginning
300+
recentFiles = [...recentFiles.slice(recentFileCount)]; // Keep only the first 10 items
301+
localStorage.setItem('editor.recentFiles', JSON.stringify(recentFiles));
302+
303+
// signal activation done
276304
yield* put(editorDidActivateFile(action.uuid));
277305
} catch (err) {
278306
yield* put(editorDidFailToActivateFile(action.uuid, ensureError(err)));

src/editor/translations/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
"welcome": {
2020
"label": "Welcome",
21-
"openProject": "Open existing project",
21+
"openProject": "Open {fileName}",
2222
"newProject": "Open a new project"
2323
}
2424
}

0 commit comments

Comments
 (0)