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
|
||||
|
||||
assets/less
|
||||
dist
|
||||
@@ -4,3 +4,11 @@
|
||||
- npm 6.7.0
|
||||
|
||||
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",
|
||||
"start": "clap build",
|
||||
"test": "clap test",
|
||||
"validate": "npm ls"
|
||||
"validate": "npm ls",
|
||||
"watch": "clap watch"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -25,15 +26,19 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@manaflair/redux-batch": "^1.0.0",
|
||||
"@reduxjs/toolkit": "^1.5.0",
|
||||
"bootstrap": "3.4.1",
|
||||
"classnames": "^2.2.5",
|
||||
"create-react-class": "^15.6.3",
|
||||
"font-awesome": "4.6.3",
|
||||
"jquery": "^3.0.0",
|
||||
"moment": "^2.13.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-transition-group": "^1.0.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"webextension-polyfill-ts": "^0.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -54,6 +59,9 @@
|
||||
"@types/node": "^14.14.20",
|
||||
"@types/react": "^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",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
@@ -94,6 +102,8 @@
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-plugin-packagejson": "^2.2.9",
|
||||
"prettier-plugin-sort-json": "0.0.1",
|
||||
"remote-redux-devtools": "^0.5.16",
|
||||
"remotedev-server": "^0.3.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.4",
|
||||
"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 classNames from 'classnames';
|
||||
import { SuccessOrFailType } from '../config';
|
||||
import { SuccessOrFailType } from '../config/config';
|
||||
interface AlertProps {
|
||||
text: string;
|
||||
type: SuccessOrFailType;
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios';
|
||||
import moment from 'moment';
|
||||
import { Tabs } from 'webextension-polyfill-ts';
|
||||
import { User } from '../types/user';
|
||||
import config from '../config';
|
||||
import config from '../config/config';
|
||||
import { SummariesPayload, GrandTotal } from '../types/summaries';
|
||||
|
||||
class WakaTimeCore {
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from 'react';
|
||||
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 store = createStore('WakaTime-Options');
|
||||
checkCurrentUser(store)(30 * 1000);
|
||||
|
||||
const openOptions = async (): Promise<void> => {
|
||||
await browser.runtime.openOptionsPage();
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<>
|
||||
<Provider store={store}>
|
||||
<h1>POPUP GO HERE</h1>
|
||||
<div onClick={openOptions}>Open options</div>
|
||||
</>,
|
||||
</Provider>,
|
||||
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 config from '../config';
|
||||
import config from '../config/config';
|
||||
|
||||
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 changeExtensionTooltip from './changeExtensionTooltip';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { browser } from 'webextension-polyfill-ts';
|
||||
import config from '../config';
|
||||
import config from '../config/config';
|
||||
|
||||
/**
|
||||
* 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 cfg: webpack.Configuration = {
|
||||
devtool: isProd ? 'none' : 'inline-source-map',
|
||||
devtool: 'inline-source-map',
|
||||
entry: {
|
||||
background: [join(srcFolder, 'background.ts')],
|
||||
devtools: [join(srcFolder, 'devtools.ts')],
|
||||
options: [join(srcFolder, 'options.tsx')],
|
||||
popup: [join(srcFolder, 'popup.tsx')],
|
||||
},
|
||||
mode: isProd ? 'production' : 'development',
|
||||
// mode: isProd ? 'production' : 'development',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -75,7 +75,10 @@ const getConfigByBrowser = (isProd: boolean, browser: BrowserTypes): webpack.Con
|
||||
};
|
||||
return cfg;
|
||||
};
|
||||
export default (env: Record<string, string>): webpack.Configuration[] => {
|
||||
const isProd = env.mode === 'production';
|
||||
export default (
|
||||
env: Record<string, string>,
|
||||
arv: Record<string, string>,
|
||||
): webpack.Configuration[] => {
|
||||
const isProd = arv.mode !== 'development';
|
||||
return [getConfigByBrowser(isProd, 'chrome'), getConfigByBrowser(isProd, 'firefox')];
|
||||
};
|
||||
|
||||
13
xclap.ts
13
xclap.ts
@@ -4,7 +4,7 @@
|
||||
import * as fs from 'fs';
|
||||
import { join } from 'path';
|
||||
import * as shelljs from 'shelljs';
|
||||
const { load, exec, serial } = require('@xarc/run');
|
||||
const { load, exec, serial, concurrent } = require('@xarc/run');
|
||||
|
||||
const makePublicFolder = () => {
|
||||
if (!fs.existsSync('public/js')) {
|
||||
@@ -31,18 +31,21 @@ const copyFromNodeModules = () => {
|
||||
};
|
||||
load({
|
||||
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'),
|
||||
less: exec('lessc assets/less/app.less public/css/app.css'),
|
||||
lint: ['prettier', 'eslint'],
|
||||
postinstall: ['clean', makePublicFolder, copyFromNodeModules, 'less'],
|
||||
prettier: [exec('prettier --write .')],
|
||||
'remotedev-server': exec('remotedev --hostname=localhost --port=8000'),
|
||||
test: ['build', 'lint', 'test-jest', 'test-js'],
|
||||
'test-jest': [exec('jest --clearCache'), exec('jest --verbose --coverage')],
|
||||
'test-jest-update': exec('jest -u'),
|
||||
'test-js': 'phantomjs tests/run.js',
|
||||
watch: concurrent('watch-jest', 'webpack:watch', 'remotedev-server'),
|
||||
'watch-jest': exec('jest --watch'),
|
||||
webpack: [exec('webpack --mode production')],
|
||||
'webpack:dev': [exec('webpack --mode development')],
|
||||
'webpack:watch': exec('webpack --mode development --watch'),
|
||||
webpack: ['clean:webpack', exec('webpack --mode production')],
|
||||
'webpack:dev': ['clean:webpack', exec('webpack --mode development')],
|
||||
'webpack:watch': ['clean:webpack', exec('webpack --mode development --watch')],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user