Merge branch 'master' into fix-splitfreq-weights

* master: (39 commits)
  fix: correct import of merkle in test
  chore: move snapshot to new dir
  refactor: move crdt files into separate directory
  revert: revert pruning of svg imports
  style: nicer re-exporting syntax
  refactor: remove unused imports
  chore: update package scripts to take advantage of yarn v3
  build: add missing eslint dependency to loot-core
  build: add cross-env dependency to desktop-client package.json
  build: remove deprecated nohoist settings and prevent hoisting of mobile dependencies
  fix: move downshift patch to monorepo root
  CI: update CI definition to use yarn v3
  fix: wrap glob in quotes so that it's properly passed to npm-run-all
  chore: update root yarn scripts to use `workspace` command instead of cd
  chore: fix broken builds
  chore: update to yarnv3 and fix missing packages preventing install
  Force react-error-overlay to 6.0.9 to fix error
  Add docs for building for windows (contributed by @ejmurra)
  add: tsconfig.json
  build: replace jwl-dev-utils with react-dev-utils
  ...
This commit is contained in:
Tom French 2022-08-23 12:37:46 +01:00
commit 57e0d713da
107 changed files with 25496 additions and 17988 deletions

View file

@ -47,7 +47,7 @@ jobs:
keys:
- v3-dependencies-{{ checksum "yarn.lock" }}
- run: yarn install --pure-lockfile
- run: yarn install --immutable
- save_cache:
<<: *cached_files
@ -74,7 +74,7 @@ jobs:
shell: bash
- run:
command: yarn install --pure-lockfile
command: yarn install --immutable
shell: bash
- run:
@ -96,7 +96,7 @@ jobs:
keys:
- v3-dependencies-{{ checksum "yarn.lock" }}
- run: yarn install --pure-lockfile
- run: yarn install --immutable
- run: sudo npm install -g @sentry/cli --unsafe-perm

9
.gitignore vendored
View file

@ -20,3 +20,12 @@ bundle.mobile.js.map
export-2020-01-10.csv
**/*.log
# Yarn
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

File diff suppressed because one or more lines are too long

785
.yarn/releases/yarn-3.2.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

7
.yarnrc.yml Normal file
View file

@ -0,0 +1,7 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
yarnPath: .yarn/releases/yarn-3.2.0.cjs

View file

@ -80,27 +80,14 @@ if [[ $CI != true && "$OSTYPE" == "darwin"* ]]; then
yarn lint
fi
./node_modules/.bin/patch-package
yarn patch-package
(
cd packages/loot-design;
../../node_modules/.bin/patch-package
)
yarn workspace mobile patch-package
(
cd packages/mobile;
../../node_modules/.bin/patch-package
)
yarn workspace loot-core build:node
(
cd packages/loot-core;
NODE_ENV=production yarn build:node
)
yarn workspace @actual-app/web build
(
cd packages/desktop-client;
yarn build
)
rm -fr packages/desktop-electron/client-build
cp -r packages/desktop-client/build packages/desktop-electron/client-build

View file

@ -56,19 +56,8 @@ if [ $CI != true ]; then
yarn lint
fi
(
cd packages/loot-design;
../../node_modules/.bin/patch-package
)
ACTUAL_RELEASE_TYPE=$RELEASE yarn workspace loot-core build:browser
(
cd packages/loot-core;
ACTUAL_RELEASE_TYPE=$RELEASE yarn build:browser
)
(
cd packages/desktop-client;
REACT_APP_RELEASE_TYPE=$RELEASE yarn build:browser
)
REACT_APP_RELEASE_TYPE=$RELEASE yarn workspace @actual-app/web build:browser
echo "packages/desktop-client/build"

View file

@ -0,0 +1,18 @@
# How to build browser for Windows
Many of the build scripts are bash scripts and not natively invokable in Windows. To solve this, you can build the project using Git Bash.
1. Install [Git & Git Bash for Windows](https://git-scm.com/downloads)
2. Install Node v16.x (latest version 17.x does not work due to issue with crypto package)
3. Clone this repo
4. From the root of this repo, run `sh` to launch a bash shell
5. From inside the bash shell, run `yarn install`
6. From still inside the shell, run `yarn start:browser`
7. Open your browser to `localhost:3001`
# How to build electron for Windows
1. Follow steps 1 - 5 above.
2. Run `yarn start`
3. If you get an error from electron, run `yarn rebuild-electron` and rerun `yarn start`;
## rsync: command not found
If you run into this error, you will need to install the rsync binary to Git Bash. Follow the [directions here](https://prasaz.medium.com/add-rsync-to-windows-git-bash-f42736bae1b3). When you get to the final step - installing the libxxhash dll - rename the dll from `msys-xxhash-0.8.0.dll` to `msys-xxhash-0.dll`

View file

@ -15,53 +15,30 @@
"workspaces": {
"packages": [
"packages/*"
],
"nohoist": [
"**/better-sqlite3",
"**/better-sqlite3/**",
"mobile/react-native",
"mobile/react-native-*",
"mobile/rn-fetch-blob",
"mobile/**/event-target-shim",
"mobile/@sentry/react-native",
"mobile/nodejs-mobile-react-native",
"**/mobile/nodejs-mobile-react-native/**",
"**/@react-native-community/**",
"**/@react-navigation/**",
"mobile/react-navigation",
"mobile/react-navigation-tabs",
"mobile/rn-snoopy",
"mobile/rn-snoopy/**",
"mobile/detox",
"mobile/detox/**",
"mobile/jsc-android",
"mobile/jsc-android/**",
"**/react-native-web",
"**/react-native-web/**",
"**/@sentry/cli"
]
},
"scripts": {
"start": "npm-run-all --parallel start:desktop-*",
"start:desktop-node": "cd packages/loot-core && yarn watch:node",
"start:desktop-client": "cd packages/desktop-client && yarn watch",
"start:desktop-electron": "cd packages/desktop-electron && yarn watch",
"start:browser": "npm-run-all --parallel start:browser-*",
"start:browser-backend": "cd packages/loot-core && yarn watch:browser",
"start:browser-frontend": "cd packages/desktop-client && yarn start:browser",
"start": "npm-run-all --parallel 'start:desktop-*'",
"start:desktop-node": "yarn workspace loot-core watch:node",
"start:desktop-client": "yarn workspace @actual-app/web watch",
"start:desktop-electron": "yarn workspace Actual watch",
"start:browser": "npm-run-all --parallel 'start:browser-*'",
"start:browser-backend": "yarn workspace loot-core watch:browser",
"start:browser-frontend": "yarn workspace @actual-app/web start:browser",
"test": "./node_modules/.bin/jest --maxWorkers=4",
"test:debug": "node ./node_modules/.bin/jest --runInBand --useStderr",
"test:debug-brk": "node --inspect-brk ./node_modules/.bin/jest --runInBand",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core",
"rebuild-node": "cd packages/loot-core && npm rebuild",
"lint": "cd packages/loot-core && yarn lint",
"rebuild-node": "yarn workspace loot-core rebuild",
"lint": "yarn workspace loot-core lint",
"postinstall": "rm -rf ./packages/loot-design/node_modules/react && rm -rf ./packages/mobile/node_modules/react && rm -rf ./node_modules/react-native && patch-package"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.15.0",
"@babel/plugin-transform-modules-commonjs": "^7.18.2",
"cross-env": "^5.1.5",
"husky": "^3.0.4",
"npm-run-all": "^4.1.3",
"patch-package": "^6.1.2",
"prettier": "^1.18.1",
"pretty-quick": "^1.11.1",
"shelljs": "^0.8.2",
@ -83,6 +60,8 @@
"@babel/preset-env": "^7.15.1",
"@babel/core": "^7.15.1",
"@babel/runtime": "^7.15.1",
"@babel/helper-plugin-utils": "^7.14.5"
}
"@babel/helper-plugin-utils": "^7.14.5",
"react-error-overlay": "6.0.9"
},
"packageManager": "yarn@3.2.0"
}

