Skip to content
This repository has been archived by the owner on Dec 31, 2024. It is now read-only.

Commit

Permalink
Added basic window functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverschwendener committed Jan 5, 2019
1 parent 115f27c commit 71f28a7
Show file tree
Hide file tree
Showing 18 changed files with 232 additions and 69 deletions.
2 changes: 1 addition & 1 deletion main.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="./styles/app.css">
<title>Electron</title>
<title>ueli</title>
</head>
<body>
<div id="app">
Expand Down
6 changes: 4 additions & 2 deletions src/common/config/default-general-options.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { GeneralOptions } from "./general-options";

export const defaultGeneralOptions: GeneralOptions = {
maxSearchResults: 8,
hotKey: "alt+space",
maxSearchResults: 32,
maxSearchResultsPerPage: 8,
refreshIntervalInSeconds: 60,
searchResultHeight: 40,
searchResultHeight: 50,
userInputHeight: 60,
windowWidth: 600,
};
2 changes: 2 additions & 0 deletions src/common/config/general-options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export interface GeneralOptions {
windowWidth: number;
maxSearchResults: number;
maxSearchResultsPerPage: number;
refreshIntervalInSeconds: number;
searchResultHeight: number;
userInputHeight: number;
hotKey: string;
}
4 changes: 4 additions & 0 deletions src/common/ipc-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ export enum IpcChannels {
search = "search",
searchResponse = "search-response",
execute = "execute",
executionSucceeded = "executed-succeeded",
mainWindowHasBeenShown = "main-window-has-been-shown",
userInputHasBeenResetAndMainWindowCanBeHidden = "user-input-has-been-reset-and-main-window-can-be-hidden",
reloadApp = "reload-app",
}
3 changes: 3 additions & 0 deletions src/common/search-result-item.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { PluginType } from "../main/plugin-type";

export interface SearchResultItem {
executionArgument: string;
icon: string;
name: string;
originPluginType: PluginType;
}
14 changes: 14 additions & 0 deletions src/main/helpers/file-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ interface FileStat {
}

