init glass player
This commit is contained in:
commit
adcc43cf8f
50 files changed
+13488
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const paths = require('./paths');
|
||||
|
||||
// Make sure that including paths.js after env.js will read .env variables.
|
||||
delete require.cache[require.resolve('./paths')];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
'The NODE_ENV environment variable is required but was not specified.'
|
||||
);
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
var dotenvFiles = [
|
||||
`${paths.dotenv}.${NODE_ENV}.local`,
|
||||
`${paths.dotenv}.${NODE_ENV}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
|
||||
paths.dotenv,
|
||||
].filter(Boolean);
|
||||
|
||||
// Load environment variables from .env* files. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require('dotenv-expand')(
|
||||
require('dotenv').config({
|
||||
path: dotenvFile,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We support resolving modules according to `NODE_PATH`.
|
||||
// This lets you use absolute paths in imports inside large monorepos:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253.
|
||||
// It works similar to `NODE_PATH` in Node itself:
|
||||
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
|
||||
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
|
||||
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421
|
||||
// We also resolve them to make sure all tools using them work consistently.
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
|
||||
// injected into the application via DefinePlugin in Webpack configuration.
|
||||
const REACT_APP = /^REACT_APP_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
return env;
|
||||
},
|
||||
{
|
||||
// Useful for determining whether we’re running in production mode.
|
||||
// Most importantly, it switches React into the correct mode.
|
||||
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||
// Useful for resolving the correct path to static assets in `public`.
|
||||
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
|
||||
// This should only be used as an escape hatch. Normally you would put
|
||||
// images into the `src` and `import` them in code to get their paths.
|
||||
PUBLIC_URL: publicUrl,
|
||||
}
|
||||
);
|
||||
// Stringify all values so we can feed into Webpack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
return env;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return { raw, stringified };
|
||||
}
|
||||
|
||||
module.exports = getClientEnvironment;
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
// This is a custom Jest transformer turning style imports into empty objects.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'cssTransform';
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// This is a custom Jest transformer turning file imports into filenames.
|
||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
||||
|
||||
module.exports = {
|
||||
process(src, filename) {
|
||||
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
|
||||
// Make sure any symlinks in the project folder are resolved:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/637
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
|
||||
|
||||
const envPublicUrl = process.env.PUBLIC_URL;
|
||||
|
||||
function ensureSlash(path, needsSlash) {
|
||||
const hasSlash = path.endsWith('/');
|
||||
if (hasSlash && !needsSlash) {
|
||||
return path.substr(path, path.length - 1);
|
||||
} else if (!hasSlash && needsSlash) {
|
||||
return `${path}/`;
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
const getPublicUrl = appPackageJson =>
|
||||
envPublicUrl || require(appPackageJson).homepage;
|
||||
|
||||
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
|
||||
// "public path" at which the app is served.
|
||||
// Webpack needs to know it to put the right <script> hrefs into HTML even in
|
||||
// single-page apps that may serve index.html for nested URLs like /todos/42.
|
||||
// We can't use a relative path in HTML because we don't want to load something
|
||||
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
|
||||
function getServedPath(appPackageJson) {
|
||||
const publicUrl = getPublicUrl(appPackageJson);
|
||||
const servedUrl =
|
||||
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/');
|
||||
return ensureSlash(servedUrl, true);
|
||||
}
|
||||
|
||||
// config after eject: we're in ./config/
|
||||
module.exports = {
|
||||
dotenv: resolveApp('.env'),
|
||||
appBuild: resolveApp('build'),
|
||||
appPublic: resolveApp('public'),
|
||||
appHtml: resolveApp('public/index.html'),
|
||||
appIndexJs: resolveApp('src/index.js'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
yarnLockFile: resolveApp('yarn.lock'),
|
||||
testsSetup: resolveApp('src/setupTests.js'),
|
||||
appNodeModules: resolveApp('node_modules'),
|
||||
publicUrl: getPublicUrl(resolveApp('package.json')),
|
||||
servedPath: getServedPath(resolveApp('package.json')),
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
if (typeof Promise === 'undefined') {
|
||||
// Rejection tracking prevents a common issue where React gets into an
|
||||
// inconsistent state due to an error, but it gets swallowed by a Promise,
|
||||
// and the user has no idea what causes React's erratic future behavior.
|
||||
require('promise/lib/rejection-tracking').enable();
|
||||
window.Promise = require('promise/lib/es6-extensions.js');
|
||||
}
|
||||
|
||||
// fetch() polyfill for making API calls.
|
||||
require('whatwg-fetch');
|
||||
|
||||
// Object.assign() is commonly used with React.
|
||||
// It will use the native implementation if it's present and isn't buggy.
|
||||
Object.assign = require('object-assign');
|
||||
|
||||
// In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
|
||||
// We don't polyfill it in the browser--this is user's responsibility.
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
require('raf').polyfill(global);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
'use strict';
|
||||
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const eslintFormatter = require('react-dev-utils/eslintFormatter');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
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 = '/';
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
|
||||
const publicUrl = '';
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// 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 = {
|
||||
// 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: [
|
||||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// 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'),
|
||||
// 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: {
|
||||
// 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: 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', '.mjs', '.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',
|
||||
},
|
||||
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, [paths.appPackageJson]),
|
||||
],
|
||||
},
|
||||
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|mjs)$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
formatter: eslintFormatter,
|
||||
eslintPath: require.resolve('eslint'),
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
// "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'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx|mjs)$/,
|
||||
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: /\.(css|styl)$/,
|
||||
use: [
|
||||
require.resolve('style-loader'),
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// loader: require.resolve('stylus-loader') // compiles stylus-loader to CSS
|
||||
// },
|
||||
{
|
||||
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',
|
||||
}),
|
||||
],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
// "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.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// its runtime that would otherwise processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env.raw),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
}),
|
||||
// Add module names to factory functions so they appear in browser profiler.
|
||||
new webpack.NamedModulesPlugin(),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/240
|
||||
new CaseSensitivePathsPlugin(),
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
// 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',
|
||||
child_process: '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,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,345 @@
|
||||
'use strict';
|
||||
|
||||
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 SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
|
||||
const eslintFormatter = require('react-dev-utils/eslintFormatter');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const paths = require('./paths');
|
||||
const getClientEnvironment = require('./env');
|
||||
|
||||
// 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;
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
const shouldUseRelativeAssetPaths = publicPath === './';
|
||||
// Source maps are resource heavy and can cause out of memory issue for large source files.
|
||||
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
const publicUrl = publicPath.slice(0, -1);
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// Assert this just to be safe.
|
||||
// Development builds of React are slow and not intended for production.
|
||||
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].[contenthash: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 = shouldUseRelativeAssetPaths
|
||||
? // Making sure that the publicPath goes back to to build folder.
|
||||
{ publicPath: Array(cssFilename.split('/').length).join('../') }
|
||||
: {};
|
||||
|
||||
// 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 = {
|
||||
// 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: shouldUseSourceMap ? 'source-map' : false,
|
||||
// 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: 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', '.mjs', '.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',
|
||||
},
|
||||
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, [paths.appPackageJson]),
|
||||
],
|
||||
},
|
||||
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|mjs)$/,
|
||||
enforce: 'pre',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
formatter: eslintFormatter,
|
||||
eslintPath: require.resolve('eslint'),
|
||||
|
||||
},
|
||||
loader: require.resolve('eslint-loader'),
|
||||
},
|
||||
],
|
||||
include: paths.appSrc,
|
||||
},
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
// back to the "file" loader at the end of the loader list.
|
||||
oneOf: [
|
||||
// "url" loader works just like "file" loader but it also embeds
|
||||
// assets smaller than specified size as data URLs to avoid requests.
|
||||
{
|
||||
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// Process JS with Babel.
|
||||
{
|
||||
test: /\.(js|jsx|mjs)$/,
|
||||
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: /\.(css|styl)$/,
|
||||
loader: ExtractTextPlugin.extract(
|
||||
Object.assign(
|
||||
{
|
||||
fallback: {
|
||||
loader: require.resolve('style-loader'),
|
||||
options: {
|
||||
hmr: false,
|
||||
},
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
minimize: true,
|
||||
sourceMap: shouldUseSourceMap,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// loader: require.resolve('stylus-loader') // compiles stylus-loader to CSS
|
||||
// },
|
||||
{
|
||||
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',
|
||||
}),
|
||||
],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
extractTextPluginOptions
|
||||
)
|
||||
),
|
||||
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
|
||||
},
|
||||
// "file" loader makes sure assets end up in the `build` folder.
|
||||
// When you `import` an asset, you get its filename.
|
||||
// This loader doesn't use a "test" so it will catch all modules
|
||||
// that fall through the other loaders.
|
||||
{
|
||||
loader: require.resolve('file-loader'),
|
||||
// Exclude `js` files to keep "css" loader working as it injects
|
||||
// it's runtime that would otherwise processed through "file" loader.
|
||||
// Also exclude `html` and `json` extensions so they get processed
|
||||
// by webpacks internal loaders.
|
||||
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]',
|
||||
},
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Make sure to add the new loader(s) before the "file" loader.
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// 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.
|
||||
new InterpolateHtmlPlugin(env.raw),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
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),
|
||||
// Minify the code.
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
// Disabled because of an issue with Uglify breaking seemingly valid code:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2376
|
||||
// Pending further investigation:
|
||||
// https://github.com/mishoo/UglifyJS2/issues/2011
|
||||
comparisons: false,
|
||||
},
|
||||
mangle: {
|
||||
safari10: true,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
// Turned on because emoji and regex is not minified properly using default
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2488
|
||||
ascii_only: true,
|
||||
},
|
||||
sourceMap: shouldUseSourceMap,
|
||||
}),
|
||||
// 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',
|
||||
}),
|
||||
// Generate a service worker script that will precache, and keep up to date,
|
||||
// the HTML & assets that are part of the Webpack build.
|
||||
new SWPrecacheWebpackPlugin({
|
||||
// By default, a cache-busting query parameter is appended to requests
|
||||
// used to populate the caches, to ensure the responses are fresh.
|
||||
// If a URL is already hashed by Webpack, then there is no concern
|
||||
// about it being stale, and the cache-busting can be skipped.
|
||||
dontCacheBustUrlsMatching: /\.\w{8}\./,
|
||||
filename: 'service-worker.js',
|
||||
logger(message) {
|
||||
if (message.indexOf('Total precache size is') === 0) {
|
||||
// This message occurs for every build and is a bit too noisy.
|
||||
return;
|
||||
}
|
||||
if (message.indexOf('Skipping static resource') === 0) {
|
||||
// This message obscures real errors so we ignore it.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2612
|
||||
return;
|
||||
}
|
||||
console.log(message);
|
||||
},
|
||||
minify: true,
|
||||
// For unknown URLs, fallback to the index page
|
||||
navigateFallback: publicUrl + '/index.html',
|
||||
// Ignores URLs starting from /__ (useful for Firebase):
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
|
||||
navigateFallbackWhitelist: [/^(?!\/__).*/],
|
||||
// Don't precache sourcemaps (they're large) and build asset manifest:
|
||||
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
|
||||
}),
|
||||
// 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',
|
||||
child_process: 'empty',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
'use strict';
|
||||
|
||||
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
|
||||
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
|
||||
const ignoredFiles = require('react-dev-utils/ignoredFiles');
|
||||
const config = require('./webpack.config.dev');
|
||||
const paths = require('./paths');
|
||||
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const host = process.env.HOST || '0.0.0.0';
|
||||
|
||||
module.exports = function(proxy, allowedHost) {
|
||||
return {
|
||||
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote
|
||||
// websites from potentially accessing local content through DNS rebinding:
|
||||
// https://github.com/webpack/webpack-dev-server/issues/887
|
||||
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
|
||||
// However, it made several existing use cases such as development in cloud
|
||||
// environment or subdomains in development significantly more complicated:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2271
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2233
|
||||
// While we're investigating better solutions, for now we will take a
|
||||
// compromise. Since our WDS configuration only serves files in the `public`
|
||||
// folder we won't consider accessing them a vulnerability. However, if you
|
||||
// use the `proxy` feature, it gets more dangerous because it can expose
|
||||
// remote code execution vulnerabilities in backends like Django and Rails.
|
||||
// So we will disable the host check normally, but enable it if you have
|
||||
// specified the `proxy` setting. Finally, we let you override it if you
|
||||
// really know what you're doing with a special environment variable.
|
||||
disableHostCheck:
|
||||
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
|
||||
// Enable gzip compression of generated files.
|
||||
compress: true,
|
||||
// Silence WebpackDevServer's own logs since they're generally not useful.
|
||||
// It will still show compile warnings and errors with this setting.
|
||||
clientLogLevel: 'none',
|
||||
// By default WebpackDevServer serves physical files from current directory
|
||||
// in addition to all the virtual build products that it serves from memory.
|
||||
// This is confusing because those files won’t automatically be available in
|
||||
// production build folder unless we copy them. However, copying the whole
|
||||
// project directory is dangerous because we may expose sensitive files.
|
||||
// Instead, we establish a convention that only files in `public` directory
|
||||
// get served. Our build script will copy `public` into the `build` folder.
|
||||
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
|
||||
// Note that we only recommend to use `public` folder as an escape hatch
|
||||
// for files like `favicon.ico`, `manifest.json`, and libraries that are
|
||||
// for some reason broken when imported through Webpack. If you just want to
|
||||
// use an image, put it in `src` and `import` it from JavaScript instead.
|
||||
contentBase: paths.appPublic,
|
||||
// By default files from `contentBase` will not trigger a page reload.
|
||||
watchContentBase: true,
|
||||
// Enable hot reloading server. It will provide /sockjs-node/ endpoint
|
||||
// for the WebpackDevServer client so it can learn when the files were
|
||||
// updated. The WebpackDevServer client is included as an entry point
|
||||
// in the Webpack development configuration. Note that only changes
|
||||
// to CSS are currently hot reloaded. JS changes will refresh the browser.
|
||||
hot: true,
|
||||
// It is important to tell WebpackDevServer to use the same "root" path
|
||||
// as we specified in the config. In development, we always serve from /.
|
||||
publicPath: config.output.publicPath,
|
||||
// WebpackDevServer is noisy by default so we emit custom message instead
|
||||
// by listening to the compiler events with `compiler.plugin` calls above.
|
||||
quiet: true,
|
||||
// Reportedly, this avoids CPU overload on some systems.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/293
|
||||
// src/node_modules is not ignored to support absolute imports
|
||||
// https://github.com/facebookincubator/create-react-app/issues/1065
|
||||
watchOptions: {
|
||||
ignored: ignoredFiles(paths.appSrc),
|
||||
},
|
||||
// Enable HTTPS if the HTTPS environment variable is set to 'true'
|
||||
https: protocol === 'https',
|
||||
host: host,
|
||||
overlay: false,
|
||||
historyApiFallback: {
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
},
|
||||
public: allowedHost,
|
||||
proxy,
|
||||
before(app) {
|
||||
// This lets us open files from the runtime error overlay.
|
||||
app.use(errorOverlayMiddleware());
|
||||
// This service worker file is effectively a 'no-op' that will reset any
|
||||
// previous service worker registered for the same host:port combination.
|
||||
// We do this in development to avoid hitting the production cache if
|
||||
// it used the same host and port.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
|
||||
app.use(noopServiceWorkerMiddleware());
|
||||
},
|
||||
};
|
||||
};
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"name": "glass-player",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"proxy": "http://node.dengtongyu.com",
|
||||
"homepage":"/pages/glassplayer/",
|
||||
"dependencies": {
|
||||
"antd": "^3.2.3",
|
||||
"autoprefixer": "7.1.6",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-jest": "20.0.3",
|
||||
"babel-loader": "7.1.2",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-preset-react-app": "^3.1.1",
|
||||
"babel-runtime": "6.26.0",
|
||||
"case-sensitive-paths-webpack-plugin": "2.1.1",
|
||||
"chalk": "1.1.3",
|
||||
"css-loader": "0.28.7",
|
||||
"dotenv": "4.0.0",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"eslint": "4.10.0",
|
||||
"eslint-config-react-app": "^2.1.0",
|
||||
"eslint-loader": "1.9.0",
|
||||
"eslint-plugin-flowtype": "2.39.1",
|
||||
"eslint-plugin-import": "2.8.0",
|
||||
"eslint-plugin-jsx-a11y": "5.1.1",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"extract-text-webpack-plugin": "3.0.2",
|
||||
"file-loader": "1.1.5",
|
||||
"fs-extra": "3.0.1",
|
||||
"html-webpack-plugin": "2.29.0",
|
||||
"jest": "20.0.4",
|
||||
"object-assign": "4.1.1",
|
||||
"postcss-flexbugs-fixes": "3.2.0",
|
||||
"postcss-loader": "2.0.8",
|
||||
"promise": "8.0.1",
|
||||
"raf": "3.4.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dev-utils": "^5.0.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-scrollbar": "^0.5.4",
|
||||
"style-loader": "0.19.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"sw-precache-webpack-plugin": "0.11.4",
|
||||
"url-loader": "0.6.2",
|
||||
"webpack": "3.8.1",
|
||||
"webpack-dev-server": "2.9.4",
|
||||
"webpack-manifest-plugin": "1.3.2",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js --env=jsdom"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx,mjs}"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/config/polyfills.js"
|
||||
],
|
||||
"testMatch": [
|
||||
"<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
|
||||
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
|
||||
],
|
||||
"testEnvironment": "node",
|
||||
"testURL": "http://localhost",
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"^react-native$": "react-native-web"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"web.js",
|
||||
"mjs",
|
||||
"js",
|
||||
"json",
|
||||
"web.jsx",
|
||||
"jsx",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"react-app"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-decorators-legacy"
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mobx": "^4.1.1",
|
||||
"mobx-react": "^5.0.0"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hans">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>琉璃音乐播放器</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'production';
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const fs = require('fs-extra');
|
||||
const webpack = require('webpack');
|
||||
const config = require('../config/webpack.config.prod');
|
||||
const paths = require('../config/paths');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
const printBuildError = require('react-dev-utils/printBuildError');
|
||||
|
||||
const measureFileSizesBeforeBuild =
|
||||
FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
|
||||
// These sizes are pretty large. We'll warn for bundles exceeding them.
|
||||
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])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
measureFileSizesBeforeBuild(paths.appBuild)
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
console.log('File sizes after gzip:\n');
|
||||
printFileSizesAfterBuild(
|
||||
stats,
|
||||
previousFileSizes,
|
||||
paths.appBuild,
|
||||
WARN_AFTER_BUNDLE_GZIP_SIZE,
|
||||
WARN_AFTER_CHUNK_GZIP_SIZE
|
||||
);
|
||||
console.log();
|
||||
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrl;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
printBuildError(err);
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
console.log('Creating an optimized production build...');
|
||||
|
||||
let compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const messages = formatWebpackMessages(stats.toJson({}, true));
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
// of the same problem, but confuse the reader with noise.
|
||||
if (messages.errors.length > 1) {
|
||||
messages.errors.length = 1;
|
||||
}
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (
|
||||
process.env.CI &&
|
||||
(typeof process.env.CI !== 'string' ||
|
||||
process.env.CI.toLowerCase() !== 'false') &&
|
||||
messages.warnings.length
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
return resolve({
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const clearConsole = require('react-dev-utils/clearConsole');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require('react-dev-utils/WebpackDevServerUtils');
|
||||
const openBrowser = require('react-dev-utils/openBrowser');
|
||||
const paths = require('../config/paths');
|
||||
const config = require('../config/webpack.config.dev');
|
||||
const createDevServerConfig = require('../config/webpackDevServer.config');
|
||||
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
|
||||
if (process.env.HOST) {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
`Attempting to bind to HOST environment variable: ${chalk.yellow(
|
||||
chalk.bold(process.env.HOST)
|
||||
)}`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
|
||||
);
|
||||
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||
choosePort(HOST, DEFAULT_PORT)
|
||||
.then(port => {
|
||||
if (port == null) {
|
||||
// We have not found a port.
|
||||
return;
|
||||
}
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
const urls = prepareUrls(protocol, HOST, port);
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
|
||||
// Load proxy config
|
||||
const proxySetting = require(paths.appPackageJson).proxy;
|
||||
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||
// Serve webpack assets generated by the compiler over a web sever.
|
||||
const serverConfig = createDevServerConfig(
|
||||
proxyConfig,
|
||||
urls.lanUrlForConfig
|
||||
);
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
}
|
||||
console.log(chalk.cyan('Starting the development server...\n'));
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
});
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'test';
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.PUBLIC_URL = '';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const jest = require('jest');
|
||||
const argv = process.argv.slice(2);
|
||||
|
||||
// Watch unless on CI or in coverage mode
|
||||
if (!process.env.CI && argv.indexOf('--coverage') < 0) {
|
||||
argv.push('--watch');
|
||||
}
|
||||
|
||||
|
||||
jest.run(argv);
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
@import '~antd/dist/antd.css';
|
||||
|
||||
|
||||
@font-face {font-family: "iconfont";
|
||||
src: url('./assets/iconfont.eot?t=1524288064827'); /* IE9*/
|
||||
src: url('./assets/iconfont.eot?t=1524288064827#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAi0AAsAAAAAEUQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kjUY21hcAAAAYAAAADNAAACdmanB+BnbHlmAAACUAAABAcAAAmQ6HPj9GhlYWQAAAZYAAAALwAAADYRIRp9aGhlYQAABogAAAAcAAAAJAfeA5BobXR4AAAGpAAAABQAAAA8O+kAAGxvY2EAAAa4AAAAIAAAACAP+hJsbWF4cAAABtgAAAAdAAAAIAEjAF1uYW1lAAAG+AAAAUUAAAJtPlT+fXBvc3QAAAhAAAAAcwAAAJmdWj12eJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sc4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDx7wtzwv4EhhrmBoQEozAiSAwA2Og1WeJzFkk0KwkAMhd/Y+v+DSxFxU0o30mJB8Wg9hKtue5GuPE1516hvmnZR0K1m+AaSDJOQFwBTAIG4iBBwLzh4qxV1XTzAqouHeMo/YK/IAgUjxkyYMeedD5as2LStXgyZlFfeRplP5vTjcI444dwfn5moUqgOZ5ir4lJ9rLHBFjslZ1/++4G5/5Ue26a7697TVFD0qEVGhqYIxoZXmonh1WZq+C1gZmja4NXQ3MHckALgzZAW4N3w28GHIX3A0vDdsTKkGdgY2L0BBHFD1AAAAHiclZbPb9tUHMDf93m2+yNJ57iOm3Rp8+p6Zlqc0bqusao1o8CBJpOQduFFSFUluhtF4jKBULUdsDjsgHpc1UkgBCcuKceqE6eC+As4pQgO/AG79sHXztYm2eGxJ9tf2/o+v8/35zNRCfn3T+VYmSEmeYMskXfJB4SAdhMWCrQKzFtp0JtgMdWypwuK53hMdxYaym2wF7Tp0vLqynVb07UpKMAcBGx51WtQD8KVdboGy6UqQHm2cq/oXisq38DEjDf3ldik34E171ybWvfF+/Xm9HLNHHuQKxbLxeLjMU1Vxyi9MlWAT+zSuDo+oYnv1amKdTx/g85DruxV2jxfmy1uf72yW3XtcYCHD8GcrRV+bBoVA48vKyWzWNav5sdmKnlncRoe/D05Y+aq1/8iOBS09RflmdIkV0mN1AlRWQM8Yx0iNge2UQDdxGeG0sBnlr53QicMwsAKLEfJnf/T3qF0p01n+pJDOxYbcRtQwglK8Wun28WjOaDUn/S0r/VSO1XqDPAo6H0H/c4sBinHAJc+ggQMTsQGfXrx6f5SH46ywE8diEeIPx+mSH1y5YLBIFXik9tIgatNl2zdjjCmuGIDFGTAGyQogDkIYzTAHMa1qb+1p1jmI749e2dxa4/Sva3+anGL0tYPI0T5UWz6GCcs3pnd5o9MS9nb2toThy8nx3Gb2iN2n388aHPfpku/UlIkC2hRHqRu/QJicUrfk7mVBkdH8Ox1/KoQndjEQ4rAcNbAcFwZyt0kOUsS2JeziCdwn0thLv0xReYzEkkMdSQNVUN9JeVfCVePc3EKsdKUhKUunhwewv3D12dRXcO1HCOQs4gNeAsrsCOF+e2wTzMcp5Qn7YBSImA6C53IdtxA94KQRex/sFmw74uNTpJw2EmvYteHEymp2OVH3XR0OLJqQzmVx4plGS82J+RwXCuwDV3F2EWSDKOJ7/N6XZxyTOZ93z+TNpM/uvVOvZvASdKDXCLJuEufTl4w6rbu6V7kRXbEMk5ZFSi5VsttbbqbeLREHknpZ1LMnzdb6YRsWkZ7JCsO/YJVJeOkTG6QWyQka4S46NUwsJnBpPWKvrcC1EdpOukVWLfnc8iJ5/KGcn6MupDr+r0u3nzr97jUwXCSKQvL75GJoZyYzLL4FglIhDY0CTGDtHbCjCqVsjaoshAtDnEf6p/P+YtBf5cZguW3Iw6GzntoC+9xccBlLRO9FaNmL03zgZgoGJFqlkFvZ/8lby5hB8WtmCkYm9JS5Kxq+KOxEDi6YwcRWilt8ygjG1/iO9zbqF/6NPGxOdX95KM55moFDSYq42u8Fh/xGLjU5gOzDKV30g2qDRzKVMvaIQcc/OzsTGY27EPZBOg/mGVC/gMsHBbFAHicY2BkYGAAYp3TRtLx/DZfGbhZGEDgOsNrBwT9/yALA7M9kMvBwAQSBQD9+QkJAHicY2BkYGBu+N/AEMPCAAJAkpEBFfADAEcVAnh4nGNhYGBgfsnAwMJAOgYAOZMBJQAAAAAAdgC+AQABagGsAfICOAKAAtYDKgOAA+AESgTIeJxjYGRgYOBnCGTgYgABJiDmArP/g/kMABKuAYEAAAB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxtyssOgyAQBdC5CtTSh/7iRKfRBIQwYOzft0m3PetDHf14+m9Ehx4GFg4XDLjC44Y7HnhixEQ4e14WVySmQ5wKl3k1OfDbZm4qZpezDrnIsaWm35aFqztSaFFMbFX8q4WgcxHZTdi02lpYV6IPnZkflAA=') format('woff'),
|
||||
url('./assets/iconfont.ttf?t=1524288064827') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url('./assets/iconfont.svg?t=1524288064827#iconfont') format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family:"iconfont" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-add:before { content: "\e623"; }
|
||||
|
||||
.icon-remove:before { content: "\e625"; }
|
||||
|
||||
.icon-search:before { content: "\e627"; }
|
||||
|
||||
.icon-play:before { content: "\e62e"; }
|
||||
|
||||
.icon-pause:before { content: "\e62f"; }
|
||||
|
||||
.icon-next:before { content: "\e631"; }
|
||||
|
||||
.icon-previous:before { content: "\e632"; }
|
||||
|
||||
.icon-repeat:before { content: "\e635"; }
|
||||
|
||||
.icon-volume:before { content: "\e636"; }
|
||||
|
||||
.icon-mute:before { content: "\e638"; }
|
||||
|
||||
.icon-fullscreen:before { content: "\e692"; }
|
||||
|
||||
.icon-list:before { content: "\e69b"; }
|
||||
|
||||
.icon-trash:before { content: "\e6e4"; }
|
||||
|
||||
|
||||
ul,ol,li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,body {
|
||||
height: 100%;
|
||||
min-height: 682px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
|
||||
.App {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 900px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
.App a{
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.App button{
|
||||
color: #fff;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bg-image-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
opacity: .3;
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(./assets/bg.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50%;
|
||||
transform: translateZ(0);
|
||||
opacity: .6;
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
min-height: 630px;
|
||||
margin: 0 5%;
|
||||
}
|
||||
|
||||
.blur {
|
||||
-webkit-filter: blur(65px);
|
||||
-moz-filter: blur(65px);
|
||||
-ms-filter: blur(65px);
|
||||
filter: blur(65px);
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.song-list-box {
|
||||
margin-left: 310px;
|
||||
margin-right: 310px;
|
||||
color: #fff;
|
||||
height: 100%;
|
||||
padding-top: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.full {
|
||||
margin-right: 0;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import React, { Component } from 'react';
|
||||
import { message } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import CoverLyrics from './components/CoverLyrics';
|
||||
import SongList from './components/SongList';
|
||||
import ControlBar from './components/ControlBar';
|
||||
import SearchBar from './components/SearchBar';
|
||||
import './App.css';
|
||||
|
||||
import { DataStore } from './DataStore';
|
||||
import PlayList from './components/PlayList';
|
||||
|
||||
@observer
|
||||
class App extends Component {
|
||||
onWindowResize = (event) => {
|
||||
DataStore.windowWidth = window.innerWidth;
|
||||
DataStore.windowHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
this.onWindowResize();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if(DataStore.errMassge) {
|
||||
message.warning(DataStore.errMassge);
|
||||
DataStore.errMassge = '';
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="bg-image-mask"></div>
|
||||
<div className="bg-image blur"
|
||||
style={{backgroundImage:'url('+ DataStore.coutSongMassge.Img +')'}}></div>
|
||||
<div className="content flex">
|
||||
<div className="main">
|
||||
<CoverLyrics />
|
||||
<div className={"song-list-box flex" + (DataStore.showPlayList ? '' : ' full')}>
|
||||
<SearchBar />
|
||||
<SongList />
|
||||
</div>
|
||||
<PlayList />
|
||||
</div>
|
||||
<ControlBar />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
@@ -0,0 +1,455 @@
|
||||
import { observable } from 'mobx'
|
||||
|
||||
let isFetching = false;
|
||||
|
||||
async function urlFetch(url) {
|
||||
if(isFetching) return Promise.reject('您点太快了!');
|
||||
|
||||
isFetching = true;
|
||||
let result = await fetch(url)
|
||||
isFetching = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
const searchUrl = '/data/songSearch';
|
||||
|
||||
async function getSearchSongList(keyWord,page,size) {
|
||||
if(!keyWord) keyWord = '';
|
||||
if(!page) page = 1;
|
||||
if(!size) size = 10;
|
||||
let url = searchUrl + '?keyword=' + keyWord + '&page=' + page + '&pagesize=' + size;
|
||||
let result = await urlFetch(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
const newSongUrl = '/data/newSong';
|
||||
|
||||
async function getNewSongList() {
|
||||
let url = newSongUrl + '?json=true';
|
||||
let result = await urlFetch(url);
|
||||
return result;
|
||||
}
|
||||
|
||||
const songMassgeUrl = '/data/songMsg';
|
||||
|
||||
async function getSongMassge(hash) {
|
||||
let url = songMassgeUrl + '?hash=' + hash;
|
||||
let result = await urlFetch(url)
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseLyric(text) {
|
||||
//将文本分隔成一行一行,存入数组
|
||||
let lines = text.split('\n'),
|
||||
//用于匹配时间的正则表达式,匹配的结果类似[xx:xx.xx]
|
||||
pattern = /\[\d{2}:\d{2}.\d{2}\]/g,
|
||||
//保存最终结果的数组
|
||||
result = [];
|
||||
//去掉不含时间的行
|
||||
while (!pattern.test(lines[0])) {
|
||||
lines = lines.slice(1);
|
||||
};
|
||||
//上面用'\n'生成生成数组时,结果中最后一个为空元素,这里将去掉
|
||||
lines[lines.length - 1].length === 0 && lines.pop();
|
||||
lines.forEach(function(v /*数组元素值*/ , i /*元素索引*/ , a /*数组本身*/ ) {
|
||||
//提取出时间[xx:xx.xx]
|
||||
var time = v.match(pattern),
|
||||
//提取歌词
|
||||
value = v.replace(pattern, '');
|
||||
//因为一行里面可能有多个时间,所以time有可能是[xx:xx.xx][xx:xx.xx][xx:xx.xx]的形式,需要进一步分隔
|
||||
time.forEach(function(v1, i1, a1) {
|
||||
//去掉时间里的中括号得到xx:xx.xx
|
||||
var t = v1.slice(1, -1).split(':');
|
||||
//将结果压入最终数组
|
||||
result.push([parseInt(t[0], 10) * 60 + parseFloat(t[1]), value]);
|
||||
});
|
||||
});
|
||||
//最后将结果数组中的元素按时间大小排序,以便保存之后正常显示歌词
|
||||
result.sort(function(a, b) {
|
||||
return a[0] - b[0];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
class DataPageStore {
|
||||
|
||||
@observable _audio = null;
|
||||
|
||||
@observable _searchSongList = [];
|
||||
|
||||
@observable _playSongList = [];
|
||||
|
||||
@observable _count = 0;
|
||||
|
||||
@observable _isPlay = false;
|
||||
|
||||
@observable _volume = 0.6;
|
||||
|
||||
@observable _silent = false;
|
||||
|
||||
@observable _disabled = false;
|
||||
|
||||
@observable _showPlayList = false;
|
||||
|
||||
@observable _currentTime = 0;
|
||||
|
||||
@observable _loop = false;
|
||||
|
||||
@observable _errMassge = '';
|
||||
|
||||
@observable _keyword = '';
|
||||
|
||||
@observable _page = 0;
|
||||
|
||||
@observable _pageSize = 10;
|
||||
|
||||
@observable _listType = 0;
|
||||
|
||||
@observable _windowWidth = 900;
|
||||
|
||||
@observable _windowHeight = 680;
|
||||
|
||||
get audio() {
|
||||
return this._audio;
|
||||
}
|
||||
|
||||
set audio(value) {
|
||||
this._audio = value;
|
||||
}
|
||||
|
||||
get searchSongList() {
|
||||
return this._searchSongList;
|
||||
}
|
||||
|
||||
set searchSongList(value) {
|
||||
this._searchSongList = value;
|
||||
}
|
||||
|
||||
get playSongList() {
|
||||
return this._playSongList;
|
||||
}
|
||||
|
||||
set playSongList(value) {
|
||||
this._playSongList = value;
|
||||
if(this.playSongList.length === 1) {
|
||||
this.loop = true;
|
||||
}else{
|
||||
this.loop = false;
|
||||
}
|
||||
}
|
||||
|
||||
get count() {
|
||||
return this._count;
|
||||
}
|
||||
|
||||
set count(value) {
|
||||
if(this.playSongList.length === 0) {
|
||||
this._count = 0;
|
||||
return;
|
||||
}
|
||||
if(value < 0) {
|
||||
this._count = this.playSongList.length - 1;
|
||||
return;
|
||||
}
|
||||
if(value > this.playSongList.length - 1) {
|
||||
this._count = 0;
|
||||
return;
|
||||
}
|
||||
this._count = value;
|
||||
}
|
||||
|
||||
get isPlay() {
|
||||
return this._isPlay;
|
||||
}
|
||||
|
||||
set isPlay(value) {
|
||||
this._isPlay = value;
|
||||
if(this._audio) {
|
||||
if(this._isPlay) this._audio.play();
|
||||
else this._audio.pause();
|
||||
}
|
||||
}
|
||||
|
||||
get volume() {
|
||||
return this._volume;
|
||||
}
|
||||
|
||||
set volume(value) {
|
||||
this._volume = value;
|
||||
if(this._audio) this._audio.volume = this._volume;
|
||||
}
|
||||
|
||||
get silent() {
|
||||
return this._silent;
|
||||
}
|
||||
|
||||
set silent(value) {
|
||||
this._silent = value;
|
||||
if(this._silent) {
|
||||
this.volume = 0;
|
||||
}else{
|
||||
this.volume = 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value) {
|
||||
this._disabled = value;
|
||||
}
|
||||
|
||||
get showPlayList() {
|
||||
return this._showPlayList;
|
||||
}
|
||||
|
||||
set showPlayList(value) {
|
||||
this._showPlayList = value;
|
||||
}
|
||||
|
||||
get currentTime() {
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
set currentTime(value) {
|
||||
this._currentTime = value;
|
||||
//if(this._audio) this._audio.currentTime = this._currentTime;
|
||||
}
|
||||
|
||||
get loop() {
|
||||
return this._loop;
|
||||
}
|
||||
|
||||
set loop(value) {
|
||||
this._loop = value;
|
||||
}
|
||||
|
||||
get errMassge() {
|
||||
return this._errMassge;
|
||||
}
|
||||
|
||||
set errMassge(value) {
|
||||
this._errMassge = value;
|
||||
}
|
||||
|
||||
get keyword() {
|
||||
return this._keyword;
|
||||
}
|
||||
|
||||
set keyword(value) {
|
||||
this._keyword = value;
|
||||
}
|
||||
|
||||
get page() {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
set page(value) {
|
||||
this._page = value;
|
||||
}
|
||||
|
||||
get pageSize() {
|
||||
return this._pageSize;
|
||||
}
|
||||
|
||||
get listType() {
|
||||
return this._listType;
|
||||
}
|
||||
|
||||
set listType(value) {
|
||||
this._listType = value;
|
||||
}
|
||||
|
||||
get windowWidth() {
|
||||
return this._windowWidth;
|
||||
}
|
||||
|
||||
set windowWidth(value) {
|
||||
if(value < 800) value = 800;
|
||||
this._windowWidth = value;
|
||||
}
|
||||
|
||||
get windowHeight() {
|
||||
return this._windowHeight;
|
||||
}
|
||||
|
||||
set windowHeight(value) {
|
||||
if(value < 680) value = 680;
|
||||
this._windowHeight = value;
|
||||
}
|
||||
|
||||
get scrollHeight() {
|
||||
return this.windowHeight - 200;
|
||||
}
|
||||
|
||||
addPlaySongList(value) {
|
||||
this.playSongList.push(value);
|
||||
|
||||
if(this.playSongList.length === 1) {
|
||||
this.loop = true;
|
||||
}else{
|
||||
this.loop = false;
|
||||
}
|
||||
}
|
||||
|
||||
get coutSongMassge() {
|
||||
if(this._playSongList.length > 0) {
|
||||
return this._playSongList[this._count];
|
||||
}else{
|
||||
return {
|
||||
SongName: '未知歌曲',
|
||||
PlayUrl: '',
|
||||
SingerName: '未知艺术家',
|
||||
AlbumName: '未知专辑',
|
||||
Img: 'https://y.gtimg.cn/music/photo_new/T002R300x300M00000366x8I0pYkOp.jpg?max_age=2592000',
|
||||
Lyrics: [],
|
||||
Duration: 0,
|
||||
Hash: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
searchSongListStore() {
|
||||
getSearchSongList(this.keyword, this.page, this.pageSize)
|
||||
.then(result => result.json())
|
||||
.then(json => {
|
||||
//console.log(json);
|
||||
if(json.result) {
|
||||
this.listType = 0;
|
||||
let songs = [];
|
||||
let list = json.data.lists;
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
let songMsg = {};
|
||||
songMsg.Hash = list[index].FileHash;
|
||||
songMsg.SongName = list[index].SongName;
|
||||
songMsg.SingerName = list[index].SingerName;
|
||||
songMsg.Duration = list[index].Duration;
|
||||
|
||||
songs.push(songMsg);
|
||||
|
||||
}
|
||||
this.searchSongList = json.data.page > 1 ? this.searchSongList.concat(songs) : songs;
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.errMassge = err;
|
||||
});
|
||||
}
|
||||
|
||||
newSongListStore() {
|
||||
getNewSongList()
|
||||
.then(result => result.json())
|
||||
.then(json => {
|
||||
//console.log(json);
|
||||
if(json.result) {
|
||||
this.listType = 1;
|
||||
let songs = [];
|
||||
let list = json.data;
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
let songMsg = {};
|
||||
songMsg.Hash = list[index].hash;
|
||||
songMsg.SongName = list[index].filename.split(' - ')[1];
|
||||
songMsg.SingerName = list[index].filename.split(' - ')[0];
|
||||
songMsg.Duration = list[index].duration;
|
||||
|
||||
songs.push(songMsg);
|
||||
|
||||
}
|
||||
this.searchSongList = songs;
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.errMassge = err;
|
||||
});
|
||||
}
|
||||
|
||||
addSongInList(hash,callback) {
|
||||
let songIndex = this.findInPlayList(hash);
|
||||
//console.log(songIndex,songData);
|
||||
if(songIndex >= 0) {
|
||||
let songData = this.playSongList[songIndex];
|
||||
if(callback) callback(songData,songIndex);
|
||||
}else{
|
||||
getSongMassge(hash)
|
||||
.then(result => result.json())
|
||||
.then(json => {
|
||||
//console.log(json);
|
||||
if(json.result) {
|
||||
let songMassge = {};
|
||||
songMassge.PlayUrl = json.data.play_url;
|
||||
songMassge.SongName = json.data.song_name;
|
||||
songMassge.SingerName = json.data.author_name;
|
||||
songMassge.Img = json.data.img;
|
||||
songMassge.Lyrics = json.data.lyrics ? parseLyric(json.data.lyrics) : [];
|
||||
songMassge.AlbumName = json.data.album_name;
|
||||
songMassge.Duration = Math.floor(json.data.timelength / 1000);
|
||||
songMassge.Hash = json.data.hash;
|
||||
|
||||
//if(this.findInPlayList(hash) > 0) return;
|
||||
|
||||
this.addPlaySongList(songMassge);
|
||||
|
||||
if(callback) callback(songMassge,this.playSongList.length - 1);
|
||||
}
|
||||
}).catch((err) => {
|
||||
//console.log(err);
|
||||
this.errMassge = err;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
addSongAndPlay(hash) {
|
||||
this.addSongInList(hash,(songMassge,index)=>{
|
||||
this.count = index;
|
||||
})
|
||||
}
|
||||
|
||||
findInPlayList(hash) {
|
||||
return this.playSongList.findIndex((item, index, arr) => {
|
||||
return hash === item.Hash;
|
||||
})
|
||||
}
|
||||
|
||||
removeInSongList(hash) {
|
||||
let curHash = this.coutSongMassge.Hash;
|
||||
|
||||
this.playSongList = this.playSongList.filter((item, index, arr) => {
|
||||
return hash !== item.Hash;
|
||||
});
|
||||
|
||||
this.count = this.findInPlayList(curHash);
|
||||
}
|
||||
|
||||
clearSongList() {
|
||||
this.playSongList = [];
|
||||
this.audio = null;
|
||||
this.count = 0;
|
||||
DataStore.isPlay = false;
|
||||
}
|
||||
|
||||
createAudio(url) {
|
||||
console.log(this.audio)
|
||||
if(this._audio && this._audio.parent) {
|
||||
document.getElementById('audio-box').removeChild(this._audio);
|
||||
}
|
||||
if(this._audio && this._audio.src === url) {
|
||||
return null;
|
||||
}
|
||||
if(url) {
|
||||
this.audio = new Audio();
|
||||
this.audio.style.display = 'none';
|
||||
this.audio.src = url;
|
||||
this.audio.volume = this.volume;
|
||||
this.audio.autoplay = true;
|
||||
document.getElementById('audio-box').appendChild(this.audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DataStore = new DataPageStore();
|
||||
|
||||
// getSearchSongList('goog').then(function (result) {
|
||||
// console.log(result);
|
||||
// });
|
||||
|
||||
export { DataStore }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!--
|
||||
2013-9-30: Created.
|
||||
-->
|
||||
<svg>
|
||||
<metadata>
|
||||
Created by iconfont
|
||||
</metadata>
|
||||
<defs>
|
||||
|
||||
<font id="iconfont" horiz-adv-x="1024" >
|
||||
<font-face
|
||||
font-family="iconfont"
|
||||
font-weight="500"
|
||||
font-stretch="normal"
|
||||
units-per-em="1024"
|
||||
ascent="896"
|
||||
descent="-128"
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="x" unicode="x" horiz-adv-x="1001"
|
||||
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
|
||||
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
|
||||
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
|
||||
|
||||
|
||||
|
||||
<glyph glyph-name="add" unicode="" d="M780.007475-62.730461 243.903497-62.730461c-98.599645 0-178.61298 79.969333-178.61298 178.61298L65.290517 651.98752c0 98.730629 80.013335 178.744987 178.61298 178.744987l536.105001 0c98.772584 0 178.700985-80.013335 178.700985-178.744987l0-536.105001C958.70846 17.239895 878.781082-62.730461 780.007475-62.730461L780.007475-62.730461zM869.44546 607.312018c0 74.03825-60.119239 134.069485-134.113487 134.069485L288.578999 741.381503c-73.994248 0-134.027529-60.031235-134.027529-134.069485l0-446.665992c0-74.124208 60.033281-134.069485 134.027529-134.069485l446.752973 0c73.994248 0 134.113487 59.945277 134.113487 134.069485L869.44546 607.312018 869.44546 607.312018zM556.630988 160.646026l-89.351004 0L467.279984 339.259006 288.578999 339.259006l0 89.440032 178.700985 0L467.279984 607.312018 556.630988 607.312018l0-178.61298 178.700985 0 0-89.440032L556.630988 339.259006 556.630988 160.646026 556.630988 160.646026z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="remove" unicode="" d="M288.623002 428.688805l446.752973 0 0-89.419566L288.623002 339.269239 288.623002 428.688805zM958.70846 651.935332l0-535.9996c0-98.625228-80.000032-178.579211-178.75522-178.579211L243.954663-62.64348c-98.579179 0-178.665169 79.953983-178.665169 178.579211L65.289494 651.935332c0 98.711186 80.08599 178.709171 178.665169 178.709171l535.998577 0C878.708428 830.644503 958.70846 750.646518 958.70846 651.935332zM869.374852 607.268016c0 74.023924-60.021002 134.044926-133.998877 134.044926L288.623002 741.312942c-73.979922 0-133.912919-60.021002-133.912919-134.044926L154.710083 160.690028c0-74.109882 59.932997-134.042879 133.912919-134.042879l446.752973 0c73.977875 0 133.998877 59.932997 133.998877 134.042879L869.374852 607.268016z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="search" unicode="" d="M556.630988 652.11748c-123.294071 0-223.290529-100.038414-223.290529-223.418442 0-47.120181 14.745842-90.660836 39.527249-126.696564l-128.96421-96.680946 89.436962-89.307002 96.680946 128.876206c36.037774-24.867364 79.57843-39.569204 126.610606-39.569204 123.380028 0 223.376487 99.996458 223.376487 223.37751C780.007475 552.079066 680.011017 652.11748 556.630988 652.11748zM556.630988 294.627507c-73.994248 0-134.027529 60.077284-134.027529 134.071532 0 74.036203 60.033281 133.981481 134.027529 133.981481 74.080206 0 134.025483-59.945277 134.025483-133.981481C690.656471 354.70479 630.711194 294.627507 556.630988 294.627507zM780.007475 830.732507 243.903497 830.732507c-98.599645 0-178.61298-79.969333-178.61298-178.615027l0-536.102954c0-98.730629 80.013335-178.744987 178.61298-178.744987l536.105001 0c98.772584 0 178.700985 80.013335 178.700985 178.744987L958.709483 652.11748C958.70846 750.763175 878.781082 830.732507 780.007475 830.732507zM869.44546 160.646026c0-73.994248-60.119239-134.069485-134.113487-134.069485L288.578999 26.576541c-73.994248 0-134.027529 60.075237-134.027529 134.069485L154.55147 607.356021c0 74.080206 60.033281 134.069485 134.027529 134.069485l446.752973 0c73.994248 0 134.113487-59.989279 134.113487-134.069485L869.44546 160.646026z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="play" unicode="" d="M377.971959 562.680519 690.656471 384.067538 377.971959 205.321528 377.971959 562.680519zM958.70846 652.11748l0-536.102954c0-98.730629-79.971379-178.744987-178.615027-178.744987L243.903497-62.730461c-98.642624 0-178.61298 80.013335-178.61298 178.744987L65.290517 652.11748c0 98.644671 79.969333 178.615027 178.61298 178.615027l536.190959 0C878.73708 830.732507 958.70846 750.763175 958.70846 652.11748zM869.401458 607.356021c0 74.080206-59.989279 134.069485-134.069485 134.069485L288.667004 741.425506c-74.082252 0-134.115534-59.989279-134.115534-134.069485L154.55147 160.690028c0-74.03825 60.033281-134.113487 134.115534-134.113487l446.664969 0c74.080206 0 134.069485 60.075237 134.069485 134.113487L869.401458 607.356021z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="pause" unicode="" d="M333.340459 562.680519l133.939525 0 0-357.357967L333.340459 205.322551 333.340459 562.680519zM556.716946 205.321528l133.983527 0L690.700473 562.680519 556.716946 562.680519 556.716946 205.321528zM958.70846 652.11748l0-536.102954c0-98.730629-79.971379-178.744987-178.700985-178.744987L243.903497-62.730461c-98.642624 0-178.61298 80.013335-178.61298 178.744987L65.290517 652.11748c0 98.644671 79.969333 178.615027 178.61298 178.615027l536.105001 0C878.73708 830.732507 958.70846 750.763175 958.70846 652.11748zM869.44546 607.356021c0 74.080206-60.119239 134.069485-134.113487 134.069485L288.667004 741.425506c-74.082252 0-134.071532-59.989279-134.071532-134.069485l0-446.709995c0-73.994248 59.989279-134.069485 134.071532-134.069485l446.664969 0c73.994248 0 134.113487 60.075237 134.113487 134.069485L869.44546 607.356021z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="next" unicode="" d="M779.991102 830.665992 243.964896 830.665992c-98.585319 0-178.674379-79.958076-178.674379-178.587398L65.290517 116.053412c0-98.717326 80.090083-178.718381 178.674379-178.718381l536.025183 0c98.759281 0 178.717358 80.002079 178.717358 178.718381L958.707436 652.078595C958.70846 750.707916 878.74936 830.665992 779.991102 830.665992zM869.414761 160.699238c0-74.003458-60.067051-134.049019-134.050042-134.049019L288.634258 26.650219c-73.982992 0-133.919059 60.045561-133.919059 134.049019L154.715199 607.323275c0 74.091462 59.936067 134.049019 133.919059 134.049019l446.731484 0c73.982992 0 134.050042-59.957557 134.050042-134.049019L869.415784 160.699238zM646.07102 562.652889l89.293699 0L735.364719 205.347111l-89.293699 0L646.07102 361.687832 333.389578 205.347111 333.389578 562.652889l312.682465-156.318208L646.072043 562.652889z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="previous" unicode="" d="M779.95324 830.644503 243.998665 830.644503c-98.623181 0-178.709171-79.953983-178.709171-178.579211l0-535.9996c0-98.711186 80.08599-178.709171 178.709171-178.709171l535.954575 0c98.755188 0 178.75522 79.997985 178.75522 178.709171L958.70846 652.065292C958.70846 750.69052 878.708428 830.644503 779.95324 830.644503zM869.286848 160.690028c0-73.979922-59.932997-134.042879-133.954875-134.042879L288.623002 26.647149c-73.979922 0-133.912919 60.062957-133.912919 134.042879L154.710083 607.312018c0 74.067926 59.932997 134.042879 133.912919 134.042879l446.708971 0c74.021877 0 133.954875-59.974953 133.954875-134.042879L869.286848 160.690028zM422.709883 406.312168l312.62209 156.333558 0-357.290429L422.709883 361.689878l0-156.333558-89.421612 0L333.288271 562.64675l89.421612 0L422.709883 406.312168z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="repeat" unicode="" d="M779.973706 830.709995 243.9168 830.709995c-98.639554 0-178.627307-79.987752-178.627307-178.627307l0-536.077372c0-98.727559 79.987752-178.715311 178.627307-178.715311l536.056905 0c98.747002 0 178.734754 79.987752 178.734754 178.715311L958.70846 652.082688C958.70846 750.722242 878.720707 830.709995 779.973706 830.709995zM869.385085 160.657282c0-73.990155-60.051701-134.063345-134.063345-134.063345L288.568766 26.593937c-74.011644 0-133.975341 60.07319-133.975341 134.063345l0 446.775486c0 73.990155 59.963697 133.975341 133.975341 133.975341l446.752973 0c74.011644 0 134.063345-59.985186 134.063345-133.975341L869.385085 160.657282zM377.980145 205.328691l401.99356 0-44.651966-89.323375L288.568766 116.005316l0 89.323375 0 89.303932-89.325421 0 134.084834 178.737824 133.954875-178.737824-89.303932 0L377.979122 205.328691zM735.32174 473.369424l89.411379 0-134.063345-178.737824-134.063345 178.737824 89.411379 0 0 89.301885L243.9168 562.671309l44.651966 89.411379 446.752973 0 0-89.411379L735.32174 473.369424z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="volume" unicode="" d="M646.042367 473.357144l44.665269 0 0-178.711218-44.665269 0L646.042367 473.357144zM556.620755 339.399199l44.753273 0 0 89.289606L556.620755 428.688805 556.620755 339.399199zM243.954663 473.357144l0-178.711218 89.421612 0L511.955486 160.690028 511.955486 607.400023 333.376275 473.357144 243.954663 473.357144zM735.375975 249.979634l44.578288 0L779.954263 517.978411l-44.578288 0L735.375975 249.979634zM958.70846 652.065292l0-535.9996c0-98.755188-80.000032-178.709171-178.75522-178.709171L243.954663-62.64348c-98.579179 0-178.665169 79.953983-178.665169 178.709171L65.289494 652.065292c0 98.581226 80.08599 178.579211 178.665169 178.579211l535.998577 0C878.708428 830.644503 958.70846 750.646518 958.70846 652.065292zM869.374852 607.400023c0 73.979922-60.021002 133.954875-133.998877 133.954875L288.623002 741.354897c-73.979922 0-133.912919-59.974953-133.912919-133.954875L154.710083 160.690028c0-73.979922 59.932997-134.042879 133.912919-134.042879l446.752973 0c73.977875 0 133.998877 60.062957 133.998877 134.042879L869.374852 607.400023z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="mute" unicode="" d="M779.780301 435.92972l-74.283844-74.283844 74.283844-74.283844-37.162388-37.164435-74.28589 74.283844-74.283844-74.283844-37.208437 37.164435 74.371848 74.283844-74.371848 74.283844 37.208437 37.208437 74.283844-74.28589 74.28589 74.28589L779.780301 435.92972zM243.998665 473.357144l0-178.711218 89.289606 0L511.999488 160.690028 511.999488 607.400023l-178.711218-134.042879L243.998665 473.357144zM958.70846 652.023336 958.70846 116.022713c0-98.711186-80.000032-178.665169-178.711218-178.665169L243.998665-62.642456c-98.623181 0-178.709171 79.953983-178.709171 178.665169L65.289494 652.023336c0 98.623181 80.08599 178.621167 178.709171 178.621167l535.998577 0C878.708428 830.644503 958.70846 750.646518 958.70846 652.023336zM869.286848 607.400023c0 73.979922-59.977 133.954875-133.954875 133.954875L288.664957 741.354897c-74.021877 0-133.954875-59.974953-133.954875-133.954875L154.710083 160.690028c0-73.979922 59.932997-134.042879 133.954875-134.042879l446.667016 0c73.977875 0 133.954875 60.062957 133.954875 134.042879L869.286848 607.400023z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="fullscreen" unicode="" d="M288.667004 160.558022l178.61298 0 0-44.675502L243.903497 115.88252l0 44.675502L243.903497 339.281519l44.763506 0L288.667004 160.558022zM377.974006 249.973494l268.049942 0L646.023948 518.003993 377.974006 518.003993 377.974006 249.973494zM958.70846 651.98752l0-536.105001c0-98.621135-79.969333-178.615027-178.700985-178.615027L243.903497-62.732507c-98.642624 0-178.61298 79.993892-178.61298 178.615027L65.290517 651.98752c0 98.752118 79.969333 178.744987 178.61298 178.744987l536.105001 0C878.739127 830.732507 958.70846 750.739639 958.70846 651.98752zM869.44546 607.333508c0 74.014714-60.119239 134.069485-134.113487 134.069485L288.667004 741.402993c-74.082252 0-134.069485-60.054771-134.069485-134.069485l0-446.775486c0-73.994248 59.987233-133.983527 134.069485-133.983527l446.664969 0c73.994248 0 134.113487 59.989279 134.113487 133.983527L869.44546 607.333508zM288.667004 607.333508l178.61298 0 0 44.654013L288.667004 651.98752l-44.763506 0 0-223.290529 44.763506 0L288.667004 607.333508zM556.718993 607.333508l178.61298 0 0-178.636516 44.675502 0L780.007475 607.333508l0 44.654013L556.718993 651.98752 556.718993 607.333508zM735.331973 160.558022 556.718993 160.558022l0-44.675502 178.61298 0 44.675502 0L780.007475 339.281519l-44.675502 0L735.331973 160.558022z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="list" unicode="" d="M243.998665 339.289706l89.289606 0 0-89.289606-89.289606 0L243.998665 339.289706zM243.998665 518.000923l89.289606 0 0 89.289606-89.289606 0L243.998665 518.000923zM243.998665 383.954975l89.289606 0 0 89.399099-89.289606 0L243.998665 383.954975zM243.998665 115.956198l89.289606 0 0 89.397053-89.289606 0L243.998665 115.956198zM958.70846 651.955798l0-535.9996c0-98.625228-79.997985-178.600701-178.753173-178.600701l-535.956621 0c-98.623181 0-178.709171 79.975473-178.709171 178.600701L65.289494 651.955798c0 98.711186 80.08599 178.688705 178.709171 178.688705l535.956621 0C878.710474 830.644503 958.70846 750.666984 958.70846 651.955798zM869.286848 607.289506c0 73.999364-59.932997 134.065392-133.954875 134.065392L288.623002 741.354897c-73.979922 0-133.912919-60.065004-133.912919-134.065392L154.710083 160.710494c0-74.087369 59.932997-134.042879 133.912919-134.042879l446.708971 0c74.021877 0 133.954875 59.95551 133.954875 134.042879L869.286848 607.289506zM422.709883 115.956198l357.245404 0 0 89.397053L422.709883 205.35325 422.709883 115.956198zM422.709883 518.000923l357.245404 0 0 89.289606L422.709883 607.290529 422.709883 518.000923zM422.709883 383.954975l357.245404 0 0 89.399099L422.709883 473.354074 422.709883 383.954975zM422.709883 250.0001l357.245404 0 0 89.289606L422.709883 339.289706 422.709883 250.0001z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
<glyph glyph-name="trash" unicode="" d="M556.630988 518.37443c0-0.088004 0.085958-0.217964 0.085958-0.391926-9.247618 0.085958-16.228613 0.085958-22.074761 0.085958C546.508443 518.286426 551.918663 518.286426 556.630988 518.37443zM646.024971 517.982504l133.983527 0 0-89.307002-44.675502 0-44.675502-312.728514-44.6315 0-44.6315 0L422.647461 115.946988 377.971959 115.946988l-44.6315 0-44.673455 312.728514-44.763506 0 0 89.307002L377.971959 517.982504c136.164193-1.264806 69.674872-0.176009 121.94126 0l-32.634258 0c55.843866 1.874697 20.068058 0 44.675502 0 0.871857 0 1.570775 0 2.356674 0l3.40147 0c5.41022 0.085958-21.289885 0.391926 16.928554 0.085958-2.574638 0-5.322216-0.085958-8.55177-0.085958l30.627555 0L646.024971 517.982504zM467.279984 428.675502 377.971959 428.675502l31.892361-223.421512 57.414641 0L467.278961 428.675502zM646.024971 428.675502l-89.307002 0 0-223.421512 57.414641 0L646.024971 428.675502zM958.70846 652.051989l0-536.105001c0-98.730629-79.971379-178.744987-178.700985-178.744987L243.903497-62.797999c-98.642624 0-178.61298 80.013335-178.61298 178.744987L65.290517 652.051989c0 98.730629 79.969333 178.744987 178.61298 178.744987l536.105001 0C878.73708 830.796976 958.70846 750.782617 958.70846 652.051989zM869.44546 607.375463c0 74.03825-60.119239 133.983527-134.113487 133.983527L288.667004 741.358991c-74.082252 0-134.071532-59.945277-134.071532-133.983527l0-446.753997c0-74.03825 59.989279-133.983527 134.071532-133.983527l446.664969 0c73.994248 0 134.113487 59.945277 134.113487 133.983527L869.44546 607.375463zM511.955486 562.743964c24.51944 0 44.455491-19.850094 44.675502-44.369533 10.556427 0.305969 17.97335 0.479931 89.39296-0.391926 0 74.080206-59.989279 134.069485-134.069485 134.069485-73.994248 0-133.983527-59.989279-133.983527-134.069485l89.307002 0C467.279984 542.719908 487.348042 562.743964 511.955486 562.743964z" horiz-adv-x="1024" />
|
||||
|
||||
|
||||
|
||||
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
|
||||
.control-bal {
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
min-width: 688px;
|
||||
/* position: absolute;
|
||||
bottom: 0; */
|
||||
color: #dddede;
|
||||
}
|
||||
|
||||
.control-bal a {
|
||||
color: #dddede;
|
||||
}
|
||||
|
||||
.control-bal>.cb-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
opacity: .3;
|
||||
filter: alpha(opacity=30);
|
||||
box-shadow: 0 -10px 20px #403f3f;
|
||||
}
|
||||
|
||||
.control-bal>.cb-main {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.control-bal .ant-slider .ant-slider-rail {
|
||||
background-color: #fff;
|
||||
opacity: .3;
|
||||
}
|
||||
|
||||
.control-bal .ant-slider .ant-slider-track {
|
||||
background-color: #fff;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.control-bal .ant-slider:hover .ant-slider-track {
|
||||
background-color: #fff;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
/* .control-bal .ant-slider .ant-slider-handle {
|
||||
border-color: rgba(255, 255, 255, .6);
|
||||
} */
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { Component } from 'react';
|
||||
import PlayerButton from './PlayerButton';
|
||||
import Progress from './Progress';
|
||||
import MediaCtrList from './MediaCtrList';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import './ControlBar.css';
|
||||
import './Progress.css';
|
||||
import './MediaCtrList.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class ControlBar extends Component {
|
||||
timeUpdateHandler = (event) => {
|
||||
DataStore.currentTime = event.target.currentTime;
|
||||
}
|
||||
|
||||
canPlayHandler = (event) => {
|
||||
DataStore.audio = event.target;
|
||||
DataStore.volume = DataStore.volume;
|
||||
}
|
||||
|
||||
endedHandler = (event) => {
|
||||
DataStore.count = DataStore.count + 1;
|
||||
}
|
||||
|
||||
setAudio = (url) => {
|
||||
if(url) {
|
||||
return (<audio
|
||||
src={url}
|
||||
style={{display:'none'}}
|
||||
autoPlay={true}
|
||||
loop={DataStore.loop}
|
||||
ref='audio'
|
||||
volume={DataStore.silent?0:DataStore.volume}
|
||||
onTimeUpdate={this.timeUpdateHandler}
|
||||
onCanPlay={this.canPlayHandler}
|
||||
onEnded={this.endedHandler}
|
||||
/>);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-bal">
|
||||
<div id="audio-box"
|
||||
style={{display:'none'}}>
|
||||
{this.setAudio(DataStore.coutSongMassge.PlayUrl)}
|
||||
</div>
|
||||
<div className="cb-main">
|
||||
<PlayerButton />
|
||||
<Progress />
|
||||
<MediaCtrList />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ControlBar;
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
.cover-lyrics {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.cover-box {
|
||||
width: 100%;
|
||||
/* height: 270px; */
|
||||
margin: 20px 0 30px;
|
||||
}
|
||||
|
||||
.cover-img {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.cover-img>img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cover-song-msg {
|
||||
color: #fff;
|
||||
opacity: .6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cover-song-msg>p {
|
||||
margin: 0;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.lyrics-box {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.lyrics-box>ul {
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.lyrics-box li {
|
||||
line-height: 50px;
|
||||
opacity: .6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.lyrics-box .cur {
|
||||
opacity: 1;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import React, { Component } from 'react';
|
||||
//import FreeScrollBar from 'react-free-scrollbar';
|
||||
import { observer } from 'mobx-react';
|
||||
import './CoverLyrics.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class CoverLyrics extends Component {
|
||||
|
||||
lightPosition = () => {
|
||||
let indexArr = [];
|
||||
for (let i = 0; i < DataStore.coutSongMassge.Lyrics.length; i++) {
|
||||
if (DataStore.currentTime > DataStore.coutSongMassge.Lyrics[i][0]) {
|
||||
indexArr.push(i);
|
||||
};
|
||||
};
|
||||
return indexArr[indexArr.length - 1];
|
||||
}
|
||||
|
||||
listItems = () => {
|
||||
if(DataStore.coutSongMassge.Lyrics.length > 0) {
|
||||
return DataStore.coutSongMassge.Lyrics.map((item,index) =>
|
||||
<li key ={index} className={this.lightPosition() === index ? 'cur' : ''}>{item[1]}</li>
|
||||
);
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
marginSize = () => {
|
||||
let row = 5;
|
||||
let mid = Math.floor(row/2);
|
||||
let step = 0;
|
||||
if(this.lightPosition() - mid < 0) {
|
||||
step = 0;
|
||||
}else if(DataStore.coutSongMassge.Lyrics.length - this.lightPosition() < row) {
|
||||
step = DataStore.coutSongMassge.Lyrics.length - row;
|
||||
}else{
|
||||
step = this.lightPosition() - mid
|
||||
}
|
||||
return -50 * step + 'px';
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="cover-lyrics">
|
||||
<div className="cover-box">
|
||||
<div className="cover-img">
|
||||
<img src={DataStore.coutSongMassge.Img}
|
||||
alt={DataStore.coutSongMassge.SongName + '-' +DataStore.coutSongMassge.SingerName}/>
|
||||
</div>
|
||||
<div className="cover-song-msg">
|
||||
<p>歌曲名:{DataStore.coutSongMassge.SongName}</p>
|
||||
<p>歌手名:{DataStore.coutSongMassge.SingerName}</p>
|
||||
<p>专辑名:{DataStore.coutSongMassge.AlbumName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lyrics-box">
|
||||
<ul style={{marginTop:this.marginSize()}}>
|
||||
{this.listItems()}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CoverLyrics;
|
||||
@@ -0,0 +1,33 @@
|
||||
.media-ctr-list-box {
|
||||
width: 182px;
|
||||
height: 36px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 12px;
|
||||
}
|
||||
.icon-ctr {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
float: left;
|
||||
}
|
||||
.icon-item {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.icon-item>button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 36px;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.icon-item>button:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.volume-ctr {
|
||||
margin-left: 20px;
|
||||
float: left;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { Component } from 'react';
|
||||
import Volume from './Volume';
|
||||
import { observer } from 'mobx-react';
|
||||
import './MediaCtrList.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class MediaCtrList extends Component {
|
||||
|
||||
onClickListIcon = (event) => {
|
||||
DataStore.showPlayList = !DataStore.showPlayList;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="media-ctr-list-box">
|
||||
<div className="icon-ctr">
|
||||
<div className="icon-item">
|
||||
<button className={"iconfont icon-" + (DataStore.showPlayList ? 'fullscreen' : 'list')}
|
||||
onClick={this.onClickListIcon}></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="volume-ctr">
|
||||
<Volume />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MediaCtrList;
|
||||
@@ -0,0 +1,87 @@
|
||||
.play-list-box {
|
||||
width: 300px;
|
||||
min-height: 612px;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/* opacity: 1;
|
||||
transition: all .6s; */
|
||||
}
|
||||
|
||||
.play-list-box-hide {
|
||||
right: -100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.play-list-box>h1 {
|
||||
color: #fff;
|
||||
opacity: .8;
|
||||
font-size: 18px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.play-list-header {
|
||||
overflow: hidden;
|
||||
border-bottom:rgba(255,255,255,.1) solid 1px;
|
||||
}
|
||||
|
||||
.play-list-body {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.play-list-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.play-list-row>li {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.play-list-row>.sort {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.play-list-row>.song-name {
|
||||
flex:1;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.play-list-row>.singer-name {
|
||||
width: 80px;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.play-list-row>.operate {
|
||||
width: 80px;
|
||||
padding-right: 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.play-list-row>.operate>button {
|
||||
font-size: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.play-list-row>.operate>button:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
ul.cur>.sort,ul.cur>.song-name,ul.cur>.singer-name {
|
||||
opacity: .8;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import ScrollArea from 'react-scrollbar';
|
||||
import './PlayList.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class PlayList extends Component {
|
||||
onClickRemoveIcon = (event) => {
|
||||
let hash = event.target.getAttribute('hash');
|
||||
DataStore.removeInSongList(hash);
|
||||
}
|
||||
|
||||
onClickPlayIcon = (event) => {
|
||||
let hash = event.target.getAttribute('hash');
|
||||
let index = DataStore.findInPlayList(hash);
|
||||
DataStore.count = index;
|
||||
DataStore.isPlay = true;
|
||||
}
|
||||
|
||||
onClearSongList = (event) => {
|
||||
DataStore.clearSongList();
|
||||
}
|
||||
|
||||
ListItems = (list) => {
|
||||
//console.log(list);
|
||||
if(list.length > 0) {
|
||||
let listItems = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
listItems.push(
|
||||
<ul className={"play-list-row" + (i === DataStore.count ? ' cur' : '')}
|
||||
key={list[i].Hash}>
|
||||
<li className="sort">{i + 1}</li>
|
||||
<li className="song-name ell">{list[i].SongName}</li>
|
||||
<li className="singer-name ell">{list[i].SingerName}</li>
|
||||
<li className="operate">
|
||||
<button className="iconfont icon-remove"
|
||||
hash={list[i].Hash}
|
||||
onClick={this.onClickRemoveIcon}></button>
|
||||
<button className="iconfont icon-play"
|
||||
hash={list[i].Hash}
|
||||
onClick={this.onClickPlayIcon}></button>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
return (<div className="play-list-body"
|
||||
style={{height:DataStore.scrollHeight + 'px'}}>
|
||||
<ScrollArea
|
||||
horizontal={false}>
|
||||
{listItems}
|
||||
</ScrollArea>
|
||||
</div>);
|
||||
}else{
|
||||
return (<div className="play-list-body">暂无数据</div>);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={"play-list-box" + (DataStore.showPlayList ? '' : ' hide')}>
|
||||
<h1>我的歌曲播放列表</h1>
|
||||
<div className="play-list-header">
|
||||
<ul className="play-list-row">
|
||||
<li className="sort"></li>
|
||||
<li className="song-name">歌曲</li>
|
||||
<li className="singer-name">歌手</li>
|
||||
<li className="operate">
|
||||
<button className="iconfont icon-trash"
|
||||
onClick={this.onClearSongList}></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{this.ListItems(DataStore.playSongList.slice())}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PlayList;
|
||||
@@ -0,0 +1,32 @@
|
||||
.player-button {
|
||||
width: 140px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
}
|
||||
|
||||
.player-button>button{
|
||||
position: absolute;
|
||||
font-size: 36px;
|
||||
line-height: 36px;
|
||||
opacity: .8;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.player-button>button:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.player-button>.left-btn {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.player-button>.mid-btn {
|
||||
left:52px;
|
||||
}
|
||||
|
||||
.player-button>.right-btn {
|
||||
left: 104px;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import './PlayerButton.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class PlayerButton extends Component {
|
||||
clickPlayButton = (event) => {
|
||||
DataStore.isPlay = !DataStore.isPlay;
|
||||
}
|
||||
|
||||
clickPrevButton = (event) => {
|
||||
DataStore.count = DataStore.count - 1;
|
||||
}
|
||||
|
||||
clickNextButton = (event) => {
|
||||
DataStore.count = DataStore.count + 1;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="player-button">
|
||||
<button className="iconfont left-btn icon-previous"
|
||||
onClick={this.clickPrevButton}></button>
|
||||
<button className={"iconfont mid-btn " + (DataStore.isPlay ? "icon-play" : "icon-pause")}
|
||||
onClick={this.clickPlayButton}></button>
|
||||
<button className="iconfont right-btn icon-next"
|
||||
onClick={this.clickNextButton}></button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PlayerButton;
|
||||
@@ -0,0 +1,32 @@
|
||||
.progress-box {
|
||||
|
||||
margin: 0 208px 0 166px;
|
||||
}
|
||||
|
||||
.song-msg {
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
/* color: #dddede; */
|
||||
color: #fff;
|
||||
position: relative;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.song-title {
|
||||
margin-right: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.song-time {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.progress-box>.ant-slider {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Slider } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import './Progress.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class Progress extends Component {
|
||||
|
||||
timeFormat = (time) => {
|
||||
let surplusMinite,
|
||||
surplusSecond,
|
||||
cTime;
|
||||
//将剩余秒数转化为分钟
|
||||
surplusMinite = Math.floor((time / 60) % 60);
|
||||
//将剩余秒数转化为秒钟
|
||||
surplusSecond = Math.floor(time % 60);
|
||||
if(surplusMinite < 10){
|
||||
surplusMinite = '0' + surplusMinite;
|
||||
}
|
||||
if(surplusSecond < 10){
|
||||
surplusSecond = '0' + surplusSecond;
|
||||
}
|
||||
cTime = surplusMinite + ':' + surplusSecond;
|
||||
|
||||
return cTime;
|
||||
}
|
||||
|
||||
sliderValue = (time) => {
|
||||
if(time <= 0) return 0;
|
||||
return Math.floor(time / DataStore.coutSongMassge.Duration * 100);
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
if(DataStore.audio) DataStore.audio.currentTime = Math.floor(value / 100 * DataStore.coutSongMassge.Duration);
|
||||
//DataStore.currentTime = Math.floor(value / 100 * DataStore.coutSongMassge.Duration);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="progress-box">
|
||||
<div className="song-msg">
|
||||
<div className="song-title ell">{DataStore.coutSongMassge.SongName}-{DataStore.coutSongMassge.SingerName}</div>
|
||||
<div className="song-time">{this.timeFormat(DataStore.currentTime)} / {this.timeFormat(DataStore.coutSongMassge.Duration)}</div>
|
||||
</div>
|
||||
<Slider
|
||||
defaultValue={0}
|
||||
value={this.sliderValue(DataStore.currentTime)}
|
||||
disabled={DataStore.disabled}
|
||||
tipFormatter={null}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Progress;
|
||||
@@ -0,0 +1,43 @@
|
||||
.search-box {
|
||||
position: relative;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
margin-right: 46px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-input>input {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 20px;
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
padding: 5px 10px;
|
||||
margin: 3px 0;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(255, 255, 255, .6);
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
line-height: 36px;
|
||||
height: 100%;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.search-btn button {
|
||||
font-size: 36px;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.search-btn button:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import './SearchBar.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class SearchBar extends Component {
|
||||
|
||||
state = {
|
||||
value:''
|
||||
}
|
||||
|
||||
onClickBtn = () => {
|
||||
if(this.state.value) {
|
||||
DataStore.keyword = this.state.value;
|
||||
DataStore.page = 1;
|
||||
DataStore.searchSongListStore();
|
||||
}else{
|
||||
DataStore.keyword = '';
|
||||
DataStore.page = 0;
|
||||
DataStore.newSongListStore();
|
||||
}
|
||||
}
|
||||
|
||||
onTextChange = (event) => {
|
||||
this.setState({
|
||||
value: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="search-box">
|
||||
<div className="search-input">
|
||||
<input type="text" placeholder="搜索歌曲或歌手"
|
||||
value={this.state.value}
|
||||
onChange={this.onTextChange} />
|
||||
</div>
|
||||
<div className="search-btn">
|
||||
<button className="iconfont icon-search"
|
||||
onClick={this.onClickBtn}></button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SearchBar;
|
||||
@@ -0,0 +1,94 @@
|
||||
|
||||
.song-list-header {
|
||||
overflow: hidden;
|
||||
border-bottom:rgba(255,255,255,.1) solid 1px;
|
||||
}
|
||||
|
||||
.song-list-main {
|
||||
margin-top: 10px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.song-list-header>.song-list-row {
|
||||
margin-bottom: 10px
|
||||
}
|
||||
|
||||
.song-list-body {
|
||||
margin-top: 10px;
|
||||
/* margin-bottom: 10px; */
|
||||
height: 480px;
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* .scrollarea-content {
|
||||
height: 100%;
|
||||
} */
|
||||
.scrollarea .scrollbar-container.vertical .scrollbar {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.song-list-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.song-list-row>li {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.song-list-row>.sort {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.song-list-row>.song-name {
|
||||
flex:1;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.song-list-row>.singer-name {
|
||||
width: 25%;
|
||||
min-width: 150px;
|
||||
padding-left: 20px;
|
||||
box-sizing: border-box;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.song-list-row>.operate {
|
||||
width: 15%;
|
||||
min-width: 80px;
|
||||
padding-right: 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.song-list-row>.operate>button {
|
||||
font-size: 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.song-list-row>.operate>button:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.more-box {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.more-box>button {
|
||||
opacity: .8;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { Component } from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import ScrollArea from 'react-scrollbar';
|
||||
import './SongList.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class SongList extends Component {
|
||||
|
||||
onClickAddIcon = (event) => {
|
||||
let hash = event.target.getAttribute('hash');
|
||||
DataStore.addSongInList(hash);
|
||||
//console.log(DataStore.playSongList.slice());
|
||||
}
|
||||
|
||||
onClickPlayIcon = (event) => {
|
||||
let hash = event.target.getAttribute('hash');
|
||||
DataStore.addSongAndPlay(hash);
|
||||
DataStore.isPlay = true;
|
||||
}
|
||||
|
||||
onClickMore = (event) => {
|
||||
DataStore.page = DataStore.page + 1;
|
||||
DataStore.searchSongListStore();
|
||||
}
|
||||
|
||||
ListItems = (list) => {
|
||||
//console.log(list);
|
||||
if(list.length > 0) {
|
||||
let listItems = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
listItems.push(
|
||||
<ul className="song-list-row" key={list[i].Hash}>
|
||||
<li className="sort">{i + 1}</li>
|
||||
<li className="song-name ell">{list[i].SongName}</li>
|
||||
<li className="singer-name ell">{list[i].SingerName}</li>
|
||||
<li className="operate">
|
||||
<button className="iconfont icon-add"
|
||||
hash={list[i].Hash}
|
||||
onClick={this.onClickAddIcon}></button>
|
||||
<button className="iconfont icon-play"
|
||||
hash={list[i].Hash}
|
||||
onClick={this.onClickPlayIcon}></button>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
if(DataStore.listType === 0 && list.length >= DataStore.pageSize) {
|
||||
listItems.push(
|
||||
<div className="more-box">
|
||||
<a onClick={this.onClickMore}>点击加载更多</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (<div className="song-list-body"
|
||||
style={{height:DataStore.scrollHeight + 'px'}}>
|
||||
<ScrollArea
|
||||
horizontal={false}>
|
||||
{listItems}
|
||||
</ScrollArea>
|
||||
</div>);
|
||||
}else{
|
||||
return (<div className="song-list-body">空空如也,赶紧去添加歌曲吧!</div>);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
DataStore.newSongListStore();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="song-list-main">
|
||||
<div className="song-list-header">
|
||||
<ul className="song-list-row">
|
||||
<li className="sort"></li>
|
||||
<li className="song-name">歌曲</li>
|
||||
<li className="singer-name">歌手</li>
|
||||
<li className="operate"></li>
|
||||
</ul>
|
||||
</div>
|
||||
{this.ListItems(DataStore.searchSongList.slice())}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SongList;
|
||||
@@ -0,0 +1,34 @@
|
||||
.volume-box {
|
||||
width: 126px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.volume-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.volume-icon>button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 36px;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.volume-icon>button:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.volume-slider {
|
||||
width: 80px;
|
||||
height: 36px;
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.volume-slider>.ant-slider {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Slider } from 'antd';
|
||||
import { observer } from 'mobx-react';
|
||||
import './Volume.css';
|
||||
|
||||
import { DataStore } from '../DataStore';
|
||||
|
||||
@observer
|
||||
class Volume extends Component {
|
||||
|
||||
handleClick = (e) => {
|
||||
DataStore.silent = !DataStore.silent;
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
if(value === 0) {
|
||||
DataStore.silent = true;
|
||||
}else{
|
||||
DataStore.silent = false;
|
||||
}
|
||||
DataStore.volume = value / 100;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="volume-box">
|
||||
<div className="volume-icon">
|
||||
<button className={(DataStore.silent?"icon-mute":"icon-volume") + " iconfont"}
|
||||
onClick={this.handleClick}></button>
|
||||
</div>
|
||||
<div className="volume-slider">
|
||||
<Slider
|
||||
defaultValue={DataStore.volume * 100}
|
||||
value={DataStore.volume * 100}
|
||||
disabled={DataStore.disabled}
|
||||
tipFormatter={null}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Volume;
|
||||
@@ -0,0 +1,24 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ell {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import registerServiceWorker from './registerServiceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
registerServiceWorker();
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,117 @@
|
||||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://goo.gl/SC7cgQ'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user