Redux toolkit (#115)
* add @redux/toolkit * bump react version to allow for hooks * add remote-redux devtools to help track extension state * add remote-redux server to allow for remote state viewing * setup react-redux for current user * setup watch mode for running redux remote dev watch to options * move screenshots
This commit is contained in:
@@ -3,3 +3,4 @@ public
|
|||||||
vendor
|
vendor
|
||||||
|
|
||||||
assets/less
|
assets/less
|
||||||
|
dist
|
||||||
@@ -4,3 +4,11 @@
|
|||||||
- npm 6.7.0
|
- npm 6.7.0
|
||||||
|
|
||||||
It is suggested you use [nvm](https://github.com/nvm-sh/nvm) to manage your node version
|
It is suggested you use [nvm](https://github.com/nvm-sh/nvm) to manage your node version
|
||||||
|
|
||||||
|
It is suggested to install this globally[@xarc/run-cli](https://www.npmjs.com/package/@xarc/run-cli)
|
||||||
|
|
||||||
|
This will allow you to run varios tasks
|
||||||
|

|
||||||
|
|
||||||
|
In devmode you can open [local remote devtools](http://localhost:8000)
|
||||||
|

|
||||||
|
|||||||
1714
package-lock.json
generated
1714
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -6,7 +6,8 @@
|
|||||||
"lint": "clap lint",
|
"lint": "clap lint",
|
||||||
"start": "clap build",
|
"start": "clap build",
|
||||||
"test": "clap test",
|
"test": "clap test",
|
||||||
"validate": "npm ls"
|
"validate": "npm ls",
|
||||||
|
"watch": "clap watch"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@@ -25,15 +26,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@manaflair/redux-batch": "^1.0.0",
|
||||||
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
"bootstrap": "3.4.1",
|
"bootstrap": "3.4.1",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"create-react-class": "^15.6.3",
|
"create-react-class": "^15.6.3",
|
||||||
"font-awesome": "4.6.3",
|
"font-awesome": "4.6.3",
|
||||||
"jquery": "^3.0.0",
|
"jquery": "^3.0.0",
|
||||||
"moment": "^2.13.0",
|
"moment": "^2.13.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.14.0",
|
||||||
|
"react-redux": "^7.2.2",
|
||||||
"react-transition-group": "^1.0.0",
|
"react-transition-group": "^1.0.0",
|
||||||
|
"redux-logger": "^3.0.6",
|
||||||
"webextension-polyfill-ts": "^0.22.0"
|
"webextension-polyfill-ts": "^0.22.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -54,6 +59,9 @@
|
|||||||
"@types/node": "^14.14.20",
|
"@types/node": "^14.14.20",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^17.0.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
|
"@types/react-redux": "^7.1.15",
|
||||||
|
"@types/redux-logger": "^3.0.8",
|
||||||
|
"@types/remote-redux-devtools": "^0.5.4",
|
||||||
"@types/shelljs": "^0.8.8",
|
"@types/shelljs": "^0.8.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||||
"@typescript-eslint/parser": "^4.13.0",
|
"@typescript-eslint/parser": "^4.13.0",
|
||||||
@@ -94,6 +102,8 @@
|
|||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"prettier-plugin-packagejson": "^2.2.9",
|
"prettier-plugin-packagejson": "^2.2.9",
|
||||||
"prettier-plugin-sort-json": "0.0.1",
|
"prettier-plugin-sort-json": "0.0.1",
|
||||||
|
"remote-redux-devtools": "^0.5.16",
|
||||||
|
"remotedev-server": "^0.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.8.4",
|
||||||
"sinon": "^4.2.2",
|
"sinon": "^4.2.2",
|
||||||
|
|||||||
BIN
screenshots/remote-redux-devtools.png
Normal file
BIN
screenshots/remote-redux-devtools.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
screenshots/xrun-autocomplete.png
Normal file
BIN
screenshots/xrun-autocomplete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { SuccessOrFailType } from '../config';
|
import { SuccessOrFailType } from '../config/config';
|
||||||
interface AlertProps {
|
interface AlertProps {
|
||||||
text: string;
|
text: string;
|
||||||
type: SuccessOrFailType;
|
type: SuccessOrFailType;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Tabs } from 'webextension-polyfill-ts';
|
import { Tabs } from 'webextension-polyfill-ts';
|
||||||
import { User } from '../types/user';
|
import { User } from '../types/user';
|
||||||
import config from '../config';
|
import config from '../config/config';
|
||||||
import { SummariesPayload, GrandTotal } from '../types/summaries';
|
import { SummariesPayload, GrandTotal } from '../types/summaries';
|
||||||
|
|
||||||
class WakaTimeCore {
|
class WakaTimeCore {
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import createStore from './stores/createStore';
|
||||||
|
import checkCurrentUser from './utils/checkCurrentUser';
|
||||||
const container = document.getElementById('wakatime');
|
const container = document.getElementById('wakatime');
|
||||||
|
|
||||||
|
const store = createStore('WakaTime-Options');
|
||||||
|
checkCurrentUser(store)(30 * 1000);
|
||||||
|
|
||||||
const openOptions = async (): Promise<void> => {
|
const openOptions = async (): Promise<void> => {
|
||||||
await browser.runtime.openOptionsPage();
|
await browser.runtime.openOptionsPage();
|
||||||
};
|
};
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<>
|
<Provider store={store}>
|
||||||
<h1>POPUP GO HERE</h1>
|
<h1>POPUP GO HERE</h1>
|
||||||
<div onClick={openOptions}>Open options</div>
|
<div onClick={openOptions}>Open options</div>
|
||||||
</>,
|
</Provider>,
|
||||||
container,
|
container,
|
||||||
);
|
);
|
||||||
|
|||||||
37
src/reducers/currentUser.ts
Normal file
37
src/reducers/currentUser.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import { User, UserPayload } from '../types/user';
|
||||||
|
import config from '../config/config';
|
||||||
|
|
||||||
|
type NameType = 'currentUser';
|
||||||
|
export const name: NameType = 'currentUser';
|
||||||
|
|
||||||
|
export const fetchCurrentUser = createAsyncThunk<User, undefined>(`[${name}]`, async () => {
|
||||||
|
const userPayload: AxiosResponse<UserPayload> = await axios.get(config.currentUserApiUrl);
|
||||||
|
return userPayload.data.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface CurrentUser {
|
||||||
|
error?: unknown;
|
||||||
|
pending?: boolean;
|
||||||
|
user?: User;
|
||||||
|
}
|
||||||
|
export const initialState: CurrentUser = {};
|
||||||
|
|
||||||
|
const currentUser = createSlice({
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(fetchCurrentUser.fulfilled, (state, { payload }) => {
|
||||||
|
state.user = payload;
|
||||||
|
});
|
||||||
|
builder.addCase(fetchCurrentUser.rejected, (state, { error }) => {
|
||||||
|
state.user = undefined;
|
||||||
|
state.error = error;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initialState,
|
||||||
|
name,
|
||||||
|
reducers: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const actions = currentUser.actions;
|
||||||
|
export default currentUser.reducer;
|
||||||
41
src/stores/createStore.ts
Normal file
41
src/stores/createStore.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { configureStore, Store } from '@reduxjs/toolkit';
|
||||||
|
import logger from 'redux-logger';
|
||||||
|
import { reduxBatch } from '@manaflair/redux-batch';
|
||||||
|
import devToolsEnhancer from 'remote-redux-devtools';
|
||||||
|
import currentUserReducer, {
|
||||||
|
initialState as InitalCurrentUser,
|
||||||
|
CurrentUser,
|
||||||
|
} from '../reducers/currentUser';
|
||||||
|
import isProd from '../utils/isProd';
|
||||||
|
|
||||||
|
export interface RootState {
|
||||||
|
currentUser: CurrentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preloadedState: RootState = {
|
||||||
|
currentUser: InitalCurrentUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RootStore = Store<RootState>;
|
||||||
|
export default (appName: string): RootStore => {
|
||||||
|
const enhancers = [reduxBatch];
|
||||||
|
if (!isProd()) {
|
||||||
|
enhancers.push(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
devToolsEnhancer({ hostname: 'localhost', name: appName, port: 8000, realtime: true }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const store = configureStore({
|
||||||
|
devTools: true,
|
||||||
|
enhancers,
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
|
||||||
|
preloadedState,
|
||||||
|
reducer: {
|
||||||
|
currentUser: currentUserReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return store;
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { browser } from 'webextension-polyfill-ts';
|
import { browser } from 'webextension-polyfill-ts';
|
||||||
import config from '../config';
|
import config from '../config/config';
|
||||||
|
|
||||||
type ColorIconTypes = 'gray' | 'red' | 'white' | '';
|
type ColorIconTypes = 'gray' | 'red' | 'white' | '';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import config, { ApiStates } from '../config';
|
import config, { ApiStates } from '../config/config';
|
||||||
|
|
||||||
import changeExtensionIcon from './changeExtensionIcon';
|
import changeExtensionIcon from './changeExtensionIcon';
|
||||||
import changeExtensionTooltip from './changeExtensionTooltip';
|
import changeExtensionTooltip from './changeExtensionTooltip';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { browser } from 'webextension-polyfill-ts';
|
import { browser } from 'webextension-polyfill-ts';
|
||||||
import config from '../config';
|
import config from '../config/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It changes the extension title
|
* It changes the extension title
|
||||||
|
|||||||
16
src/utils/checkCurrentUser.ts
Normal file
16
src/utils/checkCurrentUser.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { RootStore } from '../stores/createStore';
|
||||||
|
import { fetchCurrentUser } from '../reducers/currentUser';
|
||||||
|
|
||||||
|
type unsub = () => void;
|
||||||
|
export default (store: RootStore) => (time: number): unsub => {
|
||||||
|
const fetchUser = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
store.dispatch(fetchCurrentUser());
|
||||||
|
};
|
||||||
|
fetchUser();
|
||||||
|
const timeout = setInterval(fetchUser, time);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timeout);
|
||||||
|
};
|
||||||
|
};
|
||||||
1
src/utils/isProd.ts
Normal file
1
src/utils/isProd.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default (): boolean => process.env.NODE_ENV !== 'development';
|
||||||
@@ -19,14 +19,14 @@ const browserPolyfill = join(
|
|||||||
);
|
);
|
||||||
const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Configuration => {
|
const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Configuration => {
|
||||||
const cfg: webpack.Configuration = {
|
const cfg: webpack.Configuration = {
|
||||||
devtool: isProd ? 'none' : 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
entry: {
|
entry: {
|
||||||
background: [join(srcFolder, 'background.ts')],
|
background: [join(srcFolder, 'background.ts')],
|
||||||
devtools: [join(srcFolder, 'devtools.ts')],
|
devtools: [join(srcFolder, 'devtools.ts')],
|
||||||
options: [join(srcFolder, 'options.tsx')],
|
options: [join(srcFolder, 'options.tsx')],
|
||||||
popup: [join(srcFolder, 'popup.tsx')],
|
popup: [join(srcFolder, 'popup.tsx')],
|
||||||
},
|
},
|
||||||
mode: isProd ? 'production' : 'development',
|
// mode: isProd ? 'production' : 'development',
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -75,7 +75,10 @@ const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Con
|
|||||||
};
|
};
|
||||||
return cfg;
|
return cfg;
|
||||||
};
|
};
|
||||||
export default (env: Record<string, string>): webpack.Configuration[] => {
|
export default (
|
||||||
const isProd = env.mode === 'production';
|
env: Record<string, string>,
|
||||||
|
arv: Record<string, string>,
|
||||||
|
): webpack.Configuration[] => {
|
||||||
|
const isProd = arv.mode !== 'development';
|
||||||
return [getConfigByBrowser(isProd, 'chrome'), getConfigByBrowser(isProd, 'firefox')];
|
return [getConfigByBrowser(isProd, 'chrome'), getConfigByBrowser(isProd, 'firefox')];
|
||||||
};
|
};
|
||||||
|
|||||||
13
xclap.ts
13
xclap.ts
@@ -4,7 +4,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as shelljs from 'shelljs';
|
import * as shelljs from 'shelljs';
|
||||||
const { load, exec, serial } = require('@xarc/run');
|
const { load, exec, serial, concurrent } = require('@xarc/run');
|
||||||
|
|
||||||
const makePublicFolder = () => {
|
const makePublicFolder = () => {
|
||||||
if (!fs.existsSync('public/js')) {
|
if (!fs.existsSync('public/js')) {
|
||||||
@@ -31,18 +31,21 @@ const copyFromNodeModules = () => {
|
|||||||
};
|
};
|
||||||
load({
|
load({
|
||||||
build: [serial('postinstall', exec('gulp')), 'webpack'],
|
build: [serial('postinstall', exec('gulp')), 'webpack'],
|
||||||
clean: exec('rimraf public coverage vendor'),
|
clean: [exec('rimraf public coverage vendor'), 'clean:webpack'],
|
||||||
|
'clean:webpack': exec('rimraf dist'),
|
||||||
eslint: exec('eslint src . --fix'),
|
eslint: exec('eslint src . --fix'),
|
||||||
less: exec('lessc assets/less/app.less public/css/app.css'),
|
less: exec('lessc assets/less/app.less public/css/app.css'),
|
||||||
lint: ['prettier', 'eslint'],
|
lint: ['prettier', 'eslint'],
|
||||||
postinstall: ['clean', makePublicFolder, copyFromNodeModules, 'less'],
|
postinstall: ['clean', makePublicFolder, copyFromNodeModules, 'less'],
|
||||||
prettier: [exec('prettier --write .')],
|
prettier: [exec('prettier --write .')],
|
||||||
|
'remotedev-server': exec('remotedev --hostname=localhost --port=8000'),
|
||||||
test: ['build', 'lint', 'test-jest', 'test-js'],
|
test: ['build', 'lint', 'test-jest', 'test-js'],
|
||||||
'test-jest': [exec('jest --clearCache'), exec('jest --verbose --coverage')],
|
'test-jest': [exec('jest --clearCache'), exec('jest --verbose --coverage')],
|
||||||
'test-jest-update': exec('jest -u'),
|
'test-jest-update': exec('jest -u'),
|
||||||
'test-js': 'phantomjs tests/run.js',
|
'test-js': 'phantomjs tests/run.js',
|
||||||
|
watch: concurrent('watch-jest', 'webpack:watch', 'remotedev-server'),
|
||||||
'watch-jest': exec('jest --watch'),
|
'watch-jest': exec('jest --watch'),
|
||||||
webpack: [exec('webpack --mode production')],
|
webpack: ['clean:webpack', exec('webpack --mode production')],
|
||||||
'webpack:dev': [exec('webpack --mode development')],
|
'webpack:dev': ['clean:webpack', exec('webpack --mode development')],
|
||||||
'webpack:watch': exec('webpack --mode development --watch'),
|
'webpack:watch': ['clean:webpack', exec('webpack --mode development --watch')],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user