mirror of
https://github.com/tengge1/ShadowEditor.git
synced 2026-01-25 15:08:11 +00:00
新版ui合并到web中。
This commit is contained in:
parent
38c4c88a6f
commit
59a90678f9
34
ShadowEditor.Web/src/ui/Config.css
Normal file
34
ShadowEditor.Web/src/ui/Config.css
Normal file
@ -0,0 +1,34 @@
|
||||
:root {
|
||||
--theme-blue: #3399FF;
|
||||
--theme-green: #64CF40;
|
||||
--theme-orange: #F6A623;
|
||||
--theme-black: #FFFFFF;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0.2em;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:horizontal {
|
||||
height: 0.5em;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:window-inactive {
|
||||
background: rgba(169, 169, 169, 0.4);
|
||||
}
|
||||
36
ShadowEditor.Web/src/ui/canvas/Canvas.jsx
Normal file
36
ShadowEditor.Web/src/ui/canvas/Canvas.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import './css/Canvas.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 画布
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Canvas extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.dom = React.createRef();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, ...others } = this.props;
|
||||
|
||||
return <canvas
|
||||
className={classNames('Canvas', className)}
|
||||
style={style}
|
||||
ref={this.dom}
|
||||
{...others}></canvas>;
|
||||
}
|
||||
}
|
||||
|
||||
Canvas.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
Canvas.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default Canvas;
|
||||
0
ShadowEditor.Web/src/ui/canvas/css/Canvas.css
Normal file
0
ShadowEditor.Web/src/ui/canvas/css/Canvas.css
Normal file
11
ShadowEditor.Web/src/ui/common/Accordion.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Accordion.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 折叠面板
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Accordion extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Accordion;
|
||||
11
ShadowEditor.Web/src/ui/common/Buttons.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Buttons.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 很多按钮
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Buttons extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Buttons;
|
||||
11
ShadowEditor.Web/src/ui/common/Column.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Column.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 列
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Column extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Column;
|
||||
11
ShadowEditor.Web/src/ui/common/Columns.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Columns.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 很多列
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Columns extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Columns;
|
||||
11
ShadowEditor.Web/src/ui/common/Content.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Content.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 内容
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Content extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Content;
|
||||
11
ShadowEditor.Web/src/ui/common/Row.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Row.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 行
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Row extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Row;
|
||||
11
ShadowEditor.Web/src/ui/common/Rows.jsx
Normal file
11
ShadowEditor.Web/src/ui/common/Rows.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 很多行
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Rows extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Rows;
|
||||
38
ShadowEditor.Web/src/ui/form/Button.jsx
Normal file
38
ShadowEditor.Web/src/ui/form/Button.jsx
Normal file
@ -0,0 +1,38 @@
|
||||
import './css/Button.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 按钮
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Button extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, color, disabled, ...others } = this.props;
|
||||
return <button
|
||||
className={classNames('Button', color, disabled && 'disabled', className)}
|
||||
style={style}
|
||||
disabled={disabled}
|
||||
{...others}>
|
||||
{children}
|
||||
</button>;
|
||||
}
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
color: PropTypes.oneOf(['primary', 'success', 'warn', 'danger']),
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
color: null,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export default Button;
|
||||
61
ShadowEditor.Web/src/ui/form/CheckBox.jsx
Normal file
61
ShadowEditor.Web/src/ui/form/CheckBox.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
import './css/CheckBox.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 复选框
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class CheckBox extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
checked: props.checked,
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
const target = event.target;
|
||||
const name = target.getAttribute('name');
|
||||
const checked = target.checked;
|
||||
|
||||
this.setState({ checked });
|
||||
|
||||
onChange && onChange(name, checked, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, name, checked, disabled, onChange } = this.props;
|
||||
return <input
|
||||
type={'checkbox'}
|
||||
className={classNames('CheckBox', this.state.checked && 'checked', disabled && 'disabled', className)}
|
||||
style={style}
|
||||
name={name}
|
||||
defaultChecked={this.state.checked}
|
||||
disabled={disabled}
|
||||
onClick={this.handleChange} />;
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
checked: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
CheckBox.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
name: undefined,
|
||||
checked: false,
|
||||
disabled: false,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
export default CheckBox;
|
||||
51
ShadowEditor.Web/src/ui/form/Form.jsx
Normal file
51
ShadowEditor.Web/src/ui/form/Form.jsx
Normal file
@ -0,0 +1,51 @@
|
||||
import './css/Form.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表单
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Form extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this, props.onSubmit);
|
||||
}
|
||||
|
||||
handleSubmit(onSubmit) {
|
||||
event.preventDefault();
|
||||
onSubmit && onSubmit();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, children, direction, onSubmit, ...others } = this.props;
|
||||
return <form
|
||||
className={classNames('Form', direction, className)}
|
||||
style={style}
|
||||
onSubmit={this.handleSubmit}
|
||||
{...others}>
|
||||
{children}
|
||||
</form>;
|
||||
}
|
||||
}
|
||||
|
||||
Form.propTypes = {
|
||||
onSubmit: PropTypes.func,
|
||||
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
direction: PropTypes.oneOf(['horizontal', 'vertical']),
|
||||
};
|
||||
|
||||
Form.defaultProps = {
|
||||
onSubmit: null,
|
||||
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
direction: 'horizontal',
|
||||
};
|
||||
|
||||
export default Form;
|
||||
31
ShadowEditor.Web/src/ui/form/FormControl.jsx
Normal file
31
ShadowEditor.Web/src/ui/form/FormControl.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import './css/FormControl.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表单项
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class FormControl extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <div className={classNames('FormControl', className)} style={style} {...others}>
|
||||
{children}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
FormControl.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
FormControl.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default FormControl;
|
||||
41
ShadowEditor.Web/src/ui/form/IconButton.jsx
Normal file
41
ShadowEditor.Web/src/ui/form/IconButton.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import './css/IconButton.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 图标按钮
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class IconButton extends React.Component {
|
||||
render() {
|
||||
const { className, style, icon, title, selected, onClick, ...others } = this.props;
|
||||
return <button
|
||||
className={classNames('IconButton', selected && 'selected', className)}
|
||||
style={style}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
{...others}>
|
||||
<i className={classNames('iconfont', icon && 'icon-' + icon)}></i>
|
||||
</button>;
|
||||
}
|
||||
}
|
||||
|
||||
IconButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
icon: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
selected: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
IconButton.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
icon: null,
|
||||
title: null,
|
||||
selected: false,
|
||||
onClick: null,
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
56
ShadowEditor.Web/src/ui/form/Input.jsx
Normal file
56
ShadowEditor.Web/src/ui/form/Input.jsx
Normal file
@ -0,0 +1,56 @@
|
||||
import './css/Input.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 输入框
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Input extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
this.handleInput = this.handleInput.bind(this, props.onInput);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
onChange && onChange(event.target.value, event);
|
||||
}
|
||||
|
||||
handleInput(onInput, event) {
|
||||
onInput && onInput(event.target.value, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, value, disabled, onChange, onInput } = this.props;
|
||||
|
||||
return <input
|
||||
className={classNames('Input', className)}
|
||||
style={style}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onInput={this.handleInput}
|
||||
disabled={disabled} />;
|
||||
}
|
||||
}
|
||||
|
||||
Input.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
value: '',
|
||||
disabled: false,
|
||||
onChange: null,
|
||||
onInput: null,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
31
ShadowEditor.Web/src/ui/form/Label.jsx
Normal file
31
ShadowEditor.Web/src/ui/form/Label.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import './css/Label.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 标签
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Label extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <label className={classNames('Label', className)} style={style} {...others}>
|
||||
{children}
|
||||
</label>;
|
||||
}
|
||||
}
|
||||
|
||||
Label.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
Label.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default Label;
|
||||
59
ShadowEditor.Web/src/ui/form/Radio.jsx
Normal file
59
ShadowEditor.Web/src/ui/form/Radio.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
import './css/Radio.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 单选框
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Radio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: props.selected,
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
this.setState({
|
||||
selected: event.target.checked,
|
||||
});
|
||||
onChange && onChange(event.target.checked, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, selected, disabled, onChange, ...others } = this.props;
|
||||
return <input
|
||||
type={'radio'}
|
||||
className={classNames('Radio',
|
||||
this.state.selected && 'selected',
|
||||
disabled && 'disabled',
|
||||
className)}
|
||||
style={style}
|
||||
defaultChecked={this.state.selected}
|
||||
disabled={disabled}
|
||||
onClick={this.handleChange}
|
||||
{...others} />;
|
||||
}
|
||||
}
|
||||
|
||||
Radio.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
selected: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
Radio.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
selected: false,
|
||||
disabled: false,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
export default Radio;
|
||||
156
ShadowEditor.Web/src/ui/form/SearchField.jsx
Normal file
156
ShadowEditor.Web/src/ui/form/SearchField.jsx
Normal file
@ -0,0 +1,156 @@
|
||||
import './css/SearchField.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import IconButton from './IconButton.jsx';
|
||||
import CheckBox from './CheckBox.jsx';
|
||||
|
||||
/**
|
||||
* 搜索框
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class SearchField extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.value,
|
||||
categories: [],
|
||||
filterShow: false,
|
||||
};
|
||||
|
||||
this.handleAdd = this.handleAdd.bind(this, props.onAdd);
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
this.handleInput = this.handleInput.bind(this, props.onInput);
|
||||
this.handleReset = this.handleReset.bind(this, props.onInput, props.onChange);
|
||||
this.handleShowFilter = this.handleShowFilter.bind(this);
|
||||
this.handleCheckBoxChange = this.handleCheckBoxChange.bind(this, props.onInput, props.onChange);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, data, placeholder, addHidden } = this.props;
|
||||
const { value, categories, filterShow } = this.state;
|
||||
|
||||
return <div className={classNames('SearchField', className)}>
|
||||
<IconButton
|
||||
className={classNames(addHidden && 'hidden')}
|
||||
icon={'add'}
|
||||
onClick={this.handleAdd}></IconButton>
|
||||
<input
|
||||
className={'input'}
|
||||
style={style}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onInput={this.handleInput}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
/>
|
||||
<IconButton
|
||||
icon={'close'}
|
||||
onClick={this.handleReset}></IconButton>
|
||||
<IconButton
|
||||
icon={'filter'}
|
||||
className={classNames(filterShow && 'selected')}
|
||||
onClick={this.handleShowFilter}></IconButton>
|
||||
<div className={classNames('category', !filterShow && 'hidden')}>
|
||||
{data.map(n => {
|
||||
return <div className={'item'} key={n.ID}>
|
||||
<CheckBox
|
||||
name={n.ID}
|
||||
checked={categories.indexOf(n.ID) > -1}
|
||||
onChange={this.handleCheckBoxChange}></CheckBox>
|
||||
<label className={'title'}>{n.Name}</label>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
handleAdd(onAdd, event) {
|
||||
onAdd && onAdd(event);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const value = event.target.value;
|
||||
|
||||
this.setState({ value });
|
||||
|
||||
onChange && onChange(value, this.state.categories, event);
|
||||
}
|
||||
|
||||
handleInput(onInput, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const value = event.target.value;
|
||||
|
||||
this.setState({ value });
|
||||
|
||||
onInput && onInput(value, this.state.categories, event);
|
||||
}
|
||||
|
||||
handleReset(onInput, onChange, event) {
|
||||
const value = '';
|
||||
|
||||
this.setState({ value });
|
||||
|
||||
onInput && onInput(value, this.state.categories, event);
|
||||
onChange && onChange(value, this.state.categories, event);
|
||||
}
|
||||
|
||||
handleShowFilter() {
|
||||
this.setState({
|
||||
filterShow: !this.state.filterShow,
|
||||
});
|
||||
}
|
||||
|
||||
handleCheckBoxChange(onInput, onChange, name, checked, event) {
|
||||
let categories = this.state.categories;
|
||||
let index = categories.indexOf(name);
|
||||
|
||||
if (checked && index === -1) {
|
||||
categories.push(name);
|
||||
} else if (!checked && index > -1) {
|
||||
categories.splice(index, 1);
|
||||
} else {
|
||||
console.warn(`SearchField: handleCheckBoxChange error.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const value = this.state.value;
|
||||
|
||||
this.setState({ categories }, () => {
|
||||
onInput && onInput(value, categories, event);
|
||||
onChange && onChange(value, categories, event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SearchField.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
data: PropTypes.array,
|
||||
placeholder: PropTypes.string,
|
||||
onAdd: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
handleShowFilter: PropTypes.func,
|
||||
addHidden: PropTypes.bool,
|
||||
};
|
||||
|
||||
SearchField.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
value: '',
|
||||
data: [],
|
||||
placeholder: 'Enter a keyword',
|
||||
onAdd: null,
|
||||
onChange: null,
|
||||
onInput: null,
|
||||
handleShowFilter: null,
|
||||
addHidden: false,
|
||||
};
|
||||
|
||||
export default SearchField;
|
||||
63
ShadowEditor.Web/src/ui/form/Select.jsx
Normal file
63
ShadowEditor.Web/src/ui/form/Select.jsx
Normal file
@ -0,0 +1,63 @@
|
||||
import './css/Select.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 输入框
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Select extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
const selectedIndex = event.target.selectedIndex;
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
onChange && onChange(null, event);
|
||||
return;
|
||||
}
|
||||
|
||||
const value = event.target.options[selectedIndex].value;
|
||||
|
||||
onChange && onChange(value, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, options, value, disabled, onChange } = this.props;
|
||||
|
||||
return <select
|
||||
className={classNames('Select', className)}
|
||||
style={style}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}>
|
||||
{options && Object.keys(options).map(n => {
|
||||
return <option value={n} key={n}>{options[n]}</option>;
|
||||
})}
|
||||
</select>;
|
||||
}
|
||||
}
|
||||
|
||||
Select.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
options: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
Select.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
options: null,
|
||||
value: null,
|
||||
disabled: false,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
export default Select;
|
||||
64
ShadowEditor.Web/src/ui/form/TextArea.jsx
Normal file
64
ShadowEditor.Web/src/ui/form/TextArea.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import './css/TextArea.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 文本域
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TextArea extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.value,
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
this.handleInput = this.handleInput.bind(this, props.onInput);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
this.setState({
|
||||
value: event.target.value,
|
||||
});
|
||||
onChange && onChange(event.target.value, event);
|
||||
}
|
||||
|
||||
handleInput(onInput, event) {
|
||||
this.setState({
|
||||
value: event.target.value,
|
||||
});
|
||||
onInput && onInput(event.target.value, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, value, onChange, onInput, ...others } = this.props;
|
||||
|
||||
return <textarea
|
||||
className={classNames('TextArea', className)}
|
||||
style={style}
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
onInput={this.handleInput}
|
||||
{...others}></textarea>;
|
||||
}
|
||||
}
|
||||
|
||||
TextArea.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onInput: PropTypes.func,
|
||||
};
|
||||
|
||||
TextArea.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
value: '',
|
||||
onChange: null,
|
||||
onInput: null,
|
||||
};
|
||||
|
||||
export default TextArea;
|
||||
58
ShadowEditor.Web/src/ui/form/Toggle.jsx
Normal file
58
ShadowEditor.Web/src/ui/form/Toggle.jsx
Normal file
@ -0,0 +1,58 @@
|
||||
import './css/Toggle.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 开关
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Toggle extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selected: props.selected,
|
||||
};
|
||||
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
var selected = event.target.classList.contains('selected');
|
||||
|
||||
this.setState({
|
||||
selected: !selected,
|
||||
});
|
||||
onChange && onChange(!selected, event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, selected, disabled, onChange, ...others } = this.props;
|
||||
|
||||
return <div
|
||||
className={classNames('Toggle', this.state.selected && 'selected',
|
||||
disabled && 'disabled',
|
||||
className)}
|
||||
style={style}
|
||||
onClick={disabled ? null : this.handleChange}
|
||||
{...others}></div>;
|
||||
}
|
||||
}
|
||||
|
||||
Toggle.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
selected: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
Toggle.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
selected: false,
|
||||
disabled: false,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
export default Toggle;
|
||||
31
ShadowEditor.Web/src/ui/form/css/Button.css
Normal file
31
ShadowEditor.Web/src/ui/form/css/Button.css
Normal file
@ -0,0 +1,31 @@
|
||||
.Button {
|
||||
height: 24px;
|
||||
margin: 0 4px;
|
||||
padding: 0 8px;
|
||||
color: #fff;
|
||||
background-color: #e74c3c;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Button.primary {
|
||||
color: #fff;
|
||||
background-color: #3399ff;
|
||||
}
|
||||
|
||||
.Button.success {
|
||||
color: #fff;
|
||||
background-color: #64cf40;
|
||||
}
|
||||
|
||||
.Button.warn {
|
||||
color: #fff;
|
||||
background-color: #f6a623;
|
||||
}
|
||||
|
||||
.Button.disabled {
|
||||
color: #fff;
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
21
ShadowEditor.Web/src/ui/form/css/CheckBox.css
Normal file
21
ShadowEditor.Web/src/ui/form/css/CheckBox.css
Normal file
@ -0,0 +1,21 @@
|
||||
.CheckBox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAvSURBVDhPYxg6oKur6z8lGGoMAoAEyQWjBkLAqIEkgVEDIWCEGkgJhhoz6AEDAwCX46nq5LTHtAAAAABJRU5ErkJggg==);
|
||||
display: inline-block;
|
||||
-webkit-appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.CheckBox.checked {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACYSURBVDhP1YxRCoMwEERzp9ZL1RO156i306QuTJg1jpHFH/vgQXSHl/6Hx7uUKyJD1CgiMkSNIiJD1CgiMkSNznx9+UaG+OGZw6eUedn+Q4b4Y08VM5Eh7UD5XF2yviFD6mGc9uNqPoiZyBB/NPy32YuZyJB24KP+fSQyRI0q6taKDFGjiMgQNYqIDFGjiMgQNYqIzO1J6Qc9ntav8Xl7ewAAAABJRU5ErkJggg==);
|
||||
}
|
||||
|
||||
.CheckBox.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAvSURBVDhPYxg64PXr1/8pwVBjEAAkSC4YNRACRg0kCYwaCAEj1EBKMNSYQQ8YGACTLe4e+yPbzQAAAABJRU5ErkJggg==);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.CheckBox.checked.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABkSURBVDhP5dBBCsAgDADB/P+rviCXWFYsLaGNYi6VLgREdARln0opNTOduWJztZ+AZlZVta3TINj9fAr0GC2DTxiF4PkvvjeMQpD8xQijIUjRA74pkNgbYTQNzrYxmJnOfD6RAzbN65BiZNB7AAAAAElFTkSuQmCC);
|
||||
}
|
||||
3
ShadowEditor.Web/src/ui/form/css/Form.css
Normal file
3
ShadowEditor.Web/src/ui/form/css/Form.css
Normal file
@ -0,0 +1,3 @@
|
||||
.Form {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
13
ShadowEditor.Web/src/ui/form/css/FormControl.css
Normal file
13
ShadowEditor.Web/src/ui/form/css/FormControl.css
Normal file
@ -0,0 +1,13 @@
|
||||
.FormControl {
|
||||
min-height: 20px;
|
||||
margin: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.FormControl>* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.FormControl>.Label {
|
||||
width: 60px;
|
||||
}
|
||||
35
ShadowEditor.Web/src/ui/form/css/IconButton.css
Normal file
35
ShadowEditor.Web/src/ui/form/css/IconButton.css
Normal file
@ -0,0 +1,35 @@
|
||||
.IconButton {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: #555;
|
||||
background: none;
|
||||
margin: 4px;
|
||||
padding: 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.IconButton.selected {
|
||||
color: #fff;
|
||||
background: #3399ff;
|
||||
border: 1px solid #3399ff;
|
||||
}
|
||||
|
||||
.IconButton:hover {
|
||||
color: #3399ff;
|
||||
background: none;
|
||||
border: 1px solid #3399ff;
|
||||
}
|
||||
|
||||
.IconButton.selected:hover {
|
||||
color: #fff;
|
||||
background: #3399ff;
|
||||
border: 2px solid #3399ff;
|
||||
}
|
||||
|
||||
.IconButton .iconfont {
|
||||
font-size: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
9
ShadowEditor.Web/src/ui/form/css/Input.css
Normal file
9
ShadowEditor.Web/src/ui/form/css/Input.css
Normal file
@ -0,0 +1,9 @@
|
||||
.Input {
|
||||
width: 160px;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
margin: 1px 0;
|
||||
padding: 0 2px;
|
||||
border: 1px solid rgb(217, 217, 217);
|
||||
box-sizing: border-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
6
ShadowEditor.Web/src/ui/form/css/Label.css
Normal file
6
ShadowEditor.Web/src/ui/form/css/Label.css
Normal file
@ -0,0 +1,6 @@
|
||||
.Label {
|
||||
height: 20px;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
line-height: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
21
ShadowEditor.Web/src/ui/form/css/Radio.css
Normal file
21
ShadowEditor.Web/src/ui/form/css/Radio.css
Normal file
@ -0,0 +1,21 @@
|
||||
.Radio {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEiSURBVDhP1ZPLaoNAGIVdttC+TUveqHmAEkp3br0gKCq60bfwti40BNF9kodIusrOzIFTxNYxE3d+8MOg/znM/Bdt2RiG8WxZ1krEmrHSdf2Jv+/Ddd3POI4PSZKci6K4IHCOomjved4H09TwfX9XVdWpkyDMz0EQfDN9Gpi1bUupnKZpOpG7pWwcPHPqZn/BTcXzN5QPQQNQM+YqI2p6dBznkTY96CCKzjxloLFt+5U2PRgLdJJ5yuR5fjFN8402PXMNoZEZzn6yqP8LbXqwAaIpe+Ypg6akafpAmyHYgLIslW+ZZdmP0LxTPg42AEN7i7quuzAMvyibBhuAoaX2H7iZstkv2ADUB0VHJxE449vNZ8rABmBoMRYIdFPagIWgaVdKsJshjm46QwAAAABJRU5ErkJggg==);
|
||||
display: inline-block;
|
||||
-webkit-appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Radio.selected {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAFlSURBVDhP1ZO/SsNQFMYzKuimm4OPoFVxlKoFoTrqI+gDiIhro4KLm9C61ERaseDiE2gRlOqmiLRd62w61enzftcTcluTNHbrDw5cknM+zl9ruJk4wfhcAYupAnZofE+eYUx+/4/0BQ7WS2hsVeDZd+jQ+N4oo77sYF/ckrHqonZcxRciOKzCyzh4FPd4KHbzLpExXL8BGRdPEhYOy+zNrP0NvHz+Gt8m9j28tIM9Ce+GA2DPxFdz+wEsFQE1DG1885tJtozm1ClGRSaAE2TTxU9nY4qZomammypm5hwLIhPAtVAldMQPz62/Yr7xn09OTT+Vx7bIBAwqyJhQQeU4cMmzecyLTAAvgEsrfpqkQ5kuYkRkuuEFHKmlFV8Ns2GJNDMzovrXXnGxK+Hh8AIqamn7cfUKrF3iQcLi4QXYPZmaMLPEYj68gGwJTTadk6TxzZ71LTMKXgCXlmtB4zQjBzAkWNYPuWx6Vj2oQrgAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
.Radio.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEFSURBVDhP1ZNdCsIwEIT7qGBvo3gjPYCU4v0ExSOoN+ifTUvpW52hI0FJY/TNDxZLsjNsdtfovxmGIc7zfJ1l2ZbBb5wtdP0dEO/Lsrwi6qZpega/i6K44C5VWhgQnWFwRzVOjDE1TI9K90OzrusknYY5yD1J5obP9FX2DitFGxLJX8F9zJ6NqeFAc8PPXDYWTpBNH9PCoQbalWwsXIu2bXvlBcPpQ7uRjeVXQ2qchr8+uaqqGoZL2Vhwt8DlZUwLR0OZyeYVVJmiJ8FVYm0MdnEnuRuYHr9Y7INkfvgP8FWqysLMnqA3CfvDQXGSDA6AZx+fOQWKmXNpuRaKJc7cA/gTougBp8flNLlkjQ0AAAAASUVORK5CYII=);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.Radio.selected.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAE1SURBVDhP1ZNJDoJAEEVZaiK30XgjPYAhxvuZaDwCegMmGUIIC9r/6UIaRBR3vqRip4ZPdVVr/TdKKdv3/bXneVsaz/AtJDwNFB/CMLzC4jRNCxrPQRC4iO0l7TtQdIHAHd0MkiRJDNGTpI9DsTzPpfQ9zEHuWcqG4TX7nVVVpcqyrI1nE3aKMThS3gVxmzPTqZqiKBQ+orCQ2nimzwQ1N/zMRaYFyWsOXafpzkwxU9TslDXwrUSmBcnbLMuen+cV+2KNMdbA7cO3EZkWOH8SZA18r4K/XjmKohj+pci0ILZA0NVpmglLmYlMFxTsMZNnl4Td8Io0szOCZ5PgLe6kfBiIniY87KOUjcN/QL9TE+nsO7EGzMbhfLgobpLGBdD38ZrvQDNzjGCFhWzElvANL+BPsKwHYaPc6BXkh3gAAAAASUVORK5CYII=);
|
||||
}
|
||||
58
ShadowEditor.Web/src/ui/form/css/SearchField.css
Normal file
58
ShadowEditor.Web/src/ui/form/css/SearchField.css
Normal file
@ -0,0 +1,58 @@
|
||||
.SearchField {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.SearchField>.input {
|
||||
width: 0;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
margin: 1px 0;
|
||||
padding: 2px;
|
||||
border: 1px solid rgb(217, 217, 217);
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.SearchField>.IconButton {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.SearchField>.IconButton.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.SearchField>.IconButton>.iconfont {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.SearchField>.category {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 28px;
|
||||
height: 160px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-block;
|
||||
z-index: 10;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.SearchField>.category.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.SearchField>.category>.item {
|
||||
padding: 0 8px 0 2px;
|
||||
}
|
||||
|
||||
.SearchField>.category>.item>.CheckBox {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.SearchField>.category>.item>.title {
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
3
ShadowEditor.Web/src/ui/form/css/Select.css
Normal file
3
ShadowEditor.Web/src/ui/form/css/Select.css
Normal file
@ -0,0 +1,3 @@
|
||||
.Select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
10
ShadowEditor.Web/src/ui/form/css/TextArea.css
Normal file
10
ShadowEditor.Web/src/ui/form/css/TextArea.css
Normal file
@ -0,0 +1,10 @@
|
||||
.TextArea {
|
||||
width: 160px;
|
||||
height: 200px;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
margin: 0px;
|
||||
padding: 0px 2px;
|
||||
border: 1px solid rgb(217, 217, 217);
|
||||
box-sizing: border-box;
|
||||
vertical-align: top;
|
||||
}
|
||||
21
ShadowEditor.Web/src/ui/form/css/Toggle.css
Normal file
21
ShadowEditor.Web/src/ui/form/css/Toggle.css
Normal file
@ -0,0 +1,21 @@
|
||||
.Toggle {
|
||||
width: 34px;
|
||||
height: 20px;
|
||||
margin: 0 4px;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABYSURBVEhL7da9CQAgDIRR918gC2SBzJGhImlEzsofMMUJrz2+dDYRiQrqhZhZuPsxVV3Gd4yQHLt5ecg8vIshiCGIIYghiCGIIaheSA5lzKln34DfioRIdJ5veQfy/zjLAAAAAElFTkSuQmCC);
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Toggle.selected {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABRSURBVEhLYzCe+f//YMDDxyEzz/z/P4NMXL8fYQ7FDqEEnHmGMGfUISAw6hB0MOoQdDDqEHQw6hB0MOoQdDA8HQJqBpCLqdoMoBYeJA75/x8AV8uDZSB9PMIAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
.Toggle.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABVSURBVEhL7dY7CgAgDERB73/VEEiTOpJGZK38gClWmHZ56WwiEhXUCzGzcPdjqrqM7xghOXbz8pB5eBdDEEMQQxBDEEMQQ1C9kBzKmFPPvgG/FQmR6IJTLfO1daX2AAAAAElFTkSuQmCC);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.Toggle.selected.disabled {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAUCAYAAADoZO9yAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAABQSURBVEhL7dahCgAgDIRh3/9Vrw1WVhTLwmEQNci4H66Or2kD0H9YHYi7H8/M8s415KaIyDuCzAThBOEE4QThBOFqQlbP++6efgNe7RMI+gDSby3zpGs0DgAAAABJRU5ErkJggg==);
|
||||
}
|
||||
52
ShadowEditor.Web/src/ui/icon/Icon.jsx
Normal file
52
ShadowEditor.Web/src/ui/icon/Icon.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import './css/Icon.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 图标
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Icon extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleClick = this.handleClick.bind(this, props.onClick);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, name, icon, title } = this.props;
|
||||
|
||||
return <i className={classNames('Icon', 'iconfont',
|
||||
icon && 'icon-' + icon,
|
||||
className)}
|
||||
style={style}
|
||||
name={name}
|
||||
title={title}
|
||||
onClick={this.handleClick}></i>;
|
||||
}
|
||||
|
||||
handleClick(onClick, event) {
|
||||
const name = event.target.getAttribute('name');
|
||||
onClick && onClick(name, event);
|
||||
}
|
||||
}
|
||||
|
||||
Icon.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
name: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
Icon.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
name: null,
|
||||
icon: null,
|
||||
title: null,
|
||||
onClick: null,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
5
ShadowEditor.Web/src/ui/icon/css/Icon.css
Normal file
5
ShadowEditor.Web/src/ui/icon/css/Icon.css
Normal file
@ -0,0 +1,5 @@
|
||||
.Icon {
|
||||
font-size: 20px;
|
||||
margin: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
35
ShadowEditor.Web/src/ui/image/Image.jsx
Normal file
35
ShadowEditor.Web/src/ui/image/Image.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import './css/Image.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 图片
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Image extends React.Component {
|
||||
render() {
|
||||
const { className, style, src, title } = this.props;
|
||||
|
||||
return <img
|
||||
className={classNames('Image', className)}
|
||||
style={style}
|
||||
src={src}
|
||||
title={title}></img>;
|
||||
}
|
||||
}
|
||||
|
||||
Image.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
src: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
Image.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
src: null,
|
||||
title: null,
|
||||
};
|
||||
|
||||
export default Image;
|
||||
167
ShadowEditor.Web/src/ui/image/ImageList.jsx
Normal file
167
ShadowEditor.Web/src/ui/image/ImageList.jsx
Normal file
@ -0,0 +1,167 @@
|
||||
import './css/ImageList.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Icon from '../icon/Icon.jsx';
|
||||
import IconButton from '../form/IconButton.jsx';
|
||||
import Input from '../form/Input.jsx';
|
||||
|
||||
/**
|
||||
* 图片列表
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class ImageList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { onClick, onEdit, onDelete } = props;
|
||||
|
||||
this.state = {
|
||||
pageSize: 6,
|
||||
pageNum: 0,
|
||||
};
|
||||
|
||||
this.handleFirstPage = this.handleFirstPage.bind(this);
|
||||
this.handleLastPage = this.handleLastPage.bind(this);
|
||||
this.handlePreviousPage = this.handlePreviousPage.bind(this);
|
||||
this.handleNextPage = this.handleNextPage.bind(this);
|
||||
|
||||
this.handleClick = this.handleClick.bind(this, onClick);
|
||||
this.handleEdit = this.handleEdit.bind(this, onEdit);
|
||||
this.handleDelete = this.handleDelete.bind(this, onDelete);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, data, firstPageText, lastPageText, currentPageText, previousPageText, nextPageText } = this.props;
|
||||
const { pageSize, pageNum } = this.state;
|
||||
|
||||
const totalPage = this.getTotalPage();
|
||||
|
||||
const current = data.filter((n, i) => {
|
||||
return i >= pageSize * pageNum && i < pageSize * (pageNum + 1);
|
||||
});
|
||||
|
||||
return <div className={classNames('ImageList', className)} style={style}>
|
||||
<div className={'content'}>
|
||||
{current.map(n => {
|
||||
return <div className={'item'} data-id={n.id} key={n.id} onClick={this.handleClick}>
|
||||
{n.src ?
|
||||
<img className={'img'} src={n.src}></img> :
|
||||
<div className={'no-img'}>
|
||||
<Icon icon={n.icon}></Icon>
|
||||
</div>}
|
||||
<div className={'title'}>{n.title}</div>
|
||||
{n.cornerText && <div className={'cornerText'}>{n.cornerText}</div>}
|
||||
<IconButton className={'edit'} icon={'edit'} data-id={n.id} onClick={this.handleEdit}></IconButton>
|
||||
<IconButton className={'delete'} icon={'delete'} data-id={n.id} onClick={this.handleDelete}></IconButton>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
<div className={'page'}>
|
||||
<IconButton icon={'backward'} title={firstPageText} onClick={this.handleFirstPage}></IconButton>
|
||||
<IconButton icon={'left-triangle2'} title={previousPageText} onClick={this.handlePreviousPage}></IconButton>
|
||||
<Input className={'current'} value={(pageNum + 1).toString()} title={currentPageText} disabled={true} />
|
||||
<IconButton icon={'right-triangle2'} title={nextPageText} onClick={this.handleNextPage}></IconButton>
|
||||
<IconButton icon={'forward'} title={lastPageText} onClick={this.handleLastPage}></IconButton>
|
||||
<div className={'info'}>
|
||||
共<span>{totalPage}</span>页
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
handleFirstPage() {
|
||||
this.setState({
|
||||
pageNum: 0,
|
||||
});
|
||||
}
|
||||
|
||||
handleLastPage() {
|
||||
const totalPage = this.getTotalPage();
|
||||
|
||||
this.setState({
|
||||
pageNum: totalPage < 1 ? 0 : totalPage - 1,
|
||||
});
|
||||
}
|
||||
|
||||
handleNextPage() {
|
||||
this.setState(state => {
|
||||
const totalPage = this.getTotalPage();
|
||||
|
||||
return {
|
||||
pageNum: state.pageNum < totalPage - 1 ? state.pageNum + 1 : totalPage - 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handlePreviousPage() {
|
||||
this.setState(state => {
|
||||
return {
|
||||
pageNum: state.pageNum > 0 ? state.pageNum - 1 : 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleClick(onClick, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const id = event.target.getAttribute('data-id');
|
||||
const data = this.props.data.filter(n => n.id === id)[0];
|
||||
|
||||
onClick && onClick(data, event);
|
||||
}
|
||||
|
||||
handleEdit(onEdit, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const id = event.target.getAttribute('data-id');
|
||||
const data = this.props.data.filter(n => n.id === id)[0];
|
||||
|
||||
onEdit && onEdit(data, event);
|
||||
}
|
||||
|
||||
handleDelete(onDelete, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const id = event.target.getAttribute('data-id');
|
||||
const data = this.props.data.filter(n => n.id === id)[0];
|
||||
|
||||
onDelete && onDelete(data, event);
|
||||
}
|
||||
|
||||
getTotalPage() {
|
||||
const total = this.props.data.length;
|
||||
const pageSize = this.state.pageSize;
|
||||
return total % pageSize === 0 ? total / pageSize : parseInt(total / pageSize) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
ImageList.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
data: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
onEdit: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
firstPageText: PropTypes.string,
|
||||
lastPageText: PropTypes.string,
|
||||
currentPageText: PropTypes.string,
|
||||
previousPageText: PropTypes.string,
|
||||
nextPageText: PropTypes.string,
|
||||
};
|
||||
|
||||
ImageList.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
data: [],
|
||||
onClick: null,
|
||||
onEdit: null,
|
||||
onDelete: null,
|
||||
firstPageText: 'First Page',
|
||||
lastPageText: 'Last Page',
|
||||
currentPageText: 'Current Page',
|
||||
previousPageText: 'Previous Page',
|
||||
nextPageText: 'Next Page',
|
||||
};
|
||||
|
||||
export default ImageList;
|
||||
81
ShadowEditor.Web/src/ui/image/ImageUploader.jsx
Normal file
81
ShadowEditor.Web/src/ui/image/ImageUploader.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
import './css/ImageUploader.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 图片上传控件
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class ImageUploader extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this, props.onChange);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, url, server, noImageText } = this.props;
|
||||
|
||||
if (url && url != 'null') {
|
||||
return <img
|
||||
className={classNames('ImageUploader', className)}
|
||||
src={server + url}
|
||||
onClick={this.handleSelect} />;
|
||||
} else {
|
||||
return <div
|
||||
className={classNames('ImageUploader', 'empty', className)}
|
||||
onClick={this.handleSelect}>
|
||||
{noImageText}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.style.display = 'none';
|
||||
input.addEventListener('change', this.handleChange);
|
||||
|
||||
document.body.appendChild(input);
|
||||
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
var input = this.input;
|
||||
input.removeEventListener('change', this.handleChange);
|
||||
|
||||
document.body.removeChild(input);
|
||||
|
||||
this.input = null;
|
||||
}
|
||||
|
||||
handleSelect() {
|
||||
this.input.click();
|
||||
}
|
||||
|
||||
handleChange(onChange, event) {
|
||||
onChange && onChange(event.target.files[0], event);
|
||||
}
|
||||
}
|
||||
|
||||
ImageUploader.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
url: PropTypes.string,
|
||||
server: PropTypes.string,
|
||||
noImageText: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
ImageUploader.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
url: null,
|
||||
server: '',
|
||||
noImageText: 'No Image',
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
export default ImageUploader;
|
||||
0
ShadowEditor.Web/src/ui/image/css/Image.css
Normal file
0
ShadowEditor.Web/src/ui/image/css/Image.css
Normal file
133
ShadowEditor.Web/src/ui/image/css/ImageList.css
Normal file
133
ShadowEditor.Web/src/ui/image/css/ImageList.css
Normal file
@ -0,0 +1,133 @@
|
||||
.ImageList {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.ImageList>.content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 24px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item {
|
||||
position: relative;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
margin: 4px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.no-img {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.title {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
padding: 1px 4px;
|
||||
pointer-events: none;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.cornerText {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 0 2px;
|
||||
color: #555;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.IconButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 2px;
|
||||
padding: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.edit {
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.delete {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ImageList>.content>.item>.IconButton i {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ImageList>.page {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
padding-top: 3px;
|
||||
border-top: 1px solid #ddd;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ImageList>.page>.IconButton {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.ImageList>.page>.IconButton>.iconfont {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ImageList>.page>.current {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
margin: 0 2px;
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
.ImageList>.page>.info {
|
||||
margin: 0 2px;
|
||||
}
|
||||
15
ShadowEditor.Web/src/ui/image/css/ImageUploader.css
Normal file
15
ShadowEditor.Web/src/ui/image/css/ImageUploader.css
Normal file
@ -0,0 +1,15 @@
|
||||
.ImageUploader {
|
||||
max-width: 160px;
|
||||
max-height: 120px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ImageUploader.empty {
|
||||
width: 160px;
|
||||
height: 120px;
|
||||
border: 1px solid #ccc;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
88
ShadowEditor.Web/src/ui/index.js
Normal file
88
ShadowEditor.Web/src/ui/index.js
Normal file
@ -0,0 +1,88 @@
|
||||
import './Config.css';
|
||||
import '../css/icon/iconfont.css';
|
||||
|
||||
export { default as classNames } from 'classnames/bind';
|
||||
export { default as PropTypes } from 'prop-types';
|
||||
|
||||
// canvas
|
||||
export { default as Canvas } from './canvas/Canvas.jsx';
|
||||
|
||||
// common
|
||||
export { default as Accordion } from './common/Accordion.jsx';
|
||||
export { default as Buttons } from './common/Buttons.jsx';
|
||||
export { default as Column } from './common/Column.jsx';
|
||||
export { default as Columns } from './common/Columns.jsx';
|
||||
export { default as Content } from './common/Content.jsx';
|
||||
export { default as Row } from './common/Row.jsx';
|
||||
export { default as Rows } from './common/Rows.jsx';
|
||||
|
||||
// form
|
||||
export { default as Button } from './form/Button.jsx';
|
||||
export { default as CheckBox } from './form/CheckBox.jsx';
|
||||
export { default as Form } from './form/Form.jsx';
|
||||
export { default as FormControl } from './form/FormControl.jsx';
|
||||
export { default as IconButton } from './form/IconButton.jsx';
|
||||
export { default as Input } from './form/Input.jsx';
|
||||
export { default as Label } from './form/Label.jsx';
|
||||
export { default as Radio } from './form/Radio.jsx';
|
||||
export { default as SearchField } from './form/SearchField.jsx';
|
||||
export { default as Select } from './form/Select.jsx';
|
||||
export { default as TextArea } from './form/TextArea.jsx';
|
||||
export { default as Toggle } from './form/Toggle.jsx';
|
||||
|
||||
// icon
|
||||
export { default as Icon } from './icon/Icon.jsx';
|
||||
|
||||
// image
|
||||
export { default as Image } from './image/Image.jsx';
|
||||
export { default as ImageList } from './image/ImageList.jsx';
|
||||
export { default as ImageUploader } from './image/ImageUploader.jsx';
|
||||
|
||||
// layout
|
||||
export { default as AbsoluteLayout } from './layout/AbsoluteLayout.jsx';
|
||||
export { default as AccordionLayout } from './layout/AccordionLayout.jsx';
|
||||
export { default as BorderLayout } from './layout/BorderLayout.jsx';
|
||||
export { default as HBoxLayout } from './layout/HBoxLayout.jsx';
|
||||
export { default as TabLayout } from './layout/TabLayout.jsx';
|
||||
export { default as VBoxLayout } from './layout/VBoxLayout.jsx';
|
||||
|
||||
// menu
|
||||
export { default as MenuBar } from './menu/MenuBar.jsx';
|
||||
export { default as MenuBarFiller } from './menu/MenuBarFiller.jsx';
|
||||
export { default as MenuItem } from './menu/MenuItem.jsx';
|
||||
export { default as MenuItemSeparator } from './menu/MenuItemSeparator.jsx';
|
||||
|
||||
// panel
|
||||
export { default as Panel } from './panel/Panel.jsx';
|
||||
|
||||
// property
|
||||
export { default as PropertyGrid } from './property/PropertyGrid.jsx';
|
||||
|
||||
// svg
|
||||
export { default as SVG } from './svg/SVG.jsx';
|
||||
|
||||
// table
|
||||
export { default as DataGrid } from './table/DataGrid.jsx';
|
||||
export { default as Table } from './table/Table.jsx';
|
||||
export { default as TableBody } from './table/TableBody.jsx';
|
||||
export { default as TableCell } from './table/TableCell.jsx';
|
||||
export { default as TableHead } from './table/TableHead.jsx';
|
||||
export { default as TableRow } from './table/TableRow.jsx';
|
||||
|
||||
// timeline
|
||||
export { default as Timeline } from './timeline/Timeline.jsx';
|
||||
|
||||
// toolbar
|
||||
export { default as Toolbar } from './toolbar/Toolbar.jsx';
|
||||
export { default as ToolbarFiller } from './toolbar/ToolbarFiller.jsx';
|
||||
export { default as ToolbarSeparator } from './toolbar/ToolbarSeparator.jsx';
|
||||
|
||||
// tree
|
||||
export { default as Tree } from './tree/Tree.jsx';
|
||||
|
||||
// window
|
||||
export { default as Alert } from './window/Alert.jsx';
|
||||
export { default as Confirm } from './window/Confirm.jsx';
|
||||
export { default as Prompt } from './window/Prompt.jsx';
|
||||
export { default as Toast } from './window/Toast.jsx';
|
||||
export { default as Window } from './window/Window.jsx';
|
||||
41
ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx
Normal file
41
ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import './css/AbsoluteLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 绝对定位布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class AbsoluteLayout extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, left, top, ...others } = this.props;
|
||||
|
||||
const position = {
|
||||
left: left || 0,
|
||||
top: top || 0,
|
||||
};
|
||||
|
||||
return <div
|
||||
className={classNames('AbsoluteLayout', className)}
|
||||
style={style ? Object.assign({}, style, position) : position}
|
||||
{...others}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
AbsoluteLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
left: PropTypes.string,
|
||||
top: PropTypes.string,
|
||||
};
|
||||
|
||||
AbsoluteLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
left: '0',
|
||||
top: '0',
|
||||
};
|
||||
|
||||
export default AbsoluteLayout;
|
||||
67
ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx
Normal file
67
ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import './css/AccordionLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import AccordionPanel from './private/AccordionPanel.jsx';
|
||||
|
||||
/**
|
||||
* 折叠布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class AccordionLayout extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
activeIndex: props.activeIndex,
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this, props.onActive);
|
||||
}
|
||||
|
||||
handleClick(onActive, index, name, event) {
|
||||
onActive && onActive(index, name, event);
|
||||
this.setState({
|
||||
activeIndex: index,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, children } = this.props;
|
||||
|
||||
const content = Array.isArray(children) ? children : [children];
|
||||
|
||||
return <div className={classNames('AccordionLayout', className)} style={style}>
|
||||
{content.map((n, i) => {
|
||||
return <AccordionPanel
|
||||
name={n.props.name}
|
||||
title={n.props.title}
|
||||
show={n.props.show}
|
||||
total={content.length}
|
||||
index={i}
|
||||
collpased={i !== this.state.activeIndex}
|
||||
maximizable={n.props.maximizable}
|
||||
onClick={this.handleClick}
|
||||
key={i}>{n.props.children}</AccordionPanel>;
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
AccordionLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
activeIndex: PropTypes.number,
|
||||
onActive: PropTypes.func,
|
||||
};
|
||||
|
||||
AccordionLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
activeIndex: 0,
|
||||
onActive: null,
|
||||
};
|
||||
|
||||
export default AccordionLayout;
|
||||
291
ShadowEditor.Web/src/ui/layout/BorderLayout.jsx
Normal file
291
ShadowEditor.Web/src/ui/layout/BorderLayout.jsx
Normal file
@ -0,0 +1,291 @@
|
||||
import './css/BorderLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 边框布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class BorderLayout extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const children = this.props.children;
|
||||
const north = children && children.filter(n => n.props.region === 'north')[0];
|
||||
const south = children && children.filter(n => n.props.region === 'south')[0];
|
||||
const west = children && children.filter(n => n.props.region === 'west')[0];
|
||||
const east = children && children.filter(n => n.props.region === 'east')[0];
|
||||
const center = children && children.filter(n => n.props.region === 'center')[0];
|
||||
|
||||
const northSplit = north && north.props.split || false;
|
||||
const southSplit = south && south.props.split || false;
|
||||
const westSplit = west && west.props.split || false;
|
||||
const eastSplit = east && east.props.split || false;
|
||||
|
||||
const northCollapsed = north && north.props.collapsed || false;
|
||||
const southCollapsed = south && south.props.collapsed || false;
|
||||
const westCollapsed = west && west.props.collapsed || false;
|
||||
const eastCollapsed = east && east.props.collapsed || false;
|
||||
|
||||
const onNorthToggle = north && north.props.onToggle || null;
|
||||
const onSouthToggle = south && south.props.onToggle || null;
|
||||
const onWestToggle = west && west.props.onToggle || null;
|
||||
const onEastToggle = east && east.props.onToggle || null;
|
||||
|
||||
this.northRef = React.createRef();
|
||||
this.southRef = React.createRef();
|
||||
this.westRef = React.createRef();
|
||||
this.eastRef = React.createRef();
|
||||
|
||||
this.state = {
|
||||
northSplit, southSplit, westSplit, eastSplit,
|
||||
northCollapsed, southCollapsed, westCollapsed, eastCollapsed,
|
||||
};
|
||||
|
||||
this.handleNorthClick = this.handleNorthClick.bind(this, onNorthToggle);
|
||||
this.handleSouthClick = this.handleSouthClick.bind(this, onSouthToggle);
|
||||
this.handleWestClick = this.handleWestClick.bind(this, onWestToggle);
|
||||
this.handleEastClick = this.handleEastClick.bind(this, onEastToggle);
|
||||
|
||||
this.handleTransitionEnd = this.handleTransitionEnd.bind(this, onNorthToggle, onSouthToggle, onWestToggle, onEastToggle);
|
||||
}
|
||||
|
||||
handleNorthClick() {
|
||||
if (!this.state.northSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState((state, props) => {
|
||||
const collapsed = !state.northCollapsed;
|
||||
|
||||
const dom = this.northRef.current;
|
||||
const height = dom.clientHeight;
|
||||
|
||||
if (collapsed) {
|
||||
dom.style.marginTop = `-${height - 8}px`;
|
||||
} else {
|
||||
dom.style.marginTop = null;
|
||||
}
|
||||
|
||||
return {
|
||||
northCollapsed: collapsed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleSouthClick() {
|
||||
if (!this.state.southSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState((state, props) => {
|
||||
const collapsed = !state.southCollapsed;
|
||||
|
||||
const dom = this.southRef.current;
|
||||
const height = dom.clientHeight;
|
||||
|
||||
if (collapsed) {
|
||||
dom.style.marginBottom = `-${height - 8}px`;
|
||||
} else {
|
||||
dom.style.marginBottom = null;
|
||||
}
|
||||
|
||||
return {
|
||||
southCollapsed: collapsed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleWestClick() {
|
||||
if (!this.state.westSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dom = this.westRef.current;
|
||||
|
||||
this.setState((state, props) => {
|
||||
const collapsed = !state.westCollapsed;
|
||||
|
||||
const width = dom.clientWidth;
|
||||
|
||||
if (collapsed) {
|
||||
dom.style.marginLeft = `-${width - 8}px`;
|
||||
} else {
|
||||
dom.style.marginLeft = null;
|
||||
}
|
||||
|
||||
return {
|
||||
westCollapsed: collapsed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleEastClick() {
|
||||
if (!this.state.eastSplit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState((state, props) => {
|
||||
const collapsed = !state.eastCollapsed;
|
||||
|
||||
const dom = this.eastRef.current;
|
||||
const width = dom.clientWidth;
|
||||
|
||||
if (collapsed) {
|
||||
dom.style.marginRight = `-${width - 8}px`;
|
||||
} else {
|
||||
dom.style.marginRight = null;
|
||||
}
|
||||
|
||||
return {
|
||||
eastCollapsed: collapsed,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleTransitionEnd(onNorthToggle, onSouthToggle, onWestToggle, onEastToggle, event) {
|
||||
const region = event.target.getAttribute('region');
|
||||
|
||||
switch (region) {
|
||||
case 'north':
|
||||
onNorthToggle && onNorthToggle(!this.state.northCollapsed);
|
||||
break;
|
||||
case 'south':
|
||||
onSouthToggle && onSouthToggle(!this.state.southCollapsed);
|
||||
break;
|
||||
case 'west':
|
||||
onWestToggle && onWestToggle(!this.state.westCollapsed);
|
||||
break;
|
||||
case 'east':
|
||||
onEastToggle && onEastToggle(!this.state.eastCollapsed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, children } = this.props;
|
||||
|
||||
let north = [], south = [], west = [], east = [], center = [], others = [];
|
||||
|
||||
children && children.forEach(n => {
|
||||
switch (n.props.region) {
|
||||
case 'north':
|
||||
north.push(n);
|
||||
break;
|
||||
case 'south':
|
||||
south.push(n);
|
||||
break;
|
||||
case 'west':
|
||||
west.push(n);
|
||||
break;
|
||||
case 'east':
|
||||
east.push(n);
|
||||
break;
|
||||
case 'center':
|
||||
center.push(n);
|
||||
break;
|
||||
default:
|
||||
others.push(n);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (center.length === 0) {
|
||||
console.warn(`BorderLayout: center region is not defined.`);
|
||||
}
|
||||
|
||||
// north region
|
||||
const northRegion = north.length > 0 && (<div className={classNames('north',
|
||||
this.state.northSplit && 'split',
|
||||
this.state.northCollapsed && 'collapsed')}
|
||||
region={'north'}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
ref={this.northRef}>
|
||||
<div className={'content'}>
|
||||
{north}
|
||||
</div>
|
||||
{this.state.northSplit && <div className={'control'}>
|
||||
<div className={'button'} onClick={this.handleNorthClick}></div>
|
||||
</div>}
|
||||
</div>);
|
||||
|
||||
// south region
|
||||
const southRegion = south.length > 0 && (<div className={classNames('south',
|
||||
this.state.northSplit && 'split',
|
||||
this.state.southCollapsed && 'collapsed')}
|
||||
region={'south'}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
ref={this.southRef}>
|
||||
{this.state.southSplit && <div className={'control'}>
|
||||
<div className={'button'} onClick={this.handleSouthClick}></div>
|
||||
</div>}
|
||||
<div className={'content'}>
|
||||
{south}
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
// west region
|
||||
const westRegion = west.length > 0 && (<div className={classNames('west',
|
||||
this.state.westSplit && 'split',
|
||||
this.state.westCollapsed && 'collapsed')}
|
||||
region={'west'}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
ref={this.westRef}>
|
||||
<div className={'content'}>
|
||||
{west}
|
||||
</div>
|
||||
{this.state.westSplit && <div className={'control'}>
|
||||
<div className={'button'} onClick={this.handleWestClick}></div>
|
||||
</div>}
|
||||
</div>);
|
||||
|
||||
// east region
|
||||
const eastRegion = east.length > 0 && (<div className={classNames('east',
|
||||
this.state.eastSplit && 'split',
|
||||
this.state.eastCollapsed && 'collapsed')}
|
||||
region={'east'}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
ref={this.eastRef}>
|
||||
<div className={'control'}>
|
||||
<div className={'button'} onClick={this.handleEastClick}></div>
|
||||
</div>
|
||||
<div className={'content'}>
|
||||
{east}
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
// center region
|
||||
const centerRegion = center.length > 0 && (<div className={'center'}>
|
||||
{center}
|
||||
</div>);
|
||||
|
||||
const otherRegion = others.length > 0 && others;
|
||||
|
||||
return <div
|
||||
className={classNames('BorderLayout', className)}
|
||||
style={style}>
|
||||
{northRegion}
|
||||
<div className={'middle'}>
|
||||
{westRegion}
|
||||
{centerRegion}
|
||||
{eastRegion}
|
||||
</div>
|
||||
{southRegion}
|
||||
{otherRegion}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
BorderLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
BorderLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default BorderLayout;
|
||||
32
ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx
Normal file
32
ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import './css/HBoxLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 水平布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class HBoxLayout extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <div
|
||||
className={classNames('HBoxLayout', className)}
|
||||
style={style}
|
||||
{...others}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
HBoxLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
HBoxLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default HBoxLayout;
|
||||
64
ShadowEditor.Web/src/ui/layout/TabLayout.jsx
Normal file
64
ShadowEditor.Web/src/ui/layout/TabLayout.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import './css/TabLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 选项卡布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TabLayout extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
activeTab: props.activeTab,
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
var tabIndex = event.target.tabIndex;
|
||||
this.setState({
|
||||
activeTab: tabIndex,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, children, activeTab, ...others } = this.props;
|
||||
|
||||
return <div className={classNames('TabLayout', className)} style={style}>
|
||||
<div className={'tabs'} {...others}>
|
||||
{children.map((n, i) => {
|
||||
return <div
|
||||
className={classNames('tab', i === this.state.activeTab ? 'selected' : null)}
|
||||
key={i}
|
||||
tabIndex={i}
|
||||
onClick={this.handleClick}
|
||||
>{n.props.title}</div>;
|
||||
})}
|
||||
</div>
|
||||
<div className={'contents'}>
|
||||
{children.map((n, i) => {
|
||||
return <div className={classNames('content', i === this.state.activeTab ? 'show' : null)} key={i}>{n}</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
TabLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
activeTab: PropTypes.number,
|
||||
};
|
||||
|
||||
TabLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
activeTab: 0,
|
||||
};
|
||||
|
||||
export default TabLayout;
|
||||
32
ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx
Normal file
32
ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import './css/VBoxLayout.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 竖直布局
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class VBoxLayout extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <div
|
||||
className={classNames('VBoxLayout', className)}
|
||||
style={style}
|
||||
{...others}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
VBoxLayout.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
VBoxLayout.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default VBoxLayout;
|
||||
4
ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css
Normal file
4
ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css
Normal file
@ -0,0 +1,4 @@
|
||||
.AbsoluteLayout {
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
5
ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css
Normal file
5
ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css
Normal file
@ -0,0 +1,5 @@
|
||||
.AccordionLayout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
198
ShadowEditor.Web/src/ui/layout/css/BorderLayout.css
Normal file
198
ShadowEditor.Web/src/ui/layout/css/BorderLayout.css
Normal file
@ -0,0 +1,198 @@
|
||||
.BorderLayout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--up-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAJCAMAAAB30J7MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODU5RUQ4NzY3RTI2MTFFOUE4RjBBODU0OThFNTczRkMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODU5RUQ4NzU3RTI2MTFFOUE4RjBBODU0OThFNTczRkMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlbUzZ8AAAMAUExURfb29tra2tnZ2fX19YuLi4qKivf39wcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///9oQUYcAAAA3SURBVHjaYvhOJGCgRGFX1zeiFHYB0TciFHaBiW8EFXZBqa8EFHbBGN3f8Cq8hWDeJDN4AAIMAEFsVuZN2iPeAAAAAElFTkSuQmCC);
|
||||
--down-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAJCAMAAAB30J7MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEZDOUYxQTk3RTI2MTFFOTlBRTE4RTE1RkRCMUI3MEYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEZDOUYxQTg3RTI2MTFFOTlBRTE4RTE1RkRCMUI3MEYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODlBMTI3RDI2N0VFOTExOTFBREVBQjM5NUM3ODkwMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqOucMsAAAMAUExURfb29tra2tnZ2fX19YuLi4qKivf39wcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///9oQUYcAAAA/SURBVHjaYvhOJGBA5d5EMG/hVfitG8bqwm/i969d2NVhKPz+rQurOkyFYJVd34lQCFTZ9Z0ohUQFD24AEGAACN5W5nESKn0AAAAASUVORK5CYII=);
|
||||
--left-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAoCAMAAAAbvyCxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REE4QkI1QUU3OTZEMTFFOTk0NzNEQjBGRjI0QzU2NzQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REE4QkI1QUY3OTZEMTFFOTk0NzNEQjBGRjI0QzU2NzQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQThCQjVBQzc5NkQxMUU5OTQ3M0RCMEZGMjRDNTY3NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQThCQjVBRDc5NkQxMUU5OTQ3M0RCMEZGMjRDNTY3NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuJET8oAAAAVUExURfb29tra2tnZ2fX19YuLi4qKivf39wWvvrYAAAA7SURBVHjaYmCDAYbBwmJggrKYWaBiDKxQWTADxIIwQCxWNkwWGyvCPFaEyawIO1gR9jIOHp8DAUCAAQBxQwhCpBDaBAAAAABJRU5ErkJggg==);
|
||||
--right-arrow: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAoCAMAAAAbvyCxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MEI0QUI4OEY3OTZFMTFFOTlGQjFGRTU4QTJGRTA0QTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MEI0QUI4OTA3OTZFMTFFOTlGQjFGRTU4QTJGRTA0QTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowQjRBQjg4RDc5NkUxMUU5OUZCMUZFNThBMkZFMDRBOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowQjRBQjg4RTc5NkUxMUU5OUZCMUZFNThBMkZFMDRBOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtHSBrsAAAAVUExURfb29tnZ2YuLi/X19djY2IqKivf3956cLrcAAAA3SURBVHjazI/JDQAwDMLcg+4/cl8WK4SXFQISPMUAur2lbvqXJtJsJCR0MXFsYdu8Bi1XX4ABAHgwCELn0SAjAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
/* north */
|
||||
|
||||
.BorderLayout>.north {
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 100;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.BorderLayout>.north>.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.BorderLayout>.north.split>.content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.BorderLayout>.north>.control {
|
||||
height: 8px;
|
||||
border-top: 1px solid #eee;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.BorderLayout>.north>.control>.button {
|
||||
width: 40px;
|
||||
height: 9px;
|
||||
background: var(--up-arrow);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.BorderLayout>.north.collapsed>.control>.button {
|
||||
background: var(--down-arrow);
|
||||
}
|
||||
|
||||
/* south */
|
||||
|
||||
.BorderLayout>.south {
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 100;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.BorderLayout>.south>.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.BorderLayout>.south.split>.content {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.BorderLayout>.south>.control {
|
||||
height: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.BorderLayout>.south>.control>.button {
|
||||
width: 40px;
|
||||
height: 9px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.BorderLayout>.south>.control>.button {
|
||||
background: var(--down-arrow);
|
||||
}
|
||||
|
||||
.BorderLayout>.south.collapsed>.control>.button {
|
||||
background: var(--up-arrow);
|
||||
}
|
||||
|
||||
/* middle */
|
||||
|
||||
.BorderLayout>.middle {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* west */
|
||||
|
||||
.BorderLayout>.middle>.west {
|
||||
border-right: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west>.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west.split>.content {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west>.control {
|
||||
width: 8px;
|
||||
border-left: 1px solid #eee;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west>.control>.button {
|
||||
width: 9px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west>.control>.button {
|
||||
background: var(--left-arrow);
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.west.collapsed>.control>.button {
|
||||
background: var(--right-arrow);
|
||||
}
|
||||
|
||||
/* center */
|
||||
|
||||
.BorderLayout>.middle>.center {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* east */
|
||||
|
||||
.BorderLayout>.middle>.east {
|
||||
border-left: 1px solid #eee;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east>.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east.split>.content {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east>.control {
|
||||
width: 8px;
|
||||
border-right: 1px solid #eee;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east>.control>.button {
|
||||
width: 9px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east>.control>.button {
|
||||
background: var(--right-arrow);
|
||||
}
|
||||
|
||||
.BorderLayout>.middle>.east.collapsed>.control>.button {
|
||||
background: var(--left-arrow);
|
||||
}
|
||||
3
ShadowEditor.Web/src/ui/layout/css/Content.css
Normal file
3
ShadowEditor.Web/src/ui/layout/css/Content.css
Normal file
@ -0,0 +1,3 @@
|
||||
.Content {
|
||||
display: block-inline;
|
||||
}
|
||||
6
ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css
Normal file
6
ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css
Normal file
@ -0,0 +1,6 @@
|
||||
.HBoxLayout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
46
ShadowEditor.Web/src/ui/layout/css/TabLayout.css
Normal file
46
ShadowEditor.Web/src/ui/layout/css/TabLayout.css
Normal file
@ -0,0 +1,46 @@
|
||||
.TabLayout {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.TabLayout>.tabs {
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.TabLayout>.tabs>.tab {
|
||||
height: 32px;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
line-height: 32px;
|
||||
padding: 0 8px;
|
||||
color: #737373;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.TabLayout>.tabs>.tab.selected {
|
||||
color: #3399ff;
|
||||
border-bottom: 1px solid #3399ff;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.TabLayout>.contents {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 32px);
|
||||
}
|
||||
|
||||
.TabLayout>.contents>.content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.TabLayout>.contents>.content.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.TabLayout>.contents>.content>* {
|
||||
height: 100%;
|
||||
}
|
||||
6
ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css
Normal file
6
ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css
Normal file
@ -0,0 +1,6 @@
|
||||
.VBoxLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
96
ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx
Normal file
96
ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx
Normal file
@ -0,0 +1,96 @@
|
||||
import './css/AccordionPanel.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 单个折叠面板
|
||||
* @private
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class AccordionPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
maximized: props.maximized,
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this, props.onClick, props.index, props.name);
|
||||
this.handleMaximize = this.handleMaximize.bind(this, props.onMaximize);
|
||||
}
|
||||
|
||||
handleClick(onClick, index, name, event) {
|
||||
onClick && onClick(index, name, event);
|
||||
}
|
||||
|
||||
handleMaximize(onMaximize, event) {
|
||||
this.setState(state => ({
|
||||
maximized: !state.maximized,
|
||||
}));
|
||||
|
||||
onMaximize && onMaximize(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, className, style, children, show, total, index, collpased,
|
||||
maximizable, maximized, onMaximize } = this.props;
|
||||
|
||||
const maximizeControl = maximizable && <div className={'control'} onClick={this.handleMaximize}>
|
||||
{this.state.maximized ? <i className={'iconfont icon-minimize'}></i> : <i className={'iconfont icon-maximize'}></i>}
|
||||
</div>;
|
||||
|
||||
const _style = collpased ? style : Object.assign({}, style, {
|
||||
height: `calc(100% - ${26 * (total - 1)}px`,
|
||||
});
|
||||
|
||||
return <div className={classNames('AccordionPanel',
|
||||
this.state.maximized && 'maximized',
|
||||
collpased && 'collpased',
|
||||
!show && 'hidden',
|
||||
className)} style={_style}>
|
||||
<div className={'header'} onClick={this.handleClick}>
|
||||
<span className="title">{title}</span>
|
||||
<div className="controls">
|
||||
{maximizeControl}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'body'}>
|
||||
{children}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
AccordionPanel.propTypes = {
|
||||
name: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
show: PropTypes.bool,
|
||||
total: PropTypes.number,
|
||||
index: PropTypes.number,
|
||||
collpased: PropTypes.bool,
|
||||
maximizable: PropTypes.bool,
|
||||
maximized: PropTypes.bool,
|
||||
onMaximize: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
AccordionPanel.defaultProps = {
|
||||
name: null,
|
||||
title: null,
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
show: true,
|
||||
total: 1,
|
||||
index: 0,
|
||||
collpased: true,
|
||||
maximizable: false,
|
||||
maximized: false,
|
||||
onMaximize: null,
|
||||
onClick: null,
|
||||
};
|
||||
|
||||
export default AccordionPanel;
|
||||
@ -0,0 +1,82 @@
|
||||
.AccordionPanel {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 240px;
|
||||
height: 320px;
|
||||
background: #fafafa;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.AccordionPanel.collpased {
|
||||
height: 26px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.AccordionPanel.maximized {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
z-index: 9000;
|
||||
}
|
||||
|
||||
.AccordionPanel.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.AccordionPanel>.header {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
background-color: #2c3e50;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.AccordionPanel>.header>.title {
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
padding-left: 8px;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.AccordionPanel>.header>.controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 4px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.AccordionPanel>.header>.controls>.control {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 2px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.AccordionPanel>.header>.controls>.control>.iconfont {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.AccordionPanel>.body {
|
||||
height: calc(100% - 24px);
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.AccordionPanel.collapsed>.body {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
32
ShadowEditor.Web/src/ui/menu/MenuBar.jsx
Normal file
32
ShadowEditor.Web/src/ui/menu/MenuBar.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import './css/MenuBar.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 菜单栏
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class MenuBar extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <ul
|
||||
className={classNames('MenuBar', className)}
|
||||
style={style}
|
||||
{...others}>{children}</ul>;
|
||||
}
|
||||
}
|
||||
|
||||
MenuBar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
MenuBar.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default MenuBar;
|
||||
27
ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx
Normal file
27
ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import './css/MenuBarFiller.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 菜单栏填充
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class MenuBarFiller extends React.Component {
|
||||
render() {
|
||||
const { className, style } = this.props;
|
||||
|
||||
return <li className={classNames('MenuItem', 'MenuBarFiller', className)} style={style}></li>;
|
||||
}
|
||||
}
|
||||
|
||||
MenuBarFiller.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
MenuBarFiller.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default MenuBarFiller;
|
||||
59
ShadowEditor.Web/src/ui/menu/MenuItem.jsx
Normal file
59
ShadowEditor.Web/src/ui/menu/MenuItem.jsx
Normal file
@ -0,0 +1,59 @@
|
||||
import './css/MenuItem.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 菜单项
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class MenuItem extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this, props.onClick);
|
||||
}
|
||||
|
||||
handleClick(onClick, event) {
|
||||
event.stopPropagation();
|
||||
onClick && onClick(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, className, style, children, show, onClick, ...others } = this.props;
|
||||
|
||||
const subMenu = children && children.length && <><div className={'suffix'}>
|
||||
<i className={'iconfont icon-right-triangle'}></i>
|
||||
</div>
|
||||
<div className={'sub'}>
|
||||
<ul className={'wrap'}>{children}</ul>
|
||||
</div></>;
|
||||
|
||||
return <li
|
||||
className={classNames('MenuItem', !show && 'hidden', className)}
|
||||
style={style}
|
||||
onClick={this.handleClick}
|
||||
{...others}>
|
||||
<span>{title}</span>
|
||||
{subMenu}
|
||||
</li>;
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem.propTypes = {
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
show: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
MenuItem.defaultProps = {
|
||||
title: null,
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
show: true,
|
||||
onClick: null,
|
||||
};
|
||||
|
||||
export default MenuItem;
|
||||
32
ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx
Normal file
32
ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import './css/MenuItemSeparator.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 菜单项分隔符
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class MenuItemSeparator extends React.Component {
|
||||
render() {
|
||||
const { className, style, ...others } = this.props;
|
||||
|
||||
return <li
|
||||
className={classNames('MenuItemSeparator', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
<div className='separator'></div>
|
||||
</li>;
|
||||
}
|
||||
}
|
||||
|
||||
MenuItemSeparator.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
MenuItemSeparator.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default MenuItemSeparator;
|
||||
38
ShadowEditor.Web/src/ui/menu/css/MenuBar.css
Normal file
38
ShadowEditor.Web/src/ui/menu/css/MenuBar.css
Normal file
@ -0,0 +1,38 @@
|
||||
.MenuBar {
|
||||
font: 13px 'Microsoft YaHei';
|
||||
color: rgb(0, 0, 0);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(to bottom, rgb(250, 252, 253), rgb(232, 241, 251) 40%, rgb(220, 230, 243) 40%, rgb(220, 231, 245));
|
||||
border-bottom: 1px solid #bbb;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.MenuBar>.MenuItem {
|
||||
position: relative;
|
||||
min-width: auto;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
display: inline-block;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.MenuBar>.MenuItem:hover {
|
||||
background: initial;
|
||||
color: initial;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
border: initial;
|
||||
}
|
||||
|
||||
.MenuBar>.MenuItem>.suffix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MenuBar>.MenuItem>.sub {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: auto;
|
||||
}
|
||||
3
ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css
Normal file
3
ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css
Normal file
@ -0,0 +1,3 @@
|
||||
.MenuBarFiller {
|
||||
flex: 1;
|
||||
}
|
||||
69
ShadowEditor.Web/src/ui/menu/css/MenuItem.css
Normal file
69
ShadowEditor.Web/src/ui/menu/css/MenuItem.css
Normal file
@ -0,0 +1,69 @@
|
||||
.MenuItem {
|
||||
position: relative;
|
||||
min-width: 120px;
|
||||
line-height: 24px;
|
||||
margin: 3px;
|
||||
padding: 2px 14px 2px 30px;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
--right-arrow: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABAQMAAADO7O3JAAAAA3NCSVQICAjb4U/gAAAABlBMVEXi4+P///9V63aNAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M1cbXjNgAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNy8wNi8xMZANh+UAAAAKSURBVAiZY3AAAABCAEGV6TQ4AAAAAElFTkSuQmCC') repeat-y rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
.MenuItem:hover {
|
||||
background: linear-gradient(to bottom, rgba(193, 222, 255, 0.2), rgba(193, 222, 255, 0.4));
|
||||
color: black;
|
||||
padding: 1px 13px 1px 29px;
|
||||
border: 1px solid rgb(183, 212, 246);
|
||||
}
|
||||
|
||||
.MenuItem>span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.MenuItem>.suffix {
|
||||
width: 40px;
|
||||
margin-left: 48px;
|
||||
margin-right: -4px;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.MenuItem>.sub {
|
||||
position: absolute;
|
||||
left: calc(100% + 4px);
|
||||
top: -4px;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
display: none;
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.MenuItem>.sub::before {
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
height: 100%;
|
||||
width: 8px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.MenuItem:hover>.sub {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.MenuItem>.sub>.wrap {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
background: var(--right-arrow);
|
||||
background-position: 24px 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid rgb(195, 195, 195);
|
||||
border-radius: 5px;
|
||||
box-shadow: rgba(128, 128, 128, 0.5) 0px 0px 16px 1px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
11
ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css
Normal file
11
ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css
Normal file
@ -0,0 +1,11 @@
|
||||
.MenuItemSeparator {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.MenuItemSeparator .separator {
|
||||
height: 2px;
|
||||
margin-left: 24px;
|
||||
background: rgb(229, 229, 229);
|
||||
background-image: linear-gradient(to bottom, rgb(226, 226, 226), rgb(226, 226, 226) 50%, rgb(252, 252, 252) 50%, rgb(252, 252, 252));
|
||||
}
|
||||
123
ShadowEditor.Web/src/ui/panel/Panel.jsx
Normal file
123
ShadowEditor.Web/src/ui/panel/Panel.jsx
Normal file
@ -0,0 +1,123 @@
|
||||
import './css/Panel.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 面板
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Panel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
collapsed: props.collapsed,
|
||||
maximized: props.maximized,
|
||||
closed: props.closed,
|
||||
};
|
||||
|
||||
this.handleCollapse = this.handleCollapse.bind(this, props.onCollapse);
|
||||
this.handleMaximize = this.handleMaximize.bind(this, props.onMaximize);
|
||||
this.handleClose = this.handleClose.bind(this, props.onClose);
|
||||
}
|
||||
|
||||
handleCollapse(onCollapse, event) {
|
||||
this.setState(state => ({
|
||||
collapsed: !state.collapsed,
|
||||
}));
|
||||
|
||||
onCollapse && onCollapse(event);
|
||||
}
|
||||
|
||||
handleMaximize(onMaximize, event) {
|
||||
this.setState(state => ({
|
||||
maximized: !state.maximized,
|
||||
}));
|
||||
|
||||
onMaximize && onMaximize(event);
|
||||
}
|
||||
|
||||
handleClose(onClose, event) {
|
||||
this.setState(state => ({
|
||||
closed: !state.closed,
|
||||
}));
|
||||
|
||||
onClose && onClose(event);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, className, style, children, show, header,
|
||||
collapsible, collapsed, onCollapse,
|
||||
maximizable, maximized, onMaximize,
|
||||
closable, closed, onClose } = this.props;
|
||||
|
||||
const collapseControl = collapsible && <div className={'control'} onClick={this.handleCollapse}>
|
||||
{this.state.collapsed ? <i className={'iconfont icon-down-arrow'}></i> : <i className={'iconfont icon-up-arrow'}></i>}
|
||||
</div>;
|
||||
|
||||
const maximizeControl = maximizable && <div className={'control'} onClick={this.handleMaximize}>
|
||||
{this.state.maximized ? <i className={'iconfont icon-minimize'}></i> : <i className={'iconfont icon-maximize'}></i>}
|
||||
</div>;
|
||||
|
||||
const closeControl = closable && <div className={'control'} onClick={this.handleClose}>
|
||||
<i className={'iconfont icon-close-thin'}></i>
|
||||
</div>;
|
||||
|
||||
return <div className={classNames('Panel',
|
||||
this.state.maximized && 'maximized',
|
||||
this.state.collapsed && 'collapsed',
|
||||
this.state.closed && 'hidden',
|
||||
!show && 'hidden',
|
||||
className)} style={style}>
|
||||
<div className={classNames('header', header ? null : 'hidden')}>
|
||||
<span className="title">{title}</span>
|
||||
<div className="controls">
|
||||
{collapseControl}
|
||||
{maximizeControl}
|
||||
{closeControl}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'body'}>
|
||||
{children}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
Panel.propTypes = {
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
show: PropTypes.bool,
|
||||
header: PropTypes.bool,
|
||||
collapsible: PropTypes.bool,
|
||||
collapsed: PropTypes.bool,
|
||||
onCollapse: PropTypes.func,
|
||||
maximizable: PropTypes.bool,
|
||||
maximized: PropTypes.bool,
|
||||
onMaximize: PropTypes.bool,
|
||||
closable: PropTypes.bool,
|
||||
closed: PropTypes.bool,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
Panel.defaultProps = {
|
||||
title: null,
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
show: true,
|
||||
header: true,
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
onCollapse: null,
|
||||
maximizable: false,
|
||||
maximized: false,
|
||||
onMaximize: null,
|
||||
closable: false,
|
||||
closed: false,
|
||||
onClose: null,
|
||||
};
|
||||
|
||||
export default Panel;
|
||||
88
ShadowEditor.Web/src/ui/panel/css/Panel.css
Normal file
88
ShadowEditor.Web/src/ui/panel/css/Panel.css
Normal file
@ -0,0 +1,88 @@
|
||||
.Panel {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 200px;
|
||||
height: 320px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #2c3e50;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Panel.maximized {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9000;
|
||||
}
|
||||
|
||||
.Panel.collapsed {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.Panel.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Panel>.header {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
background-color: #2c3e50;
|
||||
}
|
||||
|
||||
.Panel>.header.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Panel>.header>.title {
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
padding-left: 8px;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Panel>.header>.controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 4px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Panel>.header>.controls>.control {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 4px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Panel>.header>.controls>.control>.iconfont {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.Panel>.body {
|
||||
height: calc(100% - 24px);
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Panel.collapsed>.body {
|
||||
height: 0;
|
||||
padding: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.Panel>.header.hidden+div.body {
|
||||
height: 100%;
|
||||
}
|
||||
0
ShadowEditor.Web/src/ui/property/ButtonField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/ButtonField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/ColorField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/ColorField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/IntegerField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/IntegerField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/MapField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/MapField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/NumberField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/NumberField.jsx
Normal file
60
ShadowEditor.Web/src/ui/property/PropertyGrid.jsx
Normal file
60
ShadowEditor.Web/src/ui/property/PropertyGrid.jsx
Normal file
@ -0,0 +1,60 @@
|
||||
import './css/PropertyGrid.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 属性表格
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class PropertyGrid extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
handleCollapse() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, data } = this.props;
|
||||
|
||||
return <div className={classNames('PropertyGrid', className)} style={style}>
|
||||
{data.map((group, i) => {
|
||||
return <div className={'group'} key={i}>
|
||||
<div className={'head'}>
|
||||
<div className={'icon'}>
|
||||
<i className={group.expand !== false ? 'icon-expand' : 'icon-collapse'} />
|
||||
</div>
|
||||
<div className={'title'}>{group.name}</div>
|
||||
</div>
|
||||
<div className={classNames('property', group.expand === false ? 'hide' : null)}>
|
||||
{group.children.map((item, j) => {
|
||||
return <div className={'item'} key={item.name || j}>
|
||||
<div className={'label'}>{item.label}</div>
|
||||
<div className={'value'}>{item.value}</div>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>;
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyGrid.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
data: PropTypes.array,
|
||||
};
|
||||
|
||||
PropertyGrid.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
data: [],
|
||||
};
|
||||
|
||||
export default PropertyGrid;
|
||||
0
ShadowEditor.Web/src/ui/property/TextField.jsx
Normal file
0
ShadowEditor.Web/src/ui/property/TextField.jsx
Normal file
84
ShadowEditor.Web/src/ui/property/css/PropertyGrid.css
Normal file
84
ShadowEditor.Web/src/ui/property/css/PropertyGrid.css
Normal file
@ -0,0 +1,84 @@
|
||||
.PropertyGrid {
|
||||
position: relative;
|
||||
color: black;
|
||||
font: 12px 'Microsoft YaHei';
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
--bg-expand: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAMAAAD3JJ6EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzBCN0FCRUU3NzExMTFFOTlFQjFFMDA4RkM3NzE5OTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzBCN0FCRUY3NzExMTFFOTlFQjFFMDA4RkM3NzE5OTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMEI3QUJFQzc3MTExMUU5OUVCMUUwMDhGQzc3MTk5MiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMEI3QUJFRDc3MTExMUU5OUVCMUUwMDhGQzc3MTk5MiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrAM5dcAAABFUExURb/BwfT3977BwamqqsnMzZOUlNPX19/j5ImKioeHh/f5+d7h4snLzNTY2Pr7/IiIiJOTk6mrq56foJ6fn7S1toaGhv///4ulXXIAAAAXdFJOU/////////////////////////////8A5kDmXgAAAD1JREFUeNokxkkSgCAAA8GguADukvz/qVRgDlONmUx0L8rdwX1DxuAkSL/1BfM0V5mqZL0GtTx+Zzz8JsAA9ZAFxigsqFUAAAAASUVORK5CYII=) no-repeat center;
|
||||
--bg-collapse: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAMAAAC+Ge+yAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTlEMEIzNDQ3NzEyMTFFOUE2RDFDMzI1OUNGODkzQjEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTlEMEIzNDU3NzEyMTFFOUE2RDFDMzI1OUNGODkzQjEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOUQwQjM0Mjc3MTIxMUU5QTZEMUMzMjU5Q0Y4OTNCMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOUQwQjM0Mzc3MTIxMUU5QTZEMUMzMjU5Q0Y4OTNCMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ps1N6eUAAAA/UExURYiJiZ6en6mrq8nMzamqqtTY2NPX14eHh77AwPr7+4iIiN/j5N/i45+foL/CwsnLzPj6+rS1tpOTk4aGhv///0pgFIkAAAAVdFJOU///////////////////////////ACvZfeoAAAA3SURBVHjaYmDiFgEDBmEGPgFOMENYmJENyhAWFoQxhIX4YQxmFCleVpBidi4OiDksPBADAQIMAOvOBU7M/UDuAAAAAElFTkSuQmCC) no-repeat center;
|
||||
}
|
||||
|
||||
.PropertyGrid .group {
|
||||
background: #ecf0f1;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .head {
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .head .icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .head .icon .icon-expand {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: var(--bg-expand);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .head .icon .icon-collapse {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: var(--bg-collapse);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .head .title {
|
||||
width: calc(100% - 20px);
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding-left: 4px;
|
||||
display: inline-block;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .property {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .property.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .property .item {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .property .item .label {
|
||||
width: 120px;
|
||||
padding: 0 4px;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.PropertyGrid .group .property .item .value {
|
||||
width: calc(100% - 120px);
|
||||
padding: 0 4px;
|
||||
display: inline-block;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
30
ShadowEditor.Web/src/ui/svg/SVG.jsx
Normal file
30
ShadowEditor.Web/src/ui/svg/SVG.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import './css/SVG.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* SVG
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class SVG extends React.Component {
|
||||
render() {
|
||||
const { className, style, ...others } = this.props;
|
||||
|
||||
return <svg
|
||||
className={classNames('SVG', className)}
|
||||
style={style}
|
||||
{...others}></svg>;
|
||||
}
|
||||
}
|
||||
|
||||
SVG.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
SVG.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default SVG;
|
||||
0
ShadowEditor.Web/src/ui/svg/css/SVG.css
Normal file
0
ShadowEditor.Web/src/ui/svg/css/SVG.css
Normal file
91
ShadowEditor.Web/src/ui/table/DataGrid.jsx
Normal file
91
ShadowEditor.Web/src/ui/table/DataGrid.jsx
Normal file
@ -0,0 +1,91 @@
|
||||
import './css/DataGrid.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
import Column from '../common/Column.jsx';
|
||||
import Columns from '../common/Columns.jsx';
|
||||
|
||||
/**
|
||||
* 数据表格
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class DataGrid extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
data: props.data,
|
||||
selected: null,
|
||||
};
|
||||
|
||||
this.handleClick = this.handleClick.bind(this, props.onSelect);
|
||||
}
|
||||
|
||||
handleClick(onSelect, event) {
|
||||
const id = event.currentTarget.getAttribute('data-id');
|
||||
const record = this.state.data.filter(n => n.id === id)[0];
|
||||
|
||||
this.setState({
|
||||
selected: id,
|
||||
});
|
||||
|
||||
onSelect && onSelect(record);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, children } = this.props;
|
||||
const { data, selected } = this.state;
|
||||
|
||||
const columns = children.props.children.map(n => {
|
||||
return {
|
||||
field: n.props.field,
|
||||
title: n.props.title,
|
||||
};
|
||||
});
|
||||
|
||||
const header = <thead>
|
||||
<tr>
|
||||
{columns.map(n => {
|
||||
return <td name={n.field} key={n.field}>{n.title}</td>;
|
||||
})}
|
||||
</tr>
|
||||
</thead>;
|
||||
|
||||
const body = <tbody>
|
||||
{data.map(n => {
|
||||
return <tr className={selected === n.id ? 'selected' : null} data-id={n.id} key={n.id} onClick={this.handleClick}>
|
||||
{columns.map(m => {
|
||||
return <td key={m.field}>{n[m.field]}</td>;
|
||||
})}
|
||||
</tr>;
|
||||
})}
|
||||
</tbody>;
|
||||
|
||||
return <table className={classNames('DataGrid', className)} style={style}>
|
||||
{header}
|
||||
{body}
|
||||
</table>;
|
||||
}
|
||||
}
|
||||
|
||||
DataGrid.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: (props, propName, componentName) => {
|
||||
const children = props[propName];
|
||||
if (children.type !== Columns) {
|
||||
return new TypeError(`Invalid prop \`${propName}\` of type \`${children.type.name}\` supplied to \`${componentName}\`, expected \`Columns\`.`);
|
||||
}
|
||||
},
|
||||
data: PropTypes.array,
|
||||
onSelect: PropTypes.func,
|
||||
};
|
||||
|
||||
DataGrid.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
data: [],
|
||||
onSelect: null,
|
||||
};
|
||||
|
||||
export default DataGrid;
|
||||
0
ShadowEditor.Web/src/ui/table/Pager.jsx
Normal file
0
ShadowEditor.Web/src/ui/table/Pager.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/Table.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/Table.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import './css/Table.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表格
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Table extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <table
|
||||
className={classNames('Table', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
{children}
|
||||
</table>;
|
||||
}
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
Table.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default Table;
|
||||
34
ShadowEditor.Web/src/ui/table/TableBody.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/TableBody.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import './css/TableBody.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表格内容
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TableBody extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <tbody
|
||||
className={classNames('TableBody', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
{children}
|
||||
</tbody>;
|
||||
}
|
||||
}
|
||||
|
||||
TableBody.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
TableBody.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default TableBody;
|
||||
34
ShadowEditor.Web/src/ui/table/TableCell.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/TableCell.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import './css/TableCell.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表格单元格
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TableCell extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <td
|
||||
className={classNames('TableCell', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
{children}
|
||||
</td>;
|
||||
}
|
||||
}
|
||||
|
||||
TableCell.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
TableCell.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default TableCell;
|
||||
34
ShadowEditor.Web/src/ui/table/TableHead.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/TableHead.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import './css/TableHead.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表格头部
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TableHead extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <thead
|
||||
className={classNames('TableHead', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
{children}
|
||||
</thead>;
|
||||
}
|
||||
}
|
||||
|
||||
TableHead.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
TableHead.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default TableHead;
|
||||
34
ShadowEditor.Web/src/ui/table/TableRow.jsx
Normal file
34
ShadowEditor.Web/src/ui/table/TableRow.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import './css/TableRow.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 表格行
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TableRow extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, ...others } = this.props;
|
||||
|
||||
return <tr
|
||||
className={classNames('TableRow', className)}
|
||||
style={style}
|
||||
{...others}>
|
||||
{children}
|
||||
</tr>;
|
||||
}
|
||||
}
|
||||
|
||||
TableRow.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
TableRow.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default TableRow;
|
||||
37
ShadowEditor.Web/src/ui/table/css/DataGrid.css
Normal file
37
ShadowEditor.Web/src/ui/table/css/DataGrid.css
Normal file
@ -0,0 +1,37 @@
|
||||
.DataGrid {
|
||||
display: inline-block;
|
||||
border-collapse: collapse;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.DataGrid>thead {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.DataGrid>thead>tr,
|
||||
.DataGrid>tbody>tr {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.DataGrid>tbody>tr:nth-child(even) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.DataGrid>tbody>tr.selected {
|
||||
color: #fff;
|
||||
background-color: #3399ff;
|
||||
}
|
||||
|
||||
.DataGrid>tbody>tr:hover {
|
||||
color: #fff;
|
||||
background-color: #3399ff;
|
||||
}
|
||||
|
||||
.DataGrid>thead>tr>td,
|
||||
.DataGrid>tbody>tr>td {
|
||||
padding: 0 16px;
|
||||
border: 1px solid #ebebeb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
6
ShadowEditor.Web/src/ui/table/css/Table.css
Normal file
6
ShadowEditor.Web/src/ui/table/css/Table.css
Normal file
@ -0,0 +1,6 @@
|
||||
.Table {
|
||||
display: inline-block;
|
||||
border-collapse: collapse;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
1
ShadowEditor.Web/src/ui/table/css/TableBody.css
Normal file
1
ShadowEditor.Web/src/ui/table/css/TableBody.css
Normal file
@ -0,0 +1 @@
|
||||
.TableBody {}
|
||||
5
ShadowEditor.Web/src/ui/table/css/TableCell.css
Normal file
5
ShadowEditor.Web/src/ui/table/css/TableCell.css
Normal file
@ -0,0 +1,5 @@
|
||||
.TableCell {
|
||||
padding: 0 16px;
|
||||
border: 1px solid #ebebeb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
3
ShadowEditor.Web/src/ui/table/css/TableHead.css
Normal file
3
ShadowEditor.Web/src/ui/table/css/TableHead.css
Normal file
@ -0,0 +1,3 @@
|
||||
.TableHead {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
13
ShadowEditor.Web/src/ui/table/css/TableRow.css
Normal file
13
ShadowEditor.Web/src/ui/table/css/TableRow.css
Normal file
@ -0,0 +1,13 @@
|
||||
.TableRow {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.TableRow:nth-child(even) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.TableBody .TableRow:hover {
|
||||
color: #fff;
|
||||
background-color: #3399ff;
|
||||
}
|
||||
146
ShadowEditor.Web/src/ui/timeline/Timeline.jsx
Normal file
146
ShadowEditor.Web/src/ui/timeline/Timeline.jsx
Normal file
@ -0,0 +1,146 @@
|
||||
import './css/Timeline.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import TimelineControl from './private/TimelineControl.jsx';
|
||||
import CheckBox from '../form/CheckBox.jsx';
|
||||
import Label from '../form/Label.jsx';
|
||||
|
||||
/**
|
||||
* 时间轴
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Timeline extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.canvas = React.createRef();
|
||||
this.scale = 30; // 尺寸,1秒=30像素
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, animations, tip } = this.props;
|
||||
|
||||
const infos = animations.map(layer => {
|
||||
return <div className={'info'} key={layer.uuid}>
|
||||
<CheckBox value={layer.uuid}></CheckBox>
|
||||
<Label>{layer.layerName}</Label>
|
||||
</div>;
|
||||
});
|
||||
|
||||
const layers = animations.map(layer => {
|
||||
return <div className={'layer'} droppable={'true'} key={layer.uuid}>
|
||||
{layer.animations.map(animation => {
|
||||
const style = {
|
||||
left: animation.beginTime * this.scale + 'px',
|
||||
width: (animation.endTime - animation.beginTime) * this.scale + 'px',
|
||||
};
|
||||
|
||||
return <div
|
||||
className={'item'}
|
||||
draggable={'true'}
|
||||
droppable={'false'}
|
||||
style={style}
|
||||
key={animation.uuid}>
|
||||
<span className={'smaller'}>{animation.name}</span>
|
||||
</div>;
|
||||
})}
|
||||
</div>;
|
||||
});
|
||||
|
||||
return <div className={classNames('Timeline', className)} style={style}>
|
||||
<TimelineControl tip={tip}></TimelineControl>
|
||||
<div className="box">
|
||||
<div className="left">
|
||||
{infos}
|
||||
</div>
|
||||
<div className="right">
|
||||
<canvas className={'timeline'} ref={this.canvas}></canvas>
|
||||
<div className="layers" style={{ width: '3600px' }}>
|
||||
{layers}
|
||||
</div>
|
||||
<div className="slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
var duration = 120; // 持续时长(秒)
|
||||
var scale = this.scale;
|
||||
|
||||
var width = duration * scale; // 画布宽度
|
||||
var scale5 = scale / 5; // 0.2秒像素数
|
||||
var margin = 0; // 时间轴前后间距
|
||||
|
||||
var canvas = this.canvas.current;
|
||||
canvas.style.width = (width + margin * 2) + 'px';
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = 32;
|
||||
|
||||
var context = canvas.getContext('2d');
|
||||
|
||||
// 时间轴背景
|
||||
context.fillStyle = '#fafafa';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 时间轴刻度
|
||||
context.strokeStyle = '#555';
|
||||
context.beginPath();
|
||||
|
||||
for (var i = margin; i <= width + margin; i += scale) { // 绘制每一秒
|
||||
for (var j = 0; j < 5; j++) { // 绘制每个小格
|
||||
if (j === 0) { // 长刻度
|
||||
context.moveTo(i + scale5 * j, 22);
|
||||
context.lineTo(i + scale5 * j, 30);
|
||||
} else { // 短刻度
|
||||
context.moveTo(i + scale5 * j, 26);
|
||||
context.lineTo(i + scale5 * j, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.stroke();
|
||||
|
||||
// 时间轴文字
|
||||
context.font = '12px Arial';
|
||||
context.fillStyle = '#888'
|
||||
|
||||
for (var i = 0; i <= duration; i += 2) { // 对于每两秒
|
||||
var minute = Math.floor(i / 60);
|
||||
var second = Math.floor(i % 60);
|
||||
|
||||
var text = (minute > 0 ? minute + ':' : '') + ('0' + second).slice(-2);
|
||||
|
||||
if (i === 0) {
|
||||
context.textAlign = 'left';
|
||||
} else if (i === duration) {
|
||||
context.textAlign = 'right';
|
||||
} else {
|
||||
context.textAlign = 'center';
|
||||
}
|
||||
|
||||
context.fillText(text, margin + i * scale, 16);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Timeline.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
animations: PropTypes.array,
|
||||
tip: PropTypes.string,
|
||||
};
|
||||
|
||||
Timeline.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
animations: [],
|
||||
tip: undefined,
|
||||
};
|
||||
|
||||
export default Timeline;
|
||||
102
ShadowEditor.Web/src/ui/timeline/css/Timeline.css
Normal file
102
ShadowEditor.Web/src/ui/timeline/css/Timeline.css
Normal file
@ -0,0 +1,102 @@
|
||||
.Timeline {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Timeline>.box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* left */
|
||||
|
||||
.Timeline>.box>.left {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
width: 100px;
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
padding-top: 33px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.Timeline>.box>.left>.info {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border-bottom: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Timeline>.box>.left>.info:first-child {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* right */
|
||||
|
||||
.Timeline>.box>.right {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.timeline {
|
||||
position: absolute;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.layers {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
border-top: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.layers>.layer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.layers>.layer>.item {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background: #2c3e50;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.layers>.layer>.item>.smaller {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.Timeline>.box>.right>.slider {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border: 1px solid red;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
53
ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx
Normal file
53
ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import './css/TimelineControl.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Toolbar from '../../toolbar/Toolbar.jsx';
|
||||
import ToolbarFiller from '../../toolbar/ToolbarFiller.jsx';
|
||||
import ToolbarSeparator from '../../toolbar/ToolbarSeparator.jsx';
|
||||
import IconButton from '../../form/IconButton.jsx';
|
||||
import Label from '../../form/Label.jsx';
|
||||
|
||||
/**
|
||||
* 时间轴控制
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class TimelineControl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, style, tip } = this.props;
|
||||
|
||||
return <Toolbar className={classNames('TimelineControl', className)} style={style}>
|
||||
<IconButton icon={'add'} title={'Add Layer'}></IconButton>
|
||||
<IconButton icon={'delete'} title={'Delete Layer'}></IconButton>
|
||||
<ToolbarSeparator></ToolbarSeparator>
|
||||
<IconButton icon={'backward'} title={'Slower'}></IconButton>
|
||||
<IconButton icon={'play'} title={'Play'}></IconButton>
|
||||
<IconButton icon={'pause'} title={'Pause'}></IconButton>
|
||||
<IconButton icon={'forward'} title={'Faster'}></IconButton>
|
||||
<IconButton icon={'stop'} title={'Stop'}></IconButton>
|
||||
<ToolbarSeparator></ToolbarSeparator>
|
||||
<Label>00:00</Label>
|
||||
<Label>X1</Label>
|
||||
<ToolbarFiller></ToolbarFiller>
|
||||
<Label>{tip}</Label>
|
||||
</Toolbar>;
|
||||
}
|
||||
}
|
||||
|
||||
TimelineControl.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
tip: PropTypes.string,
|
||||
};
|
||||
|
||||
TimelineControl.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
tip: 'Double click the area below to add animation.',
|
||||
};
|
||||
|
||||
export default TimelineControl;
|
||||
@ -0,0 +1,7 @@
|
||||
.TimelineControl>.IconButton {
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 4px;
|
||||
padding: 0;
|
||||
}
|
||||
33
ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx
Normal file
33
ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import './css/Toolbar.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 工具栏
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class Toolbar extends React.Component {
|
||||
render() {
|
||||
const { className, style, children, direction } = this.props;
|
||||
|
||||
return <div
|
||||
className={classNames('Toolbar', direction, className)}
|
||||
style={style}>{children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
Toolbar.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
direction: PropTypes.oneOf(['horizontal', 'vertical']),
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
Toolbar.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
direction: 'horizontal',
|
||||
children: null,
|
||||
};
|
||||
|
||||
export default Toolbar;
|
||||
27
ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx
Normal file
27
ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import './css/ToolbarFiller.css';
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 工具栏填充
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class ToolbarFiller extends React.Component {
|
||||
render() {
|
||||
const { className, style } = this.props;
|
||||
|
||||
return <div className={classNames('ToolbarFiller', className)} style={style}></div>;
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarFiller.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
ToolbarFiller.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default ToolbarFiller;
|
||||
28
ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx
Normal file
28
ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx
Normal file
@ -0,0 +1,28 @@
|
||||
import classNames from 'classnames/bind';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* 工具栏分割线
|
||||
* @author tengge / https://github.com/tengge1
|
||||
*/
|
||||
class ToolbarSeparator extends React.Component {
|
||||
render() {
|
||||
const { className, style } = this.props;
|
||||
|
||||
return <div className={classNames('ToolbarSeparator', className)} style={style}>
|
||||
<div className='separator'></div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarSeparator.propTypes = {
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
ToolbarSeparator.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
|
||||
export default ToolbarSeparator;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user