export class FileHelpers {
public static readFilesFromFoldersRecursively(folderPaths: string[]): Promise<string[]> {
return new Promise((resolve, reject) => {
Promise.all(folderPaths.map((folderPath) => this.readFilesFromFolderRecursively(folderPath)))
.then((fileLists) => {
let result: string[] = [];
fileLists.forEach((fileList) => result = result.concat(fileList));
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}

public static readFilesFromFolderRecursively(folderPath: string): Promise<string[]> {
return new Promise((resolve, reject) => {
readdir(folderPath, (readDirError, readDirResult) => {
Expand Down
67 changes: 54 additions & 13 deletions src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { app, BrowserWindow, ipcMain } from "electron";
import { app, BrowserWindow, ipcMain, globalShortcut } from "electron";
import { join } from "path";
import { IpcChannels } from "../common/ipc-channels";
import { SearchEngine } from "./search-engine";
Expand All @@ -8,11 +8,12 @@ import { defaultUserConfigOptions } from "../common/config/default-user-config-o
import { SearchResultItem } from "../common/search-result-item";
import { ApplicationIconService } from "./search-plugins/application-search-plugin/application-icon-service";
import { getWindowsAppIcons } from "./search-plugins/application-search-plugin/application-icon-helpers";
import { executeWindowsApp } from "./search-plugins/application-search-plugin/application-execution";

let window: BrowserWindow;
let mainWindow: BrowserWindow;
const config = defaultUserConfigOptions;
const searchEngine = new SearchEngine([
new ApplicationSearchPlugin(new FileApplicationRepository(new ApplicationIconService(getWindowsAppIcons), config.applicationSearchOptions)),
new ApplicationSearchPlugin(new FileApplicationRepository(new ApplicationIconService(getWindowsAppIcons), config.applicationSearchOptions), executeWindowsApp),
], config.generalOptions);

const rescanInterval = setInterval(() => {
Expand All @@ -28,34 +29,65 @@ const rescanInterval = setInterval(() => {
}, config.generalOptions.refreshIntervalInSeconds * 1000);

const getMaxWindowHeight = (): number => {
return config.generalOptions.maxSearchResults * config.generalOptions.searchResultHeight + config.generalOptions.userInputHeight;
return config.generalOptions.maxSearchResultsPerPage * config.generalOptions.searchResultHeight + config.generalOptions.userInputHeight;
};

const updateWindowSize = (searchResultCount: number) => {
const updateWindowSize = (window: BrowserWindow, searchResultCount: number) => {
window.setResizable(true);
const windowHeight = searchResultCount > config.generalOptions.maxSearchResults
const windowHeight = searchResultCount > config.generalOptions.maxSearchResultsPerPage
? getMaxWindowHeight()
: searchResultCount * config.generalOptions.searchResultHeight + config.generalOptions.userInputHeight;

window.setSize(config.generalOptions.windowWidth, windowHeight);
window.setResizable(false);
};

const registerGlobalKeyboardShortcut = (toggleAction: () => void) => {
globalShortcut.unregisterAll();
globalShortcut.register(config.generalOptions.hotKey, toggleAction);
};

const showWindow = () => {
mainWindow.show();
mainWindow.webContents.send(IpcChannels.mainWindowHasBeenShown);
};

const hideWindow = () => {
setTimeout(() => {
mainWindow.hide();
}, 25);
};

const toggleMainWindow = () => {
if (mainWindow.isVisible()) {
hideWindow();
} else {
showWindow();
}
};

const reloadApp = () => {
mainWindow.reload();
};

const quitApp = () => {
clearInterval(rescanInterval);
app.quit();
};

app.on("ready", () => {
window = new BrowserWindow({
mainWindow = new BrowserWindow({
center: true,
frame: false,
height: getMaxWindowHeight(),
resizable: false,
show: false,
skipTaskbar: true,
transparent: true,
width: config.generalOptions.windowWidth,
});
window.loadFile(join(__dirname, "..", "main.html"));
updateWindowSize(0);
mainWindow.loadFile(join(__dirname, "..", "main.html"));
updateWindowSize(mainWindow, 0);
registerGlobalKeyboardShortcut(toggleMainWindow);
});

app.on("window-all-closed", quitApp);
Expand All @@ -64,7 +96,7 @@ app.on("quit", quitApp);
ipcMain.on(IpcChannels.search, (event: Electron.Event, userInput: string) => {
searchEngine.getSearchResults(userInput)
.then((result) => {
updateWindowSize(result.length);
updateWindowSize(mainWindow, result.length);
event.sender.send(IpcChannels.searchResponse, result);
})
.catch((err) => {
Expand All @@ -74,6 +106,15 @@ ipcMain.on(IpcChannels.search, (event: Electron.Event, userInput: string) => {
});

ipcMain.on(IpcChannels.execute, (event: Electron.Event, searchResultItem: SearchResultItem) => {
// tslint:disable-next-line:no-console
console.log(`Executing: ${searchResultItem.name}`);
searchEngine.execute(searchResultItem)
.then(() => {
mainWindow.webContents.send(IpcChannels.executionSucceeded);
hideWindow();
})
// tslint:disable-next-line:no-console
.catch((err) => console.log(err));
});

ipcMain.on(IpcChannels.reloadApp, () => {
reloadApp();
});
3 changes: 3 additions & 0 deletions src/main/plugin-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum PluginType {
ApplicationSearchPlugin = "application-search-plugin",
}
13 changes: 13 additions & 0 deletions src/main/search-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ export class SearchEngine {
});
}

public execute(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve, reject) => {
const originPlugin = this.plugins.find((plugin) => plugin.pluginType === searchResultItem.originPluginType);
if (originPlugin !== undefined) {
originPlugin.execute(searchResultItem)
.then(() => resolve())
.catch((err) => reject(err));
} else {
reject("No plugin found for this search result item");
}
});
}

public refreshIndexes(): Promise<void> {
return new Promise((resolve, reject) => {
const promises = this.plugins.map((p) => p.refreshIndex());
Expand Down
3 changes: 3 additions & 0 deletions src/main/search-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { SearchResultItem } from "../common/search-result-item";
import { PluginType } from "./plugin-type";

export interface SearchPlugin {
pluginType: PluginType;
getAll(): Promise<SearchResultItem[]>;
execute(searchResultItem: SearchResultItem): Promise<void>;
refreshIndex(): Promise<void>;
clearCache(): Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { SearchResultItem } from "../../../common/search-result-item";
import { exec } from "child_process";

const executeCommand = (command: string): Promise<void> => {
return new Promise((resolve, reject) => {
exec(command, (err): void => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};

export function executeMacApp(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve, reject) => {
const command = `open "${searchResultItem.executionArgument}"`;
executeCommand(command)
.then(() => resolve())
.catch((err) => reject(err));
});
}

export function executeWindowsApp(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve, reject) => {
const command = `start "" "${searchResultItem.executionArgument}"`;
executeCommand(command)
.then(() => resolve())
.catch((err) => reject(err));
});
}
Original file line number Diff line number Diff line change
@@ -1,54 +1,57 @@
import { SearchPlugin } from "../../search-plugin";
import { SearchResultItem } from "../../../common/search-result-item";
import { ApplicationRepository } from "./application-repository";
import { PluginType } from "../../plugin-type";
import { Application } from "./application";

export class ApplicationSearchPlugin implements SearchPlugin {
public readonly pluginType = PluginType.ApplicationSearchPlugin;
private readonly applicationRepository: ApplicationRepository;
private readonly executeApplication: (searchResultItem: SearchResultItem) => Promise<void>;

constructor(applicationRepository: ApplicationRepository) {
constructor(applicationRepository: ApplicationRepository, executeApplication: (searchResultItem: SearchResultItem) => Promise<void>) {
this.applicationRepository = applicationRepository;
this.executeApplication = executeApplication;
}

public getAll(): Promise<SearchResultItem[]> {
return new Promise((resolve, reject) => {
this.applicationRepository.getAll()
.then((applications) => {
const result = applications.map((application): SearchResultItem => {
return {
executionArgument: application.filePath,
icon: application.icon,
name: application.name,
};
});
resolve(result);
})
.catch((err) => {
reject(err);
});
.then((applications) => resolve(applications.map((application) => this.createSearchResultItemFromApplication(application))))
.catch((err) => reject(err));
});
}

public execute(searchResultItem: SearchResultItem): Promise<void> {
return new Promise((resolve, reject) => {
this.executeApplication(searchResultItem)
.then(() => resolve())
.catch((err) => reject(err));
});
}

public refreshIndex(): Promise<void> {
return new Promise((resolve, reject) => {
this.applicationRepository.refreshIndex()
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
.then(() => resolve())
.catch((err) => reject(err));
});
}

public clearCache(): Promise<void> {
return new Promise((resolve, reject) => {
this.applicationRepository.clearCache()
.then(() => {
resolve();
})
.catch((err) => {
reject(`Error while trying to clear application repository cache: ${err}`);
});
.then(() => resolve())
.catch((err) => reject(`Error while trying to clear application repository cache: ${err}`));
});
}

private createSearchResultItemFromApplication(application: Application): SearchResultItem {
return {
executionArgument: application.filePath,
icon: application.icon,
name: application.name,
originPluginType: this.pluginType,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,8 @@ export class FileApplicationRepository implements ApplicationRepository {

public refreshIndex(): Promise<void> {
return new Promise((resolve, reject) => {
const applicationFilePromises = this.config.applicationFolders.map((applicationFolder) => {
return FileHelpers.readFilesFromFolderRecursively(applicationFolder);
});

Promise.all(applicationFilePromises)
.then((fileLists) => {
let files: string[] = [];
fileLists.forEach((fileList) => {
files = files.concat(fileList);
});

FileHelpers.readFilesFromFoldersRecursively(this.config.applicationFolders)
.then((files) => {
const applications = files
.filter((file) => this.filterByApplicationFileExtensions(file))
.map((applicationFile): Application => this.createApplicationFromFilePath(applicationFile));
Expand All @@ -55,8 +46,8 @@ export class FileApplicationRepository implements ApplicationRepository {
reject(err);
});
})
.catch((applicationError) => {
reject(applicationError);
.catch((err) => {
reject(err);
});
});
}
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ vueEventDispatcher.$on(VueEventChannels.handleExecution, (searchResultIem: Searc
}
});

vueEventDispatcher.$on(VueEventChannels.reloadApp, () => {
ipcRenderer.send(IpcChannels.reloadApp);
});

ipcRenderer.on(IpcChannels.searchResponse, (event: Electron.Event, searchResults: SearchResultItem[]) => {
vueEventDispatcher.$emit(VueEventChannels.searchResultsUpdated, searchResults);
});

ipcRenderer.on(IpcChannels.executionSucceeded, () => {
vueEventDispatcher.$emit(VueEventChannels.executionSucceeded);
});

ipcRenderer.on(IpcChannels.mainWindowHasBeenShown, () => {
vueEventDispatcher.$emit(VueEventChannels.mainWindowHasBeenShown);
});
Loading

0 comments on commit 71f28a7

Please sign in to comment.