Skip to content

Commit

Permalink
feat: Display all categories and update UI
Browse files Browse the repository at this point in the history
This release requires MoneyMoney version `>=2.3.27` which right now is only available as beta release.

See [MoneyMoney Beta-Versionen](https://moneymoney-app.com/beta/)
And consider [making a backup before](https://moneymoney-app.com/help/)

Due to the use of new MoneyMoney APIs, `.budget` files created prior to this can not be opened with this version. (ref #43)

Detailed changes:

* feat: split out sidebar from scroller
* feat: sync overview scroll with main view
* feat: hide default title bar
* feat: display budget name in sidebar
* feat: re-implement scrollTo on MonthOverview click
* fix: add header to all screens and fix layout issues
* feat: display unused categories
* fix(InfiniteSlider): ensure initial view can be scrolled
* feat: use uuid api from moneymoney
* feat: increase budget file format version
* feat: add header to loading and error screens
* feat: throw human readable error when budget file format is not supported
* fix: lookup accounts by uuid when re-calculating starting balance

---

ref #44
fix #22
fix #39
fix #28 (wontfix)
fix #26
  • Loading branch information
Xiphe authored Apr 29, 2020
1 parent 7ff3c1d commit 33c2ff2
Show file tree
Hide file tree
Showing 68 changed files with 1,490 additions and 1,225 deletions.
58 changes: 10 additions & 48 deletions main/moneymoney/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,59 +45,12 @@ function withRetry<T extends (...args: any[]) => Promise<any>>(
}

export default function moneymoneyHandlers(ipcMain: IpcMain) {
const unusableAccounts: string[] = [];
const usableAccounts: string[] = [];
const canHandleTransactions = async (accountNumber: string) => {
if (unusableAccounts.includes(accountNumber)) {
return false;
}
if (usableAccounts.includes(accountNumber)) {
return true;
}

try {
await osascript(
join(scriptsDir, 'exportTransactions.applescript'),
accountNumber,
new Date().toLocaleDateString(),
);
usableAccounts.push(accountNumber);
return true;
} catch (err) {
unusableAccounts.push(accountNumber);
return false;
}
};

ipcMain.handle(
'MM_EXPORT_ACCOUNTS',
withRetry(async () => {
const accounts = parse(
return parse(
await osascript(join(scriptsDir, 'exportAccounts.applescript')),
);
if (!Array.isArray(accounts)) {
throw new Error('Unexpectedly got non-array as accounts');
}
const usableAccountsOrFalse = await Promise.all(
accounts.map(
async (data: unknown): Promise<object | false> => {
if (typeof data !== 'object' || data === null) {
return false;
}
const accountNumber: unknown = (data as any).accountNumber;
if (typeof accountNumber !== 'string' || !accountNumber.length) {
return false;
}
if (!(await canHandleTransactions(accountNumber))) {
return false;
}

return data;
},
),
);

return usableAccountsOrFalse.filter(Boolean);
}),
);

Expand All @@ -113,6 +66,15 @@ export default function moneymoneyHandlers(ipcMain: IpcMain) {
}),
);

ipcMain.handle(
'MM_EXPORT_CATEGORIES',
withRetry(async () => {
return parse(
await osascript(join(scriptsDir, 'exportCategories.applescript')),
);
}),
);

ipcMain.on('MM_OPEN', () => {
exec('open -a MoneyMoney');
});
Expand Down
1 change: 1 addition & 0 deletions main/scripts/exportCategories.applescript
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tell application "MoneyMoney" to export categories
4 changes: 4 additions & 0 deletions main/windowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export default function createWindowManager(
const win = new BrowserWindow({
width: 800,
height: 600,
minWidth: 500,
minHeight: 300,
titleBarStyle: 'hiddenInset',
vibrancy: 'sidebar',
webPreferences: {
nodeIntegration: true,
},
Expand Down
35 changes: 5 additions & 30 deletions package-lock.json

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

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@
"fp-ts": "2.5.3",
"io-ts": "2.1.2",
"lodash.debounce": "4.0.8",
"memoize-one": "5.1.1",
"plist": "3.0.1",
"react": "16.13.0",
"react-dom": "16.13.0",
"react-helmet": "6.0.0"
"react-dom": "16.13.0"
},
"devDependencies": {
"@craco/craco": "5.6.4",
Expand All @@ -91,7 +91,6 @@
"@types/pouchdb": "6.4.0",
"@types/react": "16.9.23",
"@types/react-dom": "16.9.5",
"@types/react-helmet": "5.0.15",
"asar": "3.0.3",
"concurrently": "5.1.0",
"electron": "8.1.1",
Expand Down
13 changes: 12 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,20 @@
</script>
<style>
html {
background-color: var(--background-color);
font-size: 0.85em;
}
html::after {
z-index: 1000;
border-radius: 4px;
content: '';
pointer-events: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 0 0px 1px inset var(--outer-border);
}
body {
margin: 0;
padding: 0;
Expand Down
1 change: 0 additions & 1 deletion src/App.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.app {
background-color: var(--background-color);
color: var(--main-color);
}
30 changes: 15 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './theme.scss';
import React, { Suspense, useState } from 'react';
import { useInit, INIT_EMPTY } from './lib';
import { Loading } from './components';
import { ErrorBoundary, Startup } from './components';
import styles from './App.module.scss';

const Budget = React.lazy(() => import('./views/Budget'));
Expand All @@ -13,21 +13,21 @@ export default function App() {
const [init, setInitialState] = useInit();
return (
<div className={styles.app}>
<Suspense fallback={<Loading />}>
{!init ? (
<Loading />
) : init instanceof Error ? (
<p>Error: {init.message}</p>
) : init === INIT_EMPTY ? (
welcome ? (
<Welcome onCreate={() => setWelcome(false)} />
<ErrorBoundary error={init instanceof Error ? init : undefined}>
<Suspense fallback={<Startup />}>
{!init ? (
<Startup />
) : init instanceof Error ? null : init === INIT_EMPTY ? (
welcome ? (
<Welcome onCreate={() => setWelcome(false)} />
) : (
<NewBudget onCreate={setInitialState} />
)
) : (
<NewBudget onCreate={setInitialState} />
)
) : (
<Budget init={init} />
)}
</Suspense>
<Budget init={init} />
)}
</Suspense>
</ErrorBoundary>
</div>
);
}
78 changes: 76 additions & 2 deletions src/budget/Types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import * as t from 'io-ts';
import {
Balance,
Transaction,
AmountWithTransactions,
Category as MoneyMoneyCategory,
} from '../moneymoney';
import { isLeft } from 'fp-ts/lib/Either';
import { ThrowReporter } from 'io-ts/lib/ThrowReporter';

export const VERSION = '0.0.1';
export const VERSION = '0.0.2';

const categoryShape = t.intersection([
t.type({
Expand All @@ -22,7 +28,7 @@ const budgetsShape = t.record(
);
const incomeCategoryShape = t.type(
{
id: t.union([t.number, t.null]),
id: t.union([t.string, t.null]),
availableIn: t.number,
},
'incomeCategories',
Expand Down Expand Up @@ -57,9 +63,77 @@ export type IncomeCategory = t.TypeOf<typeof incomeCategoryShape>;
export type Settings = t.TypeOf<typeof settingsShape>;

export function validateBudgetState(data: unknown): BudgetState {
if (typeof data !== 'object' || data === null) {
throw new Error('Invalid budget file format');
}
const version: unknown = (data as any).version;
if (!version || version === '0.0.1') {
throw new Error(
'File format not supported. Please use an earlier version of BudgetBudget to open this file',
);
}

const c = budgetStateShape.decode(data);
if (isLeft(c)) {
throw ThrowReporter.report(c);
}
return data as BudgetState;
}

export type BudgetRow = { budgeted: number; spend: number; balance: number };

export type AmountWithPartialTransactions = {
amount: number;
transactions: Pick<Transaction, 'amount' | 'name' | 'purpose'>[];
};
export type BudgetListEntry = {
key: string;
date: Date;
available: AmountWithPartialTransactions;
overspendPrevMonth: number;
toBudget: number;
total: BudgetRow;
uncategorized: AmountWithPartialTransactions;
categories: BudgetCategoryRow[];
};

export type BudgetCategoryGroup = BudgetRow & {
uuid: string;
indent: number;
};
export type BudgetCategoryRow = BudgetCategoryGroup & {
overspendRollover: boolean;
transactions: Transaction[];
};
export function isBudgetCategoryRow(
entry: BudgetCategoryGroup | BudgetCategoryRow,
): entry is BudgetCategoryRow {
return typeof (entry as any).overspendRollover !== 'undefined';
}
export type OverspendRollover = { [key: string]: boolean };
export type Rollover = { total: number; [key: string]: number };

export type InterMonthData = {
uncategorized: AmountWithTransactions;
categories: (BudgetCategoryRow | BudgetCategoryGroup)[];
toBudget: number;
total: BudgetRow;
overspendPrevMonth: number;
overspendRolloverState: OverspendRollover;
available: AmountWithPartialTransactions[];
rollover: Rollover;
};
export type MonthData = {
key: string;
date: Date;
name: string;
get: () => InterMonthData;
};
export type MonthDataGetter<R> = (
getInitial: () => InterMonthData,
balance: Balance | undefined,
budget: Budget | undefined,
categories: MoneyMoneyCategory[],
incomeCategories: IncomeCategory[],
round: (value: number) => number,
) => R;
12 changes: 6 additions & 6 deletions src/budget/budgetReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const ACTION_SET_CATEGORY_ROLLOVER = Symbol(
);

type MonthCategory = {
categoryId: number;
categoryId: string;
monthKey: string;
};

Expand Down Expand Up @@ -81,20 +81,20 @@ type SetSettingsStartBalance = {
type UpdateSettingsIncomeCategory = {
type: typeof ACTION_SETTINGS_UPDATE_INCOME_CATEGORY;
payload: {
oldCategoryId: number | null;
categoryId: number | null;
oldCategoryId: string | null;
categoryId: string | null;
};
};
type SetSettingsIncomeAvailableIn = {
type: typeof ACTION_SETTINGS_SET_INCOME_AVAILABLE_IN;
payload: {
categoryId: number | null;
categoryId: string | null;
availableIn: number;
};
};
type RemoveSettingsIncomeCategory = {
type: typeof ACTION_SETTINGS_REMOVE_INCOME_CATEGORY;
payload: number | null;
payload: string | null;
};
type AddSettingsIncomeCategory = {
type: typeof ACTION_SETTINGS_ADD_INCOME_CATEGORY;
Expand Down Expand Up @@ -147,7 +147,7 @@ function updateCategory(

function updateSettingsIncomeCategory(
state: BudgetState,
categoryId: number | null,
categoryId: string | null,
update: (incomeCategory: IncomeCategory) => IncomeCategory | null,
) {
const incomeCategories = state.settings.incomeCategories;
Expand Down
Loading

0 comments on commit 33c2ff2

Please sign in to comment.