Add Error Modal

This commit is contained in:
Timothy Warren 2018-05-01 16:58:24 -04:00
parent da94a5ac68
commit a1eaf1fba6
2 changed files with 370 additions and 333 deletions

View File

@ -30,7 +30,7 @@ const env = getClientEnvironment(publicUrl);
// Assert this just to be safe. // Assert this just to be safe.
// Development builds of Inferno are slow and not intended for production. // Development builds of Inferno are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') { if (env.stringified['process.env'].NODE_ENV !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.'); throw new Error('Production builds must have NODE_ENV=production.');
} }
// Note: defined here because it will be used more than once. // Note: defined here because it will be used more than once.
@ -41,286 +41,286 @@ const cssFilename = 'static/css/[name].[contenthash:8].css';
// However, our output is structured with css, js and media folders. // 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. // To have this structure working with relative paths, we have to use custom options.
const extractTextPluginOptions = shouldUseRelativeAssetPaths const extractTextPluginOptions = shouldUseRelativeAssetPaths
? // Making sure that the publicPath goes back to to build folder. ? // Making sure that the publicPath goes back to to build folder.
{ publicPath: Array(cssFilename.split('/').length).join('../') } {publicPath: Array(cssFilename.split('/').length).join('../')}
: {}; : {};
// This is the production configuration. // This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle. // It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file. // The development configuration is different and lives in a separate file.
module.exports = { module.exports = {
// Don't attempt to continue if there are any errors. // Don't attempt to continue if there are any errors.
bail: true, bail: true,
// We generate sourcemaps in production. This is slow but gives good results. // We generate sourcemaps in production. This is slow but gives good results.
// You can exclude the *.map files from the build during deployment. // You can exclude the *.map files from the build during deployment.
devtool: shouldUseSourceMap ? 'source-map' : false, devtool: shouldUseSourceMap ? 'source-map' : false,
// In production, we only want to load the polyfills and the app code. // In production, we only want to load the polyfills and the app code.
entry: [require.resolve('./polyfills'), paths.appIndexJs], entry: [require.resolve('./polyfills'), paths.appIndexJs],
output: { output: {
// The build folder. // The build folder.
path: paths.appBuild, path: paths.appBuild,
// Generated JS file names (with nested folders). // Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk. // There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it. // We don't currently advertise code splitting but Webpack supports it.
filename: 'static/js/[name].[chunkhash:8].js', filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js', chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage. // We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: publicPath, publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows) // Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info => devtoolModuleFilenameTemplate: info =>
path path
.relative(paths.appSrc, info.absoluteResourcePath) .relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/'), .replace(/\\/g, '/'),
}, },
resolve: { resolve: {
// This allows you to set a fallback for where Webpack should look for modules. // 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" // We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism. // if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/infernojs/create-inferno-app/issues/253 // https://github.com/infernojs/create-inferno-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat( modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js` // It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean) process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
), ),
// These are the reasonable defaults supported by the Node ecosystem. // These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support // We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see: // some tools, although we do not recommend using it, see:
// https://github.com/infernojs/create-inferno-app/issues/290 // https://github.com/infernojs/create-inferno-app/issues/290
// `web` extension prefixes have been added for better support // `web` extension prefixes have been added for better support
// for React Native Web. // for React Native Web.
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
plugins: [ plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/). // 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. // 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, // 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. // 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. // Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
], ],
}, },
module: { module: {
strictExportPresence: true, strictExportPresence: true,
rules: [ rules: [
// TODO: Disable require.ensure as it's not a standard language feature. // TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-inferno-app/issues/2176. // We are waiting for https://github.com/facebookincubator/create-inferno-app/issues/2176.
// { parser: { requireEnsure: false } }, // { parser: { requireEnsure: false } },
// First, run the linter. // First, run the linter.
// It's important to do this before Babel processes the JS. // It's important to do this before Babel processes the JS.
{ {
test: /\.(js|jsx|mjs)$/, test: /\.(js|jsx|mjs)$/,
enforce: 'pre', enforce: 'pre',
use: [ use: [
{ {
options: { options: {
formatter: eslintFormatter, formatter: eslintFormatter,
eslintPath: require.resolve('eslint'), eslintPath: require.resolve('eslint'),
}, },
loader: require.resolve('eslint-loader'), loader: require.resolve('eslint-loader'),
}, },
], ],
include: paths.appSrc, include: paths.appSrc,
}, },
{ {
// "oneOf" will traverse all following loaders until one will // "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall // match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list. // back to the "file" loader at the end of the loader list.
oneOf: [ oneOf: [
// "url" loader works just like "file" loader but it also embeds // "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests. // assets smaller than specified size as data URLs to avoid requests.
{ {
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'), loader: require.resolve('url-loader'),
options: { options: {
limit: 10000, limit: 10000,
name: 'static/media/[name].[hash:8].[ext]', name: 'static/media/[name].[hash:8].[ext]',
}, },
}, },
// Process JS with Babel. // Process JS with Babel.
{ {
test: /\.(js|jsx|mjs)$/, test: /\.(js|jsx|mjs)$/,
include: paths.appSrc, include: paths.appSrc,
loader: require.resolve('babel-loader'), loader: require.resolve('babel-loader'),
options: { options: {
compact: true, compact: true,
}, },
}, },
// The notation here is somewhat confusing. // The notation here is somewhat confusing.
// "css" loader resolves paths in CSS and adds assets as dependencies. // "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader normally turns CSS into JS modules injecting <style>, // "style" loader normally turns CSS into JS modules injecting <style>,
// but unlike in development configuration, we do something different. // but unlike in development configuration, we do something different.
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
// (second argument), then grabs the result CSS and puts it into a // (second argument), then grabs the result CSS and puts it into a
// separate file in our build process. This way we actually ship // separate file in our build process. This way we actually ship
// a single CSS file in production instead of JS code injecting <style> // a single CSS file in production instead of JS code injecting <style>
// tags. If you use code splitting, however, any async bundles will still // 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 // use the "style" loader inside the async code so CSS from them won't be
// in the main CSS file. // in the main CSS file.
{ {
test: /\.css$/, test: /\.css$/,
loader: ExtractTextPlugin.extract( loader: ExtractTextPlugin.extract(
Object.assign( Object.assign(
{ {
fallback: { fallback: {
loader: require.resolve('style-loader'), loader: require.resolve('style-loader'),
options: { options: {
hmr: false, hmr: false,
}, },
}, },
use: [ use: [
{ {
loader: require.resolve('css-loader'), loader: require.resolve('css-loader'),
options: { options: {
importLoaders: 1, importLoaders: 1,
minimize: true, minimize: true,
sourceMap: shouldUseSourceMap, sourceMap: shouldUseSourceMap,
}, },
}, },
{ {
loader: require.resolve('postcss-loader'), loader: require.resolve('postcss-loader'),
options: { options: {
// Necessary for external CSS imports to work // Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677 // https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss', ident: 'postcss',
plugins: () => [ plugins: () => [
require('postcss-flexbugs-fixes'), require('postcss-flexbugs-fixes'),
], ],
}, },
}, },
], ],
}, },
extractTextPluginOptions extractTextPluginOptions,
) ),
), ),
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`. // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
}, },
// "file" loader makes sure assets end up in the `build` folder. // "file" loader makes sure assets end up in the `build` folder.
// When you `import` an asset, you get its filename. // When you `import` an asset, you get its filename.
// This loader doesn't use a "test" so it will catch all modules // This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders. // that fall through the other loaders.
{ {
loader: require.resolve('file-loader'), loader: require.resolve('file-loader'),
// Exclude `js` files to keep "css" loader working as it injects // Exclude `js` files to keep "css" loader working as it injects
// it's runtime that would otherwise processed through "file" loader. // it's runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed // Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders. // by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: { options: {
name: 'static/media/[name].[hash:8].[ext]', name: 'static/media/[name].[hash:8].[ext]',
}, },
}, },
// ** STOP ** Are you adding a new loader? // ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader. // Make sure to add the new loader(s) before the "file" loader.
], ],
}, },
], ],
}, },
plugins: [ plugins: [
// Makes some environment variables available in index.html. // Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.: // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In production, it will be an empty string unless you specify "homepage" // In production, it will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL. // in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin(env.raw), new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the <script> injected. // Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
template: paths.appHtml, template: paths.appHtml,
minify: { minify: {
removeComments: true, removeComments: true,
collapseWhitespace: true, collapseWhitespace: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
useShortDoctype: true, useShortDoctype: true,
removeEmptyAttributes: true, removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true, removeStyleLinkTypeAttributes: true,
keepClosingSlash: true, keepClosingSlash: true,
minifyJS: true, minifyJS: true,
minifyCSS: true, minifyCSS: true,
minifyURLs: true, minifyURLs: true,
}, },
}), }),
// Makes some environment variables available to the JS code, for example: // Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV was set to production here. // It is absolutely essential that NODE_ENV was set to production here.
// Otherwise Inferno will be compiled in the very slow development mode. // Otherwise Inferno will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified), new webpack.DefinePlugin(env.stringified),
// Minify the code. // Minify the code.
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
compress: { compress: {
screw_ie8: true, // Inferno doesn't support IE8 screw_ie8: true, // Inferno doesn't support IE8
warnings: false, warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code: // Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebookincubator/create-inferno-app/issues/2376 // https://github.com/facebookincubator/create-inferno-app/issues/2376
// Pending further investigation: // Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011 // https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false, comparisons: false,
}, },
mangle: { mangle: {
safari10: true, safari10: true,
}, },
output: { output: {
comments: false, comments: false,
// Turned on because emoji and regex is not minified properly using default // Turned on because emoji and regex is not minified properly using default
// https://github.com/facebookincubator/create-react-app/issues/2488 // https://github.com/facebookincubator/create-react-app/issues/2488
ascii_only: true, ascii_only: true,
}, },
sourceMap: shouldUseSourceMap, sourceMap: shouldUseSourceMap,
}), }),
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
new ExtractTextPlugin({ new ExtractTextPlugin({
filename: cssFilename, filename: cssFilename,
}), }),
// Generate a manifest file which contains a mapping of all asset filenames // 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 // to their corresponding output file so that tools can pick it up without
// having to parse `index.html`. // having to parse `index.html`.
new ManifestPlugin({ new ManifestPlugin({
fileName: 'asset-manifest.json', fileName: 'asset-manifest.json',
}), }),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build. // the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({ new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests // By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh. // used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern // If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped. // about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./, dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js', filename: 'service-worker.js',
logger(message) { logger (message) {
if (message.indexOf('Total precache size is') === 0) { if (message.indexOf('Total precache size is') === 0) {
// This message occurs for every build and is a bit too noisy. // This message occurs for every build and is a bit too noisy.
return; return;
} }
if (message.indexOf('Skipping static resource') === 0) { if (message.indexOf('Skipping static resource') === 0) {
// This message obscures real errors so we ignore it. // This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612 // https://github.com/facebookincubator/create-react-app/issues/2612
return; return;
} }
console.log(message); console.log(message);
}, },
minify: true, minify: true,
// For unknown URLs, fallback to the index page // For unknown URLs, fallback to the index page
navigateFallback: publicUrl + '/index.html', navigateFallback: publicUrl + '/index.html',
// Ignores URLs starting from /__ (useful for Firebase): // Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-inferno-app/issues/2237#issuecomment-302693219 // https://github.com/facebookincubator/create-inferno-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/], navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest: // Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}), }),
// Moment.js is an extremely popular library that bundles large locale files // 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 // by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales. // solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js: // You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
], ],
// Some libraries import Node modules but don't use them in the browser. // Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works. // Tell Webpack to provide empty mocks for them so importing them works.
node: { node: {
dgram: 'empty', dgram: 'empty',
fs: 'empty', fs: 'empty',
net: 'empty', net: 'empty',
tls: 'empty', tls: 'empty',
child_process: 'empty', child_process: 'empty',
}, },
}; };

View File

@ -1,76 +1,113 @@
import { Component } from 'inferno';
import { import {
Alert, Alert,
Button, Button,
Col, Col,
Container, Container,
Jumbotron, Jumbotron,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
Row, Row,
} from '../components/Bootstrap'; } from '../components/Bootstrap';
import { JSONMessage } from '../helpers/web-socket'; import { JSONMessage } from '../helpers/web-socket';
function handleDrop (e) { export class HomeView extends Component {
e.preventDefault(); constructor (props) {
e.stopPropagation(); super(props);
const draggedFiles = []; this.state = {
for (const f of e.dataTransfer.files) { showModal: false,
draggedFiles.push(f.path); };
this.toggleErrorModal = this.toggleErrorModal.bind(this);
} }
window.clientWS.send(JSONMessage('dropped-files', draggedFiles)); handleDragOver (e) {
} e.preventDefault();
e.stopPropagation();
}
function handleDragOver (e) { handleDrop (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}
function showOpenDialog () { const draggedFiles = [];
window.clientWS.send(JSONMessage('show-open-dialog', {})); for (const f of e.dataTransfer.files) {
} draggedFiles.push(f.path);
}
function showSaveDialog () { window.clientWS.send(JSONMessage('dropped-files', draggedFiles));
window.clientWS.send(JSONMessage('show-save-dialog', {})); }
}
function showErrorDialog () { showErrorDialog () {
window.clientWS.send(JSONMessage( window.clientWS.send(JSONMessage(
'show-error-box', 'show-error-box',
'Looks like there was a problem. (╥﹏╥) \n (╯°□°)╯︵ ┻━┻' 'Looks like there was a problem. (╥﹏╥) \n (╯°□°)╯︵ ┻━┻'
)); ));
} }
export const HomeView = (props) => { showOpenDialog () {
return ( window.clientWS.send(JSONMessage('show-open-dialog', {}));
<main> }
<Alert color="info">
This is a work in progress showSaveDialog () {
</Alert> window.clientWS.send(JSONMessage('show-save-dialog', {}));
<Jumbotron onDrop={handleDrop} onDragover={handleDragOver}> }
<Container className="App">
<Row> toggleErrorModal () {
<header className="App-header"> this.setState(prevState => ({
<h1>Welcome to Film Exif</h1> showModal: !prevState.showModal,
</header> }));
</Row> }
<Row>
<p className="App-intro"> render () {
Drop files here. return (
</p> <main>
</Row> <Alert color="info">
<Row> This is a work in progress
<Col md={4}> </Alert>
<Button onClick={showOpenDialog}>Show Open Dialog</Button> <Jumbotron onDrop={this.handleDrop} onDragover={this.handleDragOver}>
</Col> <Container className="App">
<Col md={4}> <Row>
<Button onClick={showSaveDialog}>Show Save Dialog</Button> <header className="App-header">
</Col> <h1>Welcome to Film Exif</h1>
<Col md={4}> </header>
<Button onClick={showErrorDialog}>Show Error Dialog</Button> </Row>
</Col> <Row>
</Row> <p className="App-intro">
</Container> Drop files here.
</Jumbotron> </p>
</main> </Row>
); <Row>
}; <Col md={3}>
<Button onClick={this.showOpenDialog}>Show Open Dialog</Button>
</Col>
<Col md={3}>
<Button onClick={this.showSaveDialog}>Show Save Dialog</Button>
</Col>
<Col md={3}>
<Button onClick={this.showErrorDialog}>Show Error Dialog</Button>
</Col>
<Col md={3}>
<Button onClick={this.toggleErrorModal}>Show Error Modal</Button>
</Col>
</Row>
</Container>
</Jumbotron>
<Modal fade isOpen={this.state.showModal} toggle={this.toggleErrorModal}>
<ModalHeader toggle={this.toggleErrorModal}>
Error Title
</ModalHeader>
<ModalBody>
Body of error message
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.toggleErrorModal}>Close</Button>
</ModalFooter>
</Modal>
</main>
);
}
}