Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into better-psd-support
Browse files Browse the repository at this point in the history
  • Loading branch information
RvanderLaan committed Sep 27, 2022
2 parents 1a8a49a + c797d3d commit 062883a
Show file tree
Hide file tree
Showing 36 changed files with 460 additions and 756 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
"curly": "error",
"no-extra-boolean-cast": "error",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/prefer-string-starts-ends-with": "error"
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"no-empty-function": "off",
"@typescript-eslint/no-empty-function": "off"
},
"overrides": [
{
Expand Down
1 change: 0 additions & 1 deletion common/ExifIO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
// finds:
// - automatically update Subject/Keywords when updating HierarchicalSubject: https://exiftool.org/forum/index.php?topic=9208.0
// Update: only doing an export/import for all images for now, not real-time updates
import { Awaited } from './core';
import fse from 'fs-extra';
import { action, makeObservable, observable, runInAction } from 'mobx';
import exiftool from 'node-exiftool';
Expand Down
8 changes: 5 additions & 3 deletions common/core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

export function clamp(value: number, min: number, max: number): number {
if (value > max) {
return max;
Expand Down Expand Up @@ -46,7 +44,7 @@ export function retainArray<T>(array: T[], predicate: (element: T, index: number
// Move retained element to the beginning of the hole (deleted elements). Doing so will
// shift the hole to the end of the array.
const holeSlot = i - deleteCount;
array.copyWithin(holeSlot, i, i + 1);
swap(array, holeSlot, i);
}
i += 1;
}
Expand All @@ -56,3 +54,7 @@ export function retainArray<T>(array: T[], predicate: (element: T, index: number
export function notEmpty<TValue>(value: TValue): value is NonNullable<TValue> {
return value !== null && value !== undefined;
}

export function swap<T>(array: Array<T>, x: number, y: number): void {
[array[x], array[y]] = [array[y], array[x]];
}
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,35 +86,35 @@
"@types/fs-extra": "^9.0.0",
"@types/jest": "^27.4.0",
"@types/offscreencanvas": "^2019.6.2",
"@types/react": "^18.0.18",
"@types/react": "^18.0.20",
"@types/react-dom": "^18.0.6",
"@types/react-window": "^1.8.5",
"@types/utif": "^3.0.1",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^5.36.2",
"@typescript-eslint/parser": "^5.36.2",
"css-loader": "^6.5.1",
"electron": "20.0.3",
"electron": "20.1.3",
"electron-builder": "^23.3.3",
"eslint": "^8.23.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.7",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"html-webpack-plugin": "^5.5.0",
"jest": "^27.4.7",
"mini-css-extract-plugin": "^2.4.6",
"ncp": "^2.0.0",
"node-loader": "^2.0.0",
"normalize.css": "^8.0.1",
"prettier": "^2.5.1",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"sass": "^1.47.0",
"sass-loader": "^12.4.0",
"style-loader": "^3.3.1",
"ts-jest": "^27.1.2",
"ts-loader": "^9.2.6",
"typescript": "^4.4.4",
"ts-loader": "^9.3.1",
"typescript": "^4.8.3",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
},
Expand All @@ -127,7 +127,7 @@
"dexie": "^3.2.2",
"dexie-export-import": "^1.0.3",
"electron-updater": "^5.0.0",
"fs-extra": "^10.0.0",
"fs-extra": "^10.1.0",
"mobx": "^6.6.2",
"mobx-react-lite": "^3.4.0",
"node-exiftool": "^2.3.0",
Expand All @@ -138,7 +138,7 @@
"react-window": "^1.8.7",
"sourcemapped-stacktrace": "^1.1.11",
"utif": "^3.1.0",
"uuid": "^8.3.2",
"uuid": "^9.0.0",
"wasm-feature-detect": "^1.2.11"
}
}
2 changes: 1 addition & 1 deletion resources/style/remake/window-titlebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
height: var(--window-titlebar-height);
width: 100%;
display: flex;

line-height: initial;
background-color: var(--titlebar-background-color);
color: var(--titlebar-color);
&.inactive {
Expand Down
5 changes: 2 additions & 3 deletions src/api/data-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface IDataStorage {
fetchFiles: (order: OrderBy<FileDTO>, fileOrder: OrderDirection) => Promise<FileDTO[]>;
fetchFilesByID: (ids: ID[]) => Promise<FileDTO[]>;
fetchFilesByKey: (key: keyof FileDTO, value: IndexableType) => Promise<FileDTO[]>;
fetchLocations: (order: keyof LocationDTO, fileOrder: OrderDirection) => Promise<LocationDTO[]>;
fetchLocations: () => Promise<LocationDTO[]>;
fetchSearches: () => Promise<FileSearchDTO[]>;
searchFiles: (
criteria: ConditionDTO<FileDTO> | [ConditionDTO<FileDTO>, ...ConditionDTO<FileDTO>[]],
Expand All @@ -20,7 +20,7 @@ export interface IDataStorage {
matchAny?: boolean,
) => Promise<FileDTO[]>;
createTag: (tag: TagDTO) => Promise<void>;
createFile: (file: FileDTO) => Promise<void>;
createFilesFromPath: (path: string, files: FileDTO[]) => Promise<void>;
createLocation: (location: LocationDTO) => Promise<void>;
createSearch: (search: FileSearchDTO) => Promise<void>;
saveTag: (tag: TagDTO) => Promise<void>;
Expand All @@ -33,7 +33,6 @@ export interface IDataStorage {
removeLocation: (location: ID) => Promise<void>;
removeSearch: (search: ID) => Promise<void>;
countFiles: () => Promise<[fileCount: number, untaggedFileCount: number]>;
createFilesFromPath: (path: string, files: FileDTO[]) => Promise<void>;
clear: () => Promise<void>;
backupToFile: (path: string) => Promise<void>;
restoreFromFile: (path: string) => Promise<void>;
Expand Down
11 changes: 4 additions & 7 deletions src/backend/backend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import { TagDTO, ROOT_TAG_ID } from '../api/tag';
import { FileDTO } from '../api/file';
import { OrderDirection } from '../api/data-storage-search';

let backend = new Backend();

const mockTag: TagDTO = {
id: 'tag1',
name: 'tag1 name',
Expand Down Expand Up @@ -45,12 +43,8 @@ const mockFile: FileDTO = {

describe('Backend', () => {
describe('Tag API', () => {
beforeEach(async () => {
backend = new Backend();
await backend.init(true);
});

it('should be able to fetch a tag after adding it', async () => {
const backend = await Backend.init();
await backend.createTag({ ...mockTag });
const dbTags = await backend.fetchTags();
expect(dbTags).toHaveLength(2);
Expand All @@ -60,6 +54,7 @@ describe('Backend', () => {

describe('removeTag', () => {
it('should remove the tag from all files with that tag when removing that tag', async () => {
const backend = await Backend.init();
await backend.createTag({ ...mockTag });
await backend.createFile({ ...mockFile, id: '1', tags: [mockTag.id] });
await backend.createFile({ ...mockFile, id: '2' });
Expand All @@ -71,6 +66,7 @@ describe('Backend', () => {
});

it('should not remove other tags from the files of which a tag was deleted', async () => {
const backend = await Backend.init();
await backend.createTag({ ...mockTag, id: 'tag1' });
await backend.createTag({ ...mockTag, id: 'tag2' });
await backend.createFile({ ...mockFile, id: '1', tags: ['tag1', 'tag2'] });
Expand All @@ -87,6 +83,7 @@ describe('Backend', () => {

describe('removeTags', () => {
it('should remove only the tags that were deleted from the files that had them', async () => {
const backend = await Backend.init();
await backend.createTag({ ...mockTag, id: 'tag1' });
await backend.createTag({ ...mockTag, id: 'tag2' });
await backend.createTag({ ...mockTag, id: 'tag3' });
Expand Down
49 changes: 24 additions & 25 deletions src/backend/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class Backend implements IDataStorage {
private db: Dexie;
private backupScheduler: BackupScheduler;

constructor() {
private constructor() {
console.info(`Initializing database "${DB_NAME}"...`);
// Initialize database tables
this.db = dbInit(dbConfig, DB_NAME);
Expand All @@ -38,26 +38,28 @@ export default class Backend implements IDataStorage {
this.backupScheduler = new BackupScheduler(this);
}

async init(isMainWindow: boolean): Promise<void> {
if (isMainWindow) {
// Create a root tag if it does not exist
const tagCount = await this.tagRepository.count();
if (tagCount === 0) {
await this.createTag({
id: ROOT_TAG_ID,
name: 'Root',
dateAdded: new Date(),
subTags: [],
color: '',
isHidden: false,
});
}
static async init(): Promise<Backend> {
const backend = new Backend();
// Create a root tag if it does not exist
const tagCount = await backend.tagRepository.count();
if (tagCount === 0) {
await backend.createTag({
id: ROOT_TAG_ID,
name: 'Root',
dateAdded: new Date(),
subTags: [],
color: '',
isHidden: false,
});
}
return backend;
}

try {
await this.backupScheduler.initialize(await RendererMessenger.getDefaultBackupDirectory());
} catch (e) {
console.error('Could not initialize backup scheduler', e);
}
async setupBackup(): Promise<void> {
try {
await this.backupScheduler.initialize(await RendererMessenger.getDefaultBackupDirectory());
} catch (e) {
console.error('Could not initialize backup scheduler', e);
}
}

Expand All @@ -82,12 +84,9 @@ export default class Backend implements IDataStorage {
return this.fileRepository.getByKey(key, value);
}

async fetchLocations(
order: keyof LocationDTO,
fileOrder: OrderDirection,
): Promise<LocationDTO[]> {
async fetchLocations(): Promise<LocationDTO[]> {
console.info('Backend: Fetching locations...');
return this.locationRepository.getAllOrdered(order, fileOrder);
return this.locationRepository.getAllOrdered('dateAdded', OrderDirection.Asc);
}

async fetchSearches(): Promise<FileSearchDTO[]> {
Expand Down
6 changes: 1 addition & 5 deletions src/backend/db-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,9 @@ type SearchConjunction = 'and' | 'or';
* Extends Dexie: https://dexie.org/docs/Tutorial/Consuming-dexie-as-a-module
*/
export default class BaseRepository<T> implements IRepository<T> {
db: Dexie;
collectionName: string;
collection: Dexie.Table<T, ID>;
private readonly collection: Dexie.Table<T, ID>;

constructor(collectionName: string, db: Dexie) {
this.db = db;
this.collectionName = collectionName;
this.collection = db.table(collectionName);
}

Expand Down
18 changes: 4 additions & 14 deletions src/entities/SearchCriteria.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, Lambda, makeObservable, observable, observe } from 'mobx';
import { action, Lambda, makeObservable, observable } from 'mobx';
import RootStore from 'src/frontend/stores/RootStore';
import { camelCaseToSpaced } from 'common/fmt';
import { FileDTO } from '../api/file';
Expand Down Expand Up @@ -104,15 +104,6 @@ export abstract class ClientFileSearchCriteria implements IBaseSearchCriteria {
}
}

observe(callback: (criteria: ClientFileSearchCriteria) => void): void {
this.disposers.push(
observe(this, 'key', () => callback(this)),
observe(this, 'valueType', () => callback(this)),
observe(this, 'operator', () => callback(this)),
observe(this as typeof this & { value: unknown }, 'value', () => callback(this)),
);
}

dispose(): void {
for (const disposer of this.disposers) {
disposer();
Expand Down Expand Up @@ -149,7 +140,6 @@ export class ClientTagSearchCriteria extends ClientFileSearchCriteria {
)} ${!this.value ? 'no tags' : rootStore.tagStore.get(this.value)?.name}`;
};

@action.bound
serialize = (rootStore: RootStore): ITagSearchCriteria => {
// for the *recursive options, convert it to the corresponding non-recursive option,
// by putting all child IDs in the value in the serialization step
Expand Down Expand Up @@ -201,7 +191,7 @@ export class ClientStringSearchCriteria extends ClientFileSearchCriteria {
StringOperatorLabels[this.operator as StringOperatorType] || camelCaseToSpaced(this.operator)
} "${this.value}"`;

@action.bound serialize = (): IStringSearchCriteria => {
serialize = (): IStringSearchCriteria => {
return {
key: this.key,
valueType: this.valueType,
Expand Down Expand Up @@ -240,7 +230,7 @@ export class ClientNumberSearchCriteria extends ClientFileSearchCriteria {
NumberOperatorSymbols[this.operator as NumberOperatorType] || camelCaseToSpaced(this.operator)
} ${this.value}`;

@action.bound serialize = (): INumberSearchCriteria => {
serialize = (): INumberSearchCriteria => {
return {
key: this.key,
valueType: this.valueType,
Expand Down Expand Up @@ -281,7 +271,7 @@ export class ClientDateSearchCriteria extends ClientFileSearchCriteria {
NumberOperatorSymbols[this.operator as NumberOperatorType] || camelCaseToSpaced(this.operator)
} ${this.value.toLocaleDateString()}`;

@action.bound serialize = (): IDateSearchCriteria => {
serialize = (): IDateSearchCriteria => {
return {
key: this.key,
valueType: this.valueType,
Expand Down
2 changes: 1 addition & 1 deletion src/entities/SearchItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class ClientFileSearchItem {
this.index = newIndex;
}

@action.bound serialize(rootStore: RootStore): FileSearchDTO {
serialize(rootStore: RootStore): FileSearchDTO {
return {
id: this.id,
name: this.name,
Expand Down
13 changes: 1 addition & 12 deletions src/frontend/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect } from 'react';
import { observer } from 'mobx-react-lite';

import { useStore } from './contexts/StoreContext';

import ErrorBoundary from './containers/ErrorBoundary';
import HelpCenter from './containers/HelpCenter';
import SplashScreen from './containers/SplashScreen';
import { Toaster as CustomToaster } from './components/Toaster';

import AdvancedSearchDialog from './containers/AdvancedSearch';
Expand All @@ -19,7 +18,6 @@ import About from './containers/About';
import { CustomThemeProvider } from './hooks/useCustomTheme';
import { useClipboardImporter } from './hooks/useClipboardImporter';

const SPLASH_SCREEN_TIME = 1400;
const PLATFORM = process.platform;

const App = observer(() => {
Expand All @@ -29,14 +27,9 @@ const App = observer(() => {
useWorkerListener();
useClipboardImporter(uiStore);

// Show splash screen for some time or when app is not initialized
const [showSplash, setShowSplash] = useState(true);

const isOutlinerOpen = uiStore.isOutlinerOpen;

useEffect(() => {
setTimeout(() => setShowSplash(false), SPLASH_SCREEN_TIME);

// Add listener for global keyboard shortcuts
window.addEventListener('keydown', uiStore.processGlobalShortCuts);

Expand All @@ -50,10 +43,6 @@ const App = observer(() => {
}
}, [uiStore, isOutlinerOpen]);

if (!uiStore.isInitialized || showSplash) {
return <SplashScreen />;
}

return (
<CustomThemeProvider>
<DropContextProvider onDragEnter={openOutlinerOnDragEnter}>
Expand Down
Loading

0 comments on commit 062883a

Please sign in to comment.