View file

@ -7,26 +7,24 @@ const resolve = require('resolve');
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('jwl-dev-utils/InlineChunkHtmlPlugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('jwl-dev-utils/InterpolateHtmlPlugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('jwl-dev-utils/WatchMissingNodeModulesPlugin');
const ModuleScopePlugin = require('jwl-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('jwl-dev-utils/getCSSModuleLocalIdent');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('jwl-dev-utils/ModuleNotFoundPlugin');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt');
const typescriptFormatter = require('jwl-dev-utils/typescriptFormatter');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const webpackDevClientEntry = require.resolve(
'jwl-dev-utils/webpackHotDevClient'
'react-dev-utils/webpackHotDevClient'
);
// Source maps are resource heavy and can cause out of memory issue for large source files.
@ -290,7 +288,7 @@ module.exports = function(webpackEnv) {
use: [
{
options: {
formatter: require.resolve('jwl-dev-utils/eslintFormatter'),
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint')
},
loader: require.resolve('eslint-loader')
@ -490,7 +488,7 @@ module.exports = function(webpackEnv) {
entry: webpackDevClientEntry,
// The expected exports are slightly different from what the overlay exports,
// so an interop is included here to enable feedback on module-level errors.
module: require.resolve('jwl-dev-utils/refreshOverlayInterop'),
module: require.resolve('react-dev-utils/refreshOverlayInterop'),
// Since we ship a custom dev client and overlay integration,
// the bundled socket handling logic can be eliminated.
sockIntegration: false
@ -500,12 +498,6 @@ module.exports = function(webpackEnv) {
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebook/create-react-app/issues/186
isEnvDevelopment &&
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
@ -545,6 +537,8 @@ module.exports = function(webpackEnv) {
]
}),
// TypeScript type checking
// This hasn't been tested since migrating away from `jwl-dev-utils` as we
// don't currently use TypeScript and we'll likely have to update the dependency anyway.
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', {
@ -571,7 +565,6 @@ module.exports = function(webpackEnv) {
],
watch: paths.appSrc,
silent: true,
formatter: typescriptFormatter
})
].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser.

View file

@ -1,10 +1,10 @@
'use strict';
const fs = require('fs');
const errorOverlayMiddleware = require('jwl-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('jwl-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('jwl-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('jwl-dev-utils/ignoredFiles');
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
const paths = require('./paths');
const getHttpsConfig = require('./getHttpsConfig');

View file

@ -23,6 +23,7 @@
"case-sensitive-paths-webpack-plugin": "2.1.2",
"chalk": "2.4.1",
"codemirror": "^5.37.0",
"cross-env": "^7.0.3",
"css-loader": "1.0.0",
"date-fns": "2.0.0-alpha.27",
"debounce": "^1.2.0",
@ -43,7 +44,6 @@
"html-webpack-plugin": "4.0.0-alpha.2",
"http-client": "^4.3.1",
"identity-obj-proxy": "3.0.0",
"jwl-dev-utils": "^6.1.3",
"load-js": "^3.0.3",
"lodash.memoize": "^4.1.2",
"mini-css-extract-plugin": "0.4.3",
@ -61,6 +61,7 @@
"react-addons-shallow-compare": "^15.6.0",
"react-app-polyfill": "^0.1.3",
"react-autosuggest": "9.3.2",
"react-dev-utils": "^12.0.1",
"react-dnd": "^10.0.2",
"react-dom": "16.13.1",
"react-modal": "3.4.4",

View file

@ -22,11 +22,11 @@ const webpack = require('webpack');
const bfj = require('bfj');
const configFactory = require('../config/webpack.config');
const paths = require('../config/paths');
const checkRequiredFiles = require('jwl-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('jwl-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('jwl-dev-utils/printHostingInstructions');
const FileSizeReporter = require('jwl-dev-utils/FileSizeReporter');
const printBuildError = require('jwl-dev-utils/printBuildError');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
@ -53,7 +53,7 @@ const config = configFactory('production');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('jwl-dev-utils/browsersHelper');
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.

View file

@ -19,15 +19,15 @@ const fs = require('fs');
const chalk = require('chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('jwl-dev-utils/clearConsole');
const checkRequiredFiles = require('jwl-dev-utils/checkRequiredFiles');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('jwl-dev-utils/WebpackDevServerUtils');
const openBrowser = require('jwl-dev-utils/openBrowser');
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');
@ -63,7 +63,7 @@ if (process.env.HOST) {
// We require that you explictly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('jwl-dev-utils/browsersHelper');
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
@ -80,7 +80,7 @@ checkBrowsers(paths.appPath, isInteractive)
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
const compiler = createCompiler({ webpack, config, appName, urls, useYarn });
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);

View file

@ -24,6 +24,8 @@ import ExpandArrow from 'loot-design/src/svg/ExpandArrow';
import ExclamationSolid from 'loot-design/src/svg/v1/ExclamationSolid';
import Platform from 'loot-core/src/client/platform';
import useServerVersion from '../hooks/useServerVersion';
let dateFormats = [
{ value: 'MM/dd/yyyy', label: 'MM/DD/YYYY' },
{ value: 'dd/MM/yyyy', label: 'DD/MM/YYYY' },
@ -473,6 +475,27 @@ function SettingsLink({ to, name, style, first, last }) {
);
}
function Version() {
const version = useServerVersion();
return (
<Text
style={[
{
alignSelf: 'center',
color: colors.n7,
':hover': { color: colors.n2 },
padding: '6px 10px'
},
styles.staticText,
styles.smallText
]}
>
{`App: v${window.Actual.ACTUAL_VERSION} | Server: ${version}`}
</Text>
);
}
class Settings extends React.Component {
componentDidMount() {
this.unlisten = listen('prefs-updated', () => {
@ -496,12 +519,21 @@ class Settings extends React.Component {
style={{
flexDirection: 'row',
alignSelf: 'center',
margin: '15px 0'
margin: '15px 0 5px 0'
}}
>
<SettingsLink to={`${match.path}/file`} name="File" first={true} />
<SettingsLink to={`${match.path}/global`} name="Global" last={true} />
</View>
<View
style={{
flexDirection: 'row',
alignSelf: 'center',
margin: '0 0 10px 0'
}}
>
<Version />
</View>
<View
style={[

View file

@ -4,7 +4,7 @@ import { createBrowserHistory } from 'history';
import { Switch, Redirect, Router, Route } from 'react-router-dom';
import { send } from 'loot-core/src/platform/client/fetch';
import * as actions from 'loot-core/src/client/actions';
import { View, ExternalLink, Button } from 'loot-design/src/components/common';
import { View, Text } from 'loot-design/src/components/common';
import { colors } from 'loot-design/src/style';
import ServerURL from './ServerURL';
import LoggedInUser from '../LoggedInUser';
@ -16,10 +16,13 @@ import Bootstrap from './subscribe/Bootstrap';
import Error from './subscribe/Error';
import ChangePassword from './subscribe/ChangePassword';
import ConfigServer from './ConfigServer';
import useServerVersion from '../../hooks/useServerVersion';
function Version() {
const version = useServerVersion();
return (
<ExternalLink
<Text
style={{
position: 'absolute',
bottom: 0,
@ -32,8 +35,8 @@ function Version() {
}}
href={'https://actualbudget.com/blog/' + window.Actual.ACTUAL_VERSION}
>
{window.Actual.ACTUAL_VERSION}
</ExternalLink>
{`App: v${window.Actual.ACTUAL_VERSION} | Server: ${version}`}
</Text>
);
}

View file

@ -0,0 +1,22 @@
import React, { useState, useEffect } from 'react';
import { send } from 'loot-core/src/platform/client/fetch';
function useServerVersion() {
let [version, setVersion] = useState('');
useEffect(() => {
(async () => {
const { error, version } = await send('get-server-version');
if (error) {
setVersion('');
} else {
setVersion(version);
}
})();
}, []);
return version ? `v${version}` : 'N/A';
}
export default useServerVersion;

View file

@ -2,8 +2,8 @@ const d = require('date-fns');
const normalizePathSep = require('slash');
const uuid = require('uuid');
const AdmZip = require('adm-zip');
const actual = require('@actual-app/api/methods');
const { amountToInteger } = require('@actual-app/api/utils');
const actual = require('@actual-app/api');
const amountToInteger = actual.utils.amountToInteger;
// Utils

View file

@ -1,7 +1,6 @@
const d = require('date-fns');
const uuid = require('uuid');
const actual = require('@actual-app/api/methods');
const { amountToInteger } = require('@actual-app/api/utils');
const actual = require('@actual-app/api');
function amountFromYnab(amount) {
// ynabs multiplies amount by 1000 and actual by 100

1
packages/loot-core/bin/get-db-schema Executable file → Normal file
View file

@ -3,7 +3,6 @@ process.env.LOOT_DATA_DIR = __dirname + '/../../../../data';
import * as sqlite from '../platform/server/sqlite';
import * as db from '../server/db';
import { getMessages } from '../server/sync';
import Timestamp from '../server/timestamp';
let dbPath = process.argv[2];
let tables = [

View file

@ -4,7 +4,6 @@ import os from 'os';
import * as sqlite from '../src/platform/server/sqlite';
import * as db from '../src/server/db';
import { batchMessages, setSyncingMode } from '../src/server/sync';
import Timestamp from '../src/server/timestamp';
import { runQuery } from '../src/server/aql/schema/run-query';
import asyncStorage from '../src/platform/server/asyncStorage';
import { makeChild } from '../src/shared/transactions';

View file

@ -7,8 +7,8 @@
"build:node": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.desktop.config.js",
"watch:node": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.desktop.config.js --watch",
"build:api": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.api.config.js",
"build:browser": "NODE_ENV=production ./bin/build-browser",
"watch:browser": "NODE_ENV=development ./bin/build-browser",
"build:browser": "cross-env NODE_ENV=production ./bin/build-browser",
"watch:browser": "cross-env NODE_ENV=development ./bin/build-browser",
"lint": "eslint src"
},
"author": "",
@ -43,9 +43,10 @@
"babel-jest": "25.2.6",
"babel-loader": "^8.0.6",
"buffer": "^5.5.0",
"currency-formatter": "jlongster/currency-formatter",
"cross-env": "^7.0.3",
"damerau-levenshtein": "^1.0.4",
"date-fns": "2.0.0-alpha.27",
"eslint": "5.6.0",
"esm": "^3.0.82",
"fake-indexeddb": "^3.1.3",
"fast-check": "2.13.0",
@ -55,14 +56,14 @@
"lru-cache": "^5.1.1",
"memfs": "3.1.1",
"memoize-one": "^4.0.0",
"mockdate": "^3.0.5",
"mock-require": "^3.0.2",
"mockdate": "^3.0.5",
"murmurhash": "^0.0.2",
"perf-deets": "^1.0.15",
"sanitize-filename": "^1.6.1",
"search-query-parser": "^1.3.0",
"source-map": "^0.7.3",
"snapshot-diff": "^0.2.2",
"source-map": "^0.7.3",
"throttleit": "^1.0.0",
"uuid": "3.3.2",
"webpack": "^4.41.2",

View file

@ -1,6 +1,6 @@
import { send } from '../../platform/client/fetch';
import constants from '../constants';
import { getPayees, getAccounts, filterTransactions } from './queries';
import { getPayees, getAccounts } from './queries';
import { addNotification } from './notifications';
export function setAccountsSyncing(name) {

View file

@ -1,10 +1,7 @@
import { send } from '../../platform/client/fetch';
import constants from '../constants';
import { loadPrefs } from './prefs';
import { createBudget, loadBudget } from './budgets';
import { getCategories, getAccounts, getPayees } from './queries';
import { syncAccounts } from './account';
import { setAppState } from './app';
import { pushModal } from './modals';
import { getUploadError } from '../../shared/errors';

View file

@ -1,4 +1,3 @@
import constants from '../constants';
import { send } from '../../platform/client/fetch';
import Platform from '../platform';
import { pushModal } from './modals';

View file

@ -1,5 +1,5 @@
import React, { useMemo, useEffect, useState, useContext } from 'react';
import q, { liveQuery, runQuery } from 'loot-core/src/client/query-helpers';
import React, { useEffect, useState, useContext } from 'react';
import q, { liveQuery } from 'loot-core/src/client/query-helpers';
import { getAccountsById } from 'loot-core/src/client/reducers/queries';
export function useAccounts() {

View file

@ -1,5 +1,5 @@
import React, { useMemo, useEffect, useState, useContext } from 'react';
import q, { liveQuery, runQuery } from 'loot-core/src/client/query-helpers';
import React, { useEffect, useState, useContext } from 'react';
import q, { liveQuery } from 'loot-core/src/client/query-helpers';
import { getPayeesById } from 'loot-core/src/client/reducers/queries';
export function usePayees() {

View file

@ -1,11 +1,9 @@
import React, { useMemo, useEffect, useState, useContext } from 'react';
import * as monthUtils from 'loot-core/src/shared/months';
import React, { useEffect, useState, useContext } from 'react';
import {
getStatus,
getHasTransactionsQuery
} from 'loot-core/src/shared/schedules';
import q, { liveQuery, runQuery } from 'loot-core/src/client/query-helpers';
import { send } from 'loot-core/src/platform/client/fetch';
import q, { liveQuery } from 'loot-core/src/client/query-helpers';
function loadStatuses(schedules, onData) {
return liveQuery(getHasTransactionsQuery(schedules), onData, {

View file

@ -1,5 +1,5 @@
import { listen, send } from '../platform/client/fetch';
import { once, runAgain } from '../shared/async';
import { once } from '../shared/async';
import q, { getPrimaryOrderBy } from '../shared/query';
export default q;

View file

@ -1,4 +1,3 @@
import dateFns from 'date-fns';
const uuid = require('../platform/uuid');
export function generateAccount(balance) {

View file

@ -5,7 +5,6 @@ import * as sheet from '../server/sheet';
import * as rules from '../server/accounts/transaction-rules';
import * as tracking from '../server/tracking/events';
import { setSyncingMode } from '../server/sync';
import * as monthUtils from '../shared/months';
import { updateVersion } from '../server/update';
import { resetTracer, tracer } from '../shared/test-helpers';
import {

View file

@ -4,16 +4,7 @@ require('fake-indexeddb/auto');
let FDBFactory = require('fake-indexeddb/lib/FDBFactory');
let idb = require('../indexeddb');
let {
init,
populateFileHeirarchy,
readFile,
writeFile,
exists,
pathToId,
listDir,
join
} = require('./index');
let { init, readFile, writeFile, exists, pathToId, join } = require('./index');
beforeAll(() => {
process.env.PUBLIC_URL =

View file

@ -1,4 +1,3 @@
import fs from 'fs';
import csvStringify from 'csv-stringify/lib/sync';
import { runQuery as aqlQuery } from '../aql/schema/run-query';
import { integerToAmount } from '../../shared/util';

View file

@ -7,7 +7,6 @@ import {
isAfter,
addDays,
subDays,
format as formatDate,
parseDate
} from '../../shared/months';
import { fastSetMerge } from '../../shared/util';

View file

@ -1,6 +1,5 @@
import {
parseDateString,
parseRecurDate,
rankRules,
iterateIds,
Condition,

View file

@ -1,24 +1,15 @@
import title from './title';
import * as db from '../db';
import {
mergeObjects,
hasFieldsChanged,
toRelaxedNumber,
amountToInteger,
integerToAmount
} from '../../shared/util';
import { hasFieldsChanged, amountToInteger } from '../../shared/util';
import {
makeChild as makeChildTransaction,
recalculateSplit
} from '../../shared/transactions';
import * as monthUtils from '../../shared/months';
import { transactionModel } from '../api-models';
import { getServer } from '../server-config';
import { batchMessages } from '../sync';
import { runMutator } from '../mutators';
import { getStartingBalancePayee } from './payees';
import * as transfer from './transfer';
import { TransactionError } from '../errors';
import { runRules } from './transaction-rules';
import { batchUpdateTransactions } from './transactions';

View file

@ -17,13 +17,11 @@ import {
sortNumbers,
getApproxNumberThreshold
} from '../../shared/rules';
import q from '../../shared/query';
import { requiredFields, toDateRepr } from '../models';
import { currentDay } from '../../shared/months';
import { partitionByField, fastSetMerge } from '../../shared/util';
import { setSyncingMode, batchMessages } from '../sync';
import { schemaConfig } from '../aql/schema';
const uuid = require('../../platform/uuid');
// TODO: Detect if it looks like the user is creating a rename rule
// and prompt to create it in the pre phase instead

View file

@ -1,5 +1,4 @@
import * as db from '../db';
import { Rule } from './rules';
import {
getRules,
loadRules,
@ -15,7 +14,6 @@ import {
migrateOldRules
} from './transaction-rules';
import { loadMappings } from '../db/mappings';
import { applyMigration } from '../migrate/migrations';
import { runQuery } from '../aql/schema/run-query';
import q from '../../shared/query';

View file

@ -1,4 +1,3 @@
import * as sheet from '../sheet';
import { batchMessages } from '../sync';
import * as db from '../db';
import { incrFetch, whereIn } from '../db/util';

View file

@ -1,6 +1,5 @@
import { addTransactions } from './accounts/sync';
import {
transactionModel,
accountModel,
categoryModel,
categoryGroupModel,
@ -16,17 +15,14 @@ import * as db from './db';
import * as sheet from './sheet';
import * as prefs from './prefs';
import * as monthUtils from '../shared/months';
import * as update from './update';
import * as cloudStorage from './cloud-storage';
import { setSyncingMode, batchMessages } from './sync';
import { groupById, cleanUUID } from '../shared/util';
import { getClock } from './timestamp';
import { getClock } from './crdt';
import { runMutator } from './mutators';
import { integerToAmount } from '../shared/util';
import { runQuery as aqlQuery } from './aql/schema/run-query';
import q from '../shared/query';
const { resolveName } = require('./spreadsheet/util');
const connection = require('../platform/server/connection');
let IMPORT_MODE = false;

View file

@ -1,6 +1,5 @@
import * as db from '../../db';
import { whereIn } from '../../db/util';
import { groupBy } from '../../../shared/util';
import { isAggregateQuery } from '../compiler';
import { convertOutputType } from '../schema-helpers';
import { execQuery } from '../exec';

View file

@ -1,16 +1,12 @@
import fc from 'fast-check';
import * as db from '../../db';
import query from '../../../shared/query';
import { makeChild } from '../../../shared/transactions';
import { batchMessages, setSyncingMode } from '../../sync/index';
import { setClock } from '../../timestamp';
import { setClock } from '../../crdt';
import { groupById } from '../../../shared/util';
import arbs from '../../../mocks/arbitrary-schema';
import { isAggregateQuery } from '../compiler';
import { runQuery } from './run-query';
import { toGroup, isHappyPathQuery } from './executors';
const uuid = require('../../../platform/uuid');
import { isHappyPathQuery } from './executors';
beforeEach(global.emptyDatabase());

View file

@ -1,7 +1,6 @@
import * as monthUtils from '../../shared/months';
import * as db from '../db';
import { batchMessages } from '../sync';
import { groupBySingle } from '../../shared/util';
import * as prefs from '../prefs';
import * as sheet from '../sheet';

View file

@ -1,5 +1,4 @@
import * as sheet from '../sheet';
import * as monthUtils from '../../shared/months';
import { number, sumAmounts } from './util';
const { resolveName } = require('../spreadsheet/util');

View file

@ -0,0 +1,4 @@
import * as merkle from "./merkle";
export { merkle };
export { getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp } from "./timestamp"

View file

@ -1,5 +1,5 @@
import * as merkle from './merkle';
import Timestamp from './timestamp';
import { Timestamp } from './timestamp';
function pretty(n) {
if (n < 60) {

View file

@ -1,5 +1,5 @@
import murmurhash from 'murmurhash';
const uuid = require('../platform/uuid');
const uuid = require('../../platform/uuid');
/**
* Hybrid Unique Logical Clock (HULC) timestamp generator
@ -81,7 +81,7 @@ const MAX_NODE_LENGTH = 16;
/**
* timestamp instance class
*/
export default class Timestamp {
export class Timestamp {
constructor(millis, counter, node) {
this._state = {
millis: millis,
@ -124,7 +124,7 @@ export default class Timestamp {
}
}
export class MutableTimestamp extends Timestamp {
class MutableTimestamp extends Timestamp {
setMillis(n) {
this._state.millis = n;
}

View file

@ -1,4 +1,4 @@
import Timestamp, { setClock, makeClock } from './timestamp';
import { Timestamp } from './timestamp';
describe('Timestamp', function() {
let now = 0;

View file

@ -11,13 +11,14 @@ import {
payeeRuleModel
} from '../models';
import { groupById } from '../../shared/util';
import Timestamp, {
import {
makeClock,
setClock,
serializeClock,
deserializeClock,
makeClientId
} from '../timestamp';
makeClientId,
Timestamp
} from '../crdt';
import {
convertForInsert,
convertForUpdate,

View file

@ -1,5 +1,4 @@
import './polyfills';
import { differenceInDays } from 'date-fns';
import asyncStorage from '../platform/server/asyncStorage';
import { captureException, captureBreadcrumb } from '../platform/exceptions';
import * as prefs from './prefs';
@ -31,8 +30,7 @@ import * as bankSync from './accounts/sync';
import * as link from './accounts/link';
import { uniqueFileName, idFromFileName } from './util/budget-name';
import { mutator, runHandler } from './mutators';
import * as timestamp from './timestamp';
import * as merkle from './merkle';
import { getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp, merkle } from './crdt';
import {
initialFullSync,
fullSync,
@ -1496,6 +1494,24 @@ handlers['subscribe-sign-out'] = async function() {
return 'ok';
};
handlers['get-server-version'] = async function() {
if (!getServer() || getServer().BASE_SERVER === UNCONFIGURED_SERVER) {
return { error: 'no-server' };
}
let version;
try {
const res = await get(getServer().BASE_SERVER + '/info');
const info = JSON.parse(res);
version = info.build.version;
} catch (err) {
return { error: 'network-failure' };
}
return { version };
};
handlers['get-server-url'] = async function() {
return getServer() && getServer().BASE_SERVER;
};
@ -1951,10 +1967,10 @@ async function loadBudget(id, appVersion, { showUpdate } = {}) {
//
// TODO: The client id should be stored elsewhere. It shouldn't
// work this way, but it's fine for now.
timestamp.getClock().timestamp.setNode(timestamp.makeClientId());
getClock().timestamp.setNode(makeClientId());
await db.runQuery(
'INSERT OR REPLACE INTO messages_clock (id, clock) VALUES (1, ?)',
[timestamp.serializeClock(timestamp.getClock())]
[serializeClock(getClock())]
);
await prefs.savePrefs({ resetClock: false });
@ -2236,7 +2252,9 @@ export const lib = {
// Expose CRDT mechanisms so server can use them
merkle,
timestamp,
timestamp: {
getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp
},
SyncProtoBuf: SyncPb
};

View file

@ -3,7 +3,7 @@ import * as prefs from './prefs';
import * as db from './db';
import * as budget from './budget/base';
import * as monthUtils from '../shared/months';
import { getClock, deserializeClock } from './timestamp';
import { getClock, deserializeClock } from './crdt';
import {
runHandler,
runMutator,

View file

@ -3,8 +3,7 @@ import {
withMigrationsDir,
getAppliedMigrations,
getMigrationList,
getPending,
applyMigration
getPending
} from './migrations';
import * as db from '../db';

View file

@ -1,5 +1,5 @@
import Platform from './platform';
const { PostError, HTTPError } = require('./errors');
const { PostError } = require('./errors');
const { fetch } = require('../platform/server/fetch');
function throwIfNot200(res, text) {

View file

@ -1,5 +1,5 @@
import { sendMessages } from './sync';
import Timestamp from './timestamp';
import { Timestamp } from './crdt';
const fs = require('../platform/server/fs');
let prefs = null;

View file

@ -5,8 +5,6 @@ import * as db from '../db';
import * as prefs from '../prefs';
import { toDateRepr } from '../models';
import { runQuery as aqlQuery } from '../aql/schema/run-query';
import { compileQuery } from '../aql/compiler';
import { schema, schemaConfig } from '../aql/schema';
import { dayFromDate, currentDay, parseDate } from '../../shared/months';
import q from '../../shared/query';
import {

View file

@ -1,4 +1,3 @@
import * as db from '../db';
import { runQuery as aqlQuery } from '../aql/schema/run-query';
import q from '../../shared/query';
import { loadRules, updateRule } from '../accounts/transaction-rules';
@ -6,7 +5,6 @@ import { loadMappings } from '../db/mappings';
import {
updateConditions,
getNextDate,
listSchedules,
createSchedule,
updateSchedule,
deleteSchedule,

View file

@ -7,7 +7,7 @@ import { runQuery as aqlQuery } from '../aql/schema/run-query';
import q from '../../shared/query';
import { getApproxNumberThreshold } from '../../shared/rules';
import { recurConfigToRSchedule } from '../../shared/schedules';
import { dayFromDate, parseDate, subDays } from '../../shared/months';
import { dayFromDate, parseDate } from '../../shared/months';
import { conditionsToAQL } from '../accounts/transaction-rules';
const uuid = require('../../platform/uuid');

View file

@ -1,5 +1,4 @@
import * as nodes from './nodes';
const uuid = require('../../../platform/uuid');
let _uid = 0;
function resetUid() {

View file

@ -1,5 +1,5 @@
import { compile, compileBinding } from './compiler';
import { MOV, CALL, QUERY, UOP, BOP, REG1, SP, VAR, JUMPF, JUMPT } from './ops';
import { compile } from './compiler';
import { MOV, CALL, QUERY, UOP, BOP, JUMPF, JUMPT } from './ops';
export default class VM {
constructor(db, scopes) {

View file

@ -1,5 +1,4 @@
import { sequential, once } from '../../shared/async';
import * as perf from '../perf';
import * as prefs from '../prefs';
import app from '../main-app';
import asyncStorage from '../../platform/server/asyncStorage';
@ -12,12 +11,13 @@ import { triggerBudgetChanges, setType as setBudgetType } from '../budget/base';
import * as undo from '../undo';
import { runMutator } from '../mutators';
import { setIn, getIn } from '../../shared/util';
import Timestamp, {
import {
serializeClock,
deserializeClock,
getClock
} from '../timestamp';
import * as merkle from '../merkle';
getClock,
Timestamp,
merkle
} from '../crdt';
import * as encoder from './encoder';
import { getServer } from '../server-config';
import { rebuildMerkleHash } from './repair';

View file

@ -1,5 +1,5 @@
import { addSyncListener, applyMessages } from './index';
import Timestamp from '../timestamp';
import { Timestamp } from '../crdt';
function migrateParentIds(oldValues, newValues) {
newValues.forEach((items, table) => {

View file

@ -1,12 +1,11 @@
import fc from 'fast-check';
import * as db from '../db';
import { migrateParentIds, listen, unlisten } from './migrate';
import { addSyncListener, batchMessages, sendMessages } from './index';
import { listen, unlisten } from './migrate';
import { addSyncListener, sendMessages } from './index';
import { execTracer } from '../../shared/test-helpers';
import { schema, schemaConfig } from '../aql/schema';
import arbs from '../../mocks/arbitrary-schema';
import { convertInputType } from '../aql/schema-helpers';
import { groupBy } from '../../shared/util';
beforeEach(() => {
listen();

View file

@ -1,6 +1,5 @@
import * as db from '../db';
import Timestamp, { serializeClock, getClock } from '../timestamp';
import * as merkle from '../merkle';
import { serializeClock, getClock, Timestamp, merkle } from '../crdt';
export function rebuildMerkleHash() {
let rows = db.runQuery('SELECT timestamp FROM messages_crdt', [], true);

View file

@ -2,13 +2,10 @@ import * as prefs from '../prefs';
import * as db from '../db';
import * as sheet from '../sheet';
import * as sync from './index';
import Timestamp, { deserializeClock, getClock } from '../timestamp';
import * as merkle from '../merkle';
import { getClock, Timestamp } from '../crdt';
import { merkle } from '../crdt';
import * as encoder from './encoder';
const Database = require('better-sqlite3');
const fs = require('fs');
const jsc = require('jsverify');
const uuid = require('uuid');
const uuidGenerator = jsc
.integer(97, 122)
.smap(x => String.fromCharCode(x), x => x.charCodeAt(x));

View file

@ -1,14 +1,9 @@
import * as prefs from '../prefs';
import * as db from '../db';
import * as sheet from '../sheet';
import Timestamp, { getClock } from '../timestamp';
import { getClock, Timestamp } from '../crdt';
import { resolveName } from '../spreadsheet/util';
import {
setSyncingMode,
sendMessages,
applyMessages,
fullSync
} from './index';
import { setSyncingMode, sendMessages, applyMessages, fullSync } from './index';
import * as encoder from './encoder';
const mockSyncServer = require('../tests/mockSyncServer');

View file

@ -1,6 +1,5 @@
import dateFns from 'date-fns';
import * as merkle from '../merkle';
import Timestamp, { makeClock } from '../timestamp';
import { makeClock, Timestamp, merkle } from '../crdt';
const defaultMockData = require('./mockData').basic;
const SyncPb = require('../sync/proto/sync_pb');

View file

@ -1,4 +1,3 @@
import { batchMessages } from '../sync';
import { runMutator } from '../mutators';
import { createApp } from '../app';
import * as db from '../db';

View file

@ -1,6 +1,4 @@
import asyncStorage from '../../platform/server/asyncStorage';
import { sha256String } from '../encryption-internals';
let uuid = require('../../platform/uuid');
let currentUniqueId;
let mixpanel;

View file

@ -1,6 +1,6 @@
import { sendMessages } from './sync';
import { getIn } from '../shared/util';
import Timestamp from './timestamp';
import { Timestamp } from './crdt';
import { withMutatorContext, getMutatorContext } from './mutators';
const connection = require('../platform/server/connection');

View file

@ -1,4 +1,4 @@
import { last, diffItems, getChangedValues, applyChanges } from './util';
import { last, diffItems, applyChanges } from './util';
const uuid = require('../platform/uuid');
// The amount might be null when adding a new transaction

View file

@ -5,7 +5,6 @@ import {
addSplitTransaction,
makeChild
} from './transactions';
import * as db from '../server/db';
const uuid = require('../platform/uuid');
// const data = {

View file

@ -1,5 +1,3 @@
let currencyFormatter = require('currency-formatter');
export function cleanUUID(uuid) {
return uuid.replace(/-/g, '');
}
@ -267,7 +265,7 @@ export function setNumberFormat(format) {
switch (format) {
case 'space-comma':
locale = 'za-ZA';
locale = 'en-ZA';
regex = /[^-0-9,]/g;
separator = ',';
break;
@ -286,12 +284,10 @@ export function setNumberFormat(format) {
numberFormat = {
value: format,
separator,
// This is the keep in line with the Intl API which we might
// switch to when it's available on all mobile platforms
formatter: {
format: number =>
currencyFormatter.format(number, { locale, format: '%v' })
},
formatter: new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}),
regex
};
}
@ -300,7 +296,7 @@ export function getNumberFormat() {
return numberFormat;
}
setNumberFormat('1,000.33');
setNumberFormat('comma-dot');
export function toRelaxedNumber(value) {
return integerToAmount(currencyToInteger(value) || 0);
@ -319,10 +315,6 @@ export function amountToCurrency(n) {
return numberFormat.formatter.format(n);
}
export function amountToPrettyCurrency(n, code) {
return currencyFormatter.format(n, { code });
}
export function currencyToAmount(str) {
let amount = parseFloat(
str.replace(numberFormat.regex, '').replace(numberFormat.separator, '.')

View file

@ -1,4 +1,4 @@
import { looselyParseAmount } from './util';
import { looselyParseAmount, getNumberFormat, setNumberFormat } from './util';
describe('utility functions', () => {
test('looseParseAmount works with basic numbers', () => {
@ -28,4 +28,26 @@ describe('utility functions', () => {
// thought through more.
expect(looselyParseAmount('3_45_23.10')).toBe(34523.1);
});
test('number formatting works with comma-dot format', () => {
setNumberFormat('comma-dot');
const formatter = getNumberFormat().formatter;
expect(formatter.format('1234.56')).toBe('1,234.56');
});
test('number formatting works with dot-comma format', () => {
setNumberFormat('dot-comma');
const formatter = getNumberFormat().formatter;
expect(formatter.format('1234.56')).toBe('1.234,56');
});
test('number formatting works with space-comma format', () => {
setNumberFormat('space-comma');
const formatter = getNumberFormat().formatter;
// grouping separator space char is a non-breaking space, or UTF-16 \xa0
expect(formatter.format('1234.56')).toBe('1\xa0234,56');
});
});

View file

@ -1,4 +1,3 @@
let path = require('path');
let webpack = require('webpack');
let config = require('./webpack.desktop.config');

View file

@ -58,5 +58,10 @@ module.exports = {
new webpack.IgnorePlugin({
resourceRegExp: /worker_threads|original-fs/
})
]
],
node: {
dgram: "empty",
net: 'empty',
tls: 'empty',
},
};

View file

@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { View, Text, Select } from './common';
import Autocomplete, { defaultFilterSuggestion } from './Autocomplete';
import { styles, colors } from '../style';
import { colors } from '../style';
import Split from '../svg/split';
export const NativeCategorySelect = React.forwardRef(

View file

@ -7,10 +7,9 @@ import React, {
useMemo
} from 'react';
import * as d from 'date-fns';
import { css } from 'glamor';
import Pikaday from 'pikaday';
import 'pikaday/css/pikaday.css';
import { styles, colors } from '../style';
import { colors } from '../style';
import { View, Input, Tooltip } from './common';
import {
getDayMonthFormat,

View file

@ -1,5 +1,5 @@
import React, { useState, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { getActivePayees } from 'loot-core/src/client/reducers/queries';
import { createPayee } from 'loot-core/src/client/actions/queries';
import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees';

View file

@ -1,8 +1,5 @@
import PropTypes from 'prop-types';
import React, { useEffect, useReducer, useState } from 'react';
import * as d from 'date-fns';
import { useSelector } from 'react-redux';
import { parse as parseDate } from 'date-fns';
import { sendCatch } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { getRecurringDescription } from 'loot-core/src/shared/schedules';
@ -16,7 +13,7 @@ import {
Text,
Stack
} from '../components/common';
import { colors, styles } from 'loot-design/src/style';
import { colors } from 'loot-design/src/style';
import { useTooltip } from 'loot-design/src/components/tooltips';
import SubtractIcon from 'loot-design/src/svg/Subtract';
import AddIcon from 'loot-design/src/svg/Add';

View file

@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react';
import React from 'react';
import RecurringSchedulePicker from './RecurringSchedulePicker';
import { Section } from '../guide/components';
import { Button, View } from './common';

View file

@ -1,4 +1,4 @@
import React, { useContext, useState, useMemo, useEffect } from 'react';
import React, { useContext, useState, useMemo } from 'react';
import ElementQuery from '../ElementQuery';
import {
View,

View file

@ -4,7 +4,6 @@ import React, {
useLayoutEffect,
useState,
useCallback,
useMemo
} from 'react';
import { css } from 'glamor';
import mergeRefs from 'react-merge-refs';
@ -18,7 +17,6 @@ import {
useRouteMatch
} from 'react-router-dom';
import {
Listbox,
ListboxInput,
ListboxButton,
ListboxPopover,
@ -32,12 +30,11 @@ import Loading from '../svg/v1/AnimatedLoading';
import ExpandArrow from 'loot-design/src/svg/ExpandArrow';
import View from './View';
import Text from './Text';
import Stack from './Stack';
import { useProperFocus } from './useProperFocus';
export View from './View';
export Text from './Text';
export Stack from './Stack';
export { default as View } from './View';
export { default as Text } from './Text';
export { default as Stack } from './Stack';
export const useStableCallback = callback => {
const callbackRef = useRef();

View file

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { View, Text, Modal, Button, ButtonWithLoading } from '../common';
import { styles, colors } from '../../style';
import { View, Text, Modal, ButtonWithLoading } from '../common';
import { colors } from '../../style';
export default function DeleteMenu({ modalProps, actions, file }) {
let [loadingState, setLoadingState] = useState(null);

View file

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { View, Block, Modal, Button, Link } from '../common';
import { View, Block, Modal, Button } from '../common';
import { styles, colors } from '../../style';
function getErrorMessage(error) {

View file

@ -7,7 +7,6 @@ import {
Modal,
Button,
ButtonWithLoading,
Link,
P
} from '../common';
import { styles, colors } from '../../style';

View file

@ -7,7 +7,6 @@ import {
Modal,
ButtonWithLoading,
Button,
Link,
P,
ExternalLink
} from '../common';

View file

@ -1,5 +1,3 @@
import React from 'react';
export default function InputAccessoryView() {
return null;
}

View file

@ -4,7 +4,6 @@ import { MobileScreen } from '../../guide/components';
import { categories, categoryGroups } from './budget.usage';
import { BudgetTable, BudgetAccessoryView } from './budget';
import InputAccessoryView from './InputAccessoryView';
import { debugDOM } from 'loot-core/src/mocks/util';
import SpreadsheetContext from '../spreadsheet/SpreadsheetContext';
import * as monthUtils from 'loot-core/src/shared/months';
import makeSpreadsheet from 'loot-core/src/mocks/spreadsheet';

View file

@ -1,7 +1,7 @@
import React from 'react';
import { View } from 'react-native';
import { MobileSection } from '../../guide/components';
import { BudgetTable, BudgetHeader, BudgetAccessoryView } from './budget';
import { BudgetTable, BudgetAccessoryView } from './budget';
import InputAccessoryView from './InputAccessoryView';
import { generateCategoryGroups } from 'loot-core/src/mocks';
import SpreadsheetContext from '../spreadsheet/SpreadsheetContext';

View file

@ -22,7 +22,7 @@ import {
deleteTransaction,
realizeTempTransactions
} from 'loot-core/src/shared/transactions';
import { applyChanges, titleFirst } from 'loot-core/src/shared/util';
import { titleFirst } from 'loot-core/src/shared/util';
import {
integerToCurrency,
integerToAmount,
@ -48,9 +48,6 @@ import {
import EditSkull1 from '../../svg/v2/EditSkull1';
import AlertTriangle from '../../svg/v2/AlertTriangle';
import CalendarIcon from '../../svg/v2/Calendar';
import ValidationCheck from '../../svg/v2/ValidationCheck';
import FavoriteStar from '../../svg/v2/FavoriteStar';
import CheckCircle1 from '../../svg/v2/CheckCircle1';
import ArrowsSynchronize from 'loot-design/src/svg/v2/ArrowsSynchronize';

View file

@ -8,7 +8,6 @@ import {
P,
Select,
FormError,
AnchorLink
} from '../common';
import { integerToCurrency } from 'loot-core/src/shared/util';
import { colors } from '../../style';

View file

@ -1,7 +1,6 @@
import React from 'react';
import { toRelaxedNumber } from 'loot-core/src/shared/util';
import { determineOffBudget } from 'loot-core/src/shared/accounts';
import { styles, colors } from '../../style';
import {
View,
Modal,

View file

@ -4,7 +4,7 @@ import { parseISO, format as formatDate, parse as parseDate } from 'date-fns';
import * as actions from 'loot-core/src/client/actions';
import { amountToInteger } from 'loot-core/src/shared/util';
import { currentDay, dayFromDate } from 'loot-core/src/shared/months';
import { View, Modal, Stack, Button, Input } from '../common';
import { View, Modal, Input } from '../common';
import DateSelect from '../DateSelect';
import CategoryAutocomplete from '../CategorySelect';
import AccountAutocomplete from '../AccountAutocomplete';

View file

@ -19,7 +19,7 @@ import {
} from '../common';
import { Checkbox } from '../forms';
import { TableHeader, TableWithNavigator, Row, Field } from '../table';
import { SectionLabel, FieldLabel } from '../forms';
import { SectionLabel } from '../forms';
import { colors, styles } from '../../style';
let dateFormats = [
@ -31,7 +31,7 @@ let dateFormats = [
{ format: 'dd mm yy', label: 'DD MM YY' }
];
function parseDate(str, order) {
export function parseDate(str, order) {
if (typeof str !== 'string') {
return null;
}
@ -40,37 +40,48 @@ function parseDate(str, order) {
return v && v.length === 1 ? '0' + v : v;
}
let parts = str.replace(/ /g, '').split(/[^0-9]/);
const dateGroups = (a, b) => str => {
const digits = str.replace(/[^\d]/g, '');
return [digits.slice(0, a), digits.slice(a, a + b), digits.slice(a + b)];
};
const yearFirst = dateGroups(4, 2);
const twoDig = dateGroups(2, 2);
let year, month, day;
let parts, year, month, day;
switch (order) {
case 'dd mm yyyy':
parts = twoDig(str);
year = parts[2];
month = parts[1];
day = parts[0];
break;
case 'dd mm yy':
parts = twoDig(str);
year = `20${parts[2]}`;
month = parts[1];
day = parts[0];
break;
case 'yyyy mm dd':
parts = yearFirst(str);
year = parts[0];
month = parts[1];
day = parts[2];
break;
case 'yy mm dd':
parts = twoDig(str);
year = `20${parts[0]}`;
month = parts[1];
day = parts[2];
break;
case 'mm dd yy':
parts = twoDig(str);
year = `20${parts[2]}`;
month = parts[0];
day = parts[1];
break;
default:
case 'mm dd yyyy':
parts = twoDig(str);
year = parts[2];
month = parts[0];
day = parts[1];
@ -336,10 +347,7 @@ function DateFormatSelect({
// try to figure out what delimiter the date is using, and default
// to space if we can't figure it out.
let delimiter = '-';
if (
transactions.length > 0 &&
(fieldMappings && fieldMappings.date != null)
) {
if (transactions.length > 0 && fieldMappings && fieldMappings.date != null) {
let date = transactions[0][fieldMappings.date];
let m = date && date.match(/[/.,-/\\]/);
delimiter = m ? m[0] : ' ';

View file

@ -0,0 +1,123 @@
import { parseDate } from './ImportTransactions';
describe('Import transactions', function() {
describe('date parsing', function() {
it('should not parse', function() {
const invalidInputs = [
{ str: '', order: 'yyyy mm dd' },
{ str: null, order: 'yyyy mm dd' },
{ str: 42, order: 'yyyy mm dd' },
{ str: {}, order: 'yyyy mm dd' },
{ str: [], order: 'yyyy mm dd' },
{ str: 'invalid', order: 'yyyy mm dd' },
{ str: '2020 Dec 24', order: 'yyyy mm dd' },
{ str: '12 24 20', order: 'mm dd yyyy' },
{ str: '20 12 24', order: 'yyyy mm dd' },
{ str: '2020 12 24', order: 'yy mm dd' },
{ str: '12 24 2020', order: 'mm dd yy' },
{ str: '12 00 2020', order: 'mm dd yyyy' },
{ str: '12 32 2020', order: 'mm dd yyyy' },
{ str: '13 24 2020', order: 'mm dd yyyy' },
{ str: '00 24 2020', order: 'mm dd yyyy' },
{ str: '02 30 2020', order: 'mm dd yyyy' },
{ str: '04 31 2020', order: 'mm dd yyyy' },
{ str: '04 31 2020', order: 'mm dd yyyy' },
{ str: '06 31 2020', order: 'mm dd yyyy' },
{ str: '09 31 2020', order: 'mm dd yyyy' },
{ str: '11 31 2020', order: 'mm dd yyyy' },
{ str: '2046 31 2020', order: 'mm dd yyyy' },
{ str: '2011 31 2020', order: 'mm dd yy' },
{ str: '2020', order: 'mm dd yy' }
];
for (const { str, order } of invalidInputs) {
expect(parseDate(str, order)).toBe(null);
}
});
it('should parse', function() {
const validInputs = [
{
order: 'yyyy mm dd',
cases: [
'20201224',
'2020 12 24',
'2020-12-24',
'2020/12/24',
' 2020 / 12 / 24',
'2020/12/24 ',
'2020 12-24 '
]
},
{
order: 'yy mm dd',
cases: [
'201224',
'20 12 24',
'20-12-24',
'20/12/24',
'20/12/24',
'20/12/24 ',
'20 12-24 '
]
},
{
order: 'mm dd yyyy',
cases: [
'12242020',
'12 24 2020 ',
'12-24-2020',
'12/24/2020',
' 12/24/2020',
'12/24/2020',
'12 24-2020'
]
},
{
order: 'mm dd yy',
cases: [
'122420',
'12 24 20 ',
'12-24-20',
'12/24/20',
' 12/24/20',
'12/24/20',
'12 24-20'
]
},
{
order: 'dd mm yyyy',
cases: [
'24122020',
'24 12 2020 ',
'24-12-2020',
'24/12/2020',
' 24/12/2020',
'24/12/2020 ',
'24-12 2020'
]
},
{
order: 'dd mm yy',
cases: [
'241220',
'2412 20 ',
'24-12-20',
'24/12/20',
' 24/12/20',
'24/12/20 ',
'24 12-20 '
]
}
];
for (const { order, cases } of validInputs) {
for (const str of cases) {
const parsed = parseDate(str, order);
expect(typeof parsed).toBe('string');
expect(parsed).toBe('2020-12-24');
}
}
});
});
});

View file

@ -1,7 +1,7 @@
import React from 'react';
import { View, Text, Block, Modal, Button } from '../common';
import { Row, Cell, DeleteCell } from '../table';
import { styles, colors } from '../../style';
import { Row, Cell } from '../table';
import { colors } from '../../style';
class BackupTable extends React.Component {
state = { hoveredBackup: null };

View file

@ -1,7 +1,6 @@
import React, { useState } from 'react';
import { styles, colors } from '../../style';
import { View, Text, Modal, P, Button } from '../common';
import { amountToPrettyCurrency } from 'loot-core/src/shared/util';
import {
fromPlaidAccountType,
prettyAccountType

View file

@ -8,7 +8,6 @@ import makeSpreadsheet from 'loot-core/src/mocks/spreadsheet';
import { Sidebar } from './sidebar';
import { Section } from '../guide/components';
import { generateAccount } from 'loot-core/src/mocks';
import { colors } from '../style';
function withState(state, render) {
const Component = lively(render, { getInitialState: () => state });

Some files were not shown because too many files have changed in this diff Show more