1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-20 21:55:03 -04:00

New: Project Aphrodite

This commit is contained in:
Qstick
2018-11-23 02:04:42 -05:00
parent 65efa15551
commit 8430cb40ab
1080 changed files with 73015 additions and 0 deletions
@@ -0,0 +1,48 @@
.column {
display: flex;
align-items: stretch;
width: 100%;
border: 1px solid #aaa;
border-radius: 4px;
background: #fafafa;
}
.checkContainer {
position: relative;
margin-right: 4px;
margin-bottom: 7px;
margin-left: 8px;
}
.label {
display: flex;
flex-grow: 1;
margin-bottom: 0;
margin-left: 2px;
font-weight: normal;
line-height: 36px;
cursor: pointer;
}
.dragHandle {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-left: auto;
width: $dragHandleWidth;
text-align: center;
cursor: grab;
}
.dragIcon {
top: 0;
}
.isDragging {
opacity: 0.25;
}
.notDragable {
padding: 4px 0;
}
@@ -0,0 +1,68 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import CheckInput from 'Components/Form/CheckInput';
import styles from './TableOptionsColumn.css';
function TableOptionsColumn(props) {
const {
name,
label,
isVisible,
isModifiable,
isDragging,
connectDragSource,
onVisibleChange
} = props;
return (
<div className={isModifiable ? undefined : styles.notDragable}>
<div
className={classNames(
styles.column,
isDragging && styles.isDragging
)}
>
<label
className={styles.label}
>
<CheckInput
containerClassName={styles.checkContainer}
name={name}
value={isVisible}
isDisabled={isModifiable === false}
onChange={onVisibleChange}
/>
{label}
</label>
{
!!connectDragSource &&
connectDragSource(
<div className={styles.dragHandle}>
<Icon
className={styles.dragIcon}
name={icons.REORDER}
/>
</div>
)
}
</div>
</div>
);
}
TableOptionsColumn.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,
isDragging: PropTypes.bool,
connectDragSource: PropTypes.func,
onVisibleChange: PropTypes.func.isRequired
};
export default TableOptionsColumn;
@@ -0,0 +1,4 @@
.dragPreview {
width: 380px;
opacity: 0.75;
}
@@ -0,0 +1,78 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DragLayer } from 'react-dnd';
import dimensions from 'Styles/Variables/dimensions.js';
import { TABLE_COLUMN } from 'Helpers/dragTypes';
import DragPreviewLayer from 'Components/DragPreviewLayer';
import TableOptionsColumn from './TableOptionsColumn';
import styles from './TableOptionsColumnDragPreview.css';
const formGroupSmallWidth = parseInt(dimensions.formGroupSmallWidth);
const formLabelLargeWidth = parseInt(dimensions.formLabelLargeWidth);
const formLabelRightMarginWidth = parseInt(dimensions.formLabelRightMarginWidth);
const dragHandleWidth = parseInt(dimensions.dragHandleWidth);
function collectDragLayer(monitor) {
return {
item: monitor.getItem(),
itemType: monitor.getItemType(),
currentOffset: monitor.getSourceClientOffset()
};
}
class TableOptionsColumnDragPreview extends Component {
//
// Render
render() {
const {
item,
itemType,
currentOffset
} = this.props;
if (!currentOffset || itemType !== TABLE_COLUMN) {
return null;
}
// The offset is shifted because the drag handle is on the right edge of the
// list item and the preview is wider than the drag handle.
const { x, y } = currentOffset;
const handleOffset = formGroupSmallWidth - formLabelLargeWidth - formLabelRightMarginWidth - dragHandleWidth;
const transform = `translate3d(${x - handleOffset}px, ${y}px, 0)`;
const style = {
position: 'absolute',
WebkitTransform: transform,
msTransform: transform,
transform
};
return (
<DragPreviewLayer>
<div
className={styles.dragPreview}
style={style}
>
<TableOptionsColumn
isDragging={false}
{...item}
/>
</div>
</DragPreviewLayer>
);
}
}
TableOptionsColumnDragPreview.propTypes = {
item: PropTypes.object,
itemType: PropTypes.string,
currentOffset: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired
})
};
export default DragLayer(collectDragLayer)(TableOptionsColumnDragPreview);
@@ -0,0 +1,18 @@
.columnDragSource {
padding: 4px 0;
}
.columnPlaceholder {
width: 100%;
height: 36px;
border: 1px dotted #aaa;
border-radius: 4px;
}
.columnPlaceholderBefore {
margin-bottom: 8px;
}
.columnPlaceholderAfter {
margin-top: 8px;
}
@@ -0,0 +1,164 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { DragSource, DropTarget } from 'react-dnd';
import classNames from 'classnames';
import { TABLE_COLUMN } from 'Helpers/dragTypes';
import TableOptionsColumn from './TableOptionsColumn';
import styles from './TableOptionsColumnDragSource.css';
const columnDragSource = {
beginDrag(column) {
return column;
},
endDrag(props, monitor, component) {
props.onColumnDragEnd(monitor.getItem(), monitor.didDrop());
}
};
const columnDropTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex === hoverIndex) {
return;
}
// When moving up, only trigger if drag position is above 50% and
// when moving down, only trigger if drag position is below 50%.
// If we're moving down the hoverIndex needs to be increased
// by one so it's ordered properly. Otherwise the hoverIndex will work.
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
props.onColumnDragMove(dragIndex, hoverIndex);
}
};
function collectDragSource(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
}
function collectDropTarget(connect, monitor) {
return {
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
};
}
class TableOptionsColumnDragSource extends Component {
//
// Render
render() {
const {
name,
label,
isVisible,
isModifiable,
index,
isDragging,
isDraggingUp,
isDraggingDown,
isOver,
connectDragSource,
connectDropTarget,
onVisibleChange
} = this.props;
const isBefore = !isDragging && isDraggingUp && isOver;
const isAfter = !isDragging && isDraggingDown && isOver;
// if (isDragging && !isOver) {
// return null;
// }
return connectDropTarget(
<div
className={classNames(
styles.columnDragSource,
isBefore && styles.isDraggingUp,
isAfter && styles.isDraggingDown
)}
>
{
isBefore &&
<div
className={classNames(
styles.columnPlaceholder,
styles.columnPlaceholderBefore
)}
/>
}
<TableOptionsColumn
name={name}
label={label}
isVisible={isVisible}
isModifiable={isModifiable}
index={index}
isDragging={isDragging}
isOver={isOver}
connectDragSource={connectDragSource}
onVisibleChange={onVisibleChange}
/>
{
isAfter &&
<div
className={classNames(
styles.columnPlaceholder,
styles.columnPlaceholderAfter
)}
/>
}
</div>
);
}
}
TableOptionsColumnDragSource.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isVisible: PropTypes.bool.isRequired,
isModifiable: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,
isDragging: PropTypes.bool,
isDraggingUp: PropTypes.bool,
isDraggingDown: PropTypes.bool,
isOver: PropTypes.bool,
connectDragSource: PropTypes.func,
connectDropTarget: PropTypes.func,
onVisibleChange: PropTypes.func.isRequired,
onColumnDragMove: PropTypes.func.isRequired,
onColumnDragEnd: PropTypes.func.isRequired
};
export default DropTarget(
TABLE_COLUMN,
columnDropTarget,
collectDropTarget
)(DragSource(
TABLE_COLUMN,
columnDragSource,
collectDragSource
)(TableOptionsColumnDragSource));
@@ -0,0 +1,5 @@
.columns {
margin-top: 10px;
width: 100%;
user-select: none;
}
@@ -0,0 +1,252 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import { inputTypes } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputHelpText from 'Components/Form/FormInputHelpText';
import FormInputGroup from 'Components/Form/FormInputGroup';
import Modal from 'Components/Modal/Modal';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import TableOptionsColumn from './TableOptionsColumn';
import TableOptionsColumnDragSource from './TableOptionsColumnDragSource';
import TableOptionsColumnDragPreview from './TableOptionsColumnDragPreview';
import styles from './TableOptionsModal.css';
class TableOptionsModal extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
hasPageSize: !!props.pageSize,
pageSize: props.pageSize,
pageSizeError: null,
dragIndex: null,
dropIndex: null
};
}
componentDidUpdate(prevProps) {
if (prevProps.pageSize !== this.state.pageSize) {
this.setState({ pageSize: this.props.pageSize });
}
}
//
// Listeners
onPageSizeChange = ({ value }) => {
let pageSizeError = null;
if (value < 5) {
pageSizeError = 'Page size must be at least 5';
} else if (value > 250) {
pageSizeError = 'Page size must not exceed 250';
} else {
this.props.onTableOptionChange({ pageSize: value });
}
this.setState({
pageSize: value,
pageSizeError
});
}
onVisibleChange = ({ name, value }) => {
const columns = _.cloneDeep(this.props.columns);
const column = _.find(columns, { name });
column.isVisible = value;
this.props.onTableOptionChange({ columns });
}
onColumnDragMove = (dragIndex, dropIndex) => {
if (this.state.dragIndex !== dragIndex || this.state.dropIndex !== dropIndex) {
this.setState({
dragIndex,
dropIndex
});
}
}
onColumnDragEnd = ({ id }, didDrop) => {
const {
dragIndex,
dropIndex
} = this.state;
if (didDrop && dropIndex !== null) {
const columns = _.cloneDeep(this.props.columns);
const items = columns.splice(dragIndex, 1);
columns.splice(dropIndex, 0, items[0]);
this.props.onTableOptionChange({ columns });
}
this.setState({
dragIndex: null,
dropIndex: null
});
}
//
// Render
render() {
const {
isOpen,
columns,
canModifyColumns,
optionsComponent: OptionsComponent,
onTableOptionChange,
onModalClose
} = this.props;
const {
hasPageSize,
pageSize,
pageSizeError,
dragIndex,
dropIndex
} = this.state;
const isDragging = dropIndex !== null;
const isDraggingUp = isDragging && dropIndex < dragIndex;
const isDraggingDown = isDragging && dropIndex > dragIndex;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Table Options
</ModalHeader>
<ModalBody>
<Form>
{
hasPageSize &&
<FormGroup>
<FormLabel>Page Size</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="pageSize"
value={pageSize || 0}
helpText="Number of items to show on each page"
errors={pageSizeError ? [{ message: pageSizeError }] : undefined}
onChange={this.onPageSizeChange}
/>
</FormGroup>
}
{
!!OptionsComponent &&
<OptionsComponent
onTableOptionChange={onTableOptionChange}
/>
}
{
canModifyColumns &&
<FormGroup>
<FormLabel>Columns</FormLabel>
<div>
<FormInputHelpText
text="Choose which columns are visible and which order they appear in"
/>
<div className={styles.columns}>
{
columns.map((column, index) => {
const {
name,
label,
columnLabel,
isVisible,
isModifiable
} = column;
if (isModifiable !== false) {
return (
<TableOptionsColumnDragSource
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
isModifiable={true}
index={index}
isDragging={isDragging}
isDraggingUp={isDraggingUp}
isDraggingDown={isDraggingDown}
onVisibleChange={this.onVisibleChange}
onColumnDragMove={this.onColumnDragMove}
onColumnDragEnd={this.onColumnDragEnd}
/>
);
}
return (
<TableOptionsColumn
key={name}
name={name}
label={label || columnLabel}
isVisible={isVisible}
index={index}
isModifiable={false}
onVisibleChange={this.onVisibleChange}
/>
);
})
}
<TableOptionsColumnDragPreview />
</div>
</div>
</FormGroup>
}
</Form>
</ModalBody>
<ModalFooter>
<Button
onPress={onModalClose}
>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
TableOptionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
pageSize: PropTypes.number,
canModifyColumns: PropTypes.bool.isRequired,
optionsComponent: PropTypes.func,
onTableOptionChange: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
TableOptionsModal.defaultProps = {
canModifyColumns: true
};
export default DragDropContext(HTML5Backend)(TableOptionsModal);
@@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import TableOptionsModal from './TableOptionsModal';
class TableOptionsModalWrapper extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isTableOptionsModalOpen: false
};
}
//
// Listeners
onTableOptionsPress = () => {
this.setState({ isTableOptionsModalOpen: true });
}
onTableOptionsModalClose = () => {
this.setState({ isTableOptionsModalOpen: false });
}
//
// Render
render() {
const {
columns,
children,
...otherProps
} = this.props;
return (
<Fragment>
{
React.cloneElement(children, { onPress: this.onTableOptionsPress })
}
<TableOptionsModal
{...otherProps}
isOpen={this.state.isTableOptionsModalOpen}
columns={columns}
onModalClose={this.onTableOptionsModalClose}
/>
</Fragment>
);
}
}
TableOptionsModalWrapper.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
children: PropTypes.node.isRequired
};
export default TableOptionsModalWrapper;