mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-13 14:26:37 +00:00
ATL-815: Implemented Open Recent
.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
66b711f43c
commit
6626701bc9
@ -134,6 +134,7 @@ import { Sketchbook } from './contributions/sketchbook';
|
|||||||
import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution';
|
import { DebugFrontendApplicationContribution } from './theia/debug/debug-frontend-application-contribution';
|
||||||
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
import { DebugFrontendApplicationContribution as TheiaDebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution';
|
||||||
import { BoardSelection } from './contributions/board-selection';
|
import { BoardSelection } from './contributions/board-selection';
|
||||||
|
import { OpenRecentSketch } from './contributions/open-recent-sketch';
|
||||||
|
|
||||||
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
const ElementQueries = require('css-element-queries/src/ElementQueries');
|
||||||
|
|
||||||
@ -337,6 +338,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
Contribution.configure(bind, Debug);
|
Contribution.configure(bind, Debug);
|
||||||
Contribution.configure(bind, Sketchbook);
|
Contribution.configure(bind, Sketchbook);
|
||||||
Contribution.configure(bind, BoardSelection);
|
Contribution.configure(bind, BoardSelection);
|
||||||
|
Contribution.configure(bind, OpenRecentSketch);
|
||||||
|
|
||||||
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
|
bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => {
|
||||||
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
|
WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService);
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { WorkspaceServer } from '@theia/workspace/lib/common/workspace-protocol';
|
||||||
|
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
|
||||||
|
import { SketchContribution, CommandRegistry, MenuModelRegistry, Sketch } from './contribution';
|
||||||
|
import { ArduinoMenus } from '../menu/arduino-menus';
|
||||||
|
import { MainMenuManager } from '../../common/main-menu-manager';
|
||||||
|
import { OpenSketch } from './open-sketch';
|
||||||
|
import { NotificationCenter } from '../notification-center';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class OpenRecentSketch extends SketchContribution {
|
||||||
|
|
||||||
|
@inject(CommandRegistry)
|
||||||
|
protected readonly commandRegistry: CommandRegistry;
|
||||||
|
|
||||||
|
@inject(MenuModelRegistry)
|
||||||
|
protected readonly menuRegistry: MenuModelRegistry;
|
||||||
|
|
||||||
|
@inject(MainMenuManager)
|
||||||
|
protected readonly mainMenuManager: MainMenuManager;
|
||||||
|
|
||||||
|
@inject(WorkspaceServer)
|
||||||
|
protected readonly workspaceServer: WorkspaceServer;
|
||||||
|
|
||||||
|
@inject(NotificationCenter)
|
||||||
|
protected readonly notificationCenter: NotificationCenter;
|
||||||
|
|
||||||
|
protected toDisposeBeforeRegister = new Map<string, DisposableCollection>();
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
const refreshMenu = (sketches: Sketch[]) => {
|
||||||
|
this.register(sketches);
|
||||||
|
this.mainMenuManager.update();
|
||||||
|
};
|
||||||
|
this.notificationCenter.onRecentSketchesChanged(({ sketches }) => refreshMenu(sketches));
|
||||||
|
this.sketchService.recentlyOpenedSketches().then(refreshMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerMenus(registry: MenuModelRegistry): void {
|
||||||
|
registry.registerSubmenu(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, 'Open Recent', { order: '2' });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected register(sketches: Sketch[]): void {
|
||||||
|
let order = 0;
|
||||||
|
for (const sketch of sketches) {
|
||||||
|
const { uri } = sketch;
|
||||||
|
const toDispose = this.toDisposeBeforeRegister.get(uri);
|
||||||
|
if (toDispose) {
|
||||||
|
toDispose.dispose();
|
||||||
|
}
|
||||||
|
const command = { id: `arduino-open-recent--${uri}` };
|
||||||
|
const handler = { execute: () => this.commandRegistry.executeCommand(OpenSketch.Commands.OPEN_SKETCH.id, sketch) };
|
||||||
|
this.commandRegistry.registerCommand(command, handler);
|
||||||
|
this.menuRegistry.registerMenuAction(ArduinoMenus.FILE__OPEN_RECENT_SUBMENU, { commandId: command.id, label: sketch.name, order: String(order) });
|
||||||
|
this.toDisposeBeforeRegister.set(sketch.uri, new DisposableCollection(
|
||||||
|
Disposable.create(() => this.commandRegistry.unregisterCommand(command)),
|
||||||
|
Disposable.create(() => this.menuRegistry.unregisterMenuAction(command))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -60,8 +60,8 @@ export class SaveAsSketch extends SketchContribution {
|
|||||||
}
|
}
|
||||||
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
const workspaceUri = await this.sketchService.copy(sketch, { destinationUri });
|
||||||
if (workspaceUri && openAfterMove) {
|
if (workspaceUri && openAfterMove) {
|
||||||
if (wipeOriginal) {
|
if (wipeOriginal || (openAfterMove && execOnlyIfTemp)) {
|
||||||
await this.fileService.delete(new URI(sketch.uri));
|
await this.fileService.delete(new URI(sketch.uri), { recursive: true });
|
||||||
}
|
}
|
||||||
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
this.workspaceService.open(new URI(workspaceUri), { preserveWindow: true });
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,14 @@ export namespace ArduinoMenus {
|
|||||||
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
export const FILE__SETTINGS_GROUP = [...(isOSX ? MAIN_MENU_BAR : CommonMenus.FILE), '2_settings'];
|
||||||
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
export const FILE__QUIT_GROUP = [...CommonMenus.FILE, '3_quit'];
|
||||||
|
|
||||||
|
// -- File / Open Recent
|
||||||
|
export const FILE__OPEN_RECENT_SUBMENU = [...FILE__SKETCH_GROUP, '0_open_recent'];
|
||||||
|
|
||||||
// -- File / Sketchbook
|
// -- File / Sketchbook
|
||||||
export const FILE__SKETCHBOOK_SUBMENU = [...FILE__SKETCH_GROUP, '0_sketchbook'];
|
export const FILE__SKETCHBOOK_SUBMENU = [...FILE__SKETCH_GROUP, '1_sketchbook'];
|
||||||
|
|
||||||
// -- File / Examples
|
// -- File / Examples
|
||||||
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '1_examples'];
|
export const FILE__EXAMPLES_SUBMENU = [...FILE__SKETCH_GROUP, '2_examples'];
|
||||||
export const EXAMPLES__BUILT_IN_GROUP = [...FILE__EXAMPLES_SUBMENU, '0_built_ins'];
|
export const EXAMPLES__BUILT_IN_GROUP = [...FILE__EXAMPLES_SUBMENU, '0_built_ins'];
|
||||||
export const EXAMPLES__ANY_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '1_any_board'];
|
export const EXAMPLES__ANY_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '1_any_board'];
|
||||||
export const EXAMPLES__CURRENT_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '2_current_board'];
|
export const EXAMPLES__CURRENT_BOARD_GROUP = [...FILE__EXAMPLES_SUBMENU, '2_current_board'];
|
||||||
|
@ -22,6 +22,7 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
|||||||
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
protected readonly libraryUninstalledEmitter = new Emitter<{ item: LibraryPackage }>();
|
||||||
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
protected readonly attachedBoardsChangedEmitter = new Emitter<AttachedBoardsChangeEvent>();
|
||||||
protected readonly sketchbookChangedEmitter = new Emitter<{ created: Sketch[], removed: Sketch[] }>();
|
protected readonly sketchbookChangedEmitter = new Emitter<{ created: Sketch[], removed: Sketch[] }>();
|
||||||
|
protected readonly recentSketchesChangedEmitter = new Emitter<{ sketches: Sketch[] }>();
|
||||||
|
|
||||||
protected readonly toDispose = new DisposableCollection(
|
protected readonly toDispose = new DisposableCollection(
|
||||||
this.indexUpdatedEmitter,
|
this.indexUpdatedEmitter,
|
||||||
@ -46,6 +47,7 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
|||||||
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
|
readonly onLibraryUninstalled = this.libraryUninstalledEmitter.event;
|
||||||
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
|
readonly onAttachedBoardsChanged = this.attachedBoardsChangedEmitter.event;
|
||||||
readonly onSketchbookChanged = this.sketchbookChangedEmitter.event;
|
readonly onSketchbookChanged = this.sketchbookChangedEmitter.event;
|
||||||
|
readonly onRecentSketchesChanged = this.recentSketchesChangedEmitter.event;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
@ -96,4 +98,8 @@ export class NotificationCenter implements NotificationServiceClient, FrontendAp
|
|||||||
this.sketchbookChangedEmitter.fire(event);
|
this.sketchbookChangedEmitter.fire(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||||
|
this.recentSketchesChangedEmitter.fire(event);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|||||||
import { CommandService } from '@theia/core/lib/common/command';
|
import { CommandService } from '@theia/core/lib/common/command';
|
||||||
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
||||||
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
import { FrontendApplication as TheiaFrontendApplication } from '@theia/core/lib/browser/frontend-application';
|
||||||
|
import { SketchesService } from '../../../common/protocol';
|
||||||
import { ArduinoCommands } from '../../arduino-commands';
|
import { ArduinoCommands } from '../../arduino-commands';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
@ -17,12 +18,16 @@ export class FrontendApplication extends TheiaFrontendApplication {
|
|||||||
@inject(CommandService)
|
@inject(CommandService)
|
||||||
protected readonly commandService: CommandService;
|
protected readonly commandService: CommandService;
|
||||||
|
|
||||||
|
@inject(SketchesService)
|
||||||
|
protected readonly sketchesService: SketchesService;
|
||||||
|
|
||||||
protected async initializeLayout(): Promise<void> {
|
protected async initializeLayout(): Promise<void> {
|
||||||
await super.initializeLayout();
|
await super.initializeLayout();
|
||||||
const roots = await this.workspaceService.roots;
|
const roots = await this.workspaceService.roots;
|
||||||
for (const root of roots) {
|
for (const root of roots) {
|
||||||
const exists = await this.fileService.exists(root.resource);
|
const exists = await this.fileService.exists(root.resource);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
|
this.sketchesService.markAsRecentlyOpened(root.resource.toString()); // no await, will get the notification later and rebuild the menu
|
||||||
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource);
|
await this.commandService.executeCommand(ArduinoCommands.OPEN_SKETCH_FILES.id, root.resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ export interface NotificationServiceClient {
|
|||||||
notifyLibraryUninstalled(event: { item: LibraryPackage }): void;
|
notifyLibraryUninstalled(event: { item: LibraryPackage }): void;
|
||||||
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
|
notifyAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void;
|
||||||
notifySketchbookChanged(event: { created: Sketch[], removed: Sketch[] }): void;
|
notifySketchbookChanged(event: { created: Sketch[], removed: Sketch[] }): void;
|
||||||
|
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationServicePath = '/services/notification-service';
|
export const NotificationServicePath = '/services/notification-service';
|
||||||
|
@ -48,6 +48,16 @@ export interface SketchesService {
|
|||||||
*/
|
*/
|
||||||
getSketchFolder(uri: string): Promise<Sketch | undefined>;
|
getSketchFolder(uri: string): Promise<Sketch | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the sketch with the given URI as recently opened. It does nothing if the sketch is temp or not valid.
|
||||||
|
*/
|
||||||
|
markAsRecentlyOpened(uri: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves to an array of sketches in inverse chronological order. The newest is the first.
|
||||||
|
*/
|
||||||
|
recentlyOpenedSketches(): Promise<Sketch[]>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Sketch {
|
export interface Sketch {
|
||||||
@ -72,4 +82,3 @@ export namespace Sketch {
|
|||||||
return [mainFileUri, ...otherSketchFileUris, ...additionalFileUris].indexOf(uri.toString()) !== -1;
|
return [mainFileUri, ...otherSketchFileUris, ...additionalFileUris].indexOf(uri.toString()) !== -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,10 @@ export class NotificationServiceServerImpl implements NotificationServiceServer
|
|||||||
this.clients.forEach(client => client.notifySketchbookChanged(event));
|
this.clients.forEach(client => client.notifySketchbookChanged(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyRecentSketchesChanged(event: { sketches: Sketch[] }): void {
|
||||||
|
this.clients.forEach(client => client.notifyRecentSketchesChanged(event));
|
||||||
|
}
|
||||||
|
|
||||||
setClient(client: NotificationServiceClient): void {
|
setClient(client: NotificationServiceClient): void {
|
||||||
this.clients.push(client);
|
this.clients.push(client);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import { ConfigService } from '../common/protocol/config-service';
|
|||||||
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
import { SketchesService, Sketch } from '../common/protocol/sketches-service';
|
||||||
import { firstToLowerCase } from '../common/utils';
|
import { firstToLowerCase } from '../common/utils';
|
||||||
import { NotificationServiceServerImpl } from './notification-service-server';
|
import { NotificationServiceServerImpl } from './notification-service-server';
|
||||||
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
||||||
|
import { notEmpty } from '@theia/core';
|
||||||
|
|
||||||
// As currently implemented on Linux,
|
// As currently implemented on Linux,
|
||||||
// the maximum number of symbolic links that will be followed while resolving a pathname is 40
|
// the maximum number of symbolic links that will be followed while resolving a pathname is 40
|
||||||
@ -33,7 +35,10 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
@inject(NotificationServiceServerImpl)
|
@inject(NotificationServiceServerImpl)
|
||||||
protected readonly notificationService: NotificationServiceServerImpl;
|
protected readonly notificationService: NotificationServiceServerImpl;
|
||||||
|
|
||||||
async getSketches(uri?: string): Promise<Sketch[]> {
|
@inject(EnvVariablesServer)
|
||||||
|
protected readonly envVariableServer: EnvVariablesServer;
|
||||||
|
|
||||||
|
async getSketches(uri?: string): Promise<SketchWithDetails[]> {
|
||||||
let fsPath: undefined | string;
|
let fsPath: undefined | string;
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
const { sketchDirUri } = await this.configService.getConfiguration();
|
const { sketchDirUri } = await this.configService.getConfiguration();
|
||||||
@ -57,7 +62,7 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
/**
|
/**
|
||||||
* Dev note: The keys are filesystem paths, not URI strings.
|
* Dev note: The keys are filesystem paths, not URI strings.
|
||||||
*/
|
*/
|
||||||
private sketchbooks = new Map<string, Sketch[] | Deferred<Sketch[]>>();
|
private sketchbooks = new Map<string, SketchWithDetails[] | Deferred<SketchWithDetails[]>>();
|
||||||
private fireSoonHandle?: NodeJS.Timer;
|
private fireSoonHandle?: NodeJS.Timer;
|
||||||
private bufferedSketchbookEvents: { type: 'created' | 'removed', sketch: Sketch }[] = [];
|
private bufferedSketchbookEvents: { type: 'created' | 'removed', sketch: Sketch }[] = [];
|
||||||
|
|
||||||
@ -88,7 +93,7 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
/**
|
/**
|
||||||
* Assumes the `fsPath` points to an existing directory.
|
* Assumes the `fsPath` points to an existing directory.
|
||||||
*/
|
*/
|
||||||
private async doGetSketches(sketchbookPath: string): Promise<Sketch[]> {
|
private async doGetSketches(sketchbookPath: string): Promise<SketchWithDetails[]> {
|
||||||
const resolvedSketches = this.sketchbooks.get(sketchbookPath);
|
const resolvedSketches = this.sketchbooks.get(sketchbookPath);
|
||||||
if (resolvedSketches) {
|
if (resolvedSketches) {
|
||||||
if (Array.isArray(resolvedSketches)) {
|
if (Array.isArray(resolvedSketches)) {
|
||||||
@ -97,9 +102,9 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
return resolvedSketches.promise;
|
return resolvedSketches.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deferred = new Deferred<Sketch[]>();
|
const deferred = new Deferred<SketchWithDetails[]>();
|
||||||
this.sketchbooks.set(sketchbookPath, deferred);
|
this.sketchbooks.set(sketchbookPath, deferred);
|
||||||
const sketches: Array<Sketch & { mtimeMs: number }> = [];
|
const sketches: Array<SketchWithDetails> = [];
|
||||||
const filenames = await fs.readdir(sketchbookPath);
|
const filenames = await fs.readdir(sketchbookPath);
|
||||||
for (const fileName of filenames) {
|
for (const fileName of filenames) {
|
||||||
const filePath = path.join(sketchbookPath, fileName);
|
const filePath = path.join(sketchbookPath, fileName);
|
||||||
@ -201,7 +206,7 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
* See: https://github.com/arduino/arduino-cli/issues/837
|
* See: https://github.com/arduino/arduino-cli/issues/837
|
||||||
* Based on: https://github.com/arduino/arduino-cli/blob/eef3705c4afcba4317ec38b803d9ffce5dd59a28/arduino/builder/sketch.go#L100-L215
|
* Based on: https://github.com/arduino/arduino-cli/blob/eef3705c4afcba4317ec38b803d9ffce5dd59a28/arduino/builder/sketch.go#L100-L215
|
||||||
*/
|
*/
|
||||||
async loadSketch(uri: string): Promise<Sketch> {
|
async loadSketch(uri: string): Promise<SketchWithDetails> {
|
||||||
const sketchPath = FileUri.fsPath(uri);
|
const sketchPath = FileUri.fsPath(uri);
|
||||||
const exists = await fs.exists(sketchPath);
|
const exists = await fs.exists(sketchPath);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
@ -294,7 +299,80 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private newSketch(sketchFolderPath: string, mainFilePath: string, allFilesPaths: string[]): Sketch {
|
private get recentSketchesFsPath(): Promise<string> {
|
||||||
|
return this.envVariableServer.getConfigDirUri().then(uri => path.join(FileUri.fsPath(uri), 'recent-sketches.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadRecentSketches(fsPath: string): Promise<Record<string, number>> {
|
||||||
|
let data: Record<string, number> = {};
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(fsPath, { encoding: 'utf8' });
|
||||||
|
data = JSON.parse(raw);
|
||||||
|
} catch { }
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async markAsRecentlyOpened(uri: string): Promise<void> {
|
||||||
|
let sketch: Sketch | undefined = undefined;
|
||||||
|
try {
|
||||||
|
sketch = await this.loadSketch(uri);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await this.isTemp(sketch)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fsPath = await this.recentSketchesFsPath;
|
||||||
|
const data = await this.loadRecentSketches(fsPath);
|
||||||
|
const now = Date.now();
|
||||||
|
data[sketch.uri] = now;
|
||||||
|
|
||||||
|
let toDeleteUri: string | undefined = undefined;
|
||||||
|
if (Object.keys(data).length > 10) {
|
||||||
|
let min = Number.MAX_SAFE_INTEGER;
|
||||||
|
for (const uri of Object.keys(data)) {
|
||||||
|
if (min > data[uri]) {
|
||||||
|
min = data[uri];
|
||||||
|
toDeleteUri = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDeleteUri) {
|
||||||
|
delete data[toDeleteUri];
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(fsPath, JSON.stringify(data, null, 2));
|
||||||
|
this.recentlyOpenedSketches().then(sketches => this.notificationService.notifyRecentSketchesChanged({ sketches }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async recentlyOpenedSketches(): Promise<Sketch[]> {
|
||||||
|
const configDirUri = await this.envVariableServer.getConfigDirUri();
|
||||||
|
const fsPath = path.join(FileUri.fsPath(configDirUri), 'recent-sketches.json');
|
||||||
|
let data: Record<string, number> = {};
|
||||||
|
try {
|
||||||
|
const raw = await fs.readFile(fsPath, { encoding: 'utf8' });
|
||||||
|
data = JSON.parse(raw);
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
const loadSketchSafe = (uri: string) => {
|
||||||
|
try {
|
||||||
|
return this.loadSketch(uri);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sketches = await Promise.all(Object.keys(data)
|
||||||
|
.sort((left, right) => data[right] - data[left])
|
||||||
|
.map(loadSketchSafe)
|
||||||
|
.filter(notEmpty));
|
||||||
|
|
||||||
|
return sketches;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async newSketch(sketchFolderPath: string, mainFilePath: string, allFilesPaths: string[]): Promise<SketchWithDetails> {
|
||||||
let mainFile: string | undefined;
|
let mainFile: string | undefined;
|
||||||
const paths = new Set<string>();
|
const paths = new Set<string>();
|
||||||
for (const p of allFilesPaths) {
|
for (const p of allFilesPaths) {
|
||||||
@ -326,13 +404,15 @@ export class SketchesServiceImpl implements SketchesService {
|
|||||||
additionalFiles.sort();
|
additionalFiles.sort();
|
||||||
otherSketchFiles.sort();
|
otherSketchFiles.sort();
|
||||||
|
|
||||||
|
const { mtimeMs } = await fs.lstat(sketchFolderPath);
|
||||||
return {
|
return {
|
||||||
uri: FileUri.create(sketchFolderPath).toString(),
|
uri: FileUri.create(sketchFolderPath).toString(),
|
||||||
mainFileUri: FileUri.create(mainFile).toString(),
|
mainFileUri: FileUri.create(mainFile).toString(),
|
||||||
name: path.basename(sketchFolderPath),
|
name: path.basename(sketchFolderPath),
|
||||||
additionalFileUris: additionalFiles.map(p => FileUri.create(p).toString()),
|
additionalFileUris: additionalFiles.map(p => FileUri.create(p).toString()),
|
||||||
otherSketchFileUris: otherSketchFiles.map(p => FileUri.create(p).toString())
|
otherSketchFileUris: otherSketchFiles.map(p => FileUri.create(p).toString()),
|
||||||
}
|
mtimeMs
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async cloneExample(uri: string): Promise<Sketch> {
|
async cloneExample(uri: string): Promise<Sketch> {
|
||||||
@ -538,3 +618,8 @@ class SkipDir extends Error {
|
|||||||
Object.setPrototypeOf(this, SkipDir.prototype);
|
Object.setPrototypeOf(this, SkipDir.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SketchWithDetails extends Sketch {
|
||||||
|
readonly mtimeMs: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user