mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-03 22:57:18 +02:00
New: Many UI Updates and Performance Tweaks
This commit is contained in:
parent
b24a40797f
commit
6275737ced
@ -28,6 +28,12 @@
|
|||||||
"react"
|
"react"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"filenames/match-exported": ["error"],
|
"filenames/match-exported": ["error"],
|
||||||
|
|
||||||
|
35
frontend/babel.config.js
Normal file
35
frontend/babel.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const loose = true;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
// Stage 1
|
||||||
|
'@babel/plugin-proposal-export-default-from',
|
||||||
|
['@babel/plugin-proposal-optional-chaining', { loose }],
|
||||||
|
['@babel/plugin-proposal-nullish-coalescing-operator', { loose }],
|
||||||
|
|
||||||
|
// Stage 2
|
||||||
|
'@babel/plugin-proposal-export-namespace-from',
|
||||||
|
|
||||||
|
// Stage 3
|
||||||
|
['@babel/plugin-proposal-class-properties', { loose }],
|
||||||
|
'@babel/plugin-syntax-dynamic-import'
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
development: {
|
||||||
|
presets: [
|
||||||
|
['@babel/preset-react', { development: true }]
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'babel-plugin-inline-classnames'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-react'
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'babel-plugin-transform-react-remove-prop-types'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
6
frontend/browsers.js
Normal file
6
frontend/browsers.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = [
|
||||||
|
'>0.25%',
|
||||||
|
'not ie 11',
|
||||||
|
'not op_mini all',
|
||||||
|
'not chrome < 60'
|
||||||
|
];
|
@ -1,15 +1,18 @@
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const runSequence = require('run-sequence');
|
|
||||||
|
|
||||||
require('./clean');
|
require('./clean');
|
||||||
require('./copy');
|
require('./copy');
|
||||||
|
require('./webpack');
|
||||||
|
|
||||||
|
gulp.task('build',
|
||||||
|
gulp.series('clean',
|
||||||
|
gulp.parallel(
|
||||||
|
'webpack',
|
||||||
|
'copyHtml',
|
||||||
|
'copyFonts',
|
||||||
|
'copyImages',
|
||||||
|
'copyJs'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
gulp.task('build', () => {
|
|
||||||
return runSequence('clean', [
|
|
||||||
'webpack',
|
|
||||||
'copyHtml',
|
|
||||||
'copyFonts',
|
|
||||||
'copyImages',
|
|
||||||
'copyJs'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
var path = require('path');
|
const path = require('path');
|
||||||
var gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
var print = require('gulp-print').default;
|
const print = require('gulp-print').default;
|
||||||
var cache = require('gulp-cached');
|
const cache = require('gulp-cached');
|
||||||
var livereload = require('gulp-livereload');
|
const livereload = require('gulp-livereload');
|
||||||
var paths = require('./helpers/paths.js');
|
const paths = require('./helpers/paths.js');
|
||||||
|
|
||||||
gulp.task('copyJs', () => {
|
gulp.task('copyJs', () => {
|
||||||
return gulp.src(
|
return gulp.src(
|
||||||
[
|
[
|
||||||
path.join(paths.src.root, 'polyfills.js')
|
path.join(paths.src.root, 'polyfills.js')
|
||||||
])
|
], { base: paths.src.root })
|
||||||
.pipe(cache('copyJs'))
|
.pipe(cache('copyJs'))
|
||||||
.pipe(print())
|
.pipe(print())
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
.pipe(gulp.dest(paths.dest.root))
|
||||||
@ -17,7 +17,7 @@ gulp.task('copyJs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copyHtml', () => {
|
gulp.task('copyHtml', () => {
|
||||||
return gulp.src(paths.src.html)
|
return gulp.src(paths.src.html, { base: paths.src.root })
|
||||||
.pipe(cache('copyHtml'))
|
.pipe(cache('copyHtml'))
|
||||||
.pipe(print())
|
.pipe(print())
|
||||||
.pipe(gulp.dest(paths.dest.root))
|
.pipe(gulp.dest(paths.dest.root))
|
||||||
@ -26,20 +26,20 @@ gulp.task('copyHtml', () => {
|
|||||||
|
|
||||||
gulp.task('copyFonts', () => {
|
gulp.task('copyFonts', () => {
|
||||||
return gulp.src(
|
return gulp.src(
|
||||||
path.join(paths.src.fonts, '**', '*.*')
|
path.join(paths.src.fonts, '**', '*.*'), { base: paths.src.root }
|
||||||
)
|
)
|
||||||
.pipe(cache('copyFonts'))
|
.pipe(cache('copyFonts'))
|
||||||
.pipe(print())
|
.pipe(print())
|
||||||
.pipe(gulp.dest(paths.dest.fonts))
|
.pipe(gulp.dest(paths.dest.root))
|
||||||
.pipe(livereload());
|
.pipe(livereload());
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copyImages', () => {
|
gulp.task('copyImages', () => {
|
||||||
return gulp.src(
|
return gulp.src(
|
||||||
path.join(paths.src.images, '**', '*.*')
|
path.join(paths.src.images, '**', '*.*'), { base: paths.src.root }
|
||||||
)
|
)
|
||||||
.pipe(cache('copyImages'))
|
.pipe(cache('copyImages'))
|
||||||
.pipe(print())
|
.pipe(print())
|
||||||
.pipe(gulp.dest(paths.dest.images))
|
.pipe(gulp.dest(paths.dest.root))
|
||||||
.pipe(livereload());
|
.pipe(livereload());
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
require('./build.js');
|
require('./build.js');
|
||||||
require('./clean.js');
|
require('./clean.js');
|
||||||
require('./copy.js');
|
require('./copy.js');
|
||||||
require('./imageMin.js');
|
|
||||||
require('./start.js');
|
|
||||||
require('./stripBom.js');
|
|
||||||
require('./watch.js');
|
require('./watch.js');
|
||||||
require('./webpack.js');
|
require('./webpack.js');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const gulpUtil = require('gulp-util');
|
const colors = require('ansi-colors');
|
||||||
|
|
||||||
module.exports = function errorHandler(error) {
|
module.exports = function errorHandler(error) {
|
||||||
gulpUtil.log(gulpUtil.colors.red(`Error (${error.plugin}): ${error.message}`));
|
console.log(colors.red(`Error (${error.plugin}): ${error.message}`));
|
||||||
this.emit('end');
|
this.emit('end');
|
||||||
};
|
};
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const rootPath = path.resolve(__dirname + '/../../src/');
|
|
||||||
module.exports = function(source) {
|
|
||||||
if (this.cacheable) {
|
|
||||||
this.cacheable();
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourcePath = this.resourcePath.replace(rootPath, '');
|
|
||||||
const wrappedSource =`
|
|
||||||
<!-- begin ${resourcePath} -->
|
|
||||||
${source}
|
|
||||||
<!-- end ${resourcePath} -->`;
|
|
||||||
|
|
||||||
return wrappedSource;
|
|
||||||
};
|
|
@ -1,15 +1,15 @@
|
|||||||
const root = './frontend/src/';
|
const root = './frontend/src';
|
||||||
|
|
||||||
const paths = {
|
const paths = {
|
||||||
src: {
|
src: {
|
||||||
root,
|
root,
|
||||||
html: root + '*.html',
|
html: `${root}/*.html`,
|
||||||
scripts: root + '**/*.js',
|
scripts: `${root}/**/*.js`,
|
||||||
content: root + 'Content/',
|
content: `${root}/Content/`,
|
||||||
fonts: root + 'Content/Fonts/',
|
fonts: `${root}/Content/Fonts/`,
|
||||||
images: root + 'Content/Images/',
|
images: `${root}/Content/Images/`,
|
||||||
exclude: {
|
exclude: {
|
||||||
libs: `!${root}JsLibraries/**`
|
libs: `!${root}/JsLibraries/**`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dest: {
|
dest: {
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
var gulp = require('gulp');
|
|
||||||
var print = require('gulp-print').default;
|
|
||||||
var paths = require('./helpers/paths.js');
|
|
||||||
|
|
||||||
gulp.task('imageMin', () => {
|
|
||||||
var imagemin = require('gulp-imagemin');
|
|
||||||
return gulp.src(paths.src.images)
|
|
||||||
.pipe(imagemin({
|
|
||||||
progressive: false,
|
|
||||||
optimizationLevel: 4,
|
|
||||||
svgoPlugins: [{ removeViewBox: false }]
|
|
||||||
}))
|
|
||||||
.pipe(print())
|
|
||||||
.pipe(gulp.dest(paths.src.content + 'Images/'));
|
|
||||||
});
|
|
@ -1,104 +0,0 @@
|
|||||||
// will download and run radarr (server) in a non-windows enviroment
|
|
||||||
// you can use this if you don't care about the server code and just want to work
|
|
||||||
// with the web code.
|
|
||||||
|
|
||||||
var http = require('http');
|
|
||||||
var gulp = require('gulp');
|
|
||||||
var fs = require('fs');
|
|
||||||
var targz = require('tar.gz');
|
|
||||||
var del = require('del');
|
|
||||||
var spawn = require('child_process').spawn;
|
|
||||||
|
|
||||||
function download(url, dest, cb) {
|
|
||||||
console.log('Downloading ' + url + ' to ' + dest);
|
|
||||||
var file = fs.createWriteStream(dest);
|
|
||||||
http.get(url, function(response) {
|
|
||||||
response.pipe(file);
|
|
||||||
file.on('finish', function() {
|
|
||||||
console.log('Download completed');
|
|
||||||
file.close(cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLatest(cb) {
|
|
||||||
var branch = 'develop';
|
|
||||||
process.argv.forEach(function(val) {
|
|
||||||
var branchMatch = /branch=([\S]*)/.exec(val);
|
|
||||||
if (branchMatch && branchMatch.length > 1) {
|
|
||||||
branch = branchMatch[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var url = 'http://radarr.aeonlucid.com/v1/update/' + branch + '?os=osx';
|
|
||||||
|
|
||||||
console.log('Checking for latest version:', url);
|
|
||||||
|
|
||||||
http.get(url, function(res) {
|
|
||||||
var data = '';
|
|
||||||
|
|
||||||
res.on('data', function(chunk) {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('end', function() {
|
|
||||||
var updatePackage = JSON.parse(data).updatePackage;
|
|
||||||
console.log('Latest version available: ' + updatePackage.version + ' Release Date: ' + updatePackage.releaseDate);
|
|
||||||
cb(updatePackage);
|
|
||||||
});
|
|
||||||
}).on('error', function(e) {
|
|
||||||
console.log('problem with request: ' + e.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function extract(source, dest, cb) {
|
|
||||||
console.log('extracting download page to ' + dest);
|
|
||||||
new targz().extract(source, dest, function(err) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
console.log('Update package extracted.');
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('getSonarr', function() {
|
|
||||||
try {
|
|
||||||
fs.mkdirSync('./_start/');
|
|
||||||
} catch (e) {
|
|
||||||
if (e.code !== 'EEXIST') {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getLatest(function(updatePackage) {
|
|
||||||
var packagePath = './_start/' + updatePackage.filename;
|
|
||||||
var dirName = './_start/' + updatePackage.version;
|
|
||||||
download(updatePackage.url, packagePath, function() {
|
|
||||||
extract(packagePath, dirName, function() {
|
|
||||||
// clean old binaries
|
|
||||||
console.log('Cleaning old binaries');
|
|
||||||
del.sync(['./_output/*', '!./_output/UI/']);
|
|
||||||
console.log('copying binaries to target');
|
|
||||||
gulp.src(dirName + '/NzbDrone/*.*')
|
|
||||||
.pipe(gulp.dest('./_output/'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('startSonarr', function() {
|
|
||||||
var ls = spawn('mono', ['--debug', './_output/Radarr.exe']);
|
|
||||||
|
|
||||||
ls.stdout.on('data', function(data) {
|
|
||||||
process.stdout.write(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
ls.stderr.on('data', function(data) {
|
|
||||||
process.stdout.write(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
ls.on('close', function(code) {
|
|
||||||
console.log('child process exited with code ' + code);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,13 +0,0 @@
|
|||||||
const gulp = require('gulp');
|
|
||||||
const paths = require('./helpers/paths.js');
|
|
||||||
const stripbom = require('gulp-stripbom');
|
|
||||||
|
|
||||||
function stripBom(dest) {
|
|
||||||
gulp.src([paths.src.scripts, paths.src.exclude.libs])
|
|
||||||
.pipe(stripbom({ showLog: false }))
|
|
||||||
.pipe(gulp.dest(dest));
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('stripBom', () => {
|
|
||||||
stripBom(paths.src.root);
|
|
||||||
});
|
|
@ -1,27 +1,18 @@
|
|||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const livereload = require('gulp-livereload');
|
const livereload = require('gulp-livereload');
|
||||||
const watch = require('gulp-watch');
|
const gulpWatch = require('gulp-watch');
|
||||||
const paths = require('./helpers/paths.js');
|
const paths = require('./helpers/paths.js');
|
||||||
|
|
||||||
require('./copy.js');
|
require('./copy.js');
|
||||||
require('./webpack.js');
|
require('./webpack.js');
|
||||||
|
|
||||||
function watchTask(glob, task) {
|
function watch() {
|
||||||
const options = {
|
|
||||||
name: `watch: ${task}`,
|
|
||||||
verbose: true
|
|
||||||
};
|
|
||||||
return watch(glob, options, () => {
|
|
||||||
gulp.start(task);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task('watch', ['copyHtml', 'copyFonts', 'copyImages', 'copyJs'], () => {
|
|
||||||
livereload.listen({ start: true });
|
livereload.listen({ start: true });
|
||||||
|
|
||||||
gulp.start('webpackWatch');
|
gulp.task('webpackWatch')();
|
||||||
|
gulpWatch(paths.src.html, gulp.series('copyHtml'));
|
||||||
|
gulpWatch(`${paths.src.fonts}**/*.*`, gulp.series('copyFonts'));
|
||||||
|
gulpWatch(`${paths.src.images}**/*.*`, gulp.series('copyImages'));
|
||||||
|
}
|
||||||
|
|
||||||
watchTask(paths.src.html, 'copyHtml');
|
gulp.task('watch', gulp.series('build', watch));
|
||||||
watchTask(`${paths.src.fonts}**/*.*`, 'copyFonts');
|
|
||||||
watchTask(`${paths.src.images}**/*.*`, 'copyImages');
|
|
||||||
});
|
|
||||||
|
@ -4,14 +4,15 @@ const livereload = require('gulp-livereload');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const errorHandler = require('./helpers/errorHandler');
|
const errorHandler = require('./helpers/errorHandler');
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
const browsers = require('../browsers');
|
||||||
|
|
||||||
const uiFolder = 'UI';
|
const uiFolder = 'UI';
|
||||||
const root = path.join(__dirname, '..', 'src');
|
const frontendFolder = path.join(__dirname, '..');
|
||||||
|
const srcFolder = path.join(frontendFolder, 'src');
|
||||||
const isProduction = process.argv.indexOf('--production') > -1;
|
const isProduction = process.argv.indexOf('--production') > -1;
|
||||||
|
|
||||||
console.log('ROOT:', root);
|
console.log('Source Folder:', srcFolder);
|
||||||
console.log('isProduction:', isProduction);
|
console.log('isProduction:', isProduction);
|
||||||
|
|
||||||
const cssVarsFiles = [
|
const cssVarsFiles = [
|
||||||
@ -21,40 +22,19 @@ const cssVarsFiles = [
|
|||||||
'../src/Styles/Variables/animations'
|
'../src/Styles/Variables/animations'
|
||||||
].map(require.resolve);
|
].map(require.resolve);
|
||||||
|
|
||||||
const extractCSSPlugin = new ExtractTextPlugin({
|
|
||||||
filename: path.join('_output', uiFolder, 'Content', 'styles.css'),
|
|
||||||
allChunks: true,
|
|
||||||
disable: false,
|
|
||||||
ignoreOrder: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
extractCSSPlugin,
|
|
||||||
|
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
|
||||||
name: 'vendor'
|
|
||||||
}),
|
|
||||||
|
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__DEV__: !isProduction,
|
__DEV__: !isProduction,
|
||||||
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||||
|
}),
|
||||||
|
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: path.join('_output', uiFolder, 'Content', 'styles.css')
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isProduction) {
|
|
||||||
plugins.push(new UglifyJSPlugin({
|
|
||||||
sourceMap: true,
|
|
||||||
uglifyOptions: {
|
|
||||||
mangle: false,
|
|
||||||
output: {
|
|
||||||
comments: false,
|
|
||||||
beautify: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
mode: isProduction ? 'production' : 'development',
|
||||||
devtool: '#source-map',
|
devtool: '#source-map',
|
||||||
|
|
||||||
stats: {
|
stats: {
|
||||||
@ -73,8 +53,8 @@ const config = {
|
|||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [
|
modules: [
|
||||||
root,
|
srcFolder,
|
||||||
path.join(root, 'Shims'),
|
path.join(srcFolder, 'Shims'),
|
||||||
'node_modules'
|
'node_modules'
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
@ -87,6 +67,10 @@ const config = {
|
|||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
chunkIds: 'named'
|
||||||
|
},
|
||||||
|
|
||||||
plugins,
|
plugins,
|
||||||
|
|
||||||
resolveLoader: {
|
resolveLoader: {
|
||||||
@ -101,53 +85,56 @@ const config = {
|
|||||||
{
|
{
|
||||||
test: /\.js?$/,
|
test: /\.js?$/,
|
||||||
exclude: /(node_modules|JsLibraries)/,
|
exclude: /(node_modules|JsLibraries)/,
|
||||||
loader: 'babel-loader',
|
use: [
|
||||||
query: {
|
{
|
||||||
plugins: ['transform-class-properties'],
|
loader: 'babel-loader',
|
||||||
presets: ['es2015', 'decorators-legacy', 'react', 'stage-2'],
|
options: {
|
||||||
env: {
|
configFile: `${frontendFolder}/babel.config.js`,
|
||||||
development: {
|
envName: isProduction ? 'production' : 'development',
|
||||||
plugins: ['transform-react-jsx-source']
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
modules: false,
|
||||||
|
loose: true,
|
||||||
|
debug: false,
|
||||||
|
useBuiltIns: 'entry',
|
||||||
|
targets: browsers
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// CSS Modules
|
// CSS Modules
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
exclude: /(node_modules|globals.css)/,
|
exclude: /(node_modules|globals.css)/,
|
||||||
use: extractCSSPlugin.extract({
|
use: [
|
||||||
fallback: 'style-loader',
|
{ loader: MiniCssExtractPlugin.loader },
|
||||||
use: [
|
{
|
||||||
{
|
loader: 'css-loader',
|
||||||
loader: 'css-variables-loader',
|
options: {
|
||||||
options: {
|
importLoaders: 1,
|
||||||
cssVarsFiles
|
localIdentName: '[name]/[local]/[hash:base64:5]',
|
||||||
}
|
modules: true
|
||||||
},
|
}
|
||||||
{
|
},
|
||||||
loader: 'css-loader',
|
{
|
||||||
options: {
|
loader: 'postcss-loader',
|
||||||
modules: true,
|
options: {
|
||||||
importLoaders: 1,
|
ident: 'postcss',
|
||||||
localIdentName: '[name]-[local]-[hash:base64:5]',
|
config: {
|
||||||
sourceMap: true
|
ctx: {
|
||||||
}
|
cssVarsFiles
|
||||||
},
|
},
|
||||||
{
|
path: 'frontend/postcss.config.js'
|
||||||
loader: 'postcss-loader',
|
|
||||||
options: {
|
|
||||||
config: {
|
|
||||||
ctx: {
|
|
||||||
cssVarsFiles
|
|
||||||
},
|
|
||||||
path: 'frontend/postcss.config.js'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
})
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// Global styles
|
// Global styles
|
||||||
@ -195,17 +182,16 @@ const config = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
gulp.task('webpack', () => {
|
gulp.task('webpack', () => {
|
||||||
return gulp.src('index.js')
|
return webpackStream(config)
|
||||||
.pipe(webpackStream(config))
|
.pipe(gulp.dest('./'));
|
||||||
.pipe(gulp.dest(''));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('webpackWatch', () => {
|
gulp.task('webpackWatch', () => {
|
||||||
config.watch = true;
|
config.watch = true;
|
||||||
return gulp.src('')
|
|
||||||
.pipe(webpackStream(config))
|
return webpackStream(config)
|
||||||
.on('error', errorHandler)
|
.on('error', errorHandler)
|
||||||
.pipe(gulp.dest(''))
|
.pipe(gulp.dest('./'))
|
||||||
.on('error', errorHandler)
|
.on('error', errorHandler)
|
||||||
.pipe(livereload())
|
.pipe(livereload())
|
||||||
.on('error', errorHandler);
|
.on('error', errorHandler);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const reload = require('require-nocache')(module);
|
const reload = require('require-nocache')(module);
|
||||||
|
const browsers = require('./browsers');
|
||||||
|
|
||||||
module.exports = (ctx, configPath, options) => {
|
module.exports = (ctx, configPath, options) => {
|
||||||
const config = {
|
const config = {
|
||||||
@ -17,15 +18,7 @@ module.exports = (ctx, configPath, options) => {
|
|||||||
'postcss-color-function': {},
|
'postcss-color-function': {},
|
||||||
'postcss-nested': {},
|
'postcss-nested': {},
|
||||||
autoprefixer: {
|
autoprefixer: {
|
||||||
browsers: [
|
browsers
|
||||||
'Chrome >= 30',
|
|
||||||
'Firefox >= 30',
|
|
||||||
'Safari >= 6',
|
|
||||||
'Edge >= 12',
|
|
||||||
'Explorer >= 11',
|
|
||||||
'iOS >= 7',
|
|
||||||
'Android >= 4.4'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
.language,
|
.language,
|
||||||
.quality {
|
.quality {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indexer {
|
.indexer {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.description {
|
.description {
|
||||||
composes: title from 'Components/DescriptionList/DescriptionListItemDescription.css';
|
composes: title from '~Components/DescriptionList/DescriptionListItemDescription.css';
|
||||||
|
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
@ -231,6 +231,16 @@ function HistoryDetails(props) {
|
|||||||
</DescriptionList>
|
</DescriptionList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DescriptionList>
|
||||||
|
<DescriptionListItem
|
||||||
|
descriptionClassName={styles.description}
|
||||||
|
title="Name"
|
||||||
|
data={sourceTitle}
|
||||||
|
/>
|
||||||
|
</DescriptionList>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryDetails.propTypes = {
|
HistoryDetails.propTypes = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.markAsFailedButton {
|
.markAsFailedButton {
|
||||||
composes: button from 'Components/Link/Button.css';
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.cell {
|
.cell {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 35px;
|
width: 35px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
.downloadClient {
|
.downloadClient {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 120px;
|
width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.indexer {
|
.indexer {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.releaseGroup {
|
.releaseGroup {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 110px;
|
width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
.torrent {
|
.torrent {
|
||||||
composes: label from 'Components/Label.css';
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
border-color: $torrentColor;
|
border-color: $torrentColor;
|
||||||
background-color: $torrentColor;
|
background-color: $torrentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.usenet {
|
.usenet {
|
||||||
composes: label from 'Components/Label.css';
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
border-color: $usenetColor;
|
border-color: $usenetColor;
|
||||||
background-color: $usenetColor;
|
background-color: $usenetColor;
|
||||||
|
@ -144,6 +144,8 @@ class QueueConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QueueConnector.propTypes = {
|
QueueConnector.propTypes = {
|
||||||
|
includeUnknownMovieItems: PropTypes.bool.isRequired,
|
||||||
|
useCurrentPage: PropTypes.bool.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
fetchQueue: PropTypes.func.isRequired,
|
fetchQueue: PropTypes.func.isRequired,
|
||||||
gotoQueueFirstPage: PropTypes.func.isRequired,
|
gotoQueueFirstPage: PropTypes.func.isRequired,
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
.quality {
|
.quality {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.protocol {
|
.protocol {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
@ -197,6 +197,14 @@ class QueueRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'title') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
{title}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'estimatedCompletionTime') {
|
if (name === 'estimatedCompletionTime') {
|
||||||
return (
|
return (
|
||||||
<TimeleftCell
|
<TimeleftCell
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.status {
|
.status {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.timeleft {
|
.timeleft {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.searchInput {
|
.searchInput {
|
||||||
composes: input from 'Components/Form/TextInput.css';
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
height: 46px;
|
height: 46px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -99,7 +99,7 @@ class AddNewMovie extends Component {
|
|||||||
className={styles.searchInput}
|
className={styles.searchInput}
|
||||||
name="movieLookup"
|
name="movieLookup"
|
||||||
value={term}
|
value={term}
|
||||||
placeholder="eg. Some Movie, tmdb:####, imdb:####"
|
placeholder="eg. The Dark Knight, tmdb:155, imdb:tt0468569"
|
||||||
onChange={this.onSearchInputChange}
|
onChange={this.onSearchInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import AddNewMovie from './AddNewMovie';
|
|||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.addMovie,
|
(state) => state.addMovie,
|
||||||
(state) => state.routing.location,
|
(state) => state.router.location,
|
||||||
(addMovie, location) => {
|
(addMovie, location) => {
|
||||||
const { params } = parseUrl(location.search);
|
const { params } = parseUrl(location.search);
|
||||||
|
|
||||||
|
@ -36,24 +36,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.searchForMissingEpisodesContainer {
|
.searchForMissingEpisodesContainer {
|
||||||
composes: container from 'Components/Form/CheckInput.css';
|
composes: container from '~Components/Form/CheckInput.css';
|
||||||
|
|
||||||
flex: 0 1 0;
|
flex: 0 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchForMissingEpisodesInput {
|
.searchForMissingEpisodesInput {
|
||||||
composes: input from 'Components/Form/CheckInput.css';
|
composes: input from '~Components/Form/CheckInput.css';
|
||||||
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modalFooter {
|
.modalFooter {
|
||||||
composes: modalFooter from 'Components/Modal/ModalFooter.css';
|
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.addButton {
|
.addButton {
|
||||||
@add-mixin truncate;
|
@add-mixin truncate;
|
||||||
composes: button from 'Components/Link/SpinnerButton.css';
|
composes: button from '~Components/Link/SpinnerButton.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
@ -97,7 +97,7 @@ class ImportMovie extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Import Series">
|
<PageContent title="Import Movies">
|
||||||
<PageContentBodyConnector
|
<PageContentBodyConnector
|
||||||
ref={this.setContentBodyRef}
|
ref={this.setContentBodyRef}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
@ -115,7 +115,7 @@ class ImportMovie extends Component {
|
|||||||
{
|
{
|
||||||
!rootFoldersError && rootFoldersPopulated && !unmappedFolders.length &&
|
!rootFoldersError && rootFoldersPopulated && !unmappedFolders.length &&
|
||||||
<div>
|
<div>
|
||||||
All series in {path} have been imported
|
All movies in {path} have been imported
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.importButton {
|
.importButton {
|
||||||
composes: button from 'Components/Link/SpinnerButton.css';
|
composes: button from '~Components/Link/SpinnerButton.css';
|
||||||
|
|
||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
composes: loading from 'Components/Loading/LoadingIndicator.css';
|
composes: loading from '~Components/Loading/LoadingIndicator.css';
|
||||||
|
|
||||||
margin: 0 10px 0 12px;
|
margin: 0 10px 0 12px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
.folder {
|
.folder {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 1 0 200px;
|
flex: 1 0 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor {
|
.monitor {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 1 200px;
|
flex: 0 1 200px;
|
||||||
min-width: 185px;
|
min-width: 185px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qualityProfile {
|
.qualityProfile {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 1 250px;
|
flex: 0 1 250px;
|
||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.movie {
|
.movie {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 1 400px;
|
flex: 0 1 400px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
.selectInput {
|
.selectInput {
|
||||||
composes: input from 'Components/Form/CheckInput.css';
|
composes: input from '~Components/Form/CheckInput.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder {
|
.folder {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 1 0 200px;
|
flex: 1 0 200px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monitor {
|
.monitor {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 0 1 200px;
|
flex: 0 1 200px;
|
||||||
min-width: 185px;
|
min-width: 185px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qualityProfile {
|
.qualityProfile {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 0 1 250px;
|
flex: 0 1 250px;
|
||||||
min-width: 170px;
|
min-width: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series {
|
.series {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 0 1 400px;
|
flex: 0 1 400px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
.input {
|
.input {
|
||||||
composes: input from 'Components/Form/CheckInput.css';
|
composes: input from '~Components/Form/CheckInput.css';
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
composes: link from 'Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.searchInput {
|
.searchInput {
|
||||||
composes: input from 'Components/Form/TextInput.css';
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this._movieLookupTimeout = null;
|
this._movieLookupTimeout = null;
|
||||||
|
this._buttonRef = {};
|
||||||
|
this._contentRef = {};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
term: props.id,
|
term: props.id,
|
||||||
@ -44,14 +46,6 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
_setButtonRef = (ref) => {
|
|
||||||
this._buttonRef = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setContentRef = (ref) => {
|
|
||||||
this._contentRef = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
_addListener() {
|
_addListener() {
|
||||||
window.addEventListener('click', this.onWindowClick);
|
window.addEventListener('click', this.onWindowClick);
|
||||||
}
|
}
|
||||||
@ -64,14 +58,18 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onWindowClick = (event) => {
|
onWindowClick = (event) => {
|
||||||
const button = ReactDOM.findDOMNode(this._buttonRef);
|
const button = ReactDOM.findDOMNode(this._buttonRef.current);
|
||||||
const content = ReactDOM.findDOMNode(this._contentRef);
|
const content = ReactDOM.findDOMNode(this._contentRef.current);
|
||||||
|
|
||||||
if (!button) {
|
if (!button || !content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!button.contains(event.target) && content && !content.contains(event.target) && this.state.isOpen) {
|
if (
|
||||||
|
!button.contains(event.target) &&
|
||||||
|
!content.contains(event.target) &&
|
||||||
|
this.state.isOpen
|
||||||
|
) {
|
||||||
this.setState({ isOpen: false });
|
this.setState({ isOpen: false });
|
||||||
this._removeListener();
|
this._removeListener();
|
||||||
}
|
}
|
||||||
@ -120,7 +118,7 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
items,
|
items,
|
||||||
queued,
|
isQueued,
|
||||||
isLookingUpMovie
|
isLookingUpMovie
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -134,124 +132,145 @@ class ImportMovieSelectMovie extends Component {
|
|||||||
element: styles.tether
|
element: styles.tether
|
||||||
}}
|
}}
|
||||||
{...tetherOptions}
|
{...tetherOptions}
|
||||||
>
|
renderTarget={
|
||||||
<Link
|
(ref) => {
|
||||||
ref={this._setButtonRef}
|
this._buttonRef = ref;
|
||||||
className={styles.button}
|
|
||||||
component="div"
|
|
||||||
onPress={this.onPress}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
isLookingUpMovie && queued && !isPopulated &&
|
|
||||||
<LoadingIndicator
|
|
||||||
className={styles.loading}
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
return (
|
||||||
isPopulated && selectedMovie && isExistingMovie &&
|
<div ref={ref}>
|
||||||
<Icon
|
<Link
|
||||||
className={styles.warningIcon}
|
className={styles.button}
|
||||||
name={icons.WARNING}
|
component="div"
|
||||||
kind={kinds.WARNING}
|
onPress={this.onPress}
|
||||||
/>
|
>
|
||||||
}
|
{
|
||||||
|
isLookingUpMovie && isQueued && !isPopulated ?
|
||||||
|
<LoadingIndicator
|
||||||
|
className={styles.loading}
|
||||||
|
size={20}
|
||||||
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && selectedMovie &&
|
isPopulated && selectedMovie && isExistingMovie ?
|
||||||
<ImportMovieTitle
|
<Icon
|
||||||
title={selectedMovie.title}
|
className={styles.warningIcon}
|
||||||
year={selectedMovie.year}
|
name={icons.WARNING}
|
||||||
network={selectedMovie.network}
|
kind={kinds.WARNING}
|
||||||
isExistingMovie={isExistingMovie}
|
/> :
|
||||||
/>
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isPopulated && !selectedMovie &&
|
isPopulated && selectedMovie ?
|
||||||
<div>
|
<ImportMovieTitle
|
||||||
<Icon
|
title={selectedMovie.title}
|
||||||
className={styles.warningIcon}
|
year={selectedMovie.year}
|
||||||
name={icons.WARNING}
|
network={selectedMovie.network}
|
||||||
kind={kinds.WARNING}
|
isExistingMovie={isExistingMovie}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !selectedMovie ?
|
||||||
|
<div>
|
||||||
|
<Icon
|
||||||
|
className={styles.warningIcon}
|
||||||
|
name={icons.WARNING}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
/>
|
||||||
|
|
||||||
No match found!
|
No match found!
|
||||||
</div>
|
</div> :
|
||||||
}
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && !!error &&
|
!isFetching && !!error ?
|
||||||
<div>
|
<div>
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.warningIcon}
|
className={styles.warningIcon}
|
||||||
title={errorMessage}
|
title={errorMessage}
|
||||||
name={icons.WARNING}
|
name={icons.WARNING}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
Search failed, please try again later.
|
Search failed, please try again later.
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
<div className={styles.dropdownArrowContainer}>
|
||||||
|
<Icon
|
||||||
|
name={icons.CARET_DOWN}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
renderElement={
|
||||||
|
(ref) => {
|
||||||
|
this._contentRef = ref;
|
||||||
|
|
||||||
<div className={styles.dropdownArrowContainer}>
|
if (!this.state.isOpen) {
|
||||||
<Icon
|
return;
|
||||||
name={icons.CARET_DOWN}
|
}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{
|
return (
|
||||||
this.state.isOpen &&
|
<div
|
||||||
<div
|
ref={ref}
|
||||||
ref={this._setContentRef}
|
className={styles.contentContainer}
|
||||||
className={styles.contentContainer}
|
>
|
||||||
>
|
<div className={styles.content}>
|
||||||
<div className={styles.content}>
|
<div className={styles.searchContainer}>
|
||||||
<div className={styles.searchContainer}>
|
<div className={styles.searchIconContainer}>
|
||||||
<div className={styles.searchIconContainer}>
|
<Icon name={icons.SEARCH} />
|
||||||
<Icon name={icons.SEARCH} />
|
</div>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
className={styles.searchInput}
|
||||||
|
name={`${name}_textInput`}
|
||||||
|
value={this.state.term}
|
||||||
|
onChange={this.onSearchInputChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormInputButton
|
||||||
|
kind={kinds.DEFAULT}
|
||||||
|
spinnerIcon={icons.REFRESH}
|
||||||
|
canSpin={true}
|
||||||
|
isSpinning={isFetching}
|
||||||
|
onPress={this.onRefreshPress}
|
||||||
|
>
|
||||||
|
<Icon name={icons.REFRESH} />
|
||||||
|
</FormInputButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TextInput
|
<div className={styles.results}>
|
||||||
className={styles.searchInput}
|
{
|
||||||
name={`${name}_textInput`}
|
items.map((item) => {
|
||||||
value={this.state.term}
|
return (
|
||||||
onChange={this.onSearchInputChange}
|
<ImportMovieSearchResultConnector
|
||||||
/>
|
key={item.tmdbId}
|
||||||
|
tmdbId={item.tmdbId}
|
||||||
<FormInputButton
|
title={item.title}
|
||||||
kind={kinds.DEFAULT}
|
year={item.year}
|
||||||
spinnerIcon={icons.REFRESH}
|
studio={item.studio}
|
||||||
canSpin={true}
|
onPress={this.onMovieSelect}
|
||||||
isSpinning={isFetching}
|
/>
|
||||||
onPress={this.onRefreshPress}
|
);
|
||||||
>
|
})
|
||||||
<Icon name={icons.REFRESH} />
|
}
|
||||||
</FormInputButton>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.results}>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<ImportMovieSearchResultConnector
|
|
||||||
key={item.tmdbId}
|
|
||||||
tmdbId={item.tmdbId}
|
|
||||||
title={item.title}
|
|
||||||
year={item.year}
|
|
||||||
studio={item.studio}
|
|
||||||
onPress={this.onMovieSelect}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</TetherComponent>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,7 +283,7 @@ ImportMovieSelectMovie.propTypes = {
|
|||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
queued: PropTypes.bool.isRequired,
|
isQueued: PropTypes.bool.isRequired,
|
||||||
isLookingUpMovie: PropTypes.bool.isRequired,
|
isLookingUpMovie: PropTypes.bool.isRequired,
|
||||||
onSearchInputChange: PropTypes.func.isRequired,
|
onSearchInputChange: PropTypes.func.isRequired,
|
||||||
onMovieSelect: PropTypes.func.isRequired
|
onMovieSelect: PropTypes.func.isRequired
|
||||||
@ -274,7 +293,7 @@ ImportMovieSelectMovie.defaultProps = {
|
|||||||
isFetching: true,
|
isFetching: true,
|
||||||
isPopulated: false,
|
isPopulated: false,
|
||||||
items: [],
|
items: [],
|
||||||
queued: true
|
isQueued: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImportMovieSelectMovie;
|
export default ImportMovieSelectMovie;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
.link {
|
.link {
|
||||||
composes: link from 'Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.freeSpace,
|
.freeSpace,
|
||||||
.unmappedFolders {
|
.unmappedFolders {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 45px;
|
width: 45px;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Import Series">
|
<PageContent title="Import Movies">
|
||||||
<PageContentBodyConnector>
|
<PageContentBodyConnector>
|
||||||
{
|
{
|
||||||
isFetching && !isPopulated &&
|
isFetching && !isPopulated &&
|
||||||
@ -99,7 +99,7 @@ class ImportMovieSelectFolder extends Component {
|
|||||||
Some tips to ensure the import goes smoothly:
|
Some tips to ensure the import goes smoothly:
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.tip}>
|
<li className={styles.tip}>
|
||||||
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
Make sure your files include the quality in the name. eg. <span className={styles.code}>movie.2008.bluray.mkv</span>
|
||||||
</li>
|
</li>
|
||||||
<li className={styles.tip}>
|
<li className={styles.tip}>
|
||||||
Point Radarr to the folder containing all of your movies not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\movies' : '/movies'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}"</span>
|
Point Radarr to the folder containing all of your movies not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\movies' : '/movies'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\movies\\the matrix' : '/movies/the matrix'}"</span>
|
||||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'connected-react-router';
|
||||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||||
import { fetchRootFolders, addRootFolder, deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
import { fetchRootFolders, addRootFolder, deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||||
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
import ImportMovieSelectFolder from './ImportMovieSelectFolder';
|
||||||
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DocumentTitle from 'react-document-title';
|
import DocumentTitle from 'react-document-title';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { ConnectedRouter } from 'react-router-redux';
|
import { ConnectedRouter } from 'connected-react-router';
|
||||||
import PageConnector from 'Components/Page/PageConnector';
|
import PageConnector from 'Components/Page/PageConnector';
|
||||||
import AppRoutes from './AppRoutes';
|
import AppRoutes from './AppRoutes';
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import Settings from 'Settings/Settings';
|
|||||||
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
|
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
|
||||||
import Profiles from 'Settings/Profiles/Profiles';
|
import Profiles from 'Settings/Profiles/Profiles';
|
||||||
import Quality from 'Settings/Quality/Quality';
|
import Quality from 'Settings/Quality/Quality';
|
||||||
|
import CustomFormatsConnector from 'Settings/CustomFormats/CustomFormatsConnector';
|
||||||
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
|
import IndexerSettingsConnector from 'Settings/Indexers/IndexerSettingsConnector';
|
||||||
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
import DownloadClientSettingsConnector from 'Settings/DownloadClients/DownloadClientSettingsConnector';
|
||||||
import NetImportSettingsConnector from 'Settings/NetImport/NetImportSettingsConnector';
|
import NetImportSettingsConnector from 'Settings/NetImport/NetImportSettingsConnector';
|
||||||
@ -139,6 +140,11 @@ function AppRoutes(props) {
|
|||||||
component={Quality}
|
component={Quality}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/customformats"
|
||||||
|
component={CustomFormatsConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/indexers"
|
path="/settings/indexers"
|
||||||
component={IndexerSettingsConnector}
|
component={IndexerSettingsConnector}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $borderColor;
|
||||||
font-size: 14px;
|
font-size: $defaultFontSize;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $tableRowHoverBackgroundColor;
|
background-color: $tableRowHoverBackgroundColor;
|
||||||
@ -63,27 +63,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAir {
|
.onAir {
|
||||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.missing {
|
.missing {
|
||||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.premiere {
|
.premiere {
|
||||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
@ -123,7 +123,7 @@ class AgendaEvent extends Component {
|
|||||||
|
|
||||||
AgendaEvent.propTypes = {
|
AgendaEvent.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
episodeFile: PropTypes.object,
|
movieFile: PropTypes.object,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
inCinemas: PropTypes.string.isRequired,
|
inCinemas: PropTypes.string.isRequired,
|
||||||
|
@ -49,8 +49,20 @@ class CalendarConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
useCurrentPage,
|
||||||
|
fetchCalendar,
|
||||||
|
gotoCalendarToday
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
registerPagePopulator(this.repopulate);
|
registerPagePopulator(this.repopulate);
|
||||||
this.props.gotoCalendarToday();
|
|
||||||
|
if (useCurrentPage) {
|
||||||
|
fetchCalendar();
|
||||||
|
} else {
|
||||||
|
gotoCalendarToday();
|
||||||
|
}
|
||||||
|
|
||||||
this.scheduleUpdate();
|
this.scheduleUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +175,7 @@ class CalendarConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CalendarConnector.propTypes = {
|
CalendarConnector.propTypes = {
|
||||||
|
useCurrentPage: PropTypes.bool.isRequired,
|
||||||
time: PropTypes.string,
|
time: PropTypes.string,
|
||||||
view: PropTypes.string.isRequired,
|
view: PropTypes.string.isRequired,
|
||||||
firstDayOfWeek: PropTypes.number.isRequired,
|
firstDayOfWeek: PropTypes.number.isRequired,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
.calendarPageBody {
|
.calendarPageBody {
|
||||||
composes: contentBody from 'Components/Page/PageContentBody.css';
|
composes: contentBody from '~Components/Page/PageContentBody.css';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendarInnerPageBody {
|
.calendarInnerPageBody {
|
||||||
composes: innerContentBody from 'Components/Page/PageContentBody.css';
|
composes: innerContentBody from '~Components/Page/PageContentBody.css';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -58,6 +58,15 @@ class CalendarPage extends Component {
|
|||||||
this.setState({ isOptionsModalOpen: false });
|
this.setState({ isOptionsModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSearchMissingPress = () => {
|
||||||
|
const {
|
||||||
|
missingEpisodeIds,
|
||||||
|
onSearchMissingPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onSearchMissingPress(missingEpisodeIds);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
@ -65,7 +74,10 @@ class CalendarPage extends Component {
|
|||||||
const {
|
const {
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
hasSeries,
|
hasMovie,
|
||||||
|
missingEpisodeIds,
|
||||||
|
isSearchingForMissing,
|
||||||
|
useCurrentPage,
|
||||||
onFilterSelect
|
onFilterSelect
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
@ -75,11 +87,7 @@ class CalendarPage extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const isMeasured = this.state.width > 0;
|
const isMeasured = this.state.width > 0;
|
||||||
let PageComponent = 'div';
|
const PageComponent = hasMovie ? CalendarConnector : NoMovie;
|
||||||
|
|
||||||
if (isMeasured) {
|
|
||||||
PageComponent = hasSeries ? CalendarConnector : NoMovie;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Calendar">
|
<PageContent title="Calendar">
|
||||||
@ -90,6 +98,14 @@ class CalendarPage extends Component {
|
|||||||
iconName={icons.CALENDAR}
|
iconName={icons.CALENDAR}
|
||||||
onPress={this.onGetCalendarLinkPress}
|
onPress={this.onGetCalendarLinkPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label="Search for Missing"
|
||||||
|
iconName={icons.SEARCH}
|
||||||
|
isDisabled={!missingEpisodeIds.length}
|
||||||
|
isSpinning={isSearchingForMissing}
|
||||||
|
onPress={this.onSearchMissingPress}
|
||||||
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT}>
|
<PageToolbarSection alignContent={align.RIGHT}>
|
||||||
@ -101,7 +117,7 @@ class CalendarPage extends Component {
|
|||||||
|
|
||||||
<FilterMenu
|
<FilterMenu
|
||||||
alignMenu={align.RIGHT}
|
alignMenu={align.RIGHT}
|
||||||
isDisabled={!hasSeries}
|
isDisabled={!hasMovie}
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={[]}
|
customFilters={[]}
|
||||||
@ -118,11 +134,17 @@ class CalendarPage extends Component {
|
|||||||
whitelist={['width']}
|
whitelist={['width']}
|
||||||
onMeasure={this.onMeasure}
|
onMeasure={this.onMeasure}
|
||||||
>
|
>
|
||||||
<PageComponent />
|
{
|
||||||
|
isMeasured ?
|
||||||
|
<PageComponent
|
||||||
|
useCurrentPage={useCurrentPage}
|
||||||
|
/> :
|
||||||
|
<div />
|
||||||
|
}
|
||||||
</Measure>
|
</Measure>
|
||||||
|
|
||||||
{
|
{
|
||||||
hasSeries &&
|
hasMovie &&
|
||||||
<LegendConnector />
|
<LegendConnector />
|
||||||
}
|
}
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
@ -144,7 +166,11 @@ class CalendarPage extends Component {
|
|||||||
CalendarPage.propTypes = {
|
CalendarPage.propTypes = {
|
||||||
selectedFilterKey: PropTypes.string.isRequired,
|
selectedFilterKey: PropTypes.string.isRequired,
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
hasSeries: PropTypes.bool.isRequired,
|
hasMovie: PropTypes.bool.isRequired,
|
||||||
|
missingEpisodeIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
isSearchingForMissing: PropTypes.bool.isRequired,
|
||||||
|
useCurrentPage: PropTypes.bool.isRequired,
|
||||||
|
onSearchMissingPress: PropTypes.func.isRequired,
|
||||||
onDaysCountChange: PropTypes.func.isRequired,
|
onDaysCountChange: PropTypes.func.isRequired,
|
||||||
onFilterSelect: PropTypes.func.isRequired
|
onFilterSelect: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,80 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
import moment from 'moment';
|
||||||
|
import { isCommandExecuting } from 'Utilities/Command';
|
||||||
|
import isBefore from 'Utilities/Date/isBefore';
|
||||||
|
import withCurrentPage from 'Components/withCurrentPage';
|
||||||
|
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||||
import createMovieCountSelector from 'Store/Selectors/createMovieCountSelector';
|
import createMovieCountSelector from 'Store/Selectors/createMovieCountSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
import CalendarPage from './CalendarPage';
|
import CalendarPage from './CalendarPage';
|
||||||
|
|
||||||
|
function createMissingEpisodeIdsSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.calendar.start,
|
||||||
|
(state) => state.calendar.end,
|
||||||
|
(state) => state.calendar.items,
|
||||||
|
(state) => state.queue.details.items,
|
||||||
|
(start, end, episodes, queueDetails) => {
|
||||||
|
return episodes.reduce((acc, episode) => {
|
||||||
|
const airDateUtc = episode.airDateUtc;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!episode.episodeFileId &&
|
||||||
|
moment(airDateUtc).isAfter(start) &&
|
||||||
|
moment(airDateUtc).isBefore(end) &&
|
||||||
|
isBefore(episode.airDateUtc) &&
|
||||||
|
!queueDetails.some((details) => !!details.episode && details.episode.id === episode.id)
|
||||||
|
) {
|
||||||
|
acc.push(episode.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIsSearchingSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.calendar.searchMissingCommandId,
|
||||||
|
createCommandsSelector(),
|
||||||
|
(searchMissingCommandId, commands) => {
|
||||||
|
if (searchMissingCommandId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCommandExecuting(commands.find((command) => {
|
||||||
|
return command.id === searchMissingCommandId;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.calendar,
|
(state) => state.calendar.selectedFilterKey,
|
||||||
|
(state) => state.calendar.filters,
|
||||||
createMovieCountSelector(),
|
createMovieCountSelector(),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(calendar, seriesCount, uiSettings) => {
|
createMissingEpisodeIdsSelector(),
|
||||||
|
createIsSearchingSelector(),
|
||||||
|
(
|
||||||
|
selectedFilterKey,
|
||||||
|
filters,
|
||||||
|
movieCount,
|
||||||
|
uiSettings,
|
||||||
|
missingEpisodeIds,
|
||||||
|
isSearchingForMissing
|
||||||
|
) => {
|
||||||
return {
|
return {
|
||||||
selectedFilterKey: calendar.selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters: calendar.filters,
|
filters,
|
||||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||||
hasSeries: !!seriesCount
|
hasMovie: !!movieCount,
|
||||||
|
missingEpisodeIds,
|
||||||
|
isSearchingForMissing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -23,6 +82,10 @@ function createMapStateToProps() {
|
|||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
|
onSearchMissingPress(episodeIds) {
|
||||||
|
dispatch(searchMissing({ episodeIds }));
|
||||||
|
},
|
||||||
|
|
||||||
onDaysCountChange(dayCount) {
|
onDaysCountChange(dayCount) {
|
||||||
dispatch(setCalendarDaysCount({ dayCount }));
|
dispatch(setCalendarDaysCount({ dayCount }));
|
||||||
},
|
},
|
||||||
@ -33,4 +96,6 @@ function createMapDispatchToProps(dispatch, props) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(CalendarPage);
|
export default withCurrentPage(
|
||||||
|
connect(createMapStateToProps, createMapDispatchToProps)(CalendarPage)
|
||||||
|
);
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
border-bottom: 1px solid $borderColor;
|
border-bottom: 1px solid $borderColor;
|
||||||
border-left: 4px solid $borderColor;
|
border-left: 4px solid $borderColor;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
border-left-width: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info,
|
.info,
|
||||||
@ -22,7 +26,7 @@
|
|||||||
|
|
||||||
.seriesTitle {
|
.seriesTitle {
|
||||||
color: #3a3f51;
|
color: #3a3f51;
|
||||||
font-size: 14px;
|
font-size: $defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.absoluteEpisodeNumber {
|
.absoluteEpisodeNumber {
|
||||||
@ -39,6 +43,10 @@
|
|||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
border-left-color: $successColor !important;
|
border-left-color: $successColor !important;
|
||||||
|
|
||||||
|
&:global(.colorImpaired) {
|
||||||
|
border-left-color: color($successColor, saturation(+15%)) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
@ -49,7 +57,7 @@
|
|||||||
border-left-color: $gray !important;
|
border-left-color: $gray !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(45deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(45deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +65,7 @@
|
|||||||
border-left-color: $warningColor !important;
|
border-left-color: $warningColor !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +73,8 @@
|
|||||||
border-left-color: $dangerColor !important;
|
border-left-color: $dangerColor !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
border-left-color: color($dangerColor saturation(+15%)) !important;
|
||||||
|
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +82,6 @@
|
|||||||
border-left-color: $primaryColor !important;
|
border-left-color: $primaryColor !important;
|
||||||
|
|
||||||
&:global(.colorImpaired) {
|
&:global(.colorImpaired) {
|
||||||
background: repeating-linear-gradient(90deg, transparent, transparent 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
background: repeating-linear-gradient(90deg, $colorImpairedGradientDark, $colorImpairedGradientDark 5px, $colorImpairedGradient 5px, $colorImpairedGradient 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
flex: 1 0 1px;
|
flex: 1 0 1px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
color: #3a3f51;
|
color: #3a3f51;
|
||||||
font-size: 14px;
|
font-size: $defaultFontSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.airTime {
|
.airTime {
|
||||||
@ -58,25 +58,25 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAir {
|
.onAir {
|
||||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.missing {
|
.missing {
|
||||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.premiere {
|
.premiere {
|
||||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.todayButton {
|
.todayButton {
|
||||||
composes: button from 'Components/Link/Button.css';
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
@ -30,13 +30,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.viewMenu {
|
.viewMenu {
|
||||||
composes: menu from 'Components/Menu/Menu.css';
|
composes: menu from '~Components/Menu/Menu.css';
|
||||||
|
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
composes: loading from 'Components/Loading/LoadingIndicator.css';
|
composes: loading from '~Components/Loading/LoadingIndicator.css';
|
||||||
|
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
@ -158,6 +158,14 @@ class CalendarHeader extends Component {
|
|||||||
</MenuButton>
|
</MenuButton>
|
||||||
|
|
||||||
<MenuContent>
|
<MenuContent>
|
||||||
|
<ViewMenuItem
|
||||||
|
name={calendarViews.WEEK}
|
||||||
|
selectedView={view}
|
||||||
|
onPress={this.onViewChange}
|
||||||
|
>
|
||||||
|
Week
|
||||||
|
</ViewMenuItem>
|
||||||
|
|
||||||
<ViewMenuItem
|
<ViewMenuItem
|
||||||
name={calendarViews.FORECAST}
|
name={calendarViews.FORECAST}
|
||||||
selectedView={view}
|
selectedView={view}
|
||||||
@ -192,6 +200,27 @@ class CalendarHeader extends Component {
|
|||||||
onPress={this.onViewChange}
|
onPress={this.onViewChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CalendarHeaderViewButton
|
||||||
|
view={calendarViews.WEEK}
|
||||||
|
selectedView={view}
|
||||||
|
buttonGroupPosition={align.CENTER}
|
||||||
|
onPress={this.onViewChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CalendarHeaderViewButton
|
||||||
|
view={calendarViews.FORECAST}
|
||||||
|
selectedView={view}
|
||||||
|
buttonGroupPosition={align.CENTER}
|
||||||
|
onPress={this.onViewChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CalendarHeaderViewButton
|
||||||
|
view={calendarViews.DAY}
|
||||||
|
selectedView={view}
|
||||||
|
buttonGroupPosition={align.CENTER}
|
||||||
|
onPress={this.onViewChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<CalendarHeaderViewButton
|
<CalendarHeaderViewButton
|
||||||
view={calendarViews.AGENDA}
|
view={calendarViews.AGENDA}
|
||||||
selectedView={view}
|
selectedView={view}
|
||||||
|
@ -13,29 +13,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.downloaded {
|
.downloaded {
|
||||||
composes: downloaded from 'Calendar/Events/CalendarEvent.css';
|
composes: downloaded from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
composes: downloading from 'Calendar/Events/CalendarEvent.css';
|
composes: downloading from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unmonitored {
|
.unmonitored {
|
||||||
composes: unmonitored from 'Calendar/Events/CalendarEvent.css';
|
composes: unmonitored from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.onAir {
|
.onAir {
|
||||||
composes: onAir from 'Calendar/Events/CalendarEvent.css';
|
composes: onAir from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.missing {
|
.missing {
|
||||||
composes: missing from 'Calendar/Events/CalendarEvent.css';
|
composes: missing from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.premiere {
|
.premiere {
|
||||||
composes: premiere from 'Calendar/Events/CalendarEvent.css';
|
composes: premiere from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.unaired {
|
.unaired {
|
||||||
composes: unaired from 'Calendar/Events/CalendarEvent.css';
|
composes: unaired from '~Calendar/Events/CalendarEvent.css';
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
export const DAY = 'day';
|
||||||
|
export const WEEK = 'week';
|
||||||
export const MONTH = 'month';
|
export const MONTH = 'month';
|
||||||
|
export const FORECAST = 'forecast';
|
||||||
export const AGENDA = 'agenda';
|
export const AGENDA = 'agenda';
|
||||||
|
|
||||||
export const all = [MONTH, AGENDA];
|
export const all = [DAY, WEEK, MONTH, FORECAST, AGENDA];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.modal {
|
.modal {
|
||||||
composes: modal from 'Components/Modal/Modal.css';
|
composes: modal from '~Components/Modal/Modal.css';
|
||||||
|
|
||||||
height: 600px;
|
height: 600px;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
.modalBody {
|
.modalBody {
|
||||||
composes: modalBody from 'Components/Modal/ModalBody.css';
|
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mappedDrivesWarning {
|
.mappedDrivesWarning {
|
||||||
composes: alert from 'Components/Alert.css';
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -18,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pathInput {
|
.pathInput {
|
||||||
composes: pathInputWrapper from 'Components/Form/PathInput.css';
|
composes: inputWrapper from '~Components/Form/PathInput.css';
|
||||||
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchPaths,
|
dispatchFetchPaths: fetchPaths,
|
||||||
clearPaths
|
dispatchClearPaths: clearPaths
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileBrowserModalContentConnector extends Component {
|
class FileBrowserModalContentConnector extends Component {
|
||||||
@ -51,9 +51,16 @@ class FileBrowserModalContentConnector extends Component {
|
|||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchPaths({
|
const {
|
||||||
path: this.props.value,
|
value,
|
||||||
allowFoldersWithoutTrailingSlashes: true
|
includeFiles,
|
||||||
|
dispatchFetchPaths
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchPaths({
|
||||||
|
path: value,
|
||||||
|
allowFoldersWithoutTrailingSlashes: true,
|
||||||
|
includeFiles
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,18 +68,24 @@ class FileBrowserModalContentConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onFetchPaths = (path) => {
|
onFetchPaths = (path) => {
|
||||||
this.props.fetchPaths({
|
const {
|
||||||
|
includeFiles,
|
||||||
|
dispatchFetchPaths
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchPaths({
|
||||||
path,
|
path,
|
||||||
allowFoldersWithoutTrailingSlashes: true
|
allowFoldersWithoutTrailingSlashes: true,
|
||||||
|
includeFiles
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearPaths = () => {
|
onClearPaths = () => {
|
||||||
// this.props.clearPaths();
|
// this.props.dispatchClearPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
onModalClose = () => {
|
onModalClose = () => {
|
||||||
this.props.clearPaths();
|
this.props.dispatchClearPaths();
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,9 +106,14 @@ class FileBrowserModalContentConnector extends Component {
|
|||||||
|
|
||||||
FileBrowserModalContentConnector.propTypes = {
|
FileBrowserModalContentConnector.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
fetchPaths: PropTypes.func.isRequired,
|
includeFiles: PropTypes.bool.isRequired,
|
||||||
clearPaths: PropTypes.func.isRequired,
|
dispatchFetchPaths: PropTypes.func.isRequired,
|
||||||
|
dispatchClearPaths: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FileBrowserModalContentConnector.defaultProps = {
|
||||||
|
includeFiles: false
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(FileBrowserModalContentConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(FileBrowserModalContentConnector);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.type {
|
.type {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 32px;
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.numberInput {
|
.numberInput {
|
||||||
composes: input from 'Components/Form/TextInput.css';
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectInput {
|
.selectInput {
|
||||||
composes: select from 'Components/Form/SelectInput.css';
|
composes: select from '~Components/Form/SelectInput.css';
|
||||||
|
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ import React, { Component } from 'react';
|
|||||||
import convertToBytes from 'Utilities/Number/convertToBytes';
|
import convertToBytes from 'Utilities/Number/convertToBytes';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import { kinds, filterBuilderTypes, filterBuilderValueTypes } from 'Helpers/Props';
|
import { kinds, filterBuilderTypes, filterBuilderValueTypes } from 'Helpers/Props';
|
||||||
import TagInput, { tagShape } from 'Components/Form/TagInput';
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
|
import TagInput from 'Components/Form/TagInput';
|
||||||
import FilterBuilderRowValueTag from './FilterBuilderRowValueTag';
|
import FilterBuilderRowValueTag from './FilterBuilderRowValueTag';
|
||||||
|
|
||||||
export const NAME = 'value';
|
export const NAME = 'value';
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
composes: label from 'Components/Label.css';
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
border-style: none;
|
border-style: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
||||||
import { tagShape } from 'Components/Form/TagInput';
|
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -3,8 +3,8 @@ import React, { Component } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import getQualities from 'Utilities/Quality/getQualities';
|
import getQualities from 'Utilities/Quality/getQualities';
|
||||||
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
import { fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||||
import { tagShape } from 'Components/Form/TagInput';
|
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
.input {
|
.input {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputWrapper {
|
.inputWrapper {
|
||||||
|
@ -3,19 +3,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasButton {
|
.hasButton {
|
||||||
composes: hasButton from 'Components/Form/Input.css';
|
composes: hasButton from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.recaptchaWrapper {
|
.recaptchaWrapper {
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.helpText {
|
.helpText {
|
||||||
composes: helpText from 'Components/Form/FormInputHelpText.css';
|
composes: helpText from '~Components/Form/FormInputHelpText.css';
|
||||||
|
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inputContainer {
|
.inputContainer {
|
||||||
composes: inputContainer from './TagInput.css';
|
composes: inputContainer from '~./TagInput.css';
|
||||||
composes: hasButton from 'Components/Form/Input.css';
|
composes: hasButton from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import FormInputButton from './FormInputButton';
|
import FormInputButton from './FormInputButton';
|
||||||
import TagInput, { tagShape } from './TagInput';
|
import TagInput from './TagInput';
|
||||||
import styles from './DeviceInput.css';
|
import styles from './DeviceInput.css';
|
||||||
|
|
||||||
class DeviceInput extends Component {
|
class DeviceInput extends Component {
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.enhancedSelect {
|
.enhancedSelect {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
composes: link from 'Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -21,11 +21,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.isDisabled {
|
.isDisabled {
|
||||||
@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.optionsModalBody {
|
.optionsModalBody {
|
||||||
composes: modalBody from 'Components/Modal/ModalBody.css';
|
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -71,7 +71,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.optionsModalScroller {
|
.optionsModalScroller {
|
||||||
composes: scroller from 'Components/Scroller/Scroller.css';
|
composes: scroller from '~Components/Scroller/Scroller.css';
|
||||||
|
|
||||||
border: 1px solid $inputBorderColor;
|
border: 1px solid $inputBorderColor;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -87,6 +87,9 @@ class EnhancedSelectInput extends Component {
|
|||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this._buttonRef = {};
|
||||||
|
this._optionsRef = {};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
selectedIndex: getSelectedIndex(props),
|
selectedIndex: getSelectedIndex(props),
|
||||||
@ -106,14 +109,6 @@ class EnhancedSelectInput extends Component {
|
|||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
_setButtonRef = (ref) => {
|
|
||||||
this._buttonRef = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setOptionsRef = (ref) => {
|
|
||||||
this._optionsRef = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
_addListener() {
|
_addListener() {
|
||||||
window.addEventListener('click', this.onWindowClick);
|
window.addEventListener('click', this.onWindowClick);
|
||||||
}
|
}
|
||||||
@ -126,8 +121,8 @@ class EnhancedSelectInput extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onWindowClick = (event) => {
|
onWindowClick = (event) => {
|
||||||
const button = ReactDOM.findDOMNode(this._buttonRef);
|
const button = ReactDOM.findDOMNode(this._buttonRef.current);
|
||||||
const options = ReactDOM.findDOMNode(this._optionsRef);
|
const options = ReactDOM.findDOMNode(this._optionsRef.current);
|
||||||
|
|
||||||
if (!button || this.state.isMobile) {
|
if (!button || this.state.isMobile) {
|
||||||
return;
|
return;
|
||||||
@ -276,75 +271,91 @@ class EnhancedSelectInput extends Component {
|
|||||||
element: styles.tether
|
element: styles.tether
|
||||||
}}
|
}}
|
||||||
{...tetherOptions}
|
{...tetherOptions}
|
||||||
>
|
renderTarget={
|
||||||
<Measure
|
(ref) => {
|
||||||
whitelist={['width']}
|
this._buttonRef = ref;
|
||||||
onMeasure={this.onMeasure}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
ref={this._setButtonRef}
|
|
||||||
className={classNames(
|
|
||||||
className,
|
|
||||||
hasError && styles.hasError,
|
|
||||||
hasWarning && styles.hasWarning,
|
|
||||||
isDisabled && disabledClassName
|
|
||||||
)}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
onBlur={this.onBlur}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
onPress={this.onPress}
|
|
||||||
>
|
|
||||||
<SelectedValueComponent
|
|
||||||
{...selectedValueOptions}
|
|
||||||
{...selectedOption}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
>
|
|
||||||
{selectedOption ? selectedOption.value : null}
|
|
||||||
</SelectedValueComponent>
|
|
||||||
|
|
||||||
<div
|
return (
|
||||||
className={isDisabled ?
|
<Measure
|
||||||
styles.dropdownArrowContainerDisabled :
|
whitelist={['width']}
|
||||||
styles.dropdownArrowContainer
|
onMeasure={this.onMeasure}
|
||||||
}
|
>
|
||||||
>
|
<div ref={ref}>
|
||||||
<Icon
|
<Link
|
||||||
name={icons.CARET_DOWN}
|
className={classNames(
|
||||||
/>
|
className,
|
||||||
</div>
|
hasError && styles.hasError,
|
||||||
</Link>
|
hasWarning && styles.hasWarning,
|
||||||
</Measure>
|
isDisabled && disabledClassName
|
||||||
|
)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
onPress={this.onPress}
|
||||||
|
>
|
||||||
|
<SelectedValueComponent
|
||||||
|
{...selectedValueOptions}
|
||||||
|
{...selectedOption}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
>
|
||||||
|
{selectedOption ? selectedOption.value : null}
|
||||||
|
</SelectedValueComponent>
|
||||||
|
|
||||||
{
|
<div
|
||||||
isOpen && !isMobile &&
|
className={isDisabled ?
|
||||||
<div
|
styles.dropdownArrowContainerDisabled :
|
||||||
ref={this._setOptionsRef}
|
styles.dropdownArrowContainer
|
||||||
className={styles.optionsContainer}
|
}
|
||||||
style={{
|
>
|
||||||
minWidth: width
|
<Icon
|
||||||
}}
|
name={icons.CARET_DOWN}
|
||||||
>
|
/>
|
||||||
<div className={styles.options}>
|
</div>
|
||||||
{
|
</Link>
|
||||||
values.map((v, index) => {
|
</div>
|
||||||
return (
|
</Measure>
|
||||||
<OptionComponent
|
);
|
||||||
key={v.key}
|
}
|
||||||
id={v.key}
|
|
||||||
isSelected={index === selectedIndex}
|
|
||||||
{...v}
|
|
||||||
isMobile={false}
|
|
||||||
onSelect={this.onSelect}
|
|
||||||
>
|
|
||||||
{v.value}
|
|
||||||
</OptionComponent>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</TetherComponent>
|
renderElement={
|
||||||
|
(ref) => {
|
||||||
|
this._optionsRef = ref;
|
||||||
|
|
||||||
|
if (!isOpen || isMobile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={styles.optionsContainer}
|
||||||
|
style={{
|
||||||
|
minWidth: width
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.options}>
|
||||||
|
{
|
||||||
|
values.map((v, index) => {
|
||||||
|
return (
|
||||||
|
<OptionComponent
|
||||||
|
key={v.key}
|
||||||
|
id={v.key}
|
||||||
|
isSelected={index === selectedIndex}
|
||||||
|
{...v}
|
||||||
|
isMobile={false}
|
||||||
|
onSelect={this.onSelect}
|
||||||
|
>
|
||||||
|
{v.value}
|
||||||
|
</OptionComponent>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
isMobile &&
|
isMobile &&
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
composes: button from 'Components/Link/Button.css';
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
|
@ -6,6 +6,7 @@ import AutoCompleteInput from './AutoCompleteInput';
|
|||||||
import CaptchaInputConnector from './CaptchaInputConnector';
|
import CaptchaInputConnector from './CaptchaInputConnector';
|
||||||
import CheckInput from './CheckInput';
|
import CheckInput from './CheckInput';
|
||||||
import DeviceInputConnector from './DeviceInputConnector';
|
import DeviceInputConnector from './DeviceInputConnector';
|
||||||
|
import KeyValueListInput from './KeyValueListInput';
|
||||||
import NumberInput from './NumberInput';
|
import NumberInput from './NumberInput';
|
||||||
import OAuthInputConnector from './OAuthInputConnector';
|
import OAuthInputConnector from './OAuthInputConnector';
|
||||||
import PasswordInput from './PasswordInput';
|
import PasswordInput from './PasswordInput';
|
||||||
@ -34,6 +35,9 @@ function getComponent(type) {
|
|||||||
case inputTypes.DEVICE:
|
case inputTypes.DEVICE:
|
||||||
return DeviceInputConnector;
|
return DeviceInputConnector;
|
||||||
|
|
||||||
|
case inputTypes.KEY_VALUE_LIST:
|
||||||
|
return KeyValueListInput;
|
||||||
|
|
||||||
case inputTypes.NUMBER:
|
case inputTypes.NUMBER:
|
||||||
return NumberInput;
|
return NumberInput;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
composes: link from 'Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
21
frontend/src/Components/Form/KeyValueListInput.css
Normal file
21
frontend/src/Components/Form/KeyValueListInput.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.inputContainer {
|
||||||
|
composes: input from '~Components/Form/Input.css';
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
min-height: 35px;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.isFocused {
|
||||||
|
outline: 0;
|
||||||
|
border-color: $inputFocusBorderColor;
|
||||||
|
box-shadow: inset 0 1px 1px $inputBoxShadowColor, 0 0 8px $inputFocusBoxShadowColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasError {
|
||||||
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.hasWarning {
|
||||||
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
|
}
|
152
frontend/src/Components/Form/KeyValueListInput.js
Normal file
152
frontend/src/Components/Form/KeyValueListInput.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import KeyValueListInputItem from './KeyValueListInputItem';
|
||||||
|
import styles from './KeyValueListInput.css';
|
||||||
|
|
||||||
|
class KeyValueListInput extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isFocused: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onItemChange = (index, itemValue) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const newValue = [...value];
|
||||||
|
|
||||||
|
if (index == null) {
|
||||||
|
newValue.push(itemValue);
|
||||||
|
} else {
|
||||||
|
newValue.splice(index, 1, itemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemoveItem = (index) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const newValue = [...value];
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus = () => {
|
||||||
|
this.setState({
|
||||||
|
isFocused: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur = () => {
|
||||||
|
this.setState({
|
||||||
|
isFocused: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const newValue = value.reduce((acc, v) => {
|
||||||
|
if (v.key || v.value) {
|
||||||
|
acc.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (newValue.length !== value.length) {
|
||||||
|
onChange({
|
||||||
|
name,
|
||||||
|
value: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
keyPlaceholder,
|
||||||
|
valuePlaceholder
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { isFocused } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(
|
||||||
|
className,
|
||||||
|
isFocused && styles.isFocused
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
[...value, { key: '', value: '' }].map((v, index) => {
|
||||||
|
return (
|
||||||
|
<KeyValueListInputItem
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
keyValue={v.key}
|
||||||
|
value={v.value}
|
||||||
|
keyPlaceholder={keyPlaceholder}
|
||||||
|
valuePlaceholder={valuePlaceholder}
|
||||||
|
isNew={index === value.length}
|
||||||
|
onChange={this.onItemChange}
|
||||||
|
onRemove={this.onRemoveItem}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValueListInput.propTypes = {
|
||||||
|
className: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
hasError: PropTypes.bool,
|
||||||
|
hasWarning: PropTypes.bool,
|
||||||
|
keyPlaceholder: PropTypes.string,
|
||||||
|
valuePlaceholder: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyValueListInput.defaultProps = {
|
||||||
|
className: styles.inputContainer,
|
||||||
|
value: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeyValueListInput;
|
14
frontend/src/Components/Form/KeyValueListInputItem.css
Normal file
14
frontend/src/Components/Form/KeyValueListInputItem.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.itemContainer {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
border-bottom: 1px solid $inputBorderColor;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyInput,
|
||||||
|
.valueInput {
|
||||||
|
border: none;
|
||||||
|
}
|
117
frontend/src/Components/Form/KeyValueListInputItem.js
Normal file
117
frontend/src/Components/Form/KeyValueListInputItem.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import TextInput from './TextInput';
|
||||||
|
import styles from './KeyValueListInputItem.css';
|
||||||
|
|
||||||
|
class KeyValueListInputItem extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onKeyChange = ({ value: keyValue }) => {
|
||||||
|
const {
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onChange(index, { key: keyValue, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChange = ({ value }) => {
|
||||||
|
// TODO: Validate here or validate at a lower level component
|
||||||
|
|
||||||
|
const {
|
||||||
|
index,
|
||||||
|
keyValue,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onChange(index, { key: keyValue, value });
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemovePress = () => {
|
||||||
|
const {
|
||||||
|
index,
|
||||||
|
onRemove
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onRemove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus = () => {
|
||||||
|
this.props.onFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur = () => {
|
||||||
|
this.props.onBlur();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
keyValue,
|
||||||
|
value,
|
||||||
|
keyPlaceholder,
|
||||||
|
valuePlaceholder,
|
||||||
|
isNew
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.itemContainer}>
|
||||||
|
<TextInput
|
||||||
|
className={styles.keyInput}
|
||||||
|
name="key"
|
||||||
|
value={keyValue}
|
||||||
|
placeholder={keyPlaceholder}
|
||||||
|
onChange={this.onKeyChange}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
className={styles.valueInput}
|
||||||
|
name="value"
|
||||||
|
value={value}
|
||||||
|
placeholder={valuePlaceholder}
|
||||||
|
onChange={this.onValueChange}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
!isNew &&
|
||||||
|
<IconButton
|
||||||
|
name={icons.REMOVE}
|
||||||
|
tabIndex={-1}
|
||||||
|
onPress={this.onRemovePress}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValueListInputItem.propTypes = {
|
||||||
|
index: PropTypes.number,
|
||||||
|
keyValue: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
|
keyPlaceholder: PropTypes.string.isRequired,
|
||||||
|
valuePlaceholder: PropTypes.string.isRequired,
|
||||||
|
isNew: PropTypes.bool.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
onFocus: PropTypes.func.isRequired,
|
||||||
|
onBlur: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyValueListInputItem.defaultProps = {
|
||||||
|
keyPlaceholder: 'Key',
|
||||||
|
valuePlaceholder: 'Value'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeyValueListInputItem;
|
@ -1,5 +1,5 @@
|
|||||||
.input {
|
.input {
|
||||||
composes: input from 'Components/Form/TextInput.css';
|
composes: input from '~Components/Form/TextInput.css';
|
||||||
|
|
||||||
font-family: $passwordFamily;
|
font-family: $passwordFamily;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
.path {
|
.path {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasFileBrowser {
|
.hasFileBrowser {
|
||||||
composes: hasButton from 'Components/Form/Input.css';
|
composes: hasButton from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.pathInputWrapper {
|
.pathInputWrapper {
|
||||||
@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fileBrowserButton {
|
.fileBrowserButton {
|
||||||
composes: button from './FormInputButton.css';
|
composes: button from '~./FormInputButton.css';
|
||||||
|
|
||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ class PathInput extends Component {
|
|||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
paths,
|
paths,
|
||||||
|
includeFiles,
|
||||||
hasError,
|
hasError,
|
||||||
hasWarning,
|
hasWarning,
|
||||||
hasFileBrowser,
|
hasFileBrowser,
|
||||||
@ -171,6 +172,7 @@ class PathInput extends Component {
|
|||||||
isOpen={this.state.isFileBrowserModalOpen}
|
isOpen={this.state.isFileBrowserModalOpen}
|
||||||
name={name}
|
name={name}
|
||||||
value={value}
|
value={value}
|
||||||
|
includeFiles={includeFiles}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onModalClose={this.onFileBrowserModalClose}
|
onModalClose={this.onFileBrowserModalClose}
|
||||||
/>
|
/>
|
||||||
@ -188,6 +190,7 @@ PathInput.propTypes = {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
paths: PropTypes.array.isRequired,
|
paths: PropTypes.array.isRequired,
|
||||||
|
includeFiles: PropTypes.bool.isRequired,
|
||||||
hasError: PropTypes.bool,
|
hasError: PropTypes.bool,
|
||||||
hasWarning: PropTypes.bool,
|
hasWarning: PropTypes.bool,
|
||||||
hasFileBrowser: PropTypes.bool,
|
hasFileBrowser: PropTypes.bool,
|
||||||
|
@ -28,8 +28,8 @@ function createMapStateToProps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchPaths,
|
dispatchFetchPaths: fetchPaths,
|
||||||
clearPaths
|
dispatchClearPaths: clearPaths
|
||||||
};
|
};
|
||||||
|
|
||||||
class PathInputConnector extends Component {
|
class PathInputConnector extends Component {
|
||||||
@ -38,11 +38,19 @@ class PathInputConnector extends Component {
|
|||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onFetchPaths = (path) => {
|
onFetchPaths = (path) => {
|
||||||
this.props.fetchPaths({ path });
|
const {
|
||||||
|
includeFiles,
|
||||||
|
dispatchFetchPaths
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchPaths({
|
||||||
|
path,
|
||||||
|
includeFiles
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearPaths = () => {
|
onClearPaths = () => {
|
||||||
this.props.clearPaths();
|
this.props.dispatchClearPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -60,8 +68,13 @@ class PathInputConnector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PathInputConnector.propTypes = {
|
PathInputConnector.propTypes = {
|
||||||
fetchPaths: PropTypes.func.isRequired,
|
includeFiles: PropTypes.bool.isRequired,
|
||||||
clearPaths: PropTypes.func.isRequired
|
dispatchFetchPaths: PropTypes.func.isRequired,
|
||||||
|
dispatchClearPaths: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
PathInputConnector.defaultProps = {
|
||||||
|
includeFiles: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(PathInputConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps)(PathInputConnector);
|
||||||
|
@ -20,6 +20,8 @@ function getType(type) {
|
|||||||
return inputTypes.NUMBER;
|
return inputTypes.NUMBER;
|
||||||
case 'path':
|
case 'path':
|
||||||
return inputTypes.PATH;
|
return inputTypes.PATH;
|
||||||
|
case 'filepath':
|
||||||
|
return inputTypes.PATH;
|
||||||
case 'select':
|
case 'select':
|
||||||
return inputTypes.SELECT;
|
return inputTypes.SELECT;
|
||||||
case 'tag':
|
case 'tag':
|
||||||
@ -84,7 +86,7 @@ function ProviderFieldFormGroup(props) {
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
warnings={warnings}
|
warnings={warnings}
|
||||||
pending={pending}
|
pending={pending}
|
||||||
hasFileBrowser={false}
|
includeFiles={type === 'filepath' ? true : undefined}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.selectedValue {
|
.selectedValue {
|
||||||
composes: selectedValue from './EnhancedSelectInputSelectedValue.css';
|
composes: selectedValue from '~./EnhancedSelectInputSelectedValue.css';
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
.select {
|
.select {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
|
|
||||||
padding: 0 11px;
|
padding: 0 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.isDisabled {
|
.isDisabled {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.inputContainer {
|
.inputContainer {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -14,11 +14,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
|
@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
|||||||
import Autosuggest from 'react-autosuggest';
|
import Autosuggest from 'react-autosuggest';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import TagInputInput from './TagInputInput';
|
import TagInputInput from './TagInputInput';
|
||||||
import TagInputTag from './TagInputTag';
|
import TagInputTag from './TagInputTag';
|
||||||
import styles from './TagInput.css';
|
import styles from './TagInput.css';
|
||||||
@ -266,11 +267,6 @@ class TagInput extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tagShape = {
|
|
||||||
id: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]).isRequired,
|
|
||||||
name: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
TagInput.propTypes = {
|
TagInput.propTypes = {
|
||||||
className: PropTypes.string.isRequired,
|
className: PropTypes.string.isRequired,
|
||||||
inputClassName: PropTypes.string.isRequired,
|
inputClassName: PropTypes.string.isRequired,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import { tagShape } from './TagInput';
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import styles from './TagInputInput.css';
|
import styles from './TagInputInput.css';
|
||||||
|
|
||||||
class TagInputInput extends Component {
|
class TagInputInput extends Component {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import tagShape from 'Helpers/Props/Shapes/tagShape';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import { tagShape } from './TagInput';
|
|
||||||
|
|
||||||
class TagInputTag extends Component {
|
class TagInputTag extends Component {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.input {
|
.input {
|
||||||
composes: input from 'Components/Form/Input.css';
|
composes: input from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.readOnly {
|
.readOnly {
|
||||||
@ -7,13 +7,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
.hasError {
|
||||||
composes: hasError from 'Components/Form/Input.css';
|
composes: hasError from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasWarning {
|
.hasWarning {
|
||||||
composes: hasWarning from 'Components/Form/Input.css';
|
composes: hasWarning from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasButton {
|
.hasButton {
|
||||||
composes: hasButton from 'Components/Form/Input.css';
|
composes: hasButton from '~Components/Form/Input.css';
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import styles from './Icon.css';
|
import styles from './Icon.css';
|
||||||
|
|
||||||
class Icon extends React.PureComponent {
|
class Icon extends PureComponent {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
containerClassName,
|
containerClassName,
|
||||||
|
40
frontend/src/Components/InfoLabel.css
Normal file
40
frontend/src/Components/InfoLabel.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.label {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 2px;
|
||||||
|
color: $white;
|
||||||
|
/** text-align: center; **/
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
color: $helpTextColor;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Kinds **/
|
||||||
|
|
||||||
|
/** Sizes **/
|
||||||
|
|
||||||
|
.small {
|
||||||
|
padding: 1px 3px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium {
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large {
|
||||||
|
padding: 3px 7px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Outline **/
|
||||||
|
|
||||||
|
.outline {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
54
frontend/src/Components/InfoLabel.js
Normal file
54
frontend/src/Components/InfoLabel.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { kinds, sizes } from 'Helpers/Props';
|
||||||
|
import styles from './InfoLabel.css';
|
||||||
|
|
||||||
|
function InfoLabel(props) {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
kind,
|
||||||
|
size,
|
||||||
|
outline,
|
||||||
|
children,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
styles[kind],
|
||||||
|
styles[size],
|
||||||
|
outline && styles.outline
|
||||||
|
)}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<div className={styles.title}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
InfoLabel.propTypes = {
|
||||||
|
className: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
kind: PropTypes.oneOf(kinds.all).isRequired,
|
||||||
|
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||||
|
outline: PropTypes.bool.isRequired,
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
InfoLabel.defaultProps = {
|
||||||
|
className: styles.label,
|
||||||
|
kind: kinds.DEFAULT,
|
||||||
|
size: sizes.SMALL,
|
||||||
|
outline: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoLabel;
|
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
composes: link from './Link.css';
|
composes: link from '~./Link.css';
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
composes: button from 'Components/Form/FormInputButton.css';
|
composes: button from '~Components/Form/FormInputButton.css';
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
composes: link from 'Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.button {
|
.button {
|
||||||
composes: button from 'Components/Link/Button.css';
|
composes: button from '~Components/Link/Button.css';
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.iconContainer {
|
.iconContainer {
|
||||||
composes: spinnerContainer from 'Components/Link/SpinnerButton.css';
|
composes: spinnerContainer from '~Components/Link/SpinnerButton.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
composes: label from 'Components/Link/SpinnerButton.css';
|
composes: label from '~Components/Link/SpinnerButton.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.showIcon {
|
.showIcon {
|
||||||
|
@ -6,9 +6,13 @@ const messages = [
|
|||||||
// TODO Add some messages here
|
// TODO Add some messages here
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let message = null;
|
||||||
|
|
||||||
function LoadingMessage() {
|
function LoadingMessage() {
|
||||||
const index = Math.floor(Math.random() * messages.length);
|
if (!message) {
|
||||||
const message = messages[index];
|
const index = Math.floor(Math.random() * messages.length);
|
||||||
|
message = messages[index];
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.loadingMessage}>
|
<div className={styles.loadingMessage}>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.filterMenu {
|
.filterMenu {
|
||||||
composes: menu from './Menu.css';
|
composes: menu from '~./Menu.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
@media only screen and (max-width: $breakpointSmall) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user