mirror of
https://github.com/zoriya/flood.git
synced 2025-12-05 23:06:20 +00:00
Introduce Typescript & CSS Modules (#815)
* Adding typescript support * Begins configuring webpack builds * Fix lint warnings * Updates react-router * Fixes lint configuration * Adds missing dependency * Restores disabled performance hints * Renames connectStores * Types connectStores * Uses correct envvars and fixes missing EOF newline * Formats files * Defaults props to empty object * Ignores type definitions in eslint * Another newline * Adjusts script invocation * Ignore jsdoc output * Undoes the autoformatting of CSS module types * Improves lint rules * Finishes webpack config changes * Updates deps * Fixes lint errors and attempts to fix SVG icon generator * Fixes SVG sprite generator * Adds type for SVG imports * Explicitly use babelrc in SVG loader * Formats files * Refactors prettier formatter, formats CSS module type defs * Updates style types * Uses nicer syntax in typed-css-modules-loader * Removes unnecessary div * optional property in package.json * package-lock * Fixes upstream lint errors * Removes unused modules
This commit is contained in:
11
.babelrc
11
.babelrc
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/react"],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties", "@babel/proposal-object-rest-spread"]
|
||||
"presets": [
|
||||
"@babel/env",
|
||||
"@babel/typescript",
|
||||
"@babel/react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/proposal-object-rest-spread"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
/node_modules
|
||||
/server/assets
|
||||
**/*.d.ts
|
||||
/docs
|
||||
|
||||
@@ -13,5 +13,6 @@ before_script:
|
||||
script:
|
||||
- npm run check-source-formatting
|
||||
- npm run lint
|
||||
- npm run check-types
|
||||
- npm run test
|
||||
- npm run build
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
parser: 'babel-eslint',
|
||||
extends: ['plugin:@typescript-eslint/recommended', 'prettier', 'prettier/@typescript-eslint'],
|
||||
env: {
|
||||
browser: 1,
|
||||
node: 0,
|
||||
@@ -12,6 +14,12 @@ module.exports = {
|
||||
},
|
||||
plugins: ['import'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-var-requires': 0,
|
||||
// This is enabled to allow BEM-style classnames to be referenced JS
|
||||
// Remvoe when BEM-style classnames are converted to locally-scoped
|
||||
// class names
|
||||
'@typescript-eslint/camelcase': ['error', {allow: [/[^\s]__[^\s]{1,}$/]}],
|
||||
camelcase: 0,
|
||||
// TODO: Enable a11y features
|
||||
'jsx-a11y/click-events-have-key-events': 0,
|
||||
'jsx-a11y/label-has-associated-control': 0,
|
||||
@@ -25,7 +33,7 @@ module.exports = {
|
||||
'react/destructuring-assignment': 0,
|
||||
'react/forbid-prop-types': 0,
|
||||
'react/jsx-closing-bracket-location': 0,
|
||||
'react/jsx-filename-extension': [1, {extensions: ['.js']}],
|
||||
'react/jsx-filename-extension': [1, {extensions: ['.js', '.tsx']}],
|
||||
'react/jsx-one-expression-per-line': 0,
|
||||
'react/jsx-wrap-multilines': 0,
|
||||
'react/no-unescaped-entities': ['error', {forbid: ['>', '}']}],
|
||||
@@ -50,4 +58,15 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '**/*.ts', '**/*.tsx'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['import', '@typescript-eslint/eslint-plugin'],
|
||||
rules: {
|
||||
'no-unused-vars': 0,
|
||||
'@typescript-eslint/no-unused-vars': 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ module.exports = {
|
||||
appBuild: resolveApp('server/assets'),
|
||||
appPublic: resolveApp('client/src/public/'),
|
||||
appHtml: resolveApp('client/src/index.html'),
|
||||
appIndexJs: resolveApp('client/src/javascript/app.js'),
|
||||
appIndex: resolveApp('client/src/javascript/app.tsx'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('./'),
|
||||
clientSrc: resolveApp('client/src'),
|
||||
|
||||
@@ -9,116 +9,128 @@ const eslintFormatter = require('react-dev-utils/eslintFormatter');
|
||||
const getClientEnvironment = require('./env');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
const publicPath = '/';
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment();
|
||||
|
||||
// This is the development configuration.
|
||||
// It is focused on developer experience and fast rebuilds.
|
||||
// The production configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV,
|
||||
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
|
||||
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
|
||||
devtool: 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
],
|
||||
output: {
|
||||
// Next line is not used in dev but WebpackDevServer crashes without it:
|
||||
path: paths.appBuild,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: true,
|
||||
// This does not produce a real file. It's just the virtual path that is
|
||||
// served by WebpackDevServer in development. This is the JS bundle
|
||||
// containing code from all our entry points, and the Webpack runtime.
|
||||
filename: 'static/js/bundle.js',
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: 'static/js/[name].chunk.js',
|
||||
// This is the URL that app is served from. We use "/" in development.
|
||||
publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web',
|
||||
'universally-shared-code': path.resolve('./shared'),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// TODO: Disable require.ensure as it's not a standard language feature.
|
||||
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
|
||||
// { parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
enforce: 'pre',
|
||||
test: /\.(ts|js)x?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
emitWarning: true,
|
||||
formatter: eslintFormatter,
|
||||
emitWarning: true,
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.clientSrc,
|
||||
},
|
||||
// ** ADDING/UPDATING LOADERS **
|
||||
// The "file" loader handles all assets unless explicitly excluded.
|
||||
// The `exclude` list *must* be updated with every change to loader extensions.
|
||||
// When adding a new loader, you must add its `test`
|
||||
// as a new entry in the `exclude` list for "file" loader.
|
||||
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
{
|
||||
exclude: [/\.html$/, /\.(js|jsx)$/, /\.css$/, /\.scss$/, /\.json$/, /\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
test: /\.(ts|js)x?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
modules: {
|
||||
mode: 'global',
|
||||
localIdentName: '[name]_[local]__[hash:base64:5]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('../scripts/typed-css-modules-loader'),
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
autoprefixer({
|
||||
browsers: ['>1%'],
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(ts|js)x?$/,
|
||||
use: ['source-map-loader'],
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.svg$/,
|
||||
issuer: /\.(ts|js)x?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'svg-sprite-loader',
|
||||
options: {
|
||||
runtimeGenerator: require.resolve('../scripts/svg-react-component-generator'),
|
||||
runtimeOptions: {
|
||||
iconModule: require.resolve('../src/javascript/components/general/Icon.tsx'),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
exclude: [
|
||||
/\.html$/,
|
||||
/\.(js|jsx|ts|tsx)$/,
|
||||
/\.css$/,
|
||||
/\.scss$/,
|
||||
/\.json$/,
|
||||
/\.bmp$/,
|
||||
/\.gif$/,
|
||||
/\.jpe?g$/,
|
||||
/\.png$/,
|
||||
/\.svg$/,
|
||||
],
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
include: [/\.svg$/],
|
||||
issuer: /\.s?css$/,
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
@@ -135,68 +147,33 @@ module.exports = {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use a plugin to extract that CSS to a file, but
|
||||
// in development "style" loader enables hot editing of CSS.
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
require.resolve('style-loader'),
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
'not ie < 9', // React doesn't support IE8 anyway
|
||||
],
|
||||
flexbox: 'no-2009',
|
||||
}),
|
||||
],
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('sass-loader'),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Remember to add the new extension(s) to the "file" loader exclusion list.
|
||||
],
|
||||
},
|
||||
entry: paths.appIndex,
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.jsx', '.ts', '.tsx', '.json'],
|
||||
alias: {
|
||||
'@shared': path.resolve('./shared'),
|
||||
},
|
||||
},
|
||||
output: {
|
||||
// Next line is not used in dev but WebpackDevServer crashes without it:
|
||||
path: paths.appBuild,
|
||||
// Add /* filename */ comments to generated require()s in the output.
|
||||
pathinfo: true,
|
||||
// This does not produce a real file. It's just the virtual path that is
|
||||
// served by WebpackDevServer in development. This is the JS bundle
|
||||
// containing code from all our entry points, and the Webpack runtime.
|
||||
filename: 'static/js/bundle.js',
|
||||
// There are also additional JS chunk files if you use code splitting.
|
||||
chunkFilename: 'static/js/[name].chunk.js',
|
||||
// This is the URL that app is served from. We use "/" in development.
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// In development, we always serve from the root. This makes config easier.
|
||||
publicPath: '/',
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available in index.html.
|
||||
// The base URI is available as %BASE_URI% in index.html, e.g.:
|
||||
@@ -224,26 +201,5 @@ module.exports = {
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
// TODO: Come back to this... pretty sure we need all of moment's locales.
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
},
|
||||
// Turn off performance hints during development because we don't do any
|
||||
// splitting or minification in interest of speed. These warnings become
|
||||
// cumbersome.
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,19 +2,11 @@ const autoprefixer = require('autoprefixer');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const eslintFormatter = require('react-dev-utils/eslintFormatter');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const paths = require('./paths');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const getClientEnvironment = require('./env');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
const publicPath = paths.servedPath;
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment();
|
||||
|
||||
// Assert this just to be safe.
|
||||
@@ -23,110 +15,119 @@ if (env.stringified['process.env'].NODE_ENV !== '"production"') {
|
||||
throw new Error('Production builds must have NODE_ENV=production.');
|
||||
}
|
||||
|
||||
// Note: defined here because it will be used more than once.
|
||||
const cssFilename = 'static/css/[name].[hash:8].css';
|
||||
|
||||
// ExtractTextPlugin expects the build output to be flat.
|
||||
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
|
||||
// However, our output is structured with css, js and media folders.
|
||||
// To have this structure working with relative paths, we have to use custom options.
|
||||
const extractTextPluginOptions = {};
|
||||
|
||||
// This is the production configuration.
|
||||
// It compiles slowly and is focused on producing a fast and minimal bundle.
|
||||
// The development configuration is different and lives in a separate file.
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV,
|
||||
// Don't attempt to continue if there are any errors.
|
||||
bail: true,
|
||||
// We generate sourcemaps in production. This is slow but gives good results.
|
||||
// You can exclude the *.map files from the build during deployment.
|
||||
devtool: 'source-map',
|
||||
// In production, we only want to load the polyfills and the app code.
|
||||
entry: [require.resolve('./polyfills'), paths.appIndexJs],
|
||||
output: {
|
||||
// The build folder.
|
||||
path: paths.appBuild,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// We don't currently advertise code splitting but Webpack supports it.
|
||||
filename: 'static/js/[name].[chunkhash:8].js',
|
||||
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/'),
|
||||
},
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
|
||||
),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
// `web` extension prefixes have been added for better support
|
||||
// for React Native Web.
|
||||
extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'],
|
||||
alias: {
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web',
|
||||
'universally-shared-code': path.resolve('./shared'),
|
||||
},
|
||||
plugins: [
|
||||
// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// TODO: Disable require.ensure as it's not a standard language feature.
|
||||
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
|
||||
// { parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
enforce: 'pre',
|
||||
test: /\.(ts|js)x?$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
formatter: eslintFormatter,
|
||||
babelrc: true,
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.clientSrc,
|
||||
},
|
||||
// ** ADDING/UPDATING LOADERS **
|
||||
// The "file" loader handles all assets unless explicitly excluded.
|
||||
// The `exclude` list *must* be updated with every change to loader extensions.
|
||||
// When adding a new loader, you must add its `test`
|
||||
// as a new entry in the `exclude` list in the "file" loader.
|
||||
|
||||
// "file" loader makes sure those assets end up in the `build` folder.
|
||||
// When you `import` an asset, you get its filename.
|
||||
{
|
||||
exclude: [/\.html$/, /\.(js|jsx)$/, /\.scss$/, /\.css$/, /\.json$/, /\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
modules: {
|
||||
mode: 'global',
|
||||
localIdentName: '[name]_[local]__[hash:base64:5]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('../scripts/typed-css-modules-loader'),
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
autoprefixer({
|
||||
browsers: ['>1%'],
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(ts|js)x?$/,
|
||||
use: ['source-map-loader'],
|
||||
enforce: 'pre',
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
issuer: /\.(ts|js)x?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'svg-sprite-loader',
|
||||
options: {
|
||||
runtimeGenerator: require.resolve('../scripts/svg-react-component-generator'),
|
||||
runtimeOptions: {
|
||||
iconModule: require.resolve('../src/javascript/components/general/Icon.tsx'),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
exclude: [
|
||||
/\.html$/,
|
||||
/\.(js|jsx|ts|tsx)$/,
|
||||
/\.css$/,
|
||||
/\.scss$/,
|
||||
/\.json$/,
|
||||
/\.bmp$/,
|
||||
/\.gif$/,
|
||||
/\.jpe?g$/,
|
||||
/\.png$/,
|
||||
/\.svg$/,
|
||||
],
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// "url" loader works just like "file" loader but it also embeds
|
||||
// assets smaller than specified size as data URLs to avoid requests.
|
||||
{
|
||||
include: [/\.svg$/],
|
||||
issuer: /\.s?css$/,
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
@@ -135,93 +136,34 @@ module.exports = {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('babel-loader'),
|
||||
options: {
|
||||
compact: true,
|
||||
},
|
||||
},
|
||||
// The notation here is somewhat confusing.
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader normally turns CSS into JS modules injecting <style>,
|
||||
// but unlike in development configuration, we do something different.
|
||||
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders
|
||||
// (second argument), then grabs the result CSS and puts it into a
|
||||
// separate file in our build process. This way we actually ship
|
||||
// a single CSS file in production instead of JS code injecting <style>
|
||||
// tags. If you use code splitting, however, any async bundles will still
|
||||
// use the "style" loader inside the async code so CSS from them won't be
|
||||
// in the main CSS file.
|
||||
{
|
||||
test: /\.scss$/,
|
||||
loader: ExtractTextPlugin.extract(
|
||||
Object.assign(
|
||||
{
|
||||
fallback: require.resolve('style-loader'),
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2677
|
||||
ident: 'postcss',
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'>1%',
|
||||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
'not ie < 9', // React doesn't support IE8 anyway
|
||||
],
|
||||
flexbox: 'no-2009',
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve('sass-loader'),
|
||||
},
|
||||
],
|
||||
},
|
||||
extractTextPluginOptions,
|
||||
),
|
||||
),
|
||||
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Remember to add the new extension(s) to the "file" loader exclusion list.
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
cache: true,
|
||||
parallel: true,
|
||||
sourceMap: true,
|
||||
}),
|
||||
],
|
||||
entry: paths.appIndex,
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.jsx', '.ts', '.tsx', '.json'],
|
||||
alias: {
|
||||
'@shared': path.resolve('./shared'),
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
// TODO: Add code-splitting and re-enable this when the bundle is smaller
|
||||
hints: false,
|
||||
output: {
|
||||
// The build folder.
|
||||
path: paths.appBuild,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// We don't currently advertise code splitting but Webpack supports it.
|
||||
filename: 'static/js/[name].[chunkhash:8].js',
|
||||
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
publicPath: paths.servedPath,
|
||||
// Point sourcemap entries to original disk location (format as URL on Windows)
|
||||
devtoolModuleFilenameTemplate: info => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/'),
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available in index.html.
|
||||
// The base URI is available as %BASE_URI% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%BASE_URI%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
@@ -240,35 +182,16 @@ module.exports = {
|
||||
minifyURLs: true,
|
||||
},
|
||||
}),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
|
||||
// It is absolutely essential that NODE_ENV was set to production here.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
|
||||
new ExtractTextPlugin({
|
||||
filename: cssFilename,
|
||||
}),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
// having to parse `index.html`.
|
||||
new ManifestPlugin({
|
||||
fileName: 'asset-manifest.json',
|
||||
}),
|
||||
// TODO: Come back to this... pretty sure we need all of moment's locales.
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
dgram: 'empty',
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty',
|
||||
performance: {
|
||||
// TODO: Add code-splitting and re-enable this when the bundle is smaller
|
||||
hints: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
|
||||
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndex])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const userConfig = require('../../config');
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndex])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
56
client/scripts/svg-react-component-generator.js
Normal file
56
client/scripts/svg-react-component-generator.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const path = require('path');
|
||||
const pascalCase = require('pascal-case');
|
||||
const {stringifyRequest} = require('loader-utils');
|
||||
|
||||
const stringifiedRegexp = /^'|".*'|"$/;
|
||||
|
||||
const stringify = content => {
|
||||
if (typeof content === 'string' && stringifiedRegexp.test(content)) {
|
||||
return content;
|
||||
}
|
||||
return JSON.stringify(content, null, 2);
|
||||
};
|
||||
|
||||
const stringifySymbol = symbol =>
|
||||
stringify({
|
||||
id: symbol.id,
|
||||
use: symbol.useId,
|
||||
viewBox: symbol.viewBox,
|
||||
content: symbol.render(),
|
||||
});
|
||||
|
||||
const runtimeGenerator = ({symbol, config, loaderContext}) => {
|
||||
const {spriteModule, symbolModule, runtimeOptions} = config;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const compilerContext = loaderContext._compiler.context;
|
||||
|
||||
const iconModulePath = path.resolve(compilerContext, runtimeOptions.iconModule);
|
||||
const iconModuleRequest = stringify(path.relative(path.dirname(symbol.request.file), iconModulePath));
|
||||
|
||||
const spriteRequest = stringifyRequest(
|
||||
{context: loaderContext.context},
|
||||
path.resolve(loaderContext.context, spriteModule),
|
||||
);
|
||||
const symbolRequest = stringifyRequest(
|
||||
{context: loaderContext.context},
|
||||
path.resolve(loaderContext.context, symbolModule),
|
||||
);
|
||||
const parentComponentDisplayName = 'SpriteSymbolComponent';
|
||||
const displayName = `${pascalCase(symbol.id)}_${parentComponentDisplayName}`;
|
||||
|
||||
return `
|
||||
import * as React from 'react';
|
||||
import SpriteSymbol from ${symbolRequest};
|
||||
import sprite from ${spriteRequest};
|
||||
import ${parentComponentDisplayName} from ${iconModuleRequest};
|
||||
|
||||
const symbol = new SpriteSymbol(${stringifySymbol(symbol)});
|
||||
sprite.add(symbol);
|
||||
const ${displayName} = props => {
|
||||
return <${parentComponentDisplayName} glyph="${symbol.id}" {...props} />;
|
||||
};
|
||||
export default ${displayName};
|
||||
`;
|
||||
};
|
||||
|
||||
module.exports = runtimeGenerator;
|
||||
28
client/scripts/typed-css-modules-loader.js
Normal file
28
client/scripts/typed-css-modules-loader.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const chalk = require('chalk');
|
||||
const DtsCreator = require('typed-css-modules');
|
||||
const prettier = require('../../scripts/prettier');
|
||||
|
||||
const creator = new DtsCreator();
|
||||
|
||||
module.exports = async function moduleLoader(source, map) {
|
||||
if (this.cacheable) {
|
||||
this.cacheable();
|
||||
}
|
||||
|
||||
try {
|
||||
const callback = this.async();
|
||||
const dtsContent = await creator.create(this.resourcePath, source);
|
||||
|
||||
await dtsContent.writeFile();
|
||||
await prettier.formatFile(dtsContent.outputFilePath, dtsContent.outputFilePath);
|
||||
|
||||
return callback(null, source, map);
|
||||
} catch (error) {
|
||||
console.log(chalk.red(chalk.red('CSS module type generation failed.')));
|
||||
console.log(error.message);
|
||||
|
||||
if (error.stack != null) {
|
||||
console.log(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import historySnapshotTypes from 'universally-shared-code/constants/historySnapshotTypes';
|
||||
import serverEventTypes from 'universally-shared-code/constants/serverEventTypes';
|
||||
import historySnapshotTypes from '@shared/constants/historySnapshotTypes';
|
||||
import serverEventTypes from '@shared/constants/serverEventTypes';
|
||||
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import {Router} from 'react-router-dom';
|
||||
import {FormattedMessage, IntlProvider} from 'react-intl';
|
||||
import {IndexRoute, Router, Route, browserHistory} from 'react-router';
|
||||
import {Route} from 'react-router';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import * as i18n from './i18n/languages';
|
||||
import connectStores from './util/connectStores';
|
||||
import connectStores, {EventListenerDescriptor} from './util/connectStores';
|
||||
import AppWrapper from './components/AppWrapper';
|
||||
import AuthActions from './actions/AuthActions';
|
||||
import EventTypes from './constants/EventTypes';
|
||||
import FloodActions from './actions/FloodActions';
|
||||
import history from './util/history';
|
||||
import Login from './components/views/Login';
|
||||
import Register from './components/views/Register';
|
||||
import SettingsStore from './stores/SettingsStore';
|
||||
@@ -17,7 +19,7 @@ import UIStore from './stores/UIStore';
|
||||
|
||||
import '../sass/style.scss';
|
||||
|
||||
const initialize = () => {
|
||||
const initialize = (): void => {
|
||||
UIStore.registerDependency({
|
||||
id: 'notifications',
|
||||
message: <FormattedMessage id="dependency.loading.notifications" defaultMessage="Notifications" />,
|
||||
@@ -47,15 +49,15 @@ const initialize = () => {
|
||||
});
|
||||
|
||||
AuthActions.verify().then(
|
||||
({initialUser}) => {
|
||||
({initialUser}): void => {
|
||||
if (initialUser) {
|
||||
browserHistory.replace('register');
|
||||
history.replace('register');
|
||||
} else {
|
||||
browserHistory.replace('overview');
|
||||
history.replace('overview');
|
||||
}
|
||||
},
|
||||
() => {
|
||||
browserHistory.replace('login');
|
||||
(): void => {
|
||||
history.replace('login');
|
||||
},
|
||||
);
|
||||
|
||||
@@ -63,27 +65,28 @@ const initialize = () => {
|
||||
};
|
||||
|
||||
const appRoutes = (
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={AppWrapper}>
|
||||
<IndexRoute component={Login} />
|
||||
<Route path="login" component={Login} />
|
||||
<Route path="register" component={Register} />
|
||||
<Route path="overview" component={TorrentClientOverview} />
|
||||
<Route path="*" component={Login} />
|
||||
</Route>
|
||||
<Router history={history}>
|
||||
<AppWrapper>
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/register" component={Register} />
|
||||
<Route path="/overview" component={TorrentClientOverview} />
|
||||
</AppWrapper>
|
||||
</Router>
|
||||
);
|
||||
|
||||
class FloodApp extends React.Component {
|
||||
componentDidMount() {
|
||||
interface InjectedFloodAppProps {
|
||||
locale: keyof typeof i18n;
|
||||
}
|
||||
|
||||
class FloodApp extends React.Component<InjectedFloodAppProps> {
|
||||
public componentDidMount(): void {
|
||||
initialize();
|
||||
}
|
||||
|
||||
render() {
|
||||
public render(): React.ReactNode {
|
||||
const {locale} = this.props;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line import/namespace
|
||||
<IntlProvider locale={locale} messages={i18n[locale]}>
|
||||
{appRoutes}
|
||||
</IntlProvider>
|
||||
@@ -91,18 +94,21 @@ class FloodApp extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const ConnectedFloodApp = connectStores(FloodApp, () => {
|
||||
return [
|
||||
{
|
||||
store: SettingsStore,
|
||||
event: EventTypes.SETTINGS_CHANGE,
|
||||
getValue: ({store}) => {
|
||||
return {
|
||||
locale: store.getFloodSettings('language'),
|
||||
};
|
||||
const ConnectedFloodApp = connectStores<InjectedFloodAppProps>(
|
||||
FloodApp,
|
||||
(): EventListenerDescriptor<InjectedFloodAppProps>[] => {
|
||||
return [
|
||||
{
|
||||
store: SettingsStore,
|
||||
event: EventTypes.SETTINGS_CHANGE,
|
||||
getValue: (): InjectedFloodAppProps => {
|
||||
return {
|
||||
locale: SettingsStore.getFloodSettings('language'),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
];
|
||||
},
|
||||
);
|
||||
|
||||
ReactDOM.render(<ConnectedFloodApp />, document.getElementById('app'));
|
||||
@@ -1,4 +1,3 @@
|
||||
import {browserHistory} from 'react-router';
|
||||
import {injectIntl} from 'react-intl';
|
||||
import React from 'react';
|
||||
|
||||
@@ -6,6 +5,7 @@ import {Button, Form, FormError, FormRow, Panel, PanelContent, PanelHeader, Pane
|
||||
import AuthActions from '../../actions/AuthActions';
|
||||
import AuthStore from '../../stores/AuthStore';
|
||||
import connectStores from '../../util/connectStores';
|
||||
import history from '../../util/history';
|
||||
import EventTypes from '../../constants/EventTypes';
|
||||
import RtorrentConnectionTypeSelection from '../general/RtorrentConnectionTypeSelection';
|
||||
|
||||
@@ -53,10 +53,10 @@ class AuthForm extends React.Component {
|
||||
password: submission.formData.password,
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({isSubmitting: false}, () => browserHistory.replace('overview'));
|
||||
this.setState({isSubmitting: false}, () => history.replace('overview'));
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({isSubmitting: false}, () => browserHistory.replace('login'));
|
||||
this.setState({isSubmitting: false}, () => history.replace('login'));
|
||||
});
|
||||
} else {
|
||||
AuthActions.register({
|
||||
@@ -67,7 +67,7 @@ class AuthForm extends React.Component {
|
||||
socketPath: submission.formData.rtorrentSocketPath,
|
||||
isAdmin: true,
|
||||
}).then(() => {
|
||||
this.setState({isSubmitting: false}, () => browserHistory.replace('overview'));
|
||||
this.setState({isSubmitting: false}, () => history.replace('overview'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,10 +52,12 @@ export default class CustomScrollbar extends React.Component {
|
||||
children,
|
||||
className,
|
||||
inverted,
|
||||
getHorizontalThumb,
|
||||
getVerticalThumb,
|
||||
nativeScrollHandler,
|
||||
scrollHandler,
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
getHorizontalThumb: _getHorizontalThumb,
|
||||
getVerticalThumb: _getVerticalThumb,
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
...otherProps
|
||||
} = this.props;
|
||||
const classes = classnames('scrollbars', className, {
|
||||
|
||||
17
client/src/javascript/components/general/Icon.scss
Normal file
17
client/src/javascript/components/general/Icon.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sizeSmall {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.sizeMedium {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.fillCurrentColor {
|
||||
fill: currentColor;
|
||||
}
|
||||
7
client/src/javascript/components/general/Icon.scss.d.ts
vendored
Normal file
7
client/src/javascript/components/general/Icon.scss.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare const styles: {
|
||||
readonly icon: string;
|
||||
readonly sizeSmall: string;
|
||||
readonly sizeMedium: string;
|
||||
readonly fillCurrentColor: string;
|
||||
};
|
||||
export = styles;
|
||||
47
client/src/javascript/components/general/Icon.tsx
Normal file
47
client/src/javascript/components/general/Icon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import classnames from 'classnames';
|
||||
import * as React from 'react';
|
||||
import * as styles from './Icon.scss';
|
||||
|
||||
enum Fills {
|
||||
NONE,
|
||||
CURRENT_COLOR,
|
||||
}
|
||||
|
||||
enum Sizes {
|
||||
SMALL,
|
||||
MEDIUM,
|
||||
}
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
fill: Fills;
|
||||
glyph: string;
|
||||
size: Sizes;
|
||||
}
|
||||
|
||||
export default class Icon extends React.PureComponent<Props> {
|
||||
public static Fills = Fills;
|
||||
|
||||
public static Sizes = Sizes;
|
||||
|
||||
public static defaultProps = {
|
||||
fill: Fills.CURRENT_COLOR,
|
||||
size: Sizes.MEDIUM,
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const {className, fill, glyph, size, ...restProps} = this.props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classnames(styles.icon, className, {
|
||||
[styles.fillCurrentColor]: fill === Fills.CURRENT_COLOR,
|
||||
[styles.sizeMedium]: size === Sizes.MEDIUM,
|
||||
[styles.sizeSmall]: size === Sizes.SMALL,
|
||||
})}
|
||||
{...restProps}>
|
||||
<use xlinkHref={`#${glyph}`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SelectItem,
|
||||
Textbox,
|
||||
} from 'flood-ui-kit';
|
||||
import formatUtil from 'universally-shared-code/util/formatUtil';
|
||||
import formatUtil from '@shared/util/formatUtil';
|
||||
import React from 'react';
|
||||
|
||||
import Edit from '../../icons/Edit';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Checkbox, Form, FormRow, Select, SelectItem, Radio} from 'flood-ui-kit';
|
||||
import {Checkbox, Form, FormRow} from 'flood-ui-kit';
|
||||
import {FormattedMessage, injectIntl} from 'react-intl';
|
||||
import React from 'react';
|
||||
|
||||
@@ -73,7 +73,7 @@ class DiskUsageTab extends SettingsTab {
|
||||
this.updateSettings(items);
|
||||
};
|
||||
|
||||
renderDiskItem = (item, index) => {
|
||||
renderDiskItem = item => {
|
||||
const {id, visible} = item;
|
||||
let checkbox = null;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import stringUtil from 'universally-shared-code/util/stringUtil';
|
||||
import torrentStatusMap from 'universally-shared-code/constants/torrentStatusMap';
|
||||
import stringUtil from '@shared/util/stringUtil';
|
||||
import torrentStatusMap from '@shared/constants/torrentStatusMap';
|
||||
|
||||
import ClockIcon from '../../icons/ClockIcon';
|
||||
import DownloadThickIcon from '../../icons/DownloadThickIcon';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classnames from 'classnames';
|
||||
import {defineMessages, injectIntl} from 'react-intl';
|
||||
import formatUtil from 'universally-shared-code/util/formatUtil';
|
||||
import formatUtil from '@shared/util/formatUtil';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtil from 'universally-shared-code/util/objectUtil';
|
||||
import objectUtil from '@shared/util/objectUtil';
|
||||
|
||||
const actionTypes = [
|
||||
'AUTH_CREATE_USER_SUCCESS',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import objectUtil from 'universally-shared-code/util/objectUtil';
|
||||
import objectUtil from '@shared/util/objectUtil';
|
||||
|
||||
const eventTypes = [
|
||||
'ALERTS_CHANGE',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import diffActionTypes from 'universally-shared-code/constants/diffActionTypes';
|
||||
import diffActionTypes from '@shared/constants/diffActionTypes';
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import serverEventTypes from 'universally-shared-code/constants/serverEventTypes';
|
||||
import serverEventTypes from '@shared/constants/serverEventTypes';
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import AlertStore from './AlertStore';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import diffActionTypes from 'universally-shared-code/constants/diffActionTypes';
|
||||
import diffActionTypes from '@shared/constants/diffActionTypes';
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const connectStores = (Component, getEventListenerDescriptors) => {
|
||||
class ConnectedComponent extends React.Component {
|
||||
eventHandlersByStore = new Map();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = getEventListenerDescriptors(props).reduce((state, eventListenerDescriptor) => {
|
||||
const {store, getValue} = eventListenerDescriptor;
|
||||
return {
|
||||
...state,
|
||||
...getValue({state, props, store, payload: null}),
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const eventListenerDescriptors = getEventListenerDescriptors(this.props);
|
||||
|
||||
eventListenerDescriptors.forEach(eventListenerDescriptor => {
|
||||
const {store, event, getValue} = eventListenerDescriptor;
|
||||
const eventHandler = payload => this.setState((state, props) => getValue({state, props, store, payload}));
|
||||
const events = Array.isArray(event) ? event : [event];
|
||||
|
||||
events.forEach(storeEvent => {
|
||||
store.listen(storeEvent, eventHandler);
|
||||
});
|
||||
|
||||
if (this.eventHandlersByStore.get(store) == null) {
|
||||
this.eventHandlersByStore.set(store, new Set());
|
||||
}
|
||||
|
||||
this.eventHandlersByStore.get(store).add({
|
||||
events,
|
||||
eventHandler,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.eventHandlersByStore.forEach((listenerDescriptors, store) => {
|
||||
listenerDescriptors.forEach(({events, eventHandler}) => {
|
||||
events.forEach(event => {
|
||||
store.unlisten(event, eventHandler);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.eventHandlersByStore.clear();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Component {...this.props} {...this.state} />;
|
||||
}
|
||||
}
|
||||
|
||||
return props => <ConnectedComponent {...props} />;
|
||||
};
|
||||
|
||||
export default connectStores;
|
||||
112
client/src/javascript/util/connectStores.tsx
Normal file
112
client/src/javascript/util/connectStores.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
|
||||
import EventTypes from '../constants/EventTypes';
|
||||
|
||||
interface GenericStore {
|
||||
listen: (event: keyof typeof EventTypes, eventHandler: (payload: unknown) => void) => void;
|
||||
unlisten: (event: keyof typeof EventTypes, eventHandler: (payload: unknown) => void) => void;
|
||||
}
|
||||
|
||||
export interface EventListenerDescriptor<DerivedState, WrappedComponentProps = {}> {
|
||||
store: GenericStore;
|
||||
event: (keyof typeof EventTypes) | (keyof typeof EventTypes)[];
|
||||
getValue: (
|
||||
props: {
|
||||
payload: unknown;
|
||||
props: WrappedComponentProps;
|
||||
state: DerivedState;
|
||||
store: GenericStore;
|
||||
},
|
||||
) => Partial<DerivedState>;
|
||||
}
|
||||
|
||||
const connectStores = <DerivedState extends object, WrappedComponentProps extends object = {}>(
|
||||
InputComponent: React.JSXElementConstructor<WrappedComponentProps & DerivedState>,
|
||||
getEventListenerDescriptors: (
|
||||
props: WrappedComponentProps,
|
||||
) => EventListenerDescriptor<DerivedState, WrappedComponentProps>[],
|
||||
): ((props: WrappedComponentProps) => React.ReactElement<WrappedComponentProps>) => {
|
||||
class ConnectedComponent extends React.Component<WrappedComponentProps, DerivedState> {
|
||||
private eventHandlersByStore: Map<
|
||||
GenericStore,
|
||||
Set<{events: (keyof typeof EventTypes)[]; eventHandler: (payload: unknown) => void}>
|
||||
> = new Map();
|
||||
|
||||
private constructor(props: WrappedComponentProps) {
|
||||
super(props);
|
||||
this.state = getEventListenerDescriptors(props).reduce(
|
||||
(state, eventListenerDescriptor): DerivedState => {
|
||||
const {store, getValue} = eventListenerDescriptor;
|
||||
return {
|
||||
...state,
|
||||
...getValue({state, props, store, payload: null}),
|
||||
};
|
||||
},
|
||||
({} as unknown) as DerivedState,
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const eventListenerDescriptors = getEventListenerDescriptors(this.props);
|
||||
|
||||
eventListenerDescriptors.forEach(
|
||||
(eventListenerDescriptor): void => {
|
||||
const {store, event, getValue} = eventListenerDescriptor;
|
||||
const eventHandler = (payload: unknown): void =>
|
||||
this.setState(
|
||||
(state: DerivedState, props: WrappedComponentProps): DerivedState =>
|
||||
getValue({state, props, store, payload}) as DerivedState,
|
||||
);
|
||||
const events = Array.isArray(event) ? event : [event];
|
||||
|
||||
events.forEach(
|
||||
(storeEvent): void => {
|
||||
store.listen(storeEvent, eventHandler);
|
||||
},
|
||||
);
|
||||
|
||||
if (this.eventHandlersByStore.get(store) == null) {
|
||||
const newSet: Set<{
|
||||
events: (keyof typeof EventTypes)[];
|
||||
eventHandler: (payload: unknown) => void;
|
||||
}> = new Set();
|
||||
this.eventHandlersByStore.set(store, newSet);
|
||||
}
|
||||
|
||||
const eventHandlersForStore = this.eventHandlersByStore.get(store);
|
||||
if (eventHandlersForStore != null) {
|
||||
eventHandlersForStore.add({events, eventHandler});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.eventHandlersByStore.forEach(
|
||||
(listenerDescriptors, store): void => {
|
||||
listenerDescriptors.forEach(
|
||||
({events, eventHandler}): void => {
|
||||
events.forEach(
|
||||
(event): void => {
|
||||
store.unlisten(event, eventHandler);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
this.eventHandlersByStore.clear();
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return <InputComponent {...this.props as WrappedComponentProps} {...this.state as DerivedState} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (props: WrappedComponentProps): React.ReactElement<WrappedComponentProps> => {
|
||||
return <ConnectedComponent {...props} />;
|
||||
};
|
||||
};
|
||||
|
||||
export default connectStores;
|
||||
@@ -1,4 +1,4 @@
|
||||
import torrentStatusMap from 'universally-shared-code/constants/torrentStatusMap';
|
||||
import torrentStatusMap from '@shared/constants/torrentStatusMap';
|
||||
|
||||
export function filterTorrents(torrentList, opts) {
|
||||
const {type, filter} = opts;
|
||||
|
||||
8
client/src/javascript/util/history.js
Normal file
8
client/src/javascript/util/history.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import {createBrowserHistory} from 'history';
|
||||
|
||||
import stringUtil from '@shared/util/stringUtil';
|
||||
import ConfigStore from '../stores/ConfigStore';
|
||||
|
||||
const history = createBrowserHistory({basename: stringUtil.withoutTrailingSlash(ConfigStore.getBaseURI())});
|
||||
|
||||
export default history;
|
||||
@@ -1,5 +1,5 @@
|
||||
import classnames from 'classnames';
|
||||
import torrentStatusMap from 'universally-shared-code/constants/torrentStatusMap';
|
||||
import torrentStatusMap from '@shared/constants/torrentStatusMap';
|
||||
|
||||
export function torrentStatusClasses(torrent, ...classes) {
|
||||
return classnames(classes, {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import torrentStatusMap from 'universally-shared-code/constants/torrentStatusMap';
|
||||
import torrentStatusMap from '@shared/constants/torrentStatusMap';
|
||||
|
||||
import ErrorIcon from '../components/icons/ErrorIcon';
|
||||
import SpinnerIcon from '../components/icons/SpinnerIcon';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import regEx from 'universally-shared-code/util/regEx';
|
||||
import regEx from '@shared/util/regEx';
|
||||
|
||||
export const isNotEmpty = value => {
|
||||
return value != null && value !== '';
|
||||
|
||||
527
client/src/sass/style.scss.d.ts
vendored
Normal file
527
client/src/sass/style.scss.d.ts
vendored
Normal file
@@ -0,0 +1,527 @@
|
||||
declare const styles: {
|
||||
readonly button: string;
|
||||
readonly 'button--primary': string;
|
||||
readonly inverse: string;
|
||||
readonly 'button--secondary': string;
|
||||
readonly 'button--tertiary': string;
|
||||
readonly 'button--quaternary': string;
|
||||
readonly 'button--is-disabled': string;
|
||||
readonly button__content: string;
|
||||
readonly icon: string;
|
||||
readonly 'icon--loading': string;
|
||||
readonly 'button--is-loading': string;
|
||||
readonly 'context-menu': string;
|
||||
readonly 'context-menu--enter': string;
|
||||
readonly 'context-menu__items': string;
|
||||
readonly 'context-menu__items--is-up': string;
|
||||
readonly 'context-menu--enter--active': string;
|
||||
readonly 'context-menu--exit': string;
|
||||
readonly 'context-menu--exit--active': string;
|
||||
readonly 'context-menu__items__padding-surrogate': string;
|
||||
readonly 'context-menu__items--match-trigger-width': string;
|
||||
readonly 'context-menu__items--no-padding': string;
|
||||
readonly 'context-menu__items--no-scrolling': string;
|
||||
readonly container: string;
|
||||
readonly error: string;
|
||||
readonly 'error--is-loading': string;
|
||||
readonly input: string;
|
||||
readonly form__row: string;
|
||||
readonly 'form__row--no-margin': string;
|
||||
readonly 'form__row--group': string;
|
||||
readonly 'form__row--align--start': string;
|
||||
readonly 'form__row--align--center': string;
|
||||
readonly 'form__row--align--end': string;
|
||||
readonly 'form__row--justify--start': string;
|
||||
readonly 'form__row--justify--center': string;
|
||||
readonly 'form__row--justify--end': string;
|
||||
readonly form__row__item: string;
|
||||
readonly 'is-first': string;
|
||||
readonly 'is-last': string;
|
||||
readonly 'form__row__item--grow': string;
|
||||
readonly 'form__row__item--shrink': string;
|
||||
readonly 'form__row__item--one-eighth': string;
|
||||
readonly 'form__row__item--one-quarter': string;
|
||||
readonly 'form__row__item--three-eighths': string;
|
||||
readonly 'form__row__item--one-half': string;
|
||||
readonly 'form__row__item--five-eighths': string;
|
||||
readonly 'form__row__item--three-quarters': string;
|
||||
readonly 'form__row__item--seven-eighths': string;
|
||||
readonly checkbox: string;
|
||||
readonly form__element__wrapper: string;
|
||||
readonly radio: string;
|
||||
readonly form__element__label: string;
|
||||
readonly 'form__element--label-offset': string;
|
||||
readonly 'form__element--match-textbox-height': string;
|
||||
readonly 'form__element--has-addon--placed-before': string;
|
||||
readonly 'form__element--has-addon--count-2': string;
|
||||
readonly 'form__element--has-addon--placed-after': string;
|
||||
readonly form__element__addon: string;
|
||||
readonly form__element: string;
|
||||
readonly 'icon--stroke': string;
|
||||
readonly 'form__element__addon--placed-before': string;
|
||||
readonly 'form__element__addon--index-2': string;
|
||||
readonly 'form__element__addon--placed-after': string;
|
||||
readonly 'form__element__addon--is-icon': string;
|
||||
readonly 'form__element__addon--is-interactive': string;
|
||||
readonly 'icon--small': string;
|
||||
readonly 'icon--large': string;
|
||||
readonly icon__element: string;
|
||||
readonly 'icon--loading--ring': string;
|
||||
readonly 'icon__ring-slice': string;
|
||||
readonly 'input--hidden': string;
|
||||
readonly 'toggle-input': string;
|
||||
readonly 'toggle-input__indicator': string;
|
||||
readonly 'toggle-input__indicator__icon': string;
|
||||
readonly 'toggle-input--is-active': string;
|
||||
readonly 'toggle-input__element': string;
|
||||
readonly overlay: string;
|
||||
readonly 'overlay--transparent': string;
|
||||
readonly 'overlay--no-interaction': string;
|
||||
readonly panel: string;
|
||||
readonly 'panel--medium': string;
|
||||
readonly panel__content: string;
|
||||
readonly panel__header: string;
|
||||
readonly panel__footer: string;
|
||||
readonly 'panel__footer--has-border': string;
|
||||
readonly 'panel--large': string;
|
||||
readonly 'panel__header--has-border': string;
|
||||
readonly h1: string;
|
||||
readonly h2: string;
|
||||
readonly h3: string;
|
||||
readonly h4: string;
|
||||
readonly h5: string;
|
||||
readonly h6: string;
|
||||
readonly 'panel__content--has-border--top': string;
|
||||
readonly 'panel__content--has-border--bottom': string;
|
||||
readonly 'panel--light': string;
|
||||
readonly portal: string;
|
||||
readonly section: string;
|
||||
readonly section__heading: string;
|
||||
readonly padded: string;
|
||||
readonly select: string;
|
||||
readonly select__button: string;
|
||||
readonly select__indicator: string;
|
||||
readonly select__item: string;
|
||||
readonly 'select__item--is-selected': string;
|
||||
readonly 'select--is-open': string;
|
||||
readonly app: string;
|
||||
readonly application: string;
|
||||
readonly application__view: string;
|
||||
readonly application__content: string;
|
||||
readonly application__panel: string;
|
||||
readonly 'application__panel--torrent-list': string;
|
||||
readonly 'is-open': string;
|
||||
readonly 'application__panel--torrent-details': string;
|
||||
readonly unit: string;
|
||||
readonly 'text-overflow': string;
|
||||
readonly 'copy--lead': string;
|
||||
readonly 'action-bar': string;
|
||||
readonly 'action-bar--is-condensed': string;
|
||||
readonly 'action-bar__item': string;
|
||||
readonly 'action-bar__item--sort-torrents': string;
|
||||
readonly dropdown: string;
|
||||
readonly dropdown__content: string;
|
||||
readonly 'action-bar__item--torrent-operations': string;
|
||||
readonly 'action-bar__group': string;
|
||||
readonly 'action-bar__group--has-divider': string;
|
||||
readonly actions: string;
|
||||
readonly action: string;
|
||||
readonly action__label: string;
|
||||
readonly 'application__loading-overlay': string;
|
||||
readonly 'application__loading-overlay-leave': string;
|
||||
readonly 'application__loading-overlay-leave-active': string;
|
||||
readonly 'application__entry-barrier': string;
|
||||
readonly alerts__list: string;
|
||||
readonly 'alerts__list-leave': string;
|
||||
readonly 'alerts__list-leave-active': string;
|
||||
readonly 'alerts__list-enter': string;
|
||||
readonly 'alerts__list-enter-active': string;
|
||||
readonly alert: string;
|
||||
readonly 'is-success': string;
|
||||
readonly alert__count: string;
|
||||
readonly 'is-error': string;
|
||||
readonly alert__content: string;
|
||||
readonly 'attached-panel': string;
|
||||
readonly 'attached-panel__content': string;
|
||||
readonly 'attached-panel__wrapper': string;
|
||||
readonly 'attached-panel-enter': string;
|
||||
readonly 'attached-panel-enter-active': string;
|
||||
readonly 'attached-panel-leave': string;
|
||||
readonly 'attached-panel-leave-active': string;
|
||||
readonly 'textbox--has-attached-panel--is-open': string;
|
||||
readonly badge: string;
|
||||
readonly menu: string;
|
||||
readonly menu__item: string;
|
||||
readonly 'menu__item__label--primary': string;
|
||||
readonly 'has-action': string;
|
||||
readonly menu__item__label: string;
|
||||
readonly menu__item__label__action: string;
|
||||
readonly 'menu__item__label--secondary': string;
|
||||
readonly 'menu__item--separator': string;
|
||||
readonly 'is-selectable': string;
|
||||
readonly 'is-selected': string;
|
||||
readonly 'menu-enter': string;
|
||||
readonly 'fade-in': string;
|
||||
readonly 'menu-leave': string;
|
||||
readonly 'fade-out': string;
|
||||
readonly 'client-stats': string;
|
||||
readonly 'client-stats__rates': string;
|
||||
readonly 'client-stats__rate': string;
|
||||
readonly 'client-stats__rate--download': string;
|
||||
readonly 'client-stats__rate__data--limit': string;
|
||||
readonly 'client-stats__rate--upload': string;
|
||||
readonly 'client-stats__rate__icon': string;
|
||||
readonly 'client-stats__rate__data--secondary': string;
|
||||
readonly 'client-stats__rate__data--timestamp': string;
|
||||
readonly 'client-stats__rate__data--primary': string;
|
||||
readonly 'is-visible': string;
|
||||
readonly 'client-stats__graph': string;
|
||||
readonly 'loading-indicator': string;
|
||||
readonly 'graph__gradient--bottom': string;
|
||||
readonly 'graph__gradient--bottom--upload': string;
|
||||
readonly 'graph__gradient--bottom--download': string;
|
||||
readonly 'graph__gradient--top': string;
|
||||
readonly 'graph__gradient--top--upload': string;
|
||||
readonly 'graph__gradient--top--download': string;
|
||||
readonly graph__area: string;
|
||||
readonly graph__line: string;
|
||||
readonly 'graph__line--upload': string;
|
||||
readonly 'graph__line--download': string;
|
||||
readonly graph__circle: string;
|
||||
readonly 'graph__circle--upload': string;
|
||||
readonly 'graph__circle--download': string;
|
||||
readonly 'connection-status': string;
|
||||
readonly 'connection-status__icon': string;
|
||||
readonly 'connection-status__copy': string;
|
||||
readonly 'dependency-list': string;
|
||||
readonly 'dependency-list__dependency': string;
|
||||
readonly 'dependency-list__dependency__icon': string;
|
||||
readonly 'dependency-list__dependency--satisfied': string;
|
||||
readonly 'directory-tree': string;
|
||||
readonly 'directory-tree__wrapper': string;
|
||||
readonly 'directory-tree__wrapper--toolbar-visible': string;
|
||||
readonly 'directory-tree__selection-toolbar': string;
|
||||
readonly 'modal__content--nested-scroll__content': string;
|
||||
readonly 'directory-tree__selection-toolbar__item': string;
|
||||
readonly 'directory-tree__selection-toolbar__item-count': string;
|
||||
readonly 'button--download': string;
|
||||
readonly dropdown__items: string;
|
||||
readonly dropdown__trigger: string;
|
||||
readonly dropdown__button: string;
|
||||
readonly dropdown__value: string;
|
||||
readonly 'directory-tree__parent-directory': string;
|
||||
readonly 'icon--disk': string;
|
||||
readonly 'directory-tree__checkbox': string;
|
||||
readonly checkbox__decoy: string;
|
||||
readonly 'directory-tree__tree': string;
|
||||
readonly 'directory-tree__node': string;
|
||||
readonly file__label: string;
|
||||
readonly 'directory-tree__checkbox__item--icon': string;
|
||||
readonly 'directory-tree__checkbox__item--checkbox': string;
|
||||
readonly 'directory-tree__node--selected': string;
|
||||
readonly 'directory-tree__node--directory': string;
|
||||
readonly 'directory-tree__node--group': string;
|
||||
readonly 'directory-tree__node--file-list': string;
|
||||
readonly file: string;
|
||||
readonly 'icon--file': string;
|
||||
readonly file__detail: string;
|
||||
readonly 'file__detail--secondary': string;
|
||||
readonly 'file__detail--priority': string;
|
||||
readonly 'directory-tree__checkbox__item': string;
|
||||
readonly 'icon--folder': string;
|
||||
readonly file__checkbox: string;
|
||||
readonly file__name: string;
|
||||
readonly 'dropdown--direction-up': string;
|
||||
readonly dropdown__content__container: string;
|
||||
readonly dropdown__label: string;
|
||||
readonly dropdown__header: string;
|
||||
readonly dropdown__item: string;
|
||||
readonly dropdown__list: string;
|
||||
readonly 'dropdown--align-right': string;
|
||||
readonly 'dropdown--match-button-width': string;
|
||||
readonly 'dropdown--width-small': string;
|
||||
readonly 'is-expanded': string;
|
||||
readonly dropzone: string;
|
||||
readonly dropzone__icon: string;
|
||||
readonly 'dropzone--is-dragging': string;
|
||||
readonly 'icon--files': string;
|
||||
readonly 'icon--files__file--right': string;
|
||||
readonly 'icon--files__file--left': string;
|
||||
readonly dropzone__copy: string;
|
||||
readonly 'dropzone__browse-button': string;
|
||||
readonly 'dropzone__selected-files': string;
|
||||
readonly 'interactive-list': string;
|
||||
readonly 'dropzone__selected-files__file': string;
|
||||
readonly 'dropzone--with-overlay': string;
|
||||
readonly dropzone__overlay: string;
|
||||
readonly 'duration--segment': string;
|
||||
readonly 'filesystem__directory-list': string;
|
||||
readonly 'filesystem__directory-list__item': string;
|
||||
readonly 'filesystem__directory-list__item--parent': string;
|
||||
readonly 'filesystem__directory-list__item--directory': string;
|
||||
readonly 'filesystem__directory-list__item--file': string;
|
||||
readonly 'floating-action__button': string;
|
||||
readonly 'floating-action__button--search': string;
|
||||
readonly 'floating-action__group--on-textbox': string;
|
||||
readonly 'icon--eta': string;
|
||||
readonly icon__ring: string;
|
||||
readonly 'icon--information__fill': string;
|
||||
readonly 'icon--information__ring': string;
|
||||
readonly 'icon--limits': string;
|
||||
readonly 'limits__bars--top': string;
|
||||
readonly 'limits__bars--middle': string;
|
||||
readonly 'limits__bars--bottom': string;
|
||||
readonly 'icon--loading-indicator': string;
|
||||
readonly 'loading-indicator--dots__dot': string;
|
||||
readonly 'loading-indicator-dots-pulse': string;
|
||||
readonly 'loading-indicator--dots__dot--center': string;
|
||||
readonly 'loading-indicator--dots__dot--right': string;
|
||||
readonly 'icon--spinner': string;
|
||||
readonly 'spinner-spin': string;
|
||||
readonly 'interactive-list--loading': string;
|
||||
readonly 'interactive-list__item': string;
|
||||
readonly 'interactive-list__icon--action': string;
|
||||
readonly 'interactive-list__icon--action--warning': string;
|
||||
readonly 'interactive-list__item--stacked-content': string;
|
||||
readonly 'interactive-list__label': string;
|
||||
readonly 'interactive-list__label__text': string;
|
||||
readonly 'interactive-list__label__tag': string;
|
||||
readonly tag: string;
|
||||
readonly 'interactive-list__loading-indicator': string;
|
||||
readonly 'interactive-list__loading-indicator-enter': string;
|
||||
readonly 'interactive-list__loading-indicator-enter-active': string;
|
||||
readonly 'interactive-list__loading-indicator-leave': string;
|
||||
readonly 'interactive-list__loading-indicator-leave-active': string;
|
||||
readonly 'interactive-list__icon': string;
|
||||
readonly 'icon--close': string;
|
||||
readonly 'interactive-list__detail--primary': string;
|
||||
readonly 'interactive-list__detail--tertiary': string;
|
||||
readonly 'interactive-list__detail-list': string;
|
||||
readonly 'interactive-list__detail-list__item': string;
|
||||
readonly 'interactive-list__detail-list__item--overflow': string;
|
||||
readonly 'is-inverse': string;
|
||||
readonly 'loading-indicator__bar': string;
|
||||
readonly 'loading-indicator-swipe': string;
|
||||
readonly 'loading-indicator__bar--1': string;
|
||||
readonly 'loading-indicator__bar--2': string;
|
||||
readonly 'loading-indicator__bar--3': string;
|
||||
readonly mediainfo: string;
|
||||
readonly mediainfo__toolbar: string;
|
||||
readonly tooltip__wrapper: string;
|
||||
readonly 'mediainfo__copy-button': string;
|
||||
readonly mediainfo__output: string;
|
||||
readonly modal: string;
|
||||
readonly modal__overlay: string;
|
||||
readonly 'modal--align-center': string;
|
||||
readonly modal__tabs: string;
|
||||
readonly modal__tab: string;
|
||||
readonly 'is-active': string;
|
||||
readonly modal__header: string;
|
||||
readonly 'modal--tabs-in-header': string;
|
||||
readonly 'has-tabs': string;
|
||||
readonly modal__content: string;
|
||||
readonly modal__content__wrapper: string;
|
||||
readonly 'modal__content--nested-scroll': string;
|
||||
readonly 'modal__content--nested-scroll__header': string;
|
||||
readonly modal__body: string;
|
||||
readonly 'modal--tabs-in-body': string;
|
||||
readonly modal__footer: string;
|
||||
readonly modal__actions: string;
|
||||
readonly 'modal__button-group': string;
|
||||
readonly 'modal__animation-enter': string;
|
||||
readonly 'modal__animation-enter-active': string;
|
||||
readonly 'modal__animation-leave': string;
|
||||
readonly 'modal__animation-leave-active': string;
|
||||
readonly 'modal--vertical': string;
|
||||
readonly 'modal--size-large': string;
|
||||
readonly form__section__heading: string;
|
||||
readonly 'form__section__sub-heading': string;
|
||||
readonly 'notifications--empty': string;
|
||||
readonly 'notifications--is-loading': string;
|
||||
readonly notifications__list: string;
|
||||
readonly 'notifications__loading-indicator': string;
|
||||
readonly notifications__badge: string;
|
||||
readonly notifications__list__item: string;
|
||||
readonly notifications__toolbar: string;
|
||||
readonly 'toolbar__item--button': string;
|
||||
readonly notification__heading: string;
|
||||
readonly notification__category: string;
|
||||
readonly 'notification__message__sub-heading': string;
|
||||
readonly 'peers-list__flag': string;
|
||||
readonly 'peers-list__flag__image': string;
|
||||
readonly 'peers-list__flag__text': string;
|
||||
readonly 'peers-list__encryption': string;
|
||||
readonly 'priority-meter': string;
|
||||
readonly 'priority-meter__wrapper': string;
|
||||
readonly 'priority-meter--max-2': string;
|
||||
readonly 'priority-meter--level-0': string;
|
||||
readonly 'priority-meter--level-1': string;
|
||||
readonly 'priority-meter--level-2': string;
|
||||
readonly 'priority-meter--max-3': string;
|
||||
readonly 'priority-meter--level-3': string;
|
||||
readonly 'progress-bar': string;
|
||||
readonly 'progress-bar__icon': string;
|
||||
readonly 'torrent--is-seeding': string;
|
||||
readonly 'torrent--is-stopped': string;
|
||||
readonly 'torrent--has-error': string;
|
||||
readonly 'torrent--is-checking': string;
|
||||
readonly 'torrent--is-selected': string;
|
||||
readonly 'progress-bar__fill': string;
|
||||
readonly 'progress-bar__fill__wrapper': string;
|
||||
readonly 'candy-stripe': string;
|
||||
readonly scrollbars__thumb: string;
|
||||
readonly 'scrollbars__thumb--surrogate': string;
|
||||
readonly 'is-inverted': string;
|
||||
readonly scrollbars: string;
|
||||
readonly search: string;
|
||||
readonly textbox: string;
|
||||
readonly 'is-in-use': string;
|
||||
readonly application__sidebar: string;
|
||||
readonly 'sidebar__icon-button': string;
|
||||
readonly 'sidebar__icon-button--interactive': string;
|
||||
readonly 'sidebar__action--last': string;
|
||||
readonly sidebar__actions: string;
|
||||
readonly sidebar__diskusage: string;
|
||||
readonly 'diskuage__size-avail': string;
|
||||
readonly 'diskusage__text-row': string;
|
||||
readonly 'diskusage__details-list': string;
|
||||
readonly 'diskusage__details-list__item': string;
|
||||
readonly 'diskusage__details-list__label': string;
|
||||
readonly 'dropdown--speed-limits': string;
|
||||
readonly 'sidebar-filter': string;
|
||||
readonly 'sidebar-filter__item': string;
|
||||
readonly 'sidebar-filter__item--heading': string;
|
||||
readonly 'sort-dropdown__item': string;
|
||||
readonly 'sort-dropdown__indicator': string;
|
||||
readonly 'sort-dropdown__indicator--asc': string;
|
||||
readonly 'sortable-list': string;
|
||||
readonly 'sortable-list__item': string;
|
||||
readonly 'sortable-list__item--is-dragging': string;
|
||||
readonly 'sortable-list__item--is-locked': string;
|
||||
readonly 'sortable-list__item--is-preview': string;
|
||||
readonly 'icon--error': string;
|
||||
readonly 'icon--lock': string;
|
||||
readonly 'sortable-list__content': string;
|
||||
readonly 'sortable-list__content__wrapper': string;
|
||||
readonly 'sortable-list__content--primary': string;
|
||||
readonly 'sortable-list__content--secondary': string;
|
||||
readonly 'sortable-list__content--secondary__copy': string;
|
||||
readonly 'table__row--heading': string;
|
||||
readonly table__heading: string;
|
||||
readonly 'table__heading--is-sorted': string;
|
||||
readonly 'table__heading--direction--asc': string;
|
||||
readonly 'table__heading--fill': string;
|
||||
readonly table__heading__handle: string;
|
||||
readonly table__heading__label: string;
|
||||
readonly 'table__heading__resize-line': string;
|
||||
readonly 'table__heading__column-fill': string;
|
||||
readonly table__cell: string;
|
||||
readonly 'textbox-repeater': string;
|
||||
readonly toolbar: string;
|
||||
readonly 'toolbar--dark': string;
|
||||
readonly 'toolbar--bottom': string;
|
||||
readonly 'toolbar--top': string;
|
||||
readonly toolbar__item: string;
|
||||
readonly 'is-disabled': string;
|
||||
readonly 'toolbar__item--centered': string;
|
||||
readonly 'toolbar__item--label': string;
|
||||
readonly tooltip: string;
|
||||
readonly tooltip__content: string;
|
||||
readonly 'tooltip__content--no-padding': string;
|
||||
readonly 'tooltip__content--padding-surrogate': string;
|
||||
readonly 'is-interactive': string;
|
||||
readonly 'tooltip--no-wrap': string;
|
||||
readonly 'tooltip--position--bottom': string;
|
||||
readonly 'tooltip--anchor--center': string;
|
||||
readonly 'tooltip--position--top': string;
|
||||
readonly 'tooltip--anchor--start': string;
|
||||
readonly 'tooltip--align--center': string;
|
||||
readonly 'tooltip--anchor--end': string;
|
||||
readonly 'tooltip--position--left': string;
|
||||
readonly 'tooltip--position--right': string;
|
||||
readonly 'tooltip--is-error': string;
|
||||
readonly 'torrent-details__heading': string;
|
||||
readonly 'torrent-details__sub-heading': string;
|
||||
readonly 'torrent-details__sub-heading__secondary': string;
|
||||
readonly 'torrent-details__sub-heading__tertiary': string;
|
||||
readonly 'torrent-details__header': string;
|
||||
readonly 'is-completed': string;
|
||||
readonly 'is-stopped': string;
|
||||
readonly 'torrent-details__action': string;
|
||||
readonly 'torrent-details__table': string;
|
||||
readonly 'torrent-details__table__heading--primary': string;
|
||||
readonly 'torrent-details__table__heading--secondary': string;
|
||||
readonly 'torrent-details__table__heading--tertiary': string;
|
||||
readonly 'torrent-details__section': string;
|
||||
readonly 'torrent-details__section__heading': string;
|
||||
readonly 'torrent-details__section__null-data': string;
|
||||
readonly 'torrent-details__section--file-tree': string;
|
||||
readonly 'directory-tree__node--selectable': string;
|
||||
readonly 'file__detail--size': string;
|
||||
readonly 'torrent-details__detail--hash': string;
|
||||
readonly 'torrent-details__detail__value': string;
|
||||
readonly 'torrent-details__detail--tags': string;
|
||||
readonly 'torrent-details__detail': string;
|
||||
readonly 'torrent-details__table__heading': string;
|
||||
readonly 'torrent-details__detail__label': string;
|
||||
readonly 'not-available': string;
|
||||
readonly torrents: string;
|
||||
readonly torrents__alert: string;
|
||||
readonly torrents__alert__wrapper: string;
|
||||
readonly torrents__alert__action: string;
|
||||
readonly torrent__list: string;
|
||||
readonly 'torrent__list__scrollbars--horizontal': string;
|
||||
readonly 'torrent__list__scrollbars--vertical': string;
|
||||
readonly torrent__list__wrapper: string;
|
||||
readonly 'torrent__list--loading-enter': string;
|
||||
readonly 'torrent__list--loading-enter-active': string;
|
||||
readonly 'torrent__list--loading-leave': string;
|
||||
readonly 'torrent__list--loading-leave-active': string;
|
||||
readonly 'torrent__list--empty': string;
|
||||
readonly 'view--torrent-list': string;
|
||||
readonly torrent: string;
|
||||
readonly 'torrent__more-info': string;
|
||||
readonly torrent__detail: string;
|
||||
readonly 'torrent__detail--name': string;
|
||||
readonly 'torrent__detail--tags': string;
|
||||
readonly torrent__tag: string;
|
||||
readonly 'torrent__detail__icon--checkmark': string;
|
||||
readonly 'torrent__details__section--secondary': string;
|
||||
readonly 'torrent__details__section--tertiary': string;
|
||||
readonly 'torrent--is-downloading--actively': string;
|
||||
readonly 'torrent__detail--downRate': string;
|
||||
readonly 'torrent-details__sub-heading__tertiary--download': string;
|
||||
readonly 'torrent--is-uploading--actively': string;
|
||||
readonly 'torrent__detail--upRate': string;
|
||||
readonly 'torrent-details__sub-heading__tertiary--upload': string;
|
||||
readonly 'torrent--is-expanded': string;
|
||||
readonly 'torrent__detail--eta': string;
|
||||
readonly torrent__details__section: string;
|
||||
readonly 'torrent__details__section--quaternary': string;
|
||||
readonly 'torrent__details__section--primary': string;
|
||||
readonly torrent__details__section__wrapper: string;
|
||||
readonly 'torrent__detail--percentComplete': string;
|
||||
readonly 'torrent__detail--upTotal': string;
|
||||
readonly 'torrent__detail--sizeBytes': string;
|
||||
readonly 'torrent__detail--freeDiskSpace': string;
|
||||
readonly 'torrent__detail--added': string;
|
||||
readonly 'torrent__detail--creationDate': string;
|
||||
readonly 'torrent__detail--isPrivate': string;
|
||||
readonly 'torrent__detail--peers': string;
|
||||
readonly 'torrent__detail--ratio': string;
|
||||
readonly 'torrent__detail--seeds': string;
|
||||
readonly torrent__tags: string;
|
||||
readonly 'torrent--is-condensed': string;
|
||||
readonly 'transfer-data--download': string;
|
||||
readonly 'transfer-data--upload': string;
|
||||
readonly 'application__view--auth-form': string;
|
||||
readonly 'form--authentication': string;
|
||||
readonly form__wrapper: string;
|
||||
readonly form__header: string;
|
||||
readonly form__label: string;
|
||||
readonly 'form__row--error': string;
|
||||
readonly form__actions: string;
|
||||
readonly 'feed-list__feed-label': string;
|
||||
readonly rotateAroundMidpoint: string;
|
||||
};
|
||||
export = styles;
|
||||
4
custom.d.ts
vendored
Normal file
4
custom.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
2621
package-lock.json
generated
2621
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@@ -10,12 +10,13 @@
|
||||
"scripts": {
|
||||
"build": "node client/scripts/build.js",
|
||||
"build-assets": "UPDATED_SCRIPT=build npm run deprecated-warning && npm run build",
|
||||
"build-docs": "./node_modules/.bin/jsdoc -c ./.jsdoc.json",
|
||||
"build-docs": "jsdoc -c ./.jsdoc.json",
|
||||
"deprecated-warning": "node client/scripts/deprecated-warning.js && sleep 10",
|
||||
"format-source": "node scripts/prettier.js format",
|
||||
"format-source": "node scripts/prettier.js formatSource",
|
||||
"check-source-formatting": "node scripts/prettier.js check",
|
||||
"lint": "NODE_ENV=development eslint --max-warnings 0 .",
|
||||
"start": "node --use_strict server/bin/start.js",
|
||||
"check-types": "tsc",
|
||||
"lint": "NODE_ENV=development eslint --max-warnings 0 . --ext .js --ext .jsx --ext .ts --ext .tsx",
|
||||
"start": "node --use_strict server/bin/start.js",
|
||||
"start:development": "UPDATED_SCRIPT=start:development:server npm run deprecated-warning && npm run start:development:server",
|
||||
"start:development:client": "node client/scripts/start.js",
|
||||
"start:development:server": "NODE_ENV=development nodemon server/bin/start.js",
|
||||
@@ -28,10 +29,12 @@
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"argon2": "^0.19.3",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/react-intl": "^2.3.18",
|
||||
"@typescript-eslint/parser": "^1.11.0",
|
||||
"argon2": "^0.24.1",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"axios": "^0.18.0",
|
||||
"babel-cli": "^6.26.0",
|
||||
"axios": "^0.19.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"body-parser": "^1.18.3",
|
||||
@@ -41,14 +44,13 @@
|
||||
"clipboard": "^2.0.4",
|
||||
"compression": "^1.7.3",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"css-loader": "^2.1.1",
|
||||
"css-loader": "^3.0.0",
|
||||
"d3": "^3.5.17",
|
||||
"debug": "^3.2.6",
|
||||
"deep-equal": "^1.0.1",
|
||||
"express": "^4.16.4",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"feedsub": "^0.6.0",
|
||||
"file-loader": "1.1.11",
|
||||
"file-loader": "^4.0.0",
|
||||
"flood-ui-kit": "^0.1.8",
|
||||
"flux": "^3.1.3",
|
||||
"fs-extra": "^5.0.0",
|
||||
@@ -66,9 +68,9 @@
|
||||
"node-sass": "^4.11.0",
|
||||
"object-assign": "4.1.1",
|
||||
"ospath": "^1.2.2",
|
||||
"pascal-case": "^2.0.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"postcss-flexbugs-fixes": "^3.3.0",
|
||||
"postcss-loader": "2.1.3",
|
||||
"promise": "^8.0.2",
|
||||
"pug": "^2.0.4",
|
||||
@@ -82,39 +84,50 @@
|
||||
"react-dropzone": "^4.3.0",
|
||||
"react-intl": "^2.7.2",
|
||||
"react-markdown": "^3.6.0",
|
||||
"react-router": "^3.2.1",
|
||||
"react-router": "^4.3.1",
|
||||
"ress": "^1.2.2",
|
||||
"rimraf": "^2.6.2",
|
||||
"run-series": "^1.1.6",
|
||||
"sass-loader": "^6.0.7",
|
||||
"sass-loader": "^7.1.0",
|
||||
"saxen": "8.1.x",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"spdy": "^3.4.7",
|
||||
"style-loader": "0.20.3",
|
||||
"svg-sprite-loader": "^4.1.6",
|
||||
"tar-stream": "^1.6.2",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"url-loader": "1.0.1",
|
||||
"typed-css-modules": "^0.5.1",
|
||||
"url-loader": "^2.0.1",
|
||||
"xmlrpc": "^1.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/keymirror": "^0.1.1",
|
||||
"@types/node": "^11.11.0",
|
||||
"@types/react": "^16.8.7",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"@types/react-transition-group": "^2.0.16",
|
||||
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||
"babel-jest": "22.4.3",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-config-prettier": "^4.2.0",
|
||||
"eslint-config-react-app": "^2.1.0",
|
||||
"eslint-import-resolver-webpack": "^0.11.1",
|
||||
"eslint-loader": "2.0.0",
|
||||
"eslint-loader": "^2.1.2",
|
||||
"eslint-plugin-flowtype": "2.46.1",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"glob": "^7.1.4",
|
||||
"jest": "22.4.3",
|
||||
"jsdoc": "~3.5.5",
|
||||
"jsdoc": "^3.6.2",
|
||||
"minami": "^1.2.3",
|
||||
"nodemon": "^1.18.7",
|
||||
"prettier": "1.14.2",
|
||||
"react-dev-utils": "^9.0.4",
|
||||
"typescript": "^3.5.2",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-server": "^3.3.1",
|
||||
|
||||
@@ -4,82 +4,105 @@ const glob = require('glob');
|
||||
const path = require('path');
|
||||
const prettier = require('prettier');
|
||||
|
||||
const filePattern = `{client,scripts,server,shared}${path.sep}!(assets){${path.sep},}{**${path.sep}*,*}.{js,json,md}`;
|
||||
const SOURCE_PATTERN = `{client,scripts,server,shared}${path.sep}!(assets){${path.sep},}{**${
|
||||
path.sep
|
||||
}*,*}.{js,jsx,ts,tsx,json,md}`;
|
||||
|
||||
const iterateOverFiles = ({onFileRead}) =>
|
||||
new Promise((resolve, reject) => {
|
||||
glob(filePattern, (error, files) => {
|
||||
if (error) {
|
||||
reject(Error(error));
|
||||
const readFile = filePath => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(filePath, 'utf8', (error, fileContent) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(
|
||||
Promise.all(
|
||||
files.map(file => {
|
||||
const filePath = path.join(process.cwd(), file);
|
||||
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||
return onFileRead(filePath, fileContents);
|
||||
}),
|
||||
),
|
||||
);
|
||||
resolve(fileContent);
|
||||
});
|
||||
});
|
||||
|
||||
const format = () => {
|
||||
console.log(chalk.reset('Formatting files...'));
|
||||
|
||||
iterateOverFiles({
|
||||
onFileRead: (filePath, fileContents) =>
|
||||
prettier.resolveConfig(filePath).then(
|
||||
options =>
|
||||
new Promise((resolve, reject) => {
|
||||
fs.writeFile(filePath, prettier.format(fileContents, {...options, filepath: filePath}), error => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
),
|
||||
})
|
||||
.then(() => {
|
||||
console.log(chalk.green('Done formatting files.'));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(chalk.red('Error formatting files:\n'), chalk.reset(error));
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
const check = () => {
|
||||
console.log(chalk.reset('Checking code formatting...'));
|
||||
const writeFile = (filePath, fileContent) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(filePath, fileContent, writeFileError => {
|
||||
if (writeFileError) {
|
||||
reject(writeFileError);
|
||||
return;
|
||||
}
|
||||
|
||||
iterateOverFiles({
|
||||
onFileRead: (filePath, fileContents) =>
|
||||
prettier.resolveConfig(filePath).then(options => {
|
||||
const isCompliant = prettier.check(fileContents, {...options, filepath: filePath});
|
||||
resolve(filePath);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const formatFile = async (inputFilePath, outputFilePath) => {
|
||||
const fileContent = await readFile(inputFilePath);
|
||||
const prettierConfig = await prettier.resolveConfig(inputFilePath);
|
||||
const writtenFilePath = await writeFile(
|
||||
outputFilePath,
|
||||
prettier.format(fileContent, {...prettierConfig, filepath: inputFilePath}),
|
||||
);
|
||||
|
||||
return writtenFilePath;
|
||||
};
|
||||
|
||||
const getSourceFilePaths = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(SOURCE_PATTERN, (error, files) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(files.map(filePath => path.join(process.cwd(), filePath)));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const formatSource = async () => {
|
||||
console.log(chalk.reset('Formatting source files...'));
|
||||
|
||||
try {
|
||||
const sourceFilePaths = await getSourceFilePaths();
|
||||
const formattedPaths = await Promise.all(sourceFilePaths.map(filePath => formatFile(filePath, filePath)));
|
||||
|
||||
console.log(chalk.green(`Formatted ${formattedPaths.length} files.`));
|
||||
} catch (error) {
|
||||
console.log(chalk.red('Problem formatting file:\n'), chalk.reset(error));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const check = async () => {
|
||||
console.log(chalk.reset('Validating source file formatting...'));
|
||||
|
||||
try {
|
||||
const sourceFilePaths = await getSourceFilePaths();
|
||||
await Promise.all(
|
||||
sourceFilePaths.map(async filePath => {
|
||||
const fileContent = await readFile(filePath);
|
||||
const prettierConfig = await prettier.resolveConfig(filePath);
|
||||
const isCompliant = prettier.check(fileContent, {...prettierConfig, filepath: filePath});
|
||||
|
||||
if (!isCompliant) {
|
||||
throw filePath;
|
||||
}
|
||||
}),
|
||||
})
|
||||
.then(() => {
|
||||
console.log(chalk.green('Done checking files.'));
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(chalk.red('Unformatted file found:\n'), chalk.reset(error));
|
||||
process.exit(1);
|
||||
});
|
||||
);
|
||||
|
||||
console.log(chalk.green('Finished validating file formatting.'));
|
||||
} catch (error) {
|
||||
console.log(chalk.red('Unformatted file found:\n'), chalk.reset(error));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const commands = {check, format};
|
||||
const commands = {check, formatSource};
|
||||
const desiredCommand = process.argv.slice(2)[0];
|
||||
|
||||
if (commands[desiredCommand] == null) {
|
||||
throw new Error(`No command ${desiredCommand}.`);
|
||||
} else {
|
||||
if (commands[desiredCommand] != null) {
|
||||
commands[desiredCommand]();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatFile,
|
||||
};
|
||||
|
||||
@@ -11,4 +11,6 @@ module.exports = {
|
||||
|
||||
return string;
|
||||
},
|
||||
|
||||
withoutTrailingSlash: input => input.replace(/\/{1,}$/, ''),
|
||||
};
|
||||
|
||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@shared/*": ["shared/*"]
|
||||
}
|
||||
},
|
||||
"include": ["./client/**/*.ts", "./client/**/*.tsx", "./server/**/*.ts", "./server/**/*.tsx", "./custom.d.ts"],
|
||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user