From 59a90678f916e7495cbfb4e2125665cef71d6420 Mon Sep 17 00:00:00 2001 From: tengge1 <930372551@qq.com> Date: Sun, 14 Jul 2019 09:51:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=89=88ui=E5=90=88=E5=B9=B6=E5=88=B0?= =?UTF-8?q?web=E4=B8=AD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ShadowEditor.Web/src/ui/Config.css | 34 ++ ShadowEditor.Web/src/ui/canvas/Canvas.jsx | 36 +++ ShadowEditor.Web/src/ui/canvas/css/Canvas.css | 0 ShadowEditor.Web/src/ui/common/Accordion.jsx | 11 + ShadowEditor.Web/src/ui/common/Buttons.jsx | 11 + ShadowEditor.Web/src/ui/common/Column.jsx | 11 + ShadowEditor.Web/src/ui/common/Columns.jsx | 11 + ShadowEditor.Web/src/ui/common/Content.jsx | 11 + ShadowEditor.Web/src/ui/common/Row.jsx | 11 + ShadowEditor.Web/src/ui/common/Rows.jsx | 11 + ShadowEditor.Web/src/ui/form/Button.jsx | 38 +++ ShadowEditor.Web/src/ui/form/CheckBox.jsx | 61 ++++ ShadowEditor.Web/src/ui/form/Form.jsx | 51 +++ ShadowEditor.Web/src/ui/form/FormControl.jsx | 31 ++ ShadowEditor.Web/src/ui/form/IconButton.jsx | 41 +++ ShadowEditor.Web/src/ui/form/Input.jsx | 56 ++++ ShadowEditor.Web/src/ui/form/Label.jsx | 31 ++ ShadowEditor.Web/src/ui/form/Radio.jsx | 59 ++++ ShadowEditor.Web/src/ui/form/SearchField.jsx | 156 ++++++++++ ShadowEditor.Web/src/ui/form/Select.jsx | 63 ++++ ShadowEditor.Web/src/ui/form/TextArea.jsx | 64 ++++ ShadowEditor.Web/src/ui/form/Toggle.jsx | 58 ++++ ShadowEditor.Web/src/ui/form/css/Button.css | 31 ++ ShadowEditor.Web/src/ui/form/css/CheckBox.css | 21 ++ ShadowEditor.Web/src/ui/form/css/Form.css | 3 + .../src/ui/form/css/FormControl.css | 13 + .../src/ui/form/css/IconButton.css | 35 +++ ShadowEditor.Web/src/ui/form/css/Input.css | 9 + ShadowEditor.Web/src/ui/form/css/Label.css | 6 + ShadowEditor.Web/src/ui/form/css/Radio.css | 21 ++ .../src/ui/form/css/SearchField.css | 58 ++++ ShadowEditor.Web/src/ui/form/css/Select.css | 3 + ShadowEditor.Web/src/ui/form/css/TextArea.css | 10 + ShadowEditor.Web/src/ui/form/css/Toggle.css | 21 ++ ShadowEditor.Web/src/ui/icon/Icon.jsx | 52 ++++ ShadowEditor.Web/src/ui/icon/css/Icon.css | 5 + ShadowEditor.Web/src/ui/image/Image.jsx | 35 +++ ShadowEditor.Web/src/ui/image/ImageList.jsx | 167 ++++++++++ .../src/ui/image/ImageUploader.jsx | 81 +++++ ShadowEditor.Web/src/ui/image/css/Image.css | 0 .../src/ui/image/css/ImageList.css | 133 ++++++++ .../src/ui/image/css/ImageUploader.css | 15 + ShadowEditor.Web/src/ui/index.js | 88 ++++++ .../src/ui/layout/AbsoluteLayout.jsx | 41 +++ .../src/ui/layout/AccordionLayout.jsx | 67 ++++ .../src/ui/layout/BorderLayout.jsx | 291 ++++++++++++++++++ ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx | 32 ++ ShadowEditor.Web/src/ui/layout/TabLayout.jsx | 64 ++++ ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx | 32 ++ .../src/ui/layout/css/AbsoluteLayout.css | 4 + .../src/ui/layout/css/AccordionLayout.css | 5 + .../src/ui/layout/css/BorderLayout.css | 198 ++++++++++++ .../src/ui/layout/css/Content.css | 3 + .../src/ui/layout/css/HBoxLayout.css | 6 + .../src/ui/layout/css/TabLayout.css | 46 +++ .../src/ui/layout/css/VBoxLayout.css | 6 + .../src/ui/layout/private/AccordionPanel.jsx | 96 ++++++ .../ui/layout/private/css/AccordionPanel.css | 82 +++++ ShadowEditor.Web/src/ui/menu/MenuBar.jsx | 32 ++ .../src/ui/menu/MenuBarFiller.jsx | 27 ++ ShadowEditor.Web/src/ui/menu/MenuItem.jsx | 59 ++++ .../src/ui/menu/MenuItemSeparator.jsx | 32 ++ ShadowEditor.Web/src/ui/menu/css/MenuBar.css | 38 +++ .../src/ui/menu/css/MenuBarFiller.css | 3 + ShadowEditor.Web/src/ui/menu/css/MenuItem.css | 69 +++++ .../src/ui/menu/css/MenuItemSeparator.css | 11 + ShadowEditor.Web/src/ui/panel/Panel.jsx | 123 ++++++++ ShadowEditor.Web/src/ui/panel/css/Panel.css | 88 ++++++ .../src/ui/property/ButtonField.jsx | 0 .../src/ui/property/ColorField.jsx | 0 .../src/ui/property/IntegerField.jsx | 0 ShadowEditor.Web/src/ui/property/MapField.jsx | 0 .../src/ui/property/NumberField.jsx | 0 .../src/ui/property/PropertyGrid.jsx | 60 ++++ .../src/ui/property/TextField.jsx | 0 .../src/ui/property/css/PropertyGrid.css | 84 +++++ ShadowEditor.Web/src/ui/svg/SVG.jsx | 30 ++ ShadowEditor.Web/src/ui/svg/css/SVG.css | 0 ShadowEditor.Web/src/ui/table/DataGrid.jsx | 91 ++++++ ShadowEditor.Web/src/ui/table/Pager.jsx | 0 ShadowEditor.Web/src/ui/table/Table.jsx | 34 ++ ShadowEditor.Web/src/ui/table/TableBody.jsx | 34 ++ ShadowEditor.Web/src/ui/table/TableCell.jsx | 34 ++ ShadowEditor.Web/src/ui/table/TableHead.jsx | 34 ++ ShadowEditor.Web/src/ui/table/TableRow.jsx | 34 ++ .../src/ui/table/css/DataGrid.css | 37 +++ ShadowEditor.Web/src/ui/table/css/Table.css | 6 + .../src/ui/table/css/TableBody.css | 1 + .../src/ui/table/css/TableCell.css | 5 + .../src/ui/table/css/TableHead.css | 3 + .../src/ui/table/css/TableRow.css | 13 + .../src/ui/table/datagrid/CheckBoxColumn.jsx | 0 .../src/ui/table/datagrid/RowNumberColumn.jsx | 0 ShadowEditor.Web/src/ui/timeline/Timeline.jsx | 146 +++++++++ .../src/ui/timeline/css/Timeline.css | 102 ++++++ .../ui/timeline/private/TimelineControl.jsx | 53 ++++ .../timeline/private/css/TimelineControl.css | 7 + ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx | 33 ++ .../src/ui/toolbar/ToolbarFiller.jsx | 27 ++ .../src/ui/toolbar/ToolbarSeparator.jsx | 28 ++ .../src/ui/toolbar/css/Toolbar.css | 52 ++++ .../src/ui/toolbar/css/ToolbarFiller.css | 3 + ShadowEditor.Web/src/ui/tree/Tree.jsx | 208 +++++++++++++ ShadowEditor.Web/src/ui/tree/css/Tree.css | 103 +++++++ ShadowEditor.Web/src/ui/window/Alert.jsx | 71 +++++ ShadowEditor.Web/src/ui/window/Confirm.jsx | 81 +++++ ShadowEditor.Web/src/ui/window/Prompt.jsx | 86 ++++++ ShadowEditor.Web/src/ui/window/Toast.jsx | 36 +++ ShadowEditor.Web/src/ui/window/Window.jsx | 138 +++++++++ ShadowEditor.Web/src/ui/window/css/Alert.css | 4 + .../src/ui/window/css/Confirm.css | 4 + ShadowEditor.Web/src/ui/window/css/Prompt.css | 9 + ShadowEditor.Web/src/ui/window/css/Toast.css | 22 ++ ShadowEditor.Web/src/ui/window/css/Window.css | 105 +++++++ 114 files changed, 4936 insertions(+) create mode 100644 ShadowEditor.Web/src/ui/Config.css create mode 100644 ShadowEditor.Web/src/ui/canvas/Canvas.jsx create mode 100644 ShadowEditor.Web/src/ui/canvas/css/Canvas.css create mode 100644 ShadowEditor.Web/src/ui/common/Accordion.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Buttons.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Column.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Columns.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Content.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Row.jsx create mode 100644 ShadowEditor.Web/src/ui/common/Rows.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Button.jsx create mode 100644 ShadowEditor.Web/src/ui/form/CheckBox.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Form.jsx create mode 100644 ShadowEditor.Web/src/ui/form/FormControl.jsx create mode 100644 ShadowEditor.Web/src/ui/form/IconButton.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Input.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Label.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Radio.jsx create mode 100644 ShadowEditor.Web/src/ui/form/SearchField.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Select.jsx create mode 100644 ShadowEditor.Web/src/ui/form/TextArea.jsx create mode 100644 ShadowEditor.Web/src/ui/form/Toggle.jsx create mode 100644 ShadowEditor.Web/src/ui/form/css/Button.css create mode 100644 ShadowEditor.Web/src/ui/form/css/CheckBox.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Form.css create mode 100644 ShadowEditor.Web/src/ui/form/css/FormControl.css create mode 100644 ShadowEditor.Web/src/ui/form/css/IconButton.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Input.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Label.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Radio.css create mode 100644 ShadowEditor.Web/src/ui/form/css/SearchField.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Select.css create mode 100644 ShadowEditor.Web/src/ui/form/css/TextArea.css create mode 100644 ShadowEditor.Web/src/ui/form/css/Toggle.css create mode 100644 ShadowEditor.Web/src/ui/icon/Icon.jsx create mode 100644 ShadowEditor.Web/src/ui/icon/css/Icon.css create mode 100644 ShadowEditor.Web/src/ui/image/Image.jsx create mode 100644 ShadowEditor.Web/src/ui/image/ImageList.jsx create mode 100644 ShadowEditor.Web/src/ui/image/ImageUploader.jsx create mode 100644 ShadowEditor.Web/src/ui/image/css/Image.css create mode 100644 ShadowEditor.Web/src/ui/image/css/ImageList.css create mode 100644 ShadowEditor.Web/src/ui/image/css/ImageUploader.css create mode 100644 ShadowEditor.Web/src/ui/index.js create mode 100644 ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/BorderLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/TabLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/BorderLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/Content.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/TabLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css create mode 100644 ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx create mode 100644 ShadowEditor.Web/src/ui/layout/private/css/AccordionPanel.css create mode 100644 ShadowEditor.Web/src/ui/menu/MenuBar.jsx create mode 100644 ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx create mode 100644 ShadowEditor.Web/src/ui/menu/MenuItem.jsx create mode 100644 ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx create mode 100644 ShadowEditor.Web/src/ui/menu/css/MenuBar.css create mode 100644 ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css create mode 100644 ShadowEditor.Web/src/ui/menu/css/MenuItem.css create mode 100644 ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css create mode 100644 ShadowEditor.Web/src/ui/panel/Panel.jsx create mode 100644 ShadowEditor.Web/src/ui/panel/css/Panel.css create mode 100644 ShadowEditor.Web/src/ui/property/ButtonField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/ColorField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/IntegerField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/MapField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/NumberField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/PropertyGrid.jsx create mode 100644 ShadowEditor.Web/src/ui/property/TextField.jsx create mode 100644 ShadowEditor.Web/src/ui/property/css/PropertyGrid.css create mode 100644 ShadowEditor.Web/src/ui/svg/SVG.jsx create mode 100644 ShadowEditor.Web/src/ui/svg/css/SVG.css create mode 100644 ShadowEditor.Web/src/ui/table/DataGrid.jsx create mode 100644 ShadowEditor.Web/src/ui/table/Pager.jsx create mode 100644 ShadowEditor.Web/src/ui/table/Table.jsx create mode 100644 ShadowEditor.Web/src/ui/table/TableBody.jsx create mode 100644 ShadowEditor.Web/src/ui/table/TableCell.jsx create mode 100644 ShadowEditor.Web/src/ui/table/TableHead.jsx create mode 100644 ShadowEditor.Web/src/ui/table/TableRow.jsx create mode 100644 ShadowEditor.Web/src/ui/table/css/DataGrid.css create mode 100644 ShadowEditor.Web/src/ui/table/css/Table.css create mode 100644 ShadowEditor.Web/src/ui/table/css/TableBody.css create mode 100644 ShadowEditor.Web/src/ui/table/css/TableCell.css create mode 100644 ShadowEditor.Web/src/ui/table/css/TableHead.css create mode 100644 ShadowEditor.Web/src/ui/table/css/TableRow.css create mode 100644 ShadowEditor.Web/src/ui/table/datagrid/CheckBoxColumn.jsx create mode 100644 ShadowEditor.Web/src/ui/table/datagrid/RowNumberColumn.jsx create mode 100644 ShadowEditor.Web/src/ui/timeline/Timeline.jsx create mode 100644 ShadowEditor.Web/src/ui/timeline/css/Timeline.css create mode 100644 ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx create mode 100644 ShadowEditor.Web/src/ui/timeline/private/css/TimelineControl.css create mode 100644 ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx create mode 100644 ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx create mode 100644 ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx create mode 100644 ShadowEditor.Web/src/ui/toolbar/css/Toolbar.css create mode 100644 ShadowEditor.Web/src/ui/toolbar/css/ToolbarFiller.css create mode 100644 ShadowEditor.Web/src/ui/tree/Tree.jsx create mode 100644 ShadowEditor.Web/src/ui/tree/css/Tree.css create mode 100644 ShadowEditor.Web/src/ui/window/Alert.jsx create mode 100644 ShadowEditor.Web/src/ui/window/Confirm.jsx create mode 100644 ShadowEditor.Web/src/ui/window/Prompt.jsx create mode 100644 ShadowEditor.Web/src/ui/window/Toast.jsx create mode 100644 ShadowEditor.Web/src/ui/window/Window.jsx create mode 100644 ShadowEditor.Web/src/ui/window/css/Alert.css create mode 100644 ShadowEditor.Web/src/ui/window/css/Confirm.css create mode 100644 ShadowEditor.Web/src/ui/window/css/Prompt.css create mode 100644 ShadowEditor.Web/src/ui/window/css/Toast.css create mode 100644 ShadowEditor.Web/src/ui/window/css/Window.css diff --git a/ShadowEditor.Web/src/ui/Config.css b/ShadowEditor.Web/src/ui/Config.css new file mode 100644 index 00000000..09748e85 --- /dev/null +++ b/ShadowEditor.Web/src/ui/Config.css @@ -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); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/canvas/Canvas.jsx b/ShadowEditor.Web/src/ui/canvas/Canvas.jsx new file mode 100644 index 00000000..9438183f --- /dev/null +++ b/ShadowEditor.Web/src/ui/canvas/Canvas.jsx @@ -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.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +Canvas.defaultProps = { + className: null, + style: null, +}; + +export default Canvas; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/canvas/css/Canvas.css b/ShadowEditor.Web/src/ui/canvas/css/Canvas.css new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/common/Accordion.jsx b/ShadowEditor.Web/src/ui/common/Accordion.jsx new file mode 100644 index 00000000..06ce1b03 --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Accordion.jsx @@ -0,0 +1,11 @@ +/** + * 折叠面板 + * @author tengge / https://github.com/tengge1 + */ +class Accordion extends React.Component { + render() { + return null; + } +} + +export default Accordion; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Buttons.jsx b/ShadowEditor.Web/src/ui/common/Buttons.jsx new file mode 100644 index 00000000..87873177 --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Buttons.jsx @@ -0,0 +1,11 @@ +/** + * 很多按钮 + * @author tengge / https://github.com/tengge1 + */ +class Buttons extends React.Component { + render() { + return null; + } +} + +export default Buttons; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Column.jsx b/ShadowEditor.Web/src/ui/common/Column.jsx new file mode 100644 index 00000000..9dfc91fb --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Column.jsx @@ -0,0 +1,11 @@ +/** + * 列 + * @author tengge / https://github.com/tengge1 + */ +class Column extends React.Component { + render() { + return null; + } +} + +export default Column; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Columns.jsx b/ShadowEditor.Web/src/ui/common/Columns.jsx new file mode 100644 index 00000000..bf0b82d9 --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Columns.jsx @@ -0,0 +1,11 @@ +/** + * 很多列 + * @author tengge / https://github.com/tengge1 + */ +class Columns extends React.Component { + render() { + return null; + } +} + +export default Columns; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Content.jsx b/ShadowEditor.Web/src/ui/common/Content.jsx new file mode 100644 index 00000000..5b676576 --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Content.jsx @@ -0,0 +1,11 @@ +/** + * 内容 + * @author tengge / https://github.com/tengge1 + */ +class Content extends React.Component { + render() { + return null; + } +} + +export default Content; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Row.jsx b/ShadowEditor.Web/src/ui/common/Row.jsx new file mode 100644 index 00000000..692d145b --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Row.jsx @@ -0,0 +1,11 @@ +/** + * 行 + * @author tengge / https://github.com/tengge1 + */ +class Row extends React.Component { + render() { + return null; + } +} + +export default Row; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/common/Rows.jsx b/ShadowEditor.Web/src/ui/common/Rows.jsx new file mode 100644 index 00000000..f002459e --- /dev/null +++ b/ShadowEditor.Web/src/ui/common/Rows.jsx @@ -0,0 +1,11 @@ +/** + * 很多行 + * @author tengge / https://github.com/tengge1 + */ +class Rows extends React.Component { + render() { + return null; + } +} + +export default Rows; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Button.jsx b/ShadowEditor.Web/src/ui/form/Button.jsx new file mode 100644 index 00000000..f02b5658 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Button.jsx @@ -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.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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/CheckBox.jsx b/ShadowEditor.Web/src/ui/form/CheckBox.jsx new file mode 100644 index 00000000..fefcf930 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/CheckBox.jsx @@ -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 ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Form.jsx b/ShadowEditor.Web/src/ui/form/Form.jsx new file mode 100644 index 00000000..94f5d992 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Form.jsx @@ -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
+ {children} +
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/FormControl.jsx b/ShadowEditor.Web/src/ui/form/FormControl.jsx new file mode 100644 index 00000000..68d27cd7 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/FormControl.jsx @@ -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
+ {children} +
; + } +} + +FormControl.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +FormControl.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default FormControl; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/IconButton.jsx b/ShadowEditor.Web/src/ui/form/IconButton.jsx new file mode 100644 index 00000000..e40ac063 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/IconButton.jsx @@ -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 ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Input.jsx b/ShadowEditor.Web/src/ui/form/Input.jsx new file mode 100644 index 00000000..13059b76 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Input.jsx @@ -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.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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Label.jsx b/ShadowEditor.Web/src/ui/form/Label.jsx new file mode 100644 index 00000000..c8eb80a5 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Label.jsx @@ -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.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +Label.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default Label; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Radio.jsx b/ShadowEditor.Web/src/ui/form/Radio.jsx new file mode 100644 index 00000000..902ea2fe --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Radio.jsx @@ -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 ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/SearchField.jsx b/ShadowEditor.Web/src/ui/form/SearchField.jsx new file mode 100644 index 00000000..736da8c9 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/SearchField.jsx @@ -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
+ + + + +
+ {data.map(n => { + return
+ -1} + onChange={this.handleCheckBoxChange}> + +
; + })} +
+
; + } + + 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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Select.jsx b/ShadowEditor.Web/src/ui/form/Select.jsx new file mode 100644 index 00000000..d9e9db64 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Select.jsx @@ -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.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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/TextArea.jsx b/ShadowEditor.Web/src/ui/form/TextArea.jsx new file mode 100644 index 00000000..de84578d --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/TextArea.jsx @@ -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.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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/Toggle.jsx b/ShadowEditor.Web/src/ui/form/Toggle.jsx new file mode 100644 index 00000000..8a93ebdd --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/Toggle.jsx @@ -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
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Button.css b/ShadowEditor.Web/src/ui/form/css/Button.css new file mode 100644 index 00000000..08a588f9 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Button.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/CheckBox.css b/ShadowEditor.Web/src/ui/form/css/CheckBox.css new file mode 100644 index 00000000..ec2aa5f4 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/CheckBox.css @@ -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); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Form.css b/ShadowEditor.Web/src/ui/form/css/Form.css new file mode 100644 index 00000000..b1c7a853 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Form.css @@ -0,0 +1,3 @@ +.Form { + box-sizing: border-box; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/FormControl.css b/ShadowEditor.Web/src/ui/form/css/FormControl.css new file mode 100644 index 00000000..e26a5f0d --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/FormControl.css @@ -0,0 +1,13 @@ +.FormControl { + min-height: 20px; + margin: 4px; + box-sizing: border-box; +} + +.FormControl>* { + vertical-align: middle; +} + +.FormControl>.Label { + width: 60px; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/IconButton.css b/ShadowEditor.Web/src/ui/form/css/IconButton.css new file mode 100644 index 00000000..29aa4d84 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/IconButton.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Input.css b/ShadowEditor.Web/src/ui/form/css/Input.css new file mode 100644 index 00000000..8b378228 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Input.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Label.css b/ShadowEditor.Web/src/ui/form/css/Label.css new file mode 100644 index 00000000..c083c72a --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Label.css @@ -0,0 +1,6 @@ +.Label { + height: 20px; + font: 12px 'Microsoft YaHei'; + line-height: 20px; + display: inline-block; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Radio.css b/ShadowEditor.Web/src/ui/form/css/Radio.css new file mode 100644 index 00000000..bb57f091 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Radio.css @@ -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=); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/SearchField.css b/ShadowEditor.Web/src/ui/form/css/SearchField.css new file mode 100644 index 00000000..254daf6e --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/SearchField.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Select.css b/ShadowEditor.Web/src/ui/form/css/Select.css new file mode 100644 index 00000000..82f091d5 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Select.css @@ -0,0 +1,3 @@ +.Select { + box-sizing: border-box; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/TextArea.css b/ShadowEditor.Web/src/ui/form/css/TextArea.css new file mode 100644 index 00000000..146f65db --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/TextArea.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/form/css/Toggle.css b/ShadowEditor.Web/src/ui/form/css/Toggle.css new file mode 100644 index 00000000..01e8a1b0 --- /dev/null +++ b/ShadowEditor.Web/src/ui/form/css/Toggle.css @@ -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==); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/icon/Icon.jsx b/ShadowEditor.Web/src/ui/icon/Icon.jsx new file mode 100644 index 00000000..b7cf8cd1 --- /dev/null +++ b/ShadowEditor.Web/src/ui/icon/Icon.jsx @@ -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 ; + } + + 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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/icon/css/Icon.css b/ShadowEditor.Web/src/ui/icon/css/Icon.css new file mode 100644 index 00000000..78b8fcad --- /dev/null +++ b/ShadowEditor.Web/src/ui/icon/css/Icon.css @@ -0,0 +1,5 @@ +.Icon { + font-size: 20px; + margin: 2px; + display: inline-block; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/image/Image.jsx b/ShadowEditor.Web/src/ui/image/Image.jsx new file mode 100644 index 00000000..7d8dddeb --- /dev/null +++ b/ShadowEditor.Web/src/ui/image/Image.jsx @@ -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 ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/image/ImageList.jsx b/ShadowEditor.Web/src/ui/image/ImageList.jsx new file mode 100644 index 00000000..b36b7d40 --- /dev/null +++ b/ShadowEditor.Web/src/ui/image/ImageList.jsx @@ -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
+
+ {current.map(n => { + return
+ {n.src ? + : +
+ +
} +
{n.title}
+ {n.cornerText &&
{n.cornerText}
} + + +
; + })} +
+
+ + + + + +
+ 共{totalPage}页 +
+
+
; + } + + 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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/image/ImageUploader.jsx b/ShadowEditor.Web/src/ui/image/ImageUploader.jsx new file mode 100644 index 00000000..204e63c1 --- /dev/null +++ b/ShadowEditor.Web/src/ui/image/ImageUploader.jsx @@ -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 ; + } else { + return
+ {noImageText} +
; + } + } + + 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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/image/css/Image.css b/ShadowEditor.Web/src/ui/image/css/Image.css new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/image/css/ImageList.css b/ShadowEditor.Web/src/ui/image/css/ImageList.css new file mode 100644 index 00000000..36eb822c --- /dev/null +++ b/ShadowEditor.Web/src/ui/image/css/ImageList.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/image/css/ImageUploader.css b/ShadowEditor.Web/src/ui/image/css/ImageUploader.css new file mode 100644 index 00000000..b9f7b565 --- /dev/null +++ b/ShadowEditor.Web/src/ui/image/css/ImageUploader.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/index.js b/ShadowEditor.Web/src/ui/index.js new file mode 100644 index 00000000..c008efb7 --- /dev/null +++ b/ShadowEditor.Web/src/ui/index.js @@ -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'; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx b/ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx new file mode 100644 index 00000000..201bfeb1 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/AbsoluteLayout.jsx @@ -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
{children}
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx b/ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx new file mode 100644 index 00000000..b619e98f --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/AccordionLayout.jsx @@ -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
+ {content.map((n, i) => { + return {n.props.children}; + })} +
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/BorderLayout.jsx b/ShadowEditor.Web/src/ui/layout/BorderLayout.jsx new file mode 100644 index 00000000..f045847a --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/BorderLayout.jsx @@ -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 && (
+
+ {north} +
+ {this.state.northSplit &&
+
+
} +
); + + // south region + const southRegion = south.length > 0 && (
+ {this.state.southSplit &&
+
+
} +
+ {south} +
+
); + + // west region + const westRegion = west.length > 0 && (
+
+ {west} +
+ {this.state.westSplit &&
+
+
} +
); + + // east region + const eastRegion = east.length > 0 && (
+
+
+
+
+ {east} +
+
); + + // center region + const centerRegion = center.length > 0 && (
+ {center} +
); + + const otherRegion = others.length > 0 && others; + + return
+ {northRegion} +
+ {westRegion} + {centerRegion} + {eastRegion} +
+ {southRegion} + {otherRegion} +
; + } +} + +BorderLayout.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +BorderLayout.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default BorderLayout; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx b/ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx new file mode 100644 index 00000000..3fdbb0c5 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/HBoxLayout.jsx @@ -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
{children}
; + } +} + +HBoxLayout.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +HBoxLayout.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default HBoxLayout; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/TabLayout.jsx b/ShadowEditor.Web/src/ui/layout/TabLayout.jsx new file mode 100644 index 00000000..730b947a --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/TabLayout.jsx @@ -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
+
+ {children.map((n, i) => { + return
{n.props.title}
; + })} +
+
+ {children.map((n, i) => { + return
{n}
; + })} +
+
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx b/ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx new file mode 100644 index 00000000..fade0d3e --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/VBoxLayout.jsx @@ -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
{children}
; + } +} + +VBoxLayout.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +VBoxLayout.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default VBoxLayout; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css b/ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css new file mode 100644 index 00000000..9ddbf4cc --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/AbsoluteLayout.css @@ -0,0 +1,4 @@ +.AbsoluteLayout { + position: absolute; + display: block; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css b/ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css new file mode 100644 index 00000000..f2974af1 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/AccordionLayout.css @@ -0,0 +1,5 @@ +.AccordionLayout { + width: 100%; + height: 100%; + display: block; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/BorderLayout.css b/ShadowEditor.Web/src/ui/layout/css/BorderLayout.css new file mode 100644 index 00000000..3dd8bb77 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/BorderLayout.css @@ -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); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/Content.css b/ShadowEditor.Web/src/ui/layout/css/Content.css new file mode 100644 index 00000000..dea508ad --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/Content.css @@ -0,0 +1,3 @@ +.Content { + display: block-inline; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css b/ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css new file mode 100644 index 00000000..2cf31bfc --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/HBoxLayout.css @@ -0,0 +1,6 @@ +.HBoxLayout { + display: flex; + flex-direction: row; + overflow-x: auto; + overflow-y: hidden; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/TabLayout.css b/ShadowEditor.Web/src/ui/layout/css/TabLayout.css new file mode 100644 index 00000000..9db391da --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/TabLayout.css @@ -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%; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css b/ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css new file mode 100644 index 00000000..50416d08 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/css/VBoxLayout.css @@ -0,0 +1,6 @@ +.VBoxLayout { + display: flex; + flex-direction: column; + overflow-x: hidden; + overflow-y: auto; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx b/ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx new file mode 100644 index 00000000..7c4e14bc --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/private/AccordionPanel.jsx @@ -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 &&
+ {this.state.maximized ? : } +
; + + const _style = collpased ? style : Object.assign({}, style, { + height: `calc(100% - ${26 * (total - 1)}px`, + }); + + return
+
+ {title} +
+ {maximizeControl} +
+
+
+ {children} +
+
; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/layout/private/css/AccordionPanel.css b/ShadowEditor.Web/src/ui/layout/private/css/AccordionPanel.css new file mode 100644 index 00000000..d1642144 --- /dev/null +++ b/ShadowEditor.Web/src/ui/layout/private/css/AccordionPanel.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/MenuBar.jsx b/ShadowEditor.Web/src/ui/menu/MenuBar.jsx new file mode 100644 index 00000000..76a18916 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/MenuBar.jsx @@ -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 ; + } +} + +MenuBar.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +MenuBar.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default MenuBar; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx b/ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx new file mode 100644 index 00000000..11483645 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/MenuBarFiller.jsx @@ -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
  • ; + } +} + +MenuBarFiller.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +MenuBarFiller.defaultProps = { + className: null, + style: null, +}; + +export default MenuBarFiller; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/MenuItem.jsx b/ShadowEditor.Web/src/ui/menu/MenuItem.jsx new file mode 100644 index 00000000..0ac05c00 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/MenuItem.jsx @@ -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 && <>
    + +
    +
    + +
    ; + + return
  • + {title} + {subMenu} +
  • ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx b/ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx new file mode 100644 index 00000000..63cd8df4 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/MenuItemSeparator.jsx @@ -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
  • +
    +
  • ; + } +} + +MenuItemSeparator.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +MenuItemSeparator.defaultProps = { + className: null, + style: null, +}; + +export default MenuItemSeparator; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/css/MenuBar.css b/ShadowEditor.Web/src/ui/menu/css/MenuBar.css new file mode 100644 index 00000000..134757b6 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/css/MenuBar.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css b/ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css new file mode 100644 index 00000000..2143fd1f --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/css/MenuBarFiller.css @@ -0,0 +1,3 @@ +.MenuBarFiller { + flex: 1; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/css/MenuItem.css b/ShadowEditor.Web/src/ui/menu/css/MenuItem.css new file mode 100644 index 00000000..8498e3c6 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/css/MenuItem.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css b/ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css new file mode 100644 index 00000000..0cf7a024 --- /dev/null +++ b/ShadowEditor.Web/src/ui/menu/css/MenuItemSeparator.css @@ -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)); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/panel/Panel.jsx b/ShadowEditor.Web/src/ui/panel/Panel.jsx new file mode 100644 index 00000000..45e63b6e --- /dev/null +++ b/ShadowEditor.Web/src/ui/panel/Panel.jsx @@ -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 &&
    + {this.state.collapsed ? : } +
    ; + + const maximizeControl = maximizable &&
    + {this.state.maximized ? : } +
    ; + + const closeControl = closable &&
    + +
    ; + + return
    +
    + {title} +
    + {collapseControl} + {maximizeControl} + {closeControl} +
    +
    +
    + {children} +
    +
    ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/panel/css/Panel.css b/ShadowEditor.Web/src/ui/panel/css/Panel.css new file mode 100644 index 00000000..51a47618 --- /dev/null +++ b/ShadowEditor.Web/src/ui/panel/css/Panel.css @@ -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%; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/property/ButtonField.jsx b/ShadowEditor.Web/src/ui/property/ButtonField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/ColorField.jsx b/ShadowEditor.Web/src/ui/property/ColorField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/IntegerField.jsx b/ShadowEditor.Web/src/ui/property/IntegerField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/MapField.jsx b/ShadowEditor.Web/src/ui/property/MapField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/NumberField.jsx b/ShadowEditor.Web/src/ui/property/NumberField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/PropertyGrid.jsx b/ShadowEditor.Web/src/ui/property/PropertyGrid.jsx new file mode 100644 index 00000000..fad43784 --- /dev/null +++ b/ShadowEditor.Web/src/ui/property/PropertyGrid.jsx @@ -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
    + {data.map((group, i) => { + return
    +
    +
    + +
    +
    {group.name}
    +
    +
    + {group.children.map((item, j) => { + return
    +
    {item.label}
    +
    {item.value}
    +
    ; + })} +
    +
    ; + })} +
    ; + } +} + +PropertyGrid.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + data: PropTypes.array, +}; + +PropertyGrid.defaultProps = { + className: null, + style: null, + data: [], +}; + +export default PropertyGrid; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/property/TextField.jsx b/ShadowEditor.Web/src/ui/property/TextField.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/property/css/PropertyGrid.css b/ShadowEditor.Web/src/ui/property/css/PropertyGrid.css new file mode 100644 index 00000000..f9e6eac7 --- /dev/null +++ b/ShadowEditor.Web/src/ui/property/css/PropertyGrid.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/svg/SVG.jsx b/ShadowEditor.Web/src/ui/svg/SVG.jsx new file mode 100644 index 00000000..bd72cb82 --- /dev/null +++ b/ShadowEditor.Web/src/ui/svg/SVG.jsx @@ -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.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +SVG.defaultProps = { + className: null, + style: null, +}; + +export default SVG; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/svg/css/SVG.css b/ShadowEditor.Web/src/ui/svg/css/SVG.css new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/table/DataGrid.jsx b/ShadowEditor.Web/src/ui/table/DataGrid.jsx new file mode 100644 index 00000000..673ba45f --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/DataGrid.jsx @@ -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 = + + {columns.map(n => { + return {n.title}; + })} + + ; + + const body = + {data.map(n => { + return + {columns.map(m => { + return {n[m.field]}; + })} + ; + })} + ; + + return + {header} + {body} +
    ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/Pager.jsx b/ShadowEditor.Web/src/ui/table/Pager.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/table/Table.jsx b/ShadowEditor.Web/src/ui/table/Table.jsx new file mode 100644 index 00000000..b9e164b1 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/Table.jsx @@ -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 + {children} +
    ; + } +} + +Table.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +Table.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default Table; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/TableBody.jsx b/ShadowEditor.Web/src/ui/table/TableBody.jsx new file mode 100644 index 00000000..97ae9021 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/TableBody.jsx @@ -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 + {children} + ; + } +} + +TableBody.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +TableBody.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default TableBody; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/TableCell.jsx b/ShadowEditor.Web/src/ui/table/TableCell.jsx new file mode 100644 index 00000000..0f1f351f --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/TableCell.jsx @@ -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 + {children} + ; + } +} + +TableCell.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +TableCell.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default TableCell; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/TableHead.jsx b/ShadowEditor.Web/src/ui/table/TableHead.jsx new file mode 100644 index 00000000..434e6f0e --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/TableHead.jsx @@ -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 + {children} + ; + } +} + +TableHead.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +TableHead.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default TableHead; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/TableRow.jsx b/ShadowEditor.Web/src/ui/table/TableRow.jsx new file mode 100644 index 00000000..d30ed4e8 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/TableRow.jsx @@ -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 + {children} + ; + } +} + +TableRow.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +TableRow.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default TableRow; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/DataGrid.css b/ShadowEditor.Web/src/ui/table/css/DataGrid.css new file mode 100644 index 00000000..76020123 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/DataGrid.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/Table.css b/ShadowEditor.Web/src/ui/table/css/Table.css new file mode 100644 index 00000000..6e4f2345 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/Table.css @@ -0,0 +1,6 @@ +.Table { + display: inline-block; + border-collapse: collapse; + user-select: none; + cursor: default; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/TableBody.css b/ShadowEditor.Web/src/ui/table/css/TableBody.css new file mode 100644 index 00000000..5c0bfd81 --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/TableBody.css @@ -0,0 +1 @@ +.TableBody {} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/TableCell.css b/ShadowEditor.Web/src/ui/table/css/TableCell.css new file mode 100644 index 00000000..3dd7d48c --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/TableCell.css @@ -0,0 +1,5 @@ +.TableCell { + padding: 0 16px; + border: 1px solid #ebebeb; + box-sizing: border-box; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/TableHead.css b/ShadowEditor.Web/src/ui/table/css/TableHead.css new file mode 100644 index 00000000..2f0cdecf --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/TableHead.css @@ -0,0 +1,3 @@ +.TableHead { + background-color: #f7f7f7; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/css/TableRow.css b/ShadowEditor.Web/src/ui/table/css/TableRow.css new file mode 100644 index 00000000..c57f5ffe --- /dev/null +++ b/ShadowEditor.Web/src/ui/table/css/TableRow.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/table/datagrid/CheckBoxColumn.jsx b/ShadowEditor.Web/src/ui/table/datagrid/CheckBoxColumn.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/table/datagrid/RowNumberColumn.jsx b/ShadowEditor.Web/src/ui/table/datagrid/RowNumberColumn.jsx new file mode 100644 index 00000000..e69de29b diff --git a/ShadowEditor.Web/src/ui/timeline/Timeline.jsx b/ShadowEditor.Web/src/ui/timeline/Timeline.jsx new file mode 100644 index 00000000..39566b84 --- /dev/null +++ b/ShadowEditor.Web/src/ui/timeline/Timeline.jsx @@ -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
    + + +
    ; + }); + + const layers = animations.map(layer => { + return
    + {layer.animations.map(animation => { + const style = { + left: animation.beginTime * this.scale + 'px', + width: (animation.endTime - animation.beginTime) * this.scale + 'px', + }; + + return
    + {animation.name} +
    ; + })} +
    ; + }); + + return
    + +
    +
    + {infos} +
    +
    + +
    + {layers} +
    +
    +
    +
    +
    ; + } + + 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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/timeline/css/Timeline.css b/ShadowEditor.Web/src/ui/timeline/css/Timeline.css new file mode 100644 index 00000000..ba7960c8 --- /dev/null +++ b/ShadowEditor.Web/src/ui/timeline/css/Timeline.css @@ -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; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx b/ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx new file mode 100644 index 00000000..7b98a3c0 --- /dev/null +++ b/ShadowEditor.Web/src/ui/timeline/private/TimelineControl.jsx @@ -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 + + + + + + + + + + + + + + ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/timeline/private/css/TimelineControl.css b/ShadowEditor.Web/src/ui/timeline/private/css/TimelineControl.css new file mode 100644 index 00000000..0ef2f562 --- /dev/null +++ b/ShadowEditor.Web/src/ui/timeline/private/css/TimelineControl.css @@ -0,0 +1,7 @@ +.TimelineControl>.IconButton { + border: none; + width: 24px; + height: 24px; + margin: 0 4px; + padding: 0; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx b/ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx new file mode 100644 index 00000000..1ce6598c --- /dev/null +++ b/ShadowEditor.Web/src/ui/toolbar/Toolbar.jsx @@ -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
    {children}
    ; + } +} + +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; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx b/ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx new file mode 100644 index 00000000..05e67754 --- /dev/null +++ b/ShadowEditor.Web/src/ui/toolbar/ToolbarFiller.jsx @@ -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
    ; + } +} + +ToolbarFiller.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +ToolbarFiller.defaultProps = { + className: null, + style: null, +}; + +export default ToolbarFiller; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx b/ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx new file mode 100644 index 00000000..76b153a4 --- /dev/null +++ b/ShadowEditor.Web/src/ui/toolbar/ToolbarSeparator.jsx @@ -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
    +
    +
    ; + } +} + +ToolbarSeparator.propTypes = { + className: PropTypes.string, + style: PropTypes.object, +}; + +ToolbarSeparator.defaultProps = { + className: null, + style: null, +}; + +export default ToolbarSeparator; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/toolbar/css/Toolbar.css b/ShadowEditor.Web/src/ui/toolbar/css/Toolbar.css new file mode 100644 index 00000000..b17353bc --- /dev/null +++ b/ShadowEditor.Web/src/ui/toolbar/css/Toolbar.css @@ -0,0 +1,52 @@ +.Toolbar { + background: #fafafa; + display: flex !important; + align-items: center; + box-sizing: border-box; + user-select: none; + overflow: hidden; +} + +.Toolbar.horizontal { + width: 100%; + height: 25px; + flex-direction: row; + background: linear-gradient(to bottom, rgb(250, 252, 253), rgb(232, 241, 251) 40%, rgb(220, 230, 243) 40%, rgb(220, 231, 245)); +} + +.Toolbar.vertical { + width: 32px; + height: 100%; + flex-direction: column; + background: #fafafa; +} + +.Toolbar .Icon { + width: 32px; + height: 32px; + line-height: 32px; + text-align: center; +} + +/* ToolbarSeparator */ + +.ToolbarSeparator { + white-space: nowrap; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.ToolbarSeparator>.separator { + width: 2px; + height: 24px; + background: rgb(229, 229, 229); + background-image: linear-gradient(to right, rgb(226, 226, 226), rgb(226, 226, 226) 50%, rgb(252, 252, 252) 50%, rgb(252, 252, 252)); +} + +.Toolbar.vertical>.ToolbarSeparator>.separator { + width: 24px; + height: 2px; + 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)); +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/toolbar/css/ToolbarFiller.css b/ShadowEditor.Web/src/ui/toolbar/css/ToolbarFiller.css new file mode 100644 index 00000000..50519b5d --- /dev/null +++ b/ShadowEditor.Web/src/ui/toolbar/css/ToolbarFiller.css @@ -0,0 +1,3 @@ +.ToolbarFiller { + flex: 1; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/tree/Tree.jsx b/ShadowEditor.Web/src/ui/tree/Tree.jsx new file mode 100644 index 00000000..d0519c1a --- /dev/null +++ b/ShadowEditor.Web/src/ui/tree/Tree.jsx @@ -0,0 +1,208 @@ +import './css/Tree.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +import CheckBox from '../form/CheckBox.jsx'; + +/** + * 树 + * @author tengge / https://github.com/tengge1 + */ +class Tree extends React.Component { + constructor(props) { + super(props); + + const { onExpand, onSelect, onDoubleClick, onDrop } = this.props; + + this.handleExpandNode = this.handleExpandNode.bind(this, onExpand); + this.handleClick = this.handleClick.bind(this, onSelect); + this.handleDoubleClick = this.handleDoubleClick.bind(this, onDoubleClick); + + this.handleDrag = this.handleDrag.bind(this); + this.handleDragStart = this.handleDragStart.bind(this); + this.handleDragOver = this.handleDragOver.bind(this); + this.handleDragLeave = this.handleDragLeave.bind(this); + this.handleDrop = this.handleDrop.bind(this, onDrop); + } + + render() { + const { data, className, style } = this.props; + + // 创建节点 + var list = []; + + Array.isArray(data) && data.forEach(n => { + list.push(this.createNode(n)); + }); + + return ; + } + + createNode(data) { + const leaf = !data.children || data.children.length === 0; + + const children = leaf ? null : (); + + let checkbox = null; + + if (data.checked === true || data.checked === false) { + checkbox = ; + } + + return
  • + + {checkbox} + + {data.text} + {leaf ? null : children} +
  • ; + } + + handleExpandNode(onExpand, event) { + event.stopPropagation(); + const value = event.target.getAttribute('value'); + + onExpand && onExpand(value, event); + } + + handleClick(onSelect, event) { + var value = event.target.getAttribute('value'); + if (value) { + onSelect && onSelect(value, event); + } + } + + handleDoubleClick(onDoubleClick, event) { + var value = event.target.getAttribute('value'); + if (value) { + onDoubleClick && onDoubleClick(value, event); + } + } + + // --------------------- 拖拽事件 --------------------------- + + handleDrag(event) { + event.stopPropagation(); + this.currentDrag = event.currentTarget; + } + + handleDragStart(event) { + event.stopPropagation(); + event.dataTransfer.setData('text', 'foo'); + } + + handleDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + + var target = event.currentTarget; + + if (target === this.currentDrag) { + return; + } + + var area = event.nativeEvent.offsetY / target.clientHeight; + + if (area < 0.25) { + target.classList.add('dragTop'); + } else if (area > 0.75) { + target.classList.add('dragBottom'); + } else { + target.classList.add('drag'); + } + } + + handleDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + + var target = event.currentTarget; + + if (target === this.currentDrag) { + return; + } + + target.classList.remove('dragTop'); + target.classList.remove('dragBottom'); + target.classList.remove('drag'); + } + + handleDrop(onDrop, event) { + event.preventDefault(); + event.stopPropagation(); + + var target = event.currentTarget; + + if (target === this.currentDrag) { + return; + } + + target.classList.remove('dragTop'); + target.classList.remove('dragBottom'); + target.classList.remove('drag'); + + if (typeof (onDrop) === 'function') { + const area = event.nativeEvent.offsetY / target.clientHeight; + + const currentValue = this.currentDrag.getAttribute('value'); + + if (area < 0.25) { // 放在当前元素前面 + onDrop( + currentValue, // 拖动要素 + target.parentNode.parentNode.getAttribute('value'), // 新位置父级 + target.getAttribute('value'), // 新位置索引 + ); // 拖动, 父级, 索引 + } else if (area > 0.75) { // 放在当前元素后面 + onDrop( + currentValue, + target.parentNode.parentNode.getAttribute('value'), + target.nextSibling == null ? null : target.nextSibling.getAttribute('value'), // target.nextSibling为null,说明是最后一个位置 + ); + } else { // 成为该元素子级 + onDrop( + currentValue, + target.getAttribute('value'), + null, + ); + } + } + } +} + +Tree.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + data: PropTypes.array, + selected: PropTypes.string, + onExpand: PropTypes.func, + onSelect: PropTypes.func, + onDoubleClick: PropTypes.func, + onDrop: PropTypes.func, +}; + +Tree.defaultProps = { + className: null, + style: null, + data: [], + selected: null, + onExpand: null, + onSelect: null, + onDoubleClick: null, + onDrop: null, +}; + +export default Tree; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/tree/css/Tree.css b/ShadowEditor.Web/src/ui/tree/css/Tree.css new file mode 100644 index 00000000..5360945e --- /dev/null +++ b/ShadowEditor.Web/src/ui/tree/css/Tree.css @@ -0,0 +1,103 @@ +.Tree { + list-style: none; + margin: 0; + padding: 0; + font: 12px 'Microsoft YaHei'; + line-height: 18px; + --icon-plus: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGNjVEQzExQzc2NDYxMUU5OEMxN0UxQ0QyRDMwMjk0NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGNjVEQzExQjc2NDYxMUU5OEMxN0UxQ0QyRDMwMjk0NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GSEyxQAAAI1QTFRFCQsNxr6u39vSDhIWExgca4ehM0FNIyw1Q1Rla4egZYCZTmN11tHG2NPJKzdBZH6WPU5cX3iPSl5wWHCF9fXxFBofDREUUmd7z8i7VWyBRlhp6unjHCQqGSAmLTpF7e3nHygw5eHaXXaMJzE5CAoM0sy/5OHZ3NjP8PDswrio/f379/f1////AAAA////NGgXgAAAAC90Uk5T/////////////////////////////////////////////////////////////wBapTj3AAAAWUlEQVR42iTBBxKCQBAEwAExIYiIBFGScIFd9/7/POqKbrhjnBf18+Jw/e9OyIhIiPiOHzML2+mM90u88IubtYN8tCnRapNKopoDHipYPcD189hF1eI2AQYAwn4J7uCjPfoAAAAASUVORK5CYII=); + --icon-minus: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowRTM3OTM2RDc2NDcxMUU5OUYyREZCNzdBMzZGQTU0QSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowRTM3OTM2Qzc2NDcxMUU5OUYyREZCNzdBMzZGQTU0QSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+7URHdgAAAHtQTFRFDxMW9fXxJC01KzdBJC42XHWLTWFzXHSLb4yoTWF0DhIWdJSw6unjVWt/KzhBNEFOVGt/xr6uz8i7dJOvNEJO5eHab42n39vSFRof7e3nHCMqY32W0sy/5OHZ3NjPPExaaoaf8PDs2NPJwrio/f379/f1AAAA////////MX4KXQAAACl0Uk5T/////////////////////////////////////////////////////wBS9CCHAAAATUlEQVR42iTBBRKAIAAEwLO7i1ZQlP+/0EF34ar84ElfOEzPb8d6feyMw3p6icCD29tCZFpTWkvF0EhljBEjQSqG0yvh9q6NGYF7BRgAle0Iqns528wAAAAASUVORK5CYII=); + --icon-node: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2MTQyNjRFRDc2NDYxMUU5QTdGRjlBOUM1MTgxQUEyNCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MTQyNjRFQzc2NDYxMUU5QTdGRjlBOUM1MTgxQUEyNCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+2JwdnQAAAUdQTFRF2tra39/f1tbWysrK49zf8vLy3d3dxsbG7OzstbCyz8/P4+Hiz8nMzc3NvL282NjYiIeI+/v78Ors4dvd4dvempaXyMjI1tPV8vDxwry/t7K0xMLD3Nzci4eJycnJ3NXY4uLi8/Pzd3d31tHTcW5vz87Pzs7O0s7QsbGx2dPW6OLl6efo9PPz8ertpqam493gu7u7x8fH4tvegn6A3dfZxcXFpqKjycPG29TX5N3gwcHB1M7R1c/R2dnZ9fT08PDwxsDDvr290NDQwcDA6eLl2tPW19fX0dDQ9u/yxb/C4eHh8Ons19HU7ufq1dXV9Ozvwbu+u7a4ysTG8/Hy29zb/Pz839jbsq2vgX1/5+bnx8HEpqGj0MrMrqiqvb293NbZmJiY8/LztrO0jIyM+ff4zs/O7u7u9O3w7ezt/f39/v7+////////roPGVgAAAG10Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AC221EsAAADFSURBVHjaYsgBgqwcbmEhQSCVwwDkZeZwi3j46ORkg7lZOdpG6WHiIEkgNytH1dPfWspcAqw4KycjQiUg0MFGPgWoGshNNIvmjIpxddGyy8lmABoULBnEo2npyxOZkwUymdVd2YLFO14aZFR2GoMfn6O+rkmSEz9HNgMvKwO7RrIoS3iCgZu9HAObKSMDM6exrIAeF6MVE9AoNeYQ9VilOAVeQ0WQRTkyYqnOTBzsTEDngh2ZY8vGwBWK8EKOFyNYOAcgwADsYjo583VUugAAAABJRU5ErkJggg==); + --folder-open: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAANCAMAAACXZR4WAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGN0MzMTAyMDc2NDUxMUU5OTAzM0U1RjFGODU1RURCQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGN0MzMTAxRjc2NDUxMUU5OTAzM0U1RjFGODU1RURCQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+orCXfgAAAJZQTFRFzJk0zc3NoG4Iy5gz+vv8yZYx//+lxcXFmmgCnGoEnmwG2r9spHIMvYol/dVwp3UPmWcByaAhtYIdsH4Yt7e3yJUw9/CJt4Qfxpoa/9eE4rNDo3EL0dHRrHoUs4Ebwo8qonAK4sFQ8OR6uoci17hA/+uEz6ImpXMN/+R//9t1/++JxMTE//iTxpoZ//+c//+Z////////7K5NWgAAADJ0Uk5T/////////////////////////////////////////////////////////////////wANUJjvAAAAgElEQVR42kzLVxLCMAxFUTk9oYXeW+gY2dL+N4ecGA9n9HXnCZgZIE7n7IEcESXJMQQg49DgspwcphLiLkgS+x1wSp/AjHLAP+fbVgNa/2FsD9dRG377BQ4rDYz27WXjTdkG8+xk13shQcrLa1Z17sLD6Z+iqixmSgIrHSj+CjAAb/seguUxx1gAAAAASUVORK5CYII=); + --folder-close: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAANCAMAAABBwMRzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpEQTA0NkVFNDc2NDUxMUU5ODVCQ0E0NDExMUZBOTUzQiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpEQTA0NkVFMzc2NDUxMUU5ODVCQ0E0NDExMUZBOTUzQiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+eRolvgAAAIFQTFRF09PTmWcBy5gzwo8q27dxnmwGs4EbuocitYIdvYol//+6zMzMnGoEsH4YyZYxoG4Io3ELp3UPqHYQrHoUt4QfwI0opXMNq3kTx5QvyJUw1tbWrnwWwMDAmmgCxZItzJk027dS/9Rv//iT//+c/9t1/+R//++Jy8vL//////+Z////MJO0LgAAACt0Uk5T////////////////////////////////////////////////////////ACPJp9AAAABxSURBVHjaTM5XEoJAEEXRR44CYk4MmHpe73+BYpV2cf7u34Wqc2Fz0h+o896XZfRvF49fcZFt83SlCP3C5oyGD8N1hSMXOsHAu2EriPg2TAQ7vgxrQcGn4U2QcTIMKuh8cTn0+65N6uAKzItioB8BBgD8YxgHc9UPOwAAAABJRU5ErkJggg==); + overflow-y: auto; +} + +.Tree .node { + background: #fff; +} + +.Tree .node.selected { + background: #34495e; +} + +.Tree .node.drag { + border: 1px dashed #999; +} + +.Tree .node.dragTop { + border-top: 1px dashed #999; +} + +.Tree .node.dragBottom { + border-bottom: 1px dashed #999; +} + +.Tree .node>i { + display: inline-block; + vertical-align: middle; +} + +.Tree .node>i.expand { + width: 9px; + height: 9px; +} + +.Tree .node>i.expand.plus { + background: var(--icon-plus); +} + +.Tree .node>i.expand.minus { + background: var(--icon-minus); +} + +.Tree .node>i.type { + margin-left: 4px; +} + +.Tree .node>i.type.node { + width: 14px; + height: 15px; + margin-right: 2px; + background: var(--icon-node); +} + +.Tree .node>i.type.open { + width: 16px; + height: 13px; + background: var(--folder-open); +} + +.Tree .node>i.type.close { + width: 15px; + height: 13px; + margin-right: 1px; + background: var(--folder-close); +} + +.Tree .node>.CheckBox { + margin: 2px -2px 2px 4px; + vertical-align: middle; +} + +.Tree .node>a { + color: #555; + margin-left: 4px; + text-decoration: none; + vertical-align: middle; + pointer-events: none; +} + +.Tree .node.selected>a { + color: #fff; +} + +.Tree .node>.sub { + margin: 0; + padding: 0 0 0 16px; + background: #fff; + list-style: none; +} + +.Tree .node>.sub.collpase { + display: none; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/Alert.jsx b/ShadowEditor.Web/src/ui/window/Alert.jsx new file mode 100644 index 00000000..fe56e4c6 --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/Alert.jsx @@ -0,0 +1,71 @@ +import './css/Alert.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +import Window from './Window.jsx'; +import Content from '../common/Content.jsx'; +import Buttons from '../common/Buttons.jsx'; +import Button from '../form/Button.jsx'; + +/** + * 提示框 + */ +class Alert extends React.Component { + constructor(props) { + super(props); + + this.handleOK = this.handleOK.bind(this, props.onOK); + this.handleClose = this.handleClose.bind(this, props.onClose); + } + + render() { + const { className, style, title, children, hidden, mask, okText } = this.props; + + return ; + } + + handleOK(onOK, event) { + onOK && onOK(event); + } + + handleClose(onClose, event) { + onClose && onClose(event); + } +} + +Alert.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + title: PropTypes.string, + children: PropTypes.node, + hidden: PropTypes.bool, + mask: PropTypes.bool, + okText: PropTypes.string, + onOK: PropTypes.func, + onClose: PropTypes.func, +}; + +Alert.defaultProps = { + className: null, + style: null, + title: 'Message', + children: null, + hidden: false, + mask: false, + okText: 'OK', + onOK: null, + onClose: null, +}; + +export default Alert; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/Confirm.jsx b/ShadowEditor.Web/src/ui/window/Confirm.jsx new file mode 100644 index 00000000..d6d40ca2 --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/Confirm.jsx @@ -0,0 +1,81 @@ +import './css/Confirm.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +import Window from './Window.jsx'; +import Content from '../common/Content.jsx'; +import Buttons from '../common/Buttons.jsx'; +import Button from '../form/Button.jsx'; + +/** + * 询问框 + */ +class Confirm extends React.Component { + constructor(props) { + super(props); + + this.handleOK = this.handleOK.bind(this, props.onOK); + this.handleCancel = this.handleCancel.bind(this, props.onCancel); + this.handleClose = this.handleClose.bind(this, props.onClose); + } + + render() { + const { className, style, title, children, hidden, mask, okText, cancelText } = this.props; + + return ; + } + + handleOK(onOK, event) { + onOK && onOK(event); + } + + handleCancel(onCancel, event) { + onCancel && onCancel(event); + } + + handleClose(onClose, event) { + onClose && onClose(event); + } +} + +Confirm.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + title: PropTypes.string, + children: PropTypes.node, + hidden: PropTypes.bool, + mask: PropTypes.bool, + okText: PropTypes.string, + cancelText: PropTypes.string, + onOK: PropTypes.func, + onCancel: PropTypes.func, + onClose: PropTypes.func, +}; + +Confirm.defaultProps = { + className: null, + style: null, + title: 'Confirm', + children: null, + hidden: false, + mask: false, + okText: 'OK', + cancelText: 'Cancel', + onOK: null, + onCancel: null, + onClose: null, +}; + +export default Confirm; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/Prompt.jsx b/ShadowEditor.Web/src/ui/window/Prompt.jsx new file mode 100644 index 00000000..e5e71ccc --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/Prompt.jsx @@ -0,0 +1,86 @@ +import './css/Prompt.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +import Window from './Window.jsx'; +import Content from '../common/Content.jsx'; +import Input from '../form/Input.jsx'; +import Buttons from '../common/Buttons.jsx'; +import Button from '../form/Button.jsx'; + +/** + * 弹窗输入框 + */ +class Prompt extends React.Component { + constructor(props) { + super(props); + + this.state = { + value: props.value, + }; + + this.handleOK = this.handleOK.bind(this, props.onOK); + this.handleClose = this.handleClose.bind(this, props.onClose); + this.handleChange = this.handleChange.bind(this); + } + + render() { + const { className, style, title, content, hidden, mask, okText } = this.props; + + return ; + } + + handleOK(onOK, event) { + onOK && onOK(this.state.value, event); + } + + handleClose(onClose, event) { + onClose && onClose(event); + } + + handleChange(value) { + this.setState({ value }); + } +} + +Prompt.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + title: PropTypes.string, + content: PropTypes.node, + value: PropTypes.string, + hidden: PropTypes.bool, + mask: PropTypes.bool, + okText: PropTypes.string, + onOK: PropTypes.func, + onClose: PropTypes.func, +}; + +Prompt.defaultProps = { + className: null, + style: null, + title: 'Prompt', + content: null, + value: '', + hidden: false, + mask: false, + okText: 'OK', + onOK: null, + onClose: null, +}; + +export default Prompt; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/Toast.jsx b/ShadowEditor.Web/src/ui/window/Toast.jsx new file mode 100644 index 00000000..a549e19a --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/Toast.jsx @@ -0,0 +1,36 @@ +import './css/Toast.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +/** + * 提示窗 + */ +class Toast extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { className, style, children } = this.props; + + return
    +
    + {children} +
    +
    ; + } +} + +Toast.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, +}; + +Toast.defaultProps = { + className: null, + style: null, + children: null, +}; + +export default Toast; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/Window.jsx b/ShadowEditor.Web/src/ui/window/Window.jsx new file mode 100644 index 00000000..77eef58c --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/Window.jsx @@ -0,0 +1,138 @@ +import './css/Window.css'; +import classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; + +import Content from '../common/Content.jsx'; +import Buttons from '../common/Buttons.jsx'; + +/** + * 窗口 + */ +class Window extends React.Component { + constructor(props) { + super(props); + + this.dom = React.createRef(); + + this.isDown = false; + this.offsetX = 0; + this.offsetY = 0; + + this.handleMouseDown = this.handleMouseDown.bind(this); + this.handleMouseMove = this.handleMouseMove.bind(this); + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleClose = this.handleClose.bind(this, props.onClose); + } + + render() { + const { className, style, title, children, hidden, mask } = this.props; + + let _children = null; + + if (children && Array.isArray(children)) { + _children = children; + } else if (children) { + _children = [children]; + } + + const content = _children.filter(n => { + return n.type === Content; + })[0]; + + const buttons = _children.filter(n => { + return n.type === Buttons; + })[0]; + + return ; + } + + handleMouseDown(event) { + this.isDown = true; + + var dom = this.dom.current; + var left = dom.style.left === '' ? 0 : parseInt(dom.style.left.replace('px', '')); + var top = dom.style.top === '' ? 0 : parseInt(dom.style.top.replace('px', '')); + + this.offsetX = event.clientX - left; + this.offsetY = event.clientY - top; + } + + handleMouseMove(event) { + if (!this.isDown) { + return; + } + + var dx = event.clientX - this.offsetX; + var dy = event.clientY - this.offsetY; + + var dom = this.dom.current; + dom.style.left = `${dx}px`; + dom.style.top = `${dy}px`; + } + + handleMouseUp(event) { + this.isDown = false; + this.offsetX = 0; + this.offsetY = 0; + } + + handleClose(onClose, event) { + onClose && onClose(event); + } + + componentDidMount() { + document.body.addEventListener('mousemove', this.handleMouseMove); + document.body.addEventListener('mouseup', this.handleMouseUp); + } + + componentWillUnmount() { + document.body.removeEventListener('mousemove', this.handleMouseMove); + document.body.removeEventListener('mouseup', this.handleMouseUp); + } +} + +Window.show = function () { + +} + +Window.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + title: PropTypes.string, + children: PropTypes.node, + hidden: PropTypes.bool, + mask: PropTypes.bool, + onClose: PropTypes.func, +}; + +Window.defaultProps = { + className: null, + style: null, + title: 'Window', + children: null, + hidden: false, + mask: true, + onClose: null, +}; + +export default Window; \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/css/Alert.css b/ShadowEditor.Web/src/ui/window/css/Alert.css new file mode 100644 index 00000000..c9bbad98 --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/css/Alert.css @@ -0,0 +1,4 @@ +.Window.Alert { + width: 320px; + height: 180px; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/css/Confirm.css b/ShadowEditor.Web/src/ui/window/css/Confirm.css new file mode 100644 index 00000000..fc3ba3e4 --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/css/Confirm.css @@ -0,0 +1,4 @@ +.Window.Confirm { + width: 320px; + height: 180px; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/css/Prompt.css b/ShadowEditor.Web/src/ui/window/css/Prompt.css new file mode 100644 index 00000000..9d974e14 --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/css/Prompt.css @@ -0,0 +1,9 @@ +.Window.Prompt { + width: 320px; + height: 180px; +} + +.Window.Prompt>.wrap>.content>.Input { + margin-left: 6px; + vertical-align: middle; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/css/Toast.css b/ShadowEditor.Web/src/ui/window/css/Toast.css new file mode 100644 index 00000000..85b7c40a --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/css/Toast.css @@ -0,0 +1,22 @@ +.ToastMark { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 1300; + pointer-events: none; +} + +.Toast { + color: #fff; + background-color: rgba(0, 0, 0, 0.5); + padding: 16px 24px; + border-radius: 3px; + box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.14); + display: inline-block; + pointer-events: none; +} \ No newline at end of file diff --git a/ShadowEditor.Web/src/ui/window/css/Window.css b/ShadowEditor.Web/src/ui/window/css/Window.css new file mode 100644 index 00000000..71909b7c --- /dev/null +++ b/ShadowEditor.Web/src/ui/window/css/Window.css @@ -0,0 +1,105 @@ +.WindowMask { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 900; +} + +.WindowMask.hidden { + display: none; +} + +.WindowMask.mask { + background-color: rgba(0, 0, 0, 0.6); + pointer-events: all; + z-index: 1000; +} + +.Window { + position: relative; + width: 600px; + height: 400px; + padding: 0px 5px 5px; + background: rgb(7, 97, 134); + box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 10px 0px; + box-sizing: border-box; + pointer-events: all; + z-index: 200; +} + +.Window .wrap { + position: relative; + width: 100%; + height: 100%; + background: #fff; +} + +.Window .wrap .title { + font-size: 14px; + line-height: 24px; + color: #fff; + background: rgb(7, 97, 134); + text-align: left; + display: block; + cursor: move; + white-space: nowrap; + overflow: hidden; +} + +.Window .wrap .title .controls { + position: absolute; + left: 0; + top: 0; + right: 4px; + text-align: right; + white-space: nowrap; + user-select: none; +} + +.Window .wrap .title .controls .icon { + width: 16px; + height: 24px; + margin-right: 4px; + vertical-align: top; + display: inline-block; + cursor: pointer; +} + +.Window .wrap .content { + position: absolute; + left: 20px; + right: 20px; + top: 44px; + bottom: 52px; + font-size: 12px; + overflow: hidden; +} + +.Window .wrap .buttons { + position: absolute; + left: 0; + right: 0; + bottom: 0; + background: rgb(218, 236, 244); + text-align: right; + white-space: nowrap; + overflow: hidden; +} + +.Window .wrap .buttons .button-wrap { + position: relative; + height: 24px; + margin: 4px; + padding: 0; + border: none; + display: inline-block; + text-align: left; + vertical-align: middle; + box-sizing: border-box; +} \ No newline at end of file