Skip to content
This repository has been archived by the owner on Jul 21, 2020. It is now read-only.

Commit

Permalink
fix(layout): remove and prettify recent projects that have been moved (
Browse files Browse the repository at this point in the history
…fixes #109)
  • Loading branch information
connor4312 committed May 25, 2018
1 parent e81697e commit e6ed3c1
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 38 deletions.
14 changes: 13 additions & 1 deletion src/app/editor/layout/layout.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export const enum LayoutActionTypes {
SET_GOLDEN_LAYOUT = '[Layout] Set the golden layout instance',
CLEAR_GOLDEN_LAYOUT = '[Layout] Unsets the golden layout component',
GET_RECENT_PROJECTS = '[Layout] Gets the recent projects',
REMOVE_RECENT_PROJECT = '[Layout] Removes a recent project',
}

export const enum LayoutMethod {
SavePanels = '[Layout] Save panels',
LoadPanels = '[Layout] Load panels',
GetRecentProjects = '[Layout] Get recent projects',
UpdateRecentProjects = '[Layout] Update recent projects',
RemoveRecentProject = '[Layout] Remove recent project',
}

/**
Expand Down Expand Up @@ -195,11 +197,21 @@ export class GetRecentProjects implements Action {
constructor(public readonly projects?: IRecentProject[]) {}
}

/**
* Fired to remove a recent project from the list.
*/
export class RemoveRecentProject implements Action {
public readonly type = LayoutActionTypes.REMOVE_RECENT_PROJECT;

constructor(public readonly directory: string) {}
}

export type LayoutActions =
| OpenScreen
| SavePanels
| OpenPanel
| ClosePanel
| SetGoldenLayout
| ClearGoldenLayout
| GetRecentProjects;
| GetRecentProjects
| RemoveRecentProject;
13 changes: 13 additions & 0 deletions src/app/editor/layout/layout.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
OpenPanel,
OpenScreen,
panelTitles,
RemoveRecentProject,
SavePanels,
SetGoldenLayout,
} from './layout.actions';
Expand Down Expand Up @@ -66,6 +67,18 @@ export class LayoutEffects {
.ofType(ProjectActionTypes.CLOSE_PROJECT)
.pipe(mapTo(new OpenScreen(LayoutScreen.Welcome)));

/**
* Goes back to the welcome screen when we close a project.
*/
@Effect({ dispatch: false })
public readonly removeRecentProject = this.actions
.ofType<RemoveRecentProject>(LayoutActionTypes.REMOVE_RECENT_PROJECT)
.pipe(
switchMap(({ directory }) =>
this.electron.call(LayoutMethod.RemoveRecentProject, { directory }),
),
);

/**
* Persists panel configuration to the server when it chamges.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/app/editor/layout/layout.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export function layoutReducer(
return { ...state, goldenLayout: null };
case LayoutActionTypes.GET_RECENT_PROJECTS:
return { ...state, recent: action.projects || null };
case LayoutActionTypes.REMOVE_RECENT_PROJECT:
return {
...state,
recent: state.recent ? state.recent.filter(s => s.url !== action.directory) : null,
};
default:
return state;
}
Expand Down
14 changes: 12 additions & 2 deletions src/app/editor/project/project.effects.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material';
import { MatDialog, MatSnackBar } from '@angular/material';
import { Actions, Effect } from '@ngrx/effects';
import { Action, Store } from '@ngrx/store';
import { of } from 'rxjs/observable/of';
import { filter, map, startWith, switchMap, tap, withLatestFrom } from 'rxjs/operators';

import { ProjectNotFoundError } from '../../../server/errors';
import { CommonMethods } from '../bedrock.actions';
import * as fromRoot from '../bedrock.reducers';
import { ElectronService, RpcError } from '../electron.service';
import * as forLayout from '../layout/layout.actions';
import { DirectoryOpener } from '../shared/directory-opener';
import { ErrorToastComponent } from '../toasts/error-toast/error-toast.component';
import * as forToast from '../toasts/toasts.actions';
Expand Down Expand Up @@ -61,7 +63,14 @@ export class ProjectEffects {
directory: action.directory,
})
.then(results => new SetOpenProject(results))
.catch(RpcError, err => new forToast.OpenToast(ErrorToastComponent, err)),
.catch(RpcError, err => {
if (err.originalName === ProjectNotFoundError.name) {
this.snack.open(err.message, undefined, { duration: 5000 });
return new forLayout.RemoveRecentProject(action.directory);
} else {
return new forToast.OpenToast(ErrorToastComponent, err);
}
}),
),
);

Expand Down Expand Up @@ -172,5 +181,6 @@ export class ProjectEffects {
private readonly electron: ElectronService,
private readonly store: Store<fromRoot.IState>,
private readonly dialog: MatDialog,
private readonly snack: MatSnackBar,
) {}
}
13 changes: 10 additions & 3 deletions src/server/electron-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import * as forUploader from '../app/editor/uploader/uploader.actions';
import { spawn } from 'child_process';
import { IRemoteError } from '../app/editor/electron.service';
import { FileDataStore } from './datastore';
import { hasMetadata, NoAuthenticationError } from './errors';
import { hasMetadata, NoAuthenticationError, ProjectNotFoundError } from './errors';
import { OpenBuilder } from './file-selector';
import { IssueTracker } from './issue-tracker';
import { NodeChecker } from './node-checker';
Expand All @@ -29,7 +29,7 @@ import { Quickstarter } from './quickstart';
import { RecentProjects } from './recent-projects';
import { SnapshotStore } from './snapshot-store';
import { TaskList } from './tasks/task';
import { Fetcher } from './util';
import { exists, Fetcher } from './util';
import { WebpackBundleTask } from './webpack-bundler-task';
import { WebpackDevServer } from './webpack-dev-server-task';

Expand Down Expand Up @@ -155,6 +155,10 @@ const methods: { [methodName: string]: (data: any, server: ElectronServer) => Pr
return await new RecentProjects().updateProjects(options.project);
},

[forLayout.LayoutMethod.RemoveRecentProject]: async (options: { directory: string }) => {
return await new RecentProjects().removeProject(options.directory);
},

[forLayout.LayoutMethod.GetRecentProjects]: async () => {
return await new RecentProjects().loadProjects();
},
Expand Down Expand Up @@ -192,8 +196,11 @@ const methods: { [methodName: string]: (data: any, server: ElectronServer) => Pr
*/
[forProject.ProjectMethods.OpenDirectory]: async (options: { directory: string }) => {
const store = new FileDataStore();
const project = new Project(options.directory);
if (!(await exists(options.directory))) {
throw new ProjectNotFoundError();
}

const project = new Project(options.directory);
const json = await project.packageJson(); // validate the package is there, throws if not

return <forProject.IProject>{
Expand Down
10 changes: 10 additions & 0 deletions src/server/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,13 @@ export class MissingWebpackConfig extends Error {
super('Webpack config not found.');
}
}

/**
* ProjectNotFoundError is thrown when trying to operate on a Project which
* doesn't exist on the filesystem.
*/
export class ProjectNotFoundError extends Error {
constructor() {
super('Project not found. It may have been moved or deleted.');
}
}
62 changes: 31 additions & 31 deletions src/server/recent-projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,40 @@ export interface IRecentProject {
* Loads and updates the recent projects used in CDK.
*/
export class RecentProjects {
/**
* Maximum number of recent projects to store.
*/
public static readonly maxRecentProject = 8;

constructor(private readonly store: IDataStore = new FileDataStore()) {}

public async loadProjects(): Promise<IRecentProject[] | null> {
return await this.store.loadGlobal<IRecentProject[]>('recent');
public async loadProjects(): Promise<IRecentProject[]> {
return (await this.store.loadGlobal<IRecentProject[]>('recent')) || [];
}

/**
* Removes a project from the list of recent projects.
*/
public async removeProject(directory: string) {
const projects = (await this.loadProjects()) || [];
await this.store.saveGlobal('recent', projects.filter(p => p.url !== directory));
}

public async updateProjects(url: string): Promise<void> {
let projects = await this.loadProjects();
if (!projects) {
projects = [];
}
const project = new Project(url);
const pkg = await project.packageJson();
const name = pkg.name;

const index = projects.findIndex(e => e.url === url);

if (index >= 0) {
projects.splice(index, 1);
}

const newProjects: IRecentProject[] = [
{
name,
url,
},
];

for (let i = 0; i < 4; i++) {
if (projects[i]) {
newProjects.push(projects[i]);
}
}

return await this.store.saveGlobal('recent', newProjects);
/**
* Adds a project to the list of recent projects.
*/
public async updateProjects(directory: string): Promise<void> {
const projects = (await this.loadProjects()).filter(p => p.url !== directory);
const pkg = await new Project(directory).packageJson();

projects.unshift({
name: pkg.name,
url: directory,
});

return await this.store.saveGlobal(
'recent',
projects.slice(0, RecentProjects.maxRecentProject),
);
}
}
2 changes: 1 addition & 1 deletion src/server/webpack-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { WebpackState } from '../app/editor/controls/controls.actions';
import { MissingWebpackConfig } from './errors';
import { Project } from './project';
import { ConsoleTask } from './tasks/console-task';
import { exists } from './util';
import { NpmInstallTask } from './tasks/npm-install-task';
import { exists } from './util';

/**
* Just making a note of some investigations here. *Ideally* I wanted to embed
Expand Down

0 comments on commit e6ed3c1

Please sign in to comment.