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: keys:
- v3-dependencies-{{ checksum "yarn.lock" }} - v3-dependencies-{{ checksum "yarn.lock" }}
- run: yarn install --pure-lockfile - run: yarn install --immutable
- save_cache: - save_cache:
<<: *cached_files <<: *cached_files
@ -74,7 +74,7 @@ jobs:
shell: bash shell: bash
- run: - run:
command: yarn install --pure-lockfile command: yarn install --immutable
shell: bash shell: bash
- run: - run:
@ -96,7 +96,7 @@ jobs:
keys: keys:
- v3-dependencies-{{ checksum "yarn.lock" }} - v3-dependencies-{{ checksum "yarn.lock" }}
- run: yarn install --pure-lockfile - run: yarn install --immutable
- run: sudo npm install -g @sentry/cli --unsafe-perm - 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 export-2020-01-10.csv
**/*.log **/*.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 yarn lint
fi fi
./node_modules/.bin/patch-package yarn patch-package
( yarn workspace mobile patch-package
cd packages/loot-design;
../../node_modules/.bin/patch-package
)
( yarn workspace loot-core build:node
cd packages/mobile;
../../node_modules/.bin/patch-package
)
( yarn workspace @actual-app/web build
cd packages/loot-core;
NODE_ENV=production yarn build:node
)
(
cd packages/desktop-client;
yarn build
)
rm -fr packages/desktop-electron/client-build rm -fr packages/desktop-electron/client-build
cp -r packages/desktop-client/build 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 yarn lint
fi fi
( ACTUAL_RELEASE_TYPE=$RELEASE yarn workspace loot-core build:browser
cd packages/loot-design;
../../node_modules/.bin/patch-package
)
( REACT_APP_RELEASE_TYPE=$RELEASE yarn workspace @actual-app/web 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
)
echo "packages/desktop-client/build" 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": { "workspaces": {
"packages": [ "packages": [
"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": { "scripts": {
"start": "npm-run-all --parallel start:desktop-*", "start": "npm-run-all --parallel 'start:desktop-*'",
"start:desktop-node": "cd packages/loot-core && yarn watch:node", "start:desktop-node": "yarn workspace loot-core watch:node",
"start:desktop-client": "cd packages/desktop-client && yarn watch", "start:desktop-client": "yarn workspace @actual-app/web watch",
"start:desktop-electron": "cd packages/desktop-electron && yarn watch", "start:desktop-electron": "yarn workspace Actual watch",
"start:browser": "npm-run-all --parallel start:browser-*", "start:browser": "npm-run-all --parallel 'start:browser-*'",
"start:browser-backend": "cd packages/loot-core && yarn watch:browser", "start:browser-backend": "yarn workspace loot-core watch:browser",
"start:browser-frontend": "cd packages/desktop-client && yarn start:browser", "start:browser-frontend": "yarn workspace @actual-app/web start:browser",
"test": "./node_modules/.bin/jest --maxWorkers=4", "test": "./node_modules/.bin/jest --maxWorkers=4",
"test:debug": "node ./node_modules/.bin/jest --runInBand --useStderr", "test:debug": "node ./node_modules/.bin/jest --runInBand --useStderr",
"test:debug-brk": "node --inspect-brk ./node_modules/.bin/jest --runInBand", "test:debug-brk": "node --inspect-brk ./node_modules/.bin/jest --runInBand",
"rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core", "rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core",
"rebuild-node": "cd packages/loot-core && npm rebuild", "rebuild-node": "yarn workspace loot-core rebuild",
"lint": "cd packages/loot-core && yarn lint", "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" "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": { "devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.15.0", "@babel/plugin-transform-modules-commonjs": "^7.18.2",
"cross-env": "^5.1.5", "cross-env": "^5.1.5",
"husky": "^3.0.4", "husky": "^3.0.4",
"npm-run-all": "^4.1.3", "npm-run-all": "^4.1.3",
"patch-package": "^6.1.2",
"prettier": "^1.18.1", "prettier": "^1.18.1",
"pretty-quick": "^1.11.1", "pretty-quick": "^1.11.1",
"shelljs": "^0.8.2", "shelljs": "^0.8.2",
@ -83,6 +60,8 @@
"@babel/preset-env": "^7.15.1", "@babel/preset-env": "^7.15.1",
"@babel/core": "^7.15.1", "@babel/core": "^7.15.1",
"@babel/runtime": "^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 PnpWebpackPlugin = require('pnp-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-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 TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser'); const safePostCssParser = require('postcss-safe-parser');
const ManifestPlugin = require('webpack-manifest-plugin'); 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 WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const WatchMissingNodeModulesPlugin = require('jwl-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const ModuleScopePlugin = require('jwl-dev-utils/ModuleScopePlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const getCSSModuleLocalIdent = require('jwl-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths'); const paths = require('./paths');
const getClientEnvironment = require('./env'); 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 ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin-alt');
const typescriptFormatter = require('jwl-dev-utils/typescriptFormatter');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const webpackDevClientEntry = require.resolve( 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. // 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: [ use: [
{ {
options: { options: {
formatter: require.resolve('jwl-dev-utils/eslintFormatter'), formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint') eslintPath: require.resolve('eslint')
}, },
loader: require.resolve('eslint-loader') loader: require.resolve('eslint-loader')
@ -490,7 +488,7 @@ module.exports = function(webpackEnv) {
entry: webpackDevClientEntry, entry: webpackDevClientEntry,
// The expected exports are slightly different from what the overlay exports, // The expected exports are slightly different from what the overlay exports,
// so an interop is included here to enable feedback on module-level errors. // 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, // Since we ship a custom dev client and overlay integration,
// the bundled socket handling logic can be eliminated. // the bundled socket handling logic can be eliminated.
sockIntegration: false sockIntegration: false
@ -500,12 +498,6 @@ module.exports = function(webpackEnv) {
// a plugin that prints an error when you attempt to do this. // a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240 // See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(), 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 && isEnvProduction &&
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output // Options similar to the same options in webpackOptions.output
@ -545,6 +537,8 @@ module.exports = function(webpackEnv) {
] ]
}), }),
// TypeScript type checking // 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 && useTypeScript &&
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
typescript: resolve.sync('typescript', { typescript: resolve.sync('typescript', {
@ -571,7 +565,6 @@ module.exports = function(webpackEnv) {
], ],
watch: paths.appSrc, watch: paths.appSrc,
silent: true, silent: true,
formatter: typescriptFormatter
}) })
].filter(Boolean), ].filter(Boolean),
// Some libraries import Node modules but don't use them in the browser. // Some libraries import Node modules but don't use them in the browser.

View file

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

View file

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

View file

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

View file

@ -19,15 +19,15 @@ const fs = require('fs');
const chalk = require('chalk'); const chalk = require('chalk');
const webpack = require('webpack'); const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server'); const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('jwl-dev-utils/clearConsole'); const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('jwl-dev-utils/checkRequiredFiles'); const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const { const {
choosePort, choosePort,
createCompiler, createCompiler,
prepareProxy, prepareProxy,
prepareUrls, prepareUrls,
} = require('jwl-dev-utils/WebpackDevServerUtils'); } = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('jwl-dev-utils/openBrowser'); const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths'); const paths = require('../config/paths');
const configFactory = require('../config/webpack.config'); const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.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 // We require that you explictly set browsers and do not fall back to
// browserslist defaults. // browserslist defaults.
const { checkBrowsers } = require('jwl-dev-utils/browsersHelper'); const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive) checkBrowsers(paths.appPath, isInteractive)
.then(() => { .then(() => {
// We attempt to use the default port but if it is busy, we offer the user to // 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 appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port); const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages. // 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 // Load proxy config
const proxySetting = require(paths.appPackageJson).proxy; const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 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 ExclamationSolid from 'loot-design/src/svg/v1/ExclamationSolid';
import Platform from 'loot-core/src/client/platform'; import Platform from 'loot-core/src/client/platform';
import useServerVersion from '../hooks/useServerVersion';
let dateFormats = [ let dateFormats = [
{ value: 'MM/dd/yyyy', label: 'MM/DD/YYYY' }, { value: 'MM/dd/yyyy', label: 'MM/DD/YYYY' },
{ value: 'dd/MM/yyyy', label: 'DD/MM/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 { class Settings extends React.Component {
componentDidMount() { componentDidMount() {
this.unlisten = listen('prefs-updated', () => { this.unlisten = listen('prefs-updated', () => {
@ -496,12 +519,21 @@ class Settings extends React.Component {
style={{ style={{
flexDirection: 'row', flexDirection: 'row',
alignSelf: 'center', alignSelf: 'center',
margin: '15px 0' margin: '15px 0 5px 0'
}} }}
> >
<SettingsLink to={`${match.path}/file`} name="File" first={true} /> <SettingsLink to={`${match.path}/file`} name="File" first={true} />
<SettingsLink to={`${match.path}/global`} name="Global" last={true} /> <SettingsLink to={`${match.path}/global`} name="Global" last={true} />
</View> </View>
<View
style={{
flexDirection: 'row',
alignSelf: 'center',
margin: '0 0 10px 0'
}}
>
<Version />
</View>
<View <View
style={[ style={[

View file

@ -4,7 +4,7 @@ import { createBrowserHistory } from 'history';
import { Switch, Redirect, Router, Route } from 'react-router-dom'; import { Switch, Redirect, Router, Route } from 'react-router-dom';
import { send } from 'loot-core/src/platform/client/fetch'; import { send } from 'loot-core/src/platform/client/fetch';
import * as actions from 'loot-core/src/client/actions'; 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 { colors } from 'loot-design/src/style';
import ServerURL from './ServerURL'; import ServerURL from './ServerURL';
import LoggedInUser from '../LoggedInUser'; import LoggedInUser from '../LoggedInUser';
@ -16,10 +16,13 @@ import Bootstrap from './subscribe/Bootstrap';
import Error from './subscribe/Error'; import Error from './subscribe/Error';
import ChangePassword from './subscribe/ChangePassword'; import ChangePassword from './subscribe/ChangePassword';
import ConfigServer from './ConfigServer'; import ConfigServer from './ConfigServer';
import useServerVersion from '../../hooks/useServerVersion';
function Version() { function Version() {
const version = useServerVersion();
return ( return (
<ExternalLink <Text
style={{ style={{
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,
@ -32,8 +35,8 @@ function Version() {
}} }}
href={'https://actualbudget.com/blog/' + window.Actual.ACTUAL_VERSION} href={'https://actualbudget.com/blog/' + window.Actual.ACTUAL_VERSION}
> >
{window.Actual.ACTUAL_VERSION} {`App: v${window.Actual.ACTUAL_VERSION} | Server: ${version}`}
</ExternalLink> </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 normalizePathSep = require('slash');
const uuid = require('uuid'); const uuid = require('uuid');
const AdmZip = require('adm-zip'); const AdmZip = require('adm-zip');
const actual = require('@actual-app/api/methods'); const actual = require('@actual-app/api');
const { amountToInteger } = require('@actual-app/api/utils'); const amountToInteger = actual.utils.amountToInteger;
// Utils // Utils

View file

@ -1,7 +1,6 @@
const d = require('date-fns'); const d = require('date-fns');
const uuid = require('uuid'); const uuid = require('uuid');
const actual = require('@actual-app/api/methods'); const actual = require('@actual-app/api');
const { amountToInteger } = require('@actual-app/api/utils');
function amountFromYnab(amount) { function amountFromYnab(amount) {
// ynabs multiplies amount by 1000 and actual by 100 // 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 sqlite from '../platform/server/sqlite';
import * as db from '../server/db'; import * as db from '../server/db';
import { getMessages } from '../server/sync'; import { getMessages } from '../server/sync';
import Timestamp from '../server/timestamp';
let dbPath = process.argv[2]; let dbPath = process.argv[2];
let tables = [ let tables = [

View file

@ -4,7 +4,6 @@ import os from 'os';
import * as sqlite from '../src/platform/server/sqlite'; import * as sqlite from '../src/platform/server/sqlite';
import * as db from '../src/server/db'; import * as db from '../src/server/db';
import { batchMessages, setSyncingMode } from '../src/server/sync'; import { batchMessages, setSyncingMode } from '../src/server/sync';
import Timestamp from '../src/server/timestamp';
import { runQuery } from '../src/server/aql/schema/run-query'; import { runQuery } from '../src/server/aql/schema/run-query';
import asyncStorage from '../src/platform/server/asyncStorage'; import asyncStorage from '../src/platform/server/asyncStorage';
import { makeChild } from '../src/shared/transactions'; 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", "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", "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:api": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.api.config.js",
"build:browser": "NODE_ENV=production ./bin/build-browser", "build:browser": "cross-env NODE_ENV=production ./bin/build-browser",
"watch:browser": "NODE_ENV=development ./bin/build-browser", "watch:browser": "cross-env NODE_ENV=development ./bin/build-browser",
"lint": "eslint src" "lint": "eslint src"
}, },
"author": "", "author": "",
@ -43,9 +43,10 @@
"babel-jest": "25.2.6", "babel-jest": "25.2.6",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"buffer": "^5.5.0", "buffer": "^5.5.0",
"currency-formatter": "jlongster/currency-formatter", "cross-env": "^7.0.3",
"damerau-levenshtein": "^1.0.4", "damerau-levenshtein": "^1.0.4",
"date-fns": "2.0.0-alpha.27", "date-fns": "2.0.0-alpha.27",
"eslint": "5.6.0",
"esm": "^3.0.82", "esm": "^3.0.82",
"fake-indexeddb": "^3.1.3", "fake-indexeddb": "^3.1.3",
"fast-check": "2.13.0", "fast-check": "2.13.0",
@ -55,14 +56,14 @@
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
"memfs": "3.1.1", "memfs": "3.1.1",
"memoize-one": "^4.0.0", "memoize-one": "^4.0.0",
"mockdate": "^3.0.5",
"mock-require": "^3.0.2", "mock-require": "^3.0.2",
"mockdate": "^3.0.5",
"murmurhash": "^0.0.2", "murmurhash": "^0.0.2",
"perf-deets": "^1.0.15", "perf-deets": "^1.0.15",
"sanitize-filename": "^1.6.1", "sanitize-filename": "^1.6.1",
"search-query-parser": "^1.3.0", "search-query-parser": "^1.3.0",
"source-map": "^0.7.3",
"snapshot-diff": "^0.2.2", "snapshot-diff": "^0.2.2",
"source-map": "^0.7.3",
"throttleit": "^1.0.0", "throttleit": "^1.0.0",
"uuid": "3.3.2", "uuid": "3.3.2",
"webpack": "^4.41.2", "webpack": "^4.41.2",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
import dateFns from 'date-fns';
const uuid = require('../platform/uuid'); const uuid = require('../platform/uuid');
export function generateAccount(balance) { 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 rules from '../server/accounts/transaction-rules';
import * as tracking from '../server/tracking/events'; import * as tracking from '../server/tracking/events';
import { setSyncingMode } from '../server/sync'; import { setSyncingMode } from '../server/sync';
import * as monthUtils from '../shared/months';
import { updateVersion } from '../server/update'; import { updateVersion } from '../server/update';
import { resetTracer, tracer } from '../shared/test-helpers'; import { resetTracer, tracer } from '../shared/test-helpers';
import { import {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
import * as sheet from '../sheet'; import * as sheet from '../sheet';
import * as monthUtils from '../../shared/months';
import { number, sumAmounts } from './util'; import { number, sumAmounts } from './util';
const { resolveName } = require('../spreadsheet/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 * as merkle from './merkle';
import Timestamp from './timestamp'; import { Timestamp } from './timestamp';
function pretty(n) { function pretty(n) {
if (n < 60) { if (n < 60) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,8 +5,6 @@ import * as db from '../db';
import * as prefs from '../prefs'; import * as prefs from '../prefs';
import { toDateRepr } from '../models'; import { toDateRepr } from '../models';
import { runQuery as aqlQuery } from '../aql/schema/run-query'; 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 { dayFromDate, currentDay, parseDate } from '../../shared/months';
import q from '../../shared/query'; import q from '../../shared/query';
import { import {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
import * as db from '../db'; import * as db from '../db';
import Timestamp, { serializeClock, getClock } from '../timestamp'; import { serializeClock, getClock, Timestamp, merkle } from '../crdt';
import * as merkle from '../merkle';
export function rebuildMerkleHash() { export function rebuildMerkleHash() {
let rows = db.runQuery('SELECT timestamp FROM messages_crdt', [], true); 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 db from '../db';
import * as sheet from '../sheet'; import * as sheet from '../sheet';
import * as sync from './index'; import * as sync from './index';
import Timestamp, { deserializeClock, getClock } from '../timestamp'; import { getClock, Timestamp } from '../crdt';
import * as merkle from '../merkle'; import { merkle } from '../crdt';
import * as encoder from './encoder'; import * as encoder from './encoder';
const Database = require('better-sqlite3');
const fs = require('fs');
const jsc = require('jsverify'); const jsc = require('jsverify');
const uuid = require('uuid');
const uuidGenerator = jsc const uuidGenerator = jsc
.integer(97, 122) .integer(97, 122)
.smap(x => String.fromCharCode(x), x => x.charCodeAt(x)); .smap(x => String.fromCharCode(x), x => x.charCodeAt(x));

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { sendMessages } from './sync'; import { sendMessages } from './sync';
import { getIn } from '../shared/util'; import { getIn } from '../shared/util';
import Timestamp from './timestamp'; import { Timestamp } from './crdt';
import { withMutatorContext, getMutatorContext } from './mutators'; import { withMutatorContext, getMutatorContext } from './mutators';
const connection = require('../platform/server/connection'); 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'); const uuid = require('../platform/uuid');
// The amount might be null when adding a new transaction // The amount might be null when adding a new transaction

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import React, { useState, useMemo, useRef } from 'react'; 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 { getActivePayees } from 'loot-core/src/client/reducers/queries';
import { createPayee } from 'loot-core/src/client/actions/queries'; import { createPayee } from 'loot-core/src/client/actions/queries';
import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees'; 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 React, { useEffect, useReducer, useState } from 'react';
import * as d from 'date-fns';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { parse as parseDate } from 'date-fns';
import { sendCatch } from 'loot-core/src/platform/client/fetch'; import { sendCatch } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months'; import * as monthUtils from 'loot-core/src/shared/months';
import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import { getRecurringDescription } from 'loot-core/src/shared/schedules';
@ -16,7 +13,7 @@ import {
Text, Text,
Stack Stack
} from '../components/common'; } 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 { useTooltip } from 'loot-design/src/components/tooltips';
import SubtractIcon from 'loot-design/src/svg/Subtract'; import SubtractIcon from 'loot-design/src/svg/Subtract';
import AddIcon from 'loot-design/src/svg/Add'; 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 RecurringSchedulePicker from './RecurringSchedulePicker';
import { Section } from '../guide/components'; import { Section } from '../guide/components';
import { Button, View } from './common'; 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 ElementQuery from '../ElementQuery';
import { import {
View, View,

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import React, { useState } from 'react'; 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'; import { styles, colors } from '../../style';
function getErrorMessage(error) { function getErrorMessage(error) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ import {
deleteTransaction, deleteTransaction,
realizeTempTransactions realizeTempTransactions
} from 'loot-core/src/shared/transactions'; } from 'loot-core/src/shared/transactions';
import { applyChanges, titleFirst } from 'loot-core/src/shared/util'; import { titleFirst } from 'loot-core/src/shared/util';
import { import {
integerToCurrency, integerToCurrency,
integerToAmount, integerToAmount,
@ -48,9 +48,6 @@ import {
import EditSkull1 from '../../svg/v2/EditSkull1'; import EditSkull1 from '../../svg/v2/EditSkull1';
import AlertTriangle from '../../svg/v2/AlertTriangle'; 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 CheckCircle1 from '../../svg/v2/CheckCircle1';
import ArrowsSynchronize from 'loot-design/src/svg/v2/ArrowsSynchronize'; import ArrowsSynchronize from 'loot-design/src/svg/v2/ArrowsSynchronize';

View file

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

View file

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { toRelaxedNumber } from 'loot-core/src/shared/util'; import { toRelaxedNumber } from 'loot-core/src/shared/util';
import { determineOffBudget } from 'loot-core/src/shared/accounts'; import { determineOffBudget } from 'loot-core/src/shared/accounts';
import { styles, colors } from '../../style';
import { import {
View, View,
Modal, 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 * as actions from 'loot-core/src/client/actions';
import { amountToInteger } from 'loot-core/src/shared/util'; import { amountToInteger } from 'loot-core/src/shared/util';
import { currentDay, dayFromDate } from 'loot-core/src/shared/months'; 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 DateSelect from '../DateSelect';
import CategoryAutocomplete from '../CategorySelect'; import CategoryAutocomplete from '../CategorySelect';
import AccountAutocomplete from '../AccountAutocomplete'; import AccountAutocomplete from '../AccountAutocomplete';

View file

@ -19,7 +19,7 @@ import {
} from '../common'; } from '../common';
import { Checkbox } from '../forms'; import { Checkbox } from '../forms';
import { TableHeader, TableWithNavigator, Row, Field } from '../table'; import { TableHeader, TableWithNavigator, Row, Field } from '../table';
import { SectionLabel, FieldLabel } from '../forms'; import { SectionLabel } from '../forms';
import { colors, styles } from '../../style'; import { colors, styles } from '../../style';
let dateFormats = [ let dateFormats = [
@ -31,7 +31,7 @@ let dateFormats = [
{ format: 'dd mm yy', label: 'DD MM YY' } { format: 'dd mm yy', label: 'DD MM YY' }
]; ];
function parseDate(str, order) { export function parseDate(str, order) {
if (typeof str !== 'string') { if (typeof str !== 'string') {
return null; return null;
} }
@ -40,37 +40,48 @@ function parseDate(str, order) {
return v && v.length === 1 ? '0' + v : v; 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) { switch (order) {
case 'dd mm yyyy': case 'dd mm yyyy':
parts = twoDig(str);
year = parts[2]; year = parts[2];
month = parts[1]; month = parts[1];
day = parts[0]; day = parts[0];
break; break;
case 'dd mm yy': case 'dd mm yy':
parts = twoDig(str);
year = `20${parts[2]}`; year = `20${parts[2]}`;
month = parts[1]; month = parts[1];
day = parts[0]; day = parts[0];
break; break;
case 'yyyy mm dd': case 'yyyy mm dd':
parts = yearFirst(str);
year = parts[0]; year = parts[0];
month = parts[1]; month = parts[1];
day = parts[2]; day = parts[2];
break; break;
case 'yy mm dd': case 'yy mm dd':
parts = twoDig(str);
year = `20${parts[0]}`; year = `20${parts[0]}`;
month = parts[1]; month = parts[1];
day = parts[2]; day = parts[2];
break; break;
case 'mm dd yy': case 'mm dd yy':
parts = twoDig(str);
year = `20${parts[2]}`; year = `20${parts[2]}`;
month = parts[0]; month = parts[0];
day = parts[1]; day = parts[1];
break; break;
default: default:
case 'mm dd yyyy': case 'mm dd yyyy':
parts = twoDig(str);
year = parts[2]; year = parts[2];
month = parts[0]; month = parts[0];
day = parts[1]; day = parts[1];
@ -336,10 +347,7 @@ function DateFormatSelect({
// try to figure out what delimiter the date is using, and default // try to figure out what delimiter the date is using, and default
// to space if we can't figure it out. // to space if we can't figure it out.
let delimiter = '-'; let delimiter = '-';
if ( if (transactions.length > 0 && fieldMappings && fieldMappings.date != null) {
transactions.length > 0 &&
(fieldMappings && fieldMappings.date != null)
) {
let date = transactions[0][fieldMappings.date]; let date = transactions[0][fieldMappings.date];
let m = date && date.match(/[/.,-/\\]/); let m = date && date.match(/[/.,-/\\]/);
delimiter = m ? m[0] : ' '; 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 React from 'react';
import { View, Text, Block, Modal, Button } from '../common'; import { View, Text, Block, Modal, Button } from '../common';
import { Row, Cell, DeleteCell } from '../table'; import { Row, Cell } from '../table';
import { styles, colors } from '../../style'; import { colors } from '../../style';
class BackupTable extends React.Component { class BackupTable extends React.Component {
state = { hoveredBackup: null }; state = { hoveredBackup: null };

View file

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

View file

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

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