Skip to content

Commit

Permalink
Merge pull request #2 from TowhidKashem/picker-changes
Browse files Browse the repository at this point in the history
Use native directory picker and other improvements
  • Loading branch information
TowhidKashem committed Dec 28, 2022
2 parents 6c9b475 + 4882ab6 commit f4310bd
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 284 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Image Classification Dataset Prepper
</h1>

https://user-images.githubusercontent.com/1139927/209484470-7ca2c62c-bf67-4cfa-be6f-5a9e014e33dd.mp4
https://user-images.githubusercontent.com/1139927/209875258-38f3774f-0d5e-4926-964c-a2f3a80ec60a.mp4

<br />

Expand Down Expand Up @@ -49,8 +49,9 @@ This will spin up a dev server which auto-reloads on file changes.
- <kbd>←</kbd> - prev image
- <kbd>Space</kbd> - delete image
- <kbd>CMD</kbd> / <kbd>CTRL</kbd> + <kbd>Z</kbd> - undo delete
- <kbd>a</kbd> - pick image (this moves the image into a subfolder called `_picked`)
- <kbd>CMD</kbd> / <kbd>CTRL</kbd> + <kbd>R</kbd> - refresh the app
- click the "&#x21bb;" icon on the top right of the screen to reset history (visited folders appear at a lower opacity for tracking purposes)
- click the "&#x21bb;" icon on the top right of the screen to reset history (visited folders whose contents were looped through at least once appear at a lower opacity for tracking purposes)
- &#9835; pop noise sounds each time a full loop completes when cycling through a directory

## Folder structure
Expand Down Expand Up @@ -80,7 +81,6 @@ Just make sure that the selected folder contains either **only images** or **onl
- Automate deleting broken images
- Crop and keep only part of an image
- Convert all images to a particular format
- Rename all images with numeric file names

## Credits

