Skip to content

Commit

Permalink
Implement recent projects
Browse files Browse the repository at this point in the history
  • Loading branch information
pverscha committed Feb 8, 2024
1 parent 132275c commit 4c8b350
Show file tree
Hide file tree
Showing 16 changed files with 221 additions and 40 deletions.
6 changes: 4 additions & 2 deletions electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export default defineConfig({
resolve: {
alias: {
"@common": resolve("src/common"),
"@main": resolve("src/main")
"@main": resolve("src/main"),
"@renderer": resolve("src/renderer"),
}
},
},
Expand All @@ -18,7 +19,8 @@ export default defineConfig({
alias: {
"@common": resolve("src/common"),
"@preload": resolve("src/preload"),
"@main": resolve("src/main")
"@main": resolve("src/main"),
"@renderer": resolve("src/renderer"),
}
},
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"unipept-web-components": "^2.1.5",
"uuid": "^9.0.1",
"vue-router": "4",
"vuetify": "^3.4.0"
"vuetify": "^3.5.3"
},
"devDependencies": {
"@electron-toolkit/tsconfig": "^1.0.1",
Expand Down
11 changes: 11 additions & 0 deletions src/common/project/RecentProjectManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import RecentProject from "@common/project/RecentProject";

export default interface RecentProjectManager {
/**
* @return A list of all projects that were recently opened by the user, sorted descending by date. An empty list
* is returned if the recent projects file does not exist.
*/
getRecentProjects(): Promise<RecentProject[]>;

addRecentProject(projectPath: string): Promise<void>;
}
13 changes: 13 additions & 0 deletions src/main/IPCHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ConfigurationManager from "./configuration/ConfigurationManager";
import BrowserUtils from "./browser/BrowserUtils";
import DialogManager from "./dialog/DialogManager";
import AppManager from "./app/AppManager";
import FileSystemRecentProjectManager from "@main/project/FileSystemRecentProjectManager";

export default class IPCHandler {
public initializeIPC() {
Expand Down Expand Up @@ -50,5 +51,17 @@ export default class IPCHandler {
ipcMain.handle("app:get-app-version", () => appManager.getAppVersion());
ipcMain.handle("app:get-electron-version", () => appManager.getElectronVersion());
ipcMain.handle("app:get-chrome-version", () => appManager.getChromeVersion());

// Project actions
const recentProjectManager = new FileSystemRecentProjectManager();
ipcMain.handle(
"recent-projects:read-recent-projects",
() => recentProjectManager.getRecentProjects()
);
ipcMain.handle(
"recent-projects:add-recent-project",
(_, projectPath) => recentProjectManager.addRecentProject(projectPath)
);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,49 @@ import { promises as fs } from "fs";
import path from "path";

import RecentProject from "@common/project/RecentProject";
import RecentProjectManager from "@common/project/RecentProjectManager";
import { app } from "electron";
import FileSystemManager from "@main/file-system/FileSystemManager";

export default class RecentProjectsManager {
export default class FileSystemRecentProjectManager implements RecentProjectManager {
public static readonly AMOUNT_OF_RECENT_PROJECTS = 15;
private static readonly RECENT_PROJECTS_FILE = "unipept_recent_projects.config";

/**
* @return A list of all projects that were recently opened by the user, sorted descending by date. An empty list
* is returned if the recent projects file does not exist.
* @throws {Error} If the recent projects file was corrupt, not readable or otherwise damaged.
*/
public async getRecentProjects(): Promise<RecentProject[]> {
const storage = window.localStorage;
const readProjects = storage.getItem("recent-projects");
const fsManager = new FileSystemManager();

if (readProjects === null) {
return [];
}
try {
const readProjects = await fsManager.readFile(await this.getRecentProjectsPath());

if (readProjects === null) {
return [];
}

const parsedProjects: RecentProject[] = JSON.parse(readProjects).map(
(obj: any) => new RecentProject(obj.name, obj.path, new Date(parseInt(obj.lastOpened)))
);

const parsedProjects: RecentProject[] = JSON.parse(readProjects).map(
(obj: any) => new RecentProject(obj.name, obj.path, new Date(parseInt(obj.lastOpened)))
);

const filteredProjects: RecentProject[] = [];
for (const recentProject of parsedProjects) {
try {
await fs.stat(recentProject.path);
filteredProjects.push(recentProject);
} catch (err) {
// Do nothing, this project does not exist anymore.
const filteredProjects: RecentProject[] = [];
for (const recentProject of parsedProjects) {
try {
await fs.stat(recentProject.path);
filteredProjects.push(recentProject);
} catch (err) {
// Do nothing, this project does not exist anymore.
}
}

// We should also sort the filtered projects from newest to oldest.
return filteredProjects.sort((a, b) => b.lastOpened.getTime() - a.lastOpened.getTime());
} catch (err) {
return [];
}

// We should also sort the filtered projects from newest to oldest.
return filteredProjects.sort((a, b) => b.lastOpened.getTime() - a.lastOpened.getTime());
}

/**
Expand All @@ -61,7 +71,7 @@ export default class RecentProjectsManager {
recentProjects.push(new RecentProject(path.basename(projectPath), projectPath, new Date()));
}

this.writeRecentProjects(recentProjects);
await this.writeRecentProjects(recentProjects);
}

/**
Expand All @@ -70,12 +80,11 @@ export default class RecentProjectsManager {
*
* @param projects List of projects to store.
*/
private writeRecentProjects(projects: RecentProject[]): void {
const storage = window.localStorage;

storage.setItem("recent-projects", JSON.stringify(projects
private writeRecentProjects(projects: RecentProject[]): Promise<void> {
const fsManager = new FileSystemManager();
return fsManager.writeFile(this.getRecentProjectsPath(), JSON.stringify(projects
.sort((a, b) => b.lastOpened.getTime() - a.lastOpened.getTime())
.slice(0, RecentProjectsManager.AMOUNT_OF_RECENT_PROJECTS)
.slice(0, FileSystemRecentProjectManager.AMOUNT_OF_RECENT_PROJECTS)
.map(p => {
return {
name: p.name,
Expand All @@ -85,4 +94,10 @@ export default class RecentProjectsManager {
})
));
}

private getRecentProjectsPath(): string {
// Get a reference to the user data folder in which configuration data will be stored.
const configurationFolder = app.getPath("userData");
return configurationFolder + "/" + FileSystemRecentProjectManager.RECENT_PROJECTS_FILE;
}
}
9 changes: 8 additions & 1 deletion src/preload/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Configuration from '@common/configuration/Configuration';
import Configuration from "@common/configuration/Configuration";
import RecentProject from "@common/project/RecentProject";

interface ExposedAPI {
config: {
Expand All @@ -19,6 +20,12 @@ interface ExposedAPI {
chrome: Promise<string>,
electron: Promise<string>
}
},
project: {
recentProjects: {
getRecentProjects: () => Promise<RecentProject[]>,
addRecentProject: (projectPath: string) => Promise<void>
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const api = {
chrome: ipcRenderer.invoke("app:get-chrome-version"),
electron: ipcRenderer.invoke("app:get-electron-version")
}
},
project: {
recentProjects: {
getRecentProjects: () => ipcRenderer.invoke("recent-projects:read-recent-projects"),
addRecentProject: (projectPath: string) => ipcRenderer.invoke("recent-projects:add-recent-project", projectPath)
}
}
};

Expand Down
39 changes: 39 additions & 0 deletions src/renderer/components/pages/HomePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,45 @@
<v-col>
<div class="mx-12">
<h2>Project management</h2>

<v-row class="mt-1 mb-6">
<v-col md="12" lg="6">
<v-card>
<v-card-title>New here? Try our demo project!</v-card-title>
<v-card-text>
<div>
If this is the first time you're using our application, we advise you to open the
demo project and discover what this application can do for you.
</div>
<div class="text-center mt-2">
<v-btn color="primary">
Open demo project
</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>

<v-col md="12" lg="6">
<v-card class="d-flex flex-column" style="height: 100%;">
<v-card-title>Add project</v-card-title>
<v-card-text class="d-flex flex-column" style="height: 100%;">
<div class="flex-grow-1">Select an empty folder and create a new project.</div>
<div class="text-center mt-2">
<v-btn>
<v-icon class="mr-2">mdi-folder-plus-outline</v-icon>
Create project
</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>

</v-row>
</div>

<recent-project-overview />

</v-col>
<v-divider vertical />
<v-col>
Expand Down Expand Up @@ -52,6 +90,7 @@ import GithubCommunicator from "@renderer/logic/communication/github/GithubCommu
import ComparatorUtils from "@renderer/logic/utils/ComparatorUtils";
import UpdateNotesDialog from "@renderer/components/releases/UpdateNotesDialog.vue";
import ReleaseNotesCard from "@renderer/components/releases/ReleaseNotesCard.vue";
import RecentProjectOverview from "@renderer/components/project/RecentProjectOverview.vue";
const appVersion = ref(await window.api.app.versions.app);
const chromeVersion = ref(await window.api.app.versions.chrome);
Expand Down
66 changes: 66 additions & 0 deletions src/renderer/components/project/RecentProjectOverview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<div class="mx-12">
<h2>Recent projects</h2>

<v-list lines="two">
<v-list-item
v-for="recentProject of recentProjects"
:key="recentProject.path"
:title="recentProject.name"
:subtitle="recentProject.path"
>
<template v-slot:prepend>
<v-avatar>
<v-icon>mdi-folder-outline</v-icon>
</v-avatar>
</template>

<template v-slot:append>
<v-tooltip :text="recentProject.lastOpened.toLocaleDateString()">
<template #activator="{ props }">
<v-btn
icon="mdi-information-outline"
variant="text"
v-bind="props"
/>
</template>
</v-tooltip>
</template>

</v-list-item>
</v-list>

<div
class="open-project-button"
>
<v-tooltip
activator="parent"
>
Select a previously created project to open.
</v-tooltip>
<v-icon class="mr-2">
mdi-folder-open-outline
</v-icon>
<a>Open a project that's not shown in this list...</a>
</div>
</div>
</template>

<script setup lang="ts">
import { Ref, ref } from "vue";
import RemoteRecentProjectManager from "@renderer/logic/project/RemoteRecentProjectManager";
import RecentProject from "@common/project/RecentProject";
const recentProjectManager = new RemoteRecentProjectManager();
const recentProjects: Ref<RecentProject[]> = ref([]);
recentProjects.value = await recentProjectManager.getRecentProjects();
</script>

<style scoped>
.open-project-button {
display: flex;
justify-content: center;
align-items: center;
}
</style>
8 changes: 8 additions & 0 deletions src/renderer/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
/// <reference types="vite/client" />

import { ExposedAPI } from "src/preload";

declare module '*.vue' {
import type { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}

declare global {
interface Window {
api: ExposedAPI;
}
}
13 changes: 13 additions & 0 deletions src/renderer/logic/project/RemoteRecentProjectManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import RecentProject from "@common/project/RecentProject";
import RecentProjectManager from "@common/project/RecentProjectManager";

export default class RemoteRecentProjectManager implements RecentProjectManager {
constructor() {}

getRecentProjects(): Promise<RecentProject[]> {
return window.api.project.recentProjects.getRecentProjects();
}
addRecentProject(projectPath: string): Promise<void> {
return window.api.project.recentProjects.addRecentProject(projectPath);
}
}
8 changes: 4 additions & 4 deletions src/renderer/plugins/vuetify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const vuetify = createVuetify({
},
},
defaults: {
VTooltip: {
openDelay: 500,
location: "bottom"
},
// VTooltip: {
// openDelay: 500,
// location: "bottom"
// },
VDialog: {
maxWidth: 1000
}
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"src/main/**/*",
"src/main/**/*.sql",
"src/common/**/*",
"src/preload/**/*",
"src/preload/*.d.ts"
],
"compilerOptions": {
Expand Down
Loading

0 comments on commit 4c8b350

Please sign in to comment.