Expand Down
Binary file modified assets/video.mp4
Binary file not shown.
13 changes: 13 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"dictionaryDefinitions": [
{
"name": "dictionary",
"path": "./dictionary.txt",
"addWords": true
}
],
"dictionaries": ["dictionary"],
"ignorePaths": ["node_modules", "dictionary.txt"]
}
13 changes: 13 additions & 0 deletions dictionary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
arrowleft
arrowright
asar
chakra
compat
electronmon
Kashem
nsis
pmmmwh
svgr
teamsupercell
Towhid
unstage
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"name": "image-reviewer",
"main": "./src/main/main.ts",
"author": "Towhid Kashem",
"description": "A minimal desktop app to quickly review and delete images when collecting data sets for image classification models",
"version": "0.1.0",
"description": "A desktop file explorer with keyboard shortcuts to quickly prep images when collecting datasets for training image classification models",
"version": "0.2.0",
"license": "MIT",
"browserslist": {
"production": [
Expand Down Expand Up @@ -101,8 +101,10 @@
"pretty": "prettier --write '**/*.{js,ts,tsx,mts,json,yaml,yml,md}'",
"lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
"type-check": "tsc",
"bump": "yarn version --new-version",
"git:commit": "git status && git add . && git commit -m 'work' && git push",
"git:commit-skip-hooks": "git status && git add . && git commit -m 'work' --no-verify && git push --no-verify",
"git:rebase": "git fetch origin && git rebase -i origin/main",
"git:reset": "git clean --force && git reset --hard",
"git:undo-last-commit": "git reset --soft HEAD~1",
"git:unstage-all-files": "git reset HEAD -- .",
Expand Down
4 changes: 2 additions & 2 deletions release/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "image-reviewer",
"version": "0.1.0",
"description": "A minimal desktop app to quickly review and delete images when collecting data sets for image classification models",
"version": "0.2.0",
"description": "A desktop file explorer with keyboard shortcuts to quickly prep images when collecting datasets for training image classification models",
"license": "MIT",
"author": "Towhid Kashem",
"main": "./dist/main/main.js",
Expand Down
49 changes: 42 additions & 7 deletions src/main/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import path from 'path';
import { ipcMain, IpcMainInvokeEvent } from 'electron';
import { channels } from '../renderer/_data';

const TRASH_DIR = '_trash';
const PICKED_DIR = '_picked';

const handleListDirectory = async (
_e: IpcMainInvokeEvent,
filePath: string
): Promise<ResponseT<DirContentT[]>> => {
const BLOCK_LIST = ['1', '.DS_Store', 'trash.tmp'];
const BLOCK_LIST = ['1', '.DS_Store', TRASH_DIR, PICKED_DIR];

try {
const contents = fs
Expand Down Expand Up @@ -49,7 +52,7 @@ const handleDeleteFile = async (

const parentDir = pathSegments.join('/');

const trashDir = `${parentDir}/trash.tmp`;
const trashDir = `${parentDir}/${TRASH_DIR}`;

if (!fs.existsSync(trashDir)) fs.mkdirSync(trashDir);

Expand All @@ -74,7 +77,7 @@ const handleUndoDeleteFile = async (

const parentDir = pathSegments.join('/');

const trashDir = `${parentDir}/trash.tmp`;
const trashDir = `${parentDir}/${TRASH_DIR}`;

fs.renameSync(`${trashDir}/${fileToDelete}`, filePath);

Expand All @@ -91,10 +94,39 @@ const handleEmptyTrash = async (
filePath: string
): Promise<ResponseT<void>> => {
try {
fs.rmSync(`${filePath}/trash.tmp`, {
recursive: true,
force: true
});
const TRASH_DIR_PATH = `/${filePath}/${TRASH_DIR}`;

if (fs.existsSync(TRASH_DIR_PATH)) {
fs.rmSync(TRASH_DIR_PATH, {
recursive: true,
force: true
});
}

return { error: null };
} catch (error) {
return {
error: new Error(error as string)
};
}
};

const handleMoveFile = async (
_e: IpcMainInvokeEvent,
filePath: string
): Promise<ResponseT<void>> => {
try {
const pathSegments = filePath.split('/');

const fileToMove = pathSegments.pop();

const parentDir = pathSegments.join('/');

const pickedDir = `${parentDir}/${PICKED_DIR}`;

if (!fs.existsSync(pickedDir)) fs.mkdirSync(pickedDir);

fs.renameSync(filePath, `${pickedDir}/${fileToMove}`);

return { error: null };
} catch (error) {
Expand All @@ -106,6 +138,9 @@ const handleEmptyTrash = async (

// endpoints
ipcMain.handle(channels.LIST_DIR, handleListDirectory);

ipcMain.handle(channels.DELETE_FILE, handleDeleteFile);
ipcMain.handle(channels.UNDO_DELETE, handleUndoDeleteFile);
ipcMain.handle(channels.EMPTY_TRASH, handleEmptyTrash);

ipcMain.handle(channels.MOVE_FILE, handleMoveFile);
104 changes: 56 additions & 48 deletions src/main/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path';
import { app, shell, protocol, BrowserWindow } from 'electron';
import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron';
import MenuBuilder from './menu';
import { resolveHtmlPath } from './_utils';

Expand All @@ -12,7 +12,7 @@ if (process.env.NODE_ENV === 'production') {
sourceMapSupport.install();
}

const isDebug =
export const isDebug =
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';

if (isDebug) require('electron-debug')({ showDevTools: false });
Expand All @@ -22,15 +22,17 @@ const installExtensions = async () => {
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS'];

return installer
.default(
try {
return installer.default(
extensions.map((name) => installer[name]),
forceDownload
)
.catch(console.log);
);
} catch (error) {
console.error(error);
}
};

const createWindow = async () => {
const createWindow = async (): Promise<void> => {
if (isDebug) await installExtensions();

const RESOURCES_PATH = app.isPackaged
Expand All @@ -54,56 +56,45 @@ const createWindow = async () => {

mainWindow.loadURL(resolveHtmlPath('index.html'));

mainWindow.on('ready-to-show', () => {
if (!mainWindow) throw new Error('`mainWindow` is not defined');
mainWindow
.on('ready-to-show', () => {
if (!mainWindow) throw new Error('`mainWindow` is not defined');
if (process.env.START_MINIMIZED) return mainWindow.minimize();

if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});

mainWindow.on('closed', () => {
mainWindow = null;
});

const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
})
.on('closed', () => {
mainWindow = null;
});

// open urls in the user's browser
mainWindow.webContents.setWindowOpenHandler((edata) => {
shell.openExternal(edata.url);
return { action: 'deny' };
});
new MenuBuilder(mainWindow).buildMenu();
};

// event listeners

// respect the osx convention of having the application in memory even after all windows have been closed
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

// restrict navigation to known domains for better security
const NAV_ALLOW_LIST = ['https://image-reviewer.com'];
app.on('web-contents-created', (_, contents) => {
contents.on('will-navigate', (e, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
app
// respect the osx convention of having the application in memory
// even after all windows have been closed
.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
})
// restrict navigation to known domains for better security
.on('web-contents-created', (_, contents) => {
contents.on('will-navigate', (e, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
const NAV_ALLOW_LIST = ['https://image-reviewer.com'];

if (!NAV_ALLOW_LIST.includes(parsedUrl.origin)) {
e.preventDefault();
}
if (!NAV_ALLOW_LIST.includes(parsedUrl.origin)) e.preventDefault();
});
});
});

app
.whenReady()
.then(() => {
const onAppReady = async (): Promise<void> => {
try {
await app.whenReady();

createWindow();

// on mac its common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open
app.on('activate', () => {
// on mac it's common to re-create a window in the app when the dock icon is clicked and there are no other windows open
if (mainWindow === null) createWindow();
});

Expand All @@ -118,5 +109,22 @@ app
return callback({ error: 404 });
}
});
})
.catch(console.log);

// open directory picker dialog
ipcMain.on('open-picker-dialog', async () => {
try {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory']
});

mainWindow.webContents.send('dialog-picker-result', result);
} catch (error) {
console.error(error);
}
});
} catch (error) {
console.error(error);
}
};

onAppReady();
10 changes: 4 additions & 6 deletions src/main/menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { app, BrowserWindow, Menu, MenuItemConstructorOptions } from 'electron';

const isDev =
process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
import { isDebug } from './main';

interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
selector?: string;
Expand All @@ -16,7 +14,7 @@ export default class MenuBuilder {
}

buildMenu(): Menu {
if (isDev) this.setupDevelopmentEnvironment();
if (isDebug) this.setupDevelopmentEnvironment();

const template =
process.platform === 'darwin'
Expand Down Expand Up @@ -90,7 +88,7 @@ export default class MenuBuilder {
click: () =>
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
},
isDev
isDebug
? {
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
Expand Down Expand Up @@ -155,7 +153,7 @@ export default class MenuBuilder {
click: () =>
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen())
},
isDev
isDebug
? {
label: 'Toggle &Developer Tools',
accelerator: 'Alt+Ctrl+I',
Expand Down
Loading

0 comments on commit f4310bd

Please sign in to comment.