Fix up add component flow and no component selected UX.

This commit is contained in:
Robert Long 2018-06-28 14:45:06 -07:00
Родитель fd89f4896e
Коммит b104b87c98
10 изменённых файлов: 190 добавлений и 66 удалений

Просмотреть файл

@ -1,18 +1,19 @@
import React, { Component } from "react";
import React from "react";
import PropTypes from "prop-types";
import "./Button.scss";
import classNames from "classnames";
import styles from "./Button.scss";
export default class Button extends Component {
render() {
return (
<button className="Button" onClick={this.props.onClick}>
{this.props.children}
</button>
);
}
export default function Button({ className, children, ...props }) {
const fullClassName = classNames(styles.button, className);
return (
<button className={fullClassName} {...props}>
{children}
</button>
);
}
Button.propTypes = {
onClick: PropTypes.func,
className: PropTypes.string,
children: PropTypes.node
};

Просмотреть файл

@ -1,6 +1,6 @@
@import "../theme";
.Button {
:local(.button) {
display: flex;
border: none;
background: $selected;

Просмотреть файл

@ -1,3 +1,5 @@
@import "../theme";
:local(.propertyGroup) {
display: flex;
flex-direction: column;
@ -7,11 +9,19 @@
display: flex;
flex-direction: row;
align-items: center;
padding: 6px 8px;
border-top: 1px solid $border;
border-bottom: 1px solid $border;
}
:local(.content) {
padding: 0 8px;
display: flex;
flex-direction: column;
width: 100%;
& > * {
margin-top: 8px;
}
}
}

Просмотреть файл

@ -10,14 +10,15 @@ import SetGLTFComponentPropertyCommand from "../editor/commands/SetGLTFComponent
class GLTFComponentsContainer extends Component {
static propTypes = {
editor: PropTypes.object,
node: PropTypes.object,
components: PropTypes.array
node: PropTypes.object.isRequired,
components: PropTypes.array.isRequired
};
onChange = (componentName, propertyName, value) => {
this.props.editor.execute(new SetGLTFComponentPropertyCommand(this.props.node, componentName, propertyName, value));
};
render() {
if (!this.props.components) return null;
// Generate property groups for each component and property editors for each property.
return this.props.components.map(component => {
const componentDefinition = this.props.editor.gltfComponents.get(component.name);

Просмотреть файл

@ -10,6 +10,11 @@ import SetValueCommand from "../editor/commands/SetValueCommand";
import SetPositionCommand from "../editor/commands/SetPositionCommand";
import SetRotationCommand from "../editor/commands/SetRotationCommand";
import SetScaleCommand from "../editor/commands/SetScaleCommand";
import AddGLTFComponentCommand from "../editor/commands/AddGLTFComponentCommand";
import { getDisplayName } from "../editor/gltf-components";
import Select from "react-select";
import "../vendor/react-select/index.scss";
import styles from "./NodePropertyGroupContainer.scss";
const RAD2DEG = THREE.Math.RAD2DEG;
const DEG2RAD = THREE.Math.DEG2RAD;
@ -17,47 +22,56 @@ const DEG2RAD = THREE.Math.DEG2RAD;
class NodePropertyGroupContainer extends Component {
static propTypes = {
editor: PropTypes.object,
node: PropTypes.object
node: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.state = { node: null };
this.positionVector = new THREE.Vector3();
this.rotationEuler = new THREE.Euler();
this.degreesVector = new THREE.Vector3();
this.scaleVector = new THREE.Vector3();
}
updateName = e => {
if (!this.props.node) return;
this.props.editor.execute(new SetValueCommand(this.props.node, "name", e.target.value));
};
updatePosition = newPosition => {
if (!this.props.node) return;
this.props.editor.execute(new SetPositionCommand(this.props.node, this.positionVector.copy(newPosition)));
};
updateRotation = newRotation => {
if (!this.props.node) return;
this.rotationEuler.set(newRotation.x * DEG2RAD, newRotation.y * DEG2RAD, newRotation.z * DEG2RAD);
this.props.editor.execute(new SetRotationCommand(this.props.node, this.rotationEuler));
};
updateScale = newScale => {
if (!this.props.node) return;
this.props.editor.execute(new SetScaleCommand(this.props.node, this.scaleVector.copy(newScale)));
};
onChangeComponent = ({ value }) => {
this.props.editor.execute(new AddGLTFComponentCommand(this.props.node, value));
};
render() {
let name = "";
let position = null;
let rotation = null;
let scale = null;
if (this.props.node) {
const { node } = this.props;
name = node.name;
position = node.position;
scale = node.scale;
const eulerRadians = node.rotation;
this.degreesVector.set(eulerRadians.x * RAD2DEG, eulerRadians.y * RAD2DEG, eulerRadians.z * RAD2DEG);
rotation = this.degreesVector;
const node = this.props.node;
const name = node.name;
const position = node.position;
const scale = node.scale;
const eulerRadians = node.rotation;
this.degreesVector.set(eulerRadians.x * RAD2DEG, eulerRadians.y * RAD2DEG, eulerRadians.z * RAD2DEG);
const rotation = this.degreesVector;
const gltfComponentOptions = [];
for (const [name] of this.props.editor.gltfComponents) {
gltfComponentOptions.push({
value: name,
label: getDisplayName(name)
});
}
return (
<PropertyGroup name="Node">
<InputGroup name="Name">
@ -72,6 +86,14 @@ class NodePropertyGroupContainer extends Component {
<InputGroup name="Scale">
<Vector3Input value={scale} onChange={this.updateScale} />
</InputGroup>
<div className={styles.addComponentContainer}>
<Select
placeholder="none"
className={styles.addComponentSelect}
options={gltfComponentOptions}
onChange={this.onChangeComponent}
/>
</div>
</PropertyGroup>
);
}

Просмотреть файл

@ -0,0 +1,13 @@
:local(.addComponentContainer) {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 16px !important;
}
:local(.addComponentSelect) {
display: flex;
flex: 1;
margin-right: 8px;
max-width: 300px;
}

Просмотреть файл

@ -4,61 +4,50 @@ import NodePropertyGroupContainer from "./NodePropertyGroupContainer";
import GLTFComponentsContainer from "./GLTFComponentsContainer";
import styles from "./PropertiesPanelContainer.scss";
import { withEditor } from "./EditorContext";
import AddGLTFComponentCommand from "../editor/commands/AddGLTFComponentCommand";
import { getDisplayName } from "../editor/gltf-components";
class PropertiesPanelContainer extends Component {
static propTypes = {
editor: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
currentComponent: "none",
node: null,
components: null
components: []
};
this.props.editor.signals.objectSelected.add(node =>
this.setState({
node,
components: node ? node.userData.MOZ_components : null
components: (node && node.userData.MOZ_component) || []
})
);
this.props.editor.signals.objectChanged.add(object => {
if (this.state.node === object) {
this.setState({ components: object.userData.MOZ_components });
if (this.state.node === object && object.userData.MOZ_components) {
this.setState({
components: object.userData.MOZ_components || []
});
}
});
}
addComponent = () => {
if (this.state.currentComponent === "none" || !this.state.node) return;
this.props.editor.execute(new AddGLTFComponentCommand(this.state.node, this.state.currentComponent));
};
render() {
const gltfComponentOptions = [];
this.props.editor.gltfComponents.forEach((component, name) => {
gltfComponentOptions.push(
<option key={name} value={name}>
{getDisplayName(name)}
</option>
);
});
const { node } = this.state;
if (!node) {
return (
<div className={styles.propertiesPanelContainer}>
<div className={styles.noNodeSelected}>No node selected</div>
</div>
);
}
return (
<div className={styles.propertiesPanelContainer}>
<NodePropertyGroupContainer node={node} />
<div>
<select
value={this.state.currentComponent}
onChange={e => this.setState({ currentComponent: e.target.value })}
>
<option value="none">Select a component</option>
{gltfComponentOptions}
</select>
<button enabled={node} onClick={this.addComponent}>
add
</button>
</div>
<GLTFComponentsContainer node={node} components={this.state.components} />
</div>
);

Просмотреть файл

@ -4,5 +4,11 @@
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
padding: 8px;
}
:local(.noNodeSelected) {
display: flex;
flex: 1;
justify-content: center;
align-items: center;
}

Просмотреть файл

@ -1,5 +1,5 @@
$background: #1d1f27;
$input-background: rgba(0, 0, 0, 0.2);
$input-background: #222;
$border: rgba(255, 255, 255, 0.05);
$panel: #282a2e;
$selected: #373b41;

82
src/client/vendor/react-select/index.scss поставляемый Normal file
Просмотреть файл

@ -0,0 +1,82 @@
@import "../../theme";
/**
* React Select
* ============
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
* MIT License: https://github.com/JedWatson/react-select
*/
// Variables
// ------------------------------
// control options
$select-input-bg: $input-background !default;
$select-input-bg-disabled: #f9f9f9 !default;
$select-input-bg-focus: $select-input-bg !default;
$select-input-border-color: $border !default;
$select-input-border-radius: 4px !default;
$select-input-border-focus: $blue !default; // blue
$select-input-box-shadow-focus: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px fade($select-input-border-focus, 50%) !default;
$select-input-border-width: 1px !default;
$select-input-height: 36px !default;
$select-input-internal-height: ($select-input-height - ($select-input-border-width * 2)) !default;
$select-input-placeholder: #aaa !default;
$select-text-color: $text !default;
$select-link-hover-color: $select-input-border-focus !default;
$select-input-hover-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06) !default;
$select-padding-vertical: 8px !default;
$select-padding-horizontal: 10px !default;
// menu options
$select-menu-zindex: 1000 !default;
$select-menu-max-height: 200px !default;
$select-menu-box-shadow: $select-input-hover-box-shadow !default;
$select-option-color: lighten($select-text-color, 20%) !default;
$select-option-bg: $select-input-bg !default;
$select-option-focused-color: $select-text-color !default;
$select-option-focused-bg: $selected !default; // pale blue
$select-option-selected-color: $select-text-color !default;
$select-option-selected-bg: $selected !default; // lightest blue
$select-option-disabled-color: lighten($select-text-color, 60%) !default;
$select-noresults-color: lighten($select-text-color, 40%) !default;
// clear "x" button
$select-clear-size: floor(($select-input-height / 2)) !default;
$select-clear-color: #999 !default;
$select-clear-hover-color: #D0021B !default; // red
$select-clear-width: ($select-input-internal-height / 2) !default;
// arrow indicator
$select-arrow-color: #999 !default;
$select-arrow-color-hover: #666 !default;
$select-arrow-width: 5px !default;
// loading indicator
$select-loading-size: 16px !default;
$select-loading-color: $select-text-color !default;
$select-loading-color-bg: $select-input-border-color !default;
// multi-select item
$select-item-border-radius: 2px !default;
$select-item-gutter: 5px !default;
$select-item-padding-vertical: 2px !default;
$select-item-padding-horizontal: 5px !default;
$select-item-font-size: .9em !default;
$select-item-color: $text !default;
$select-item-bg: $selected !default;
$select-item-border-color: darken($select-item-bg, 10%) !default;
$select-item-hover-color: darken($select-item-color, 5%) !default; // pale blue
$select-item-hover-bg: darken($select-item-bg, 5%) !default;
$select-item-disabled-color: #333 !default;
$select-item-disabled-bg: #fcfcfc !default;
$select-item-disabled-border-color: darken($select-item-disabled-bg, 10%) !default;
@import "~react-select/scss/control";
@import "~react-select/scss/menu";
@import "~react-select/scss/mixins";
@import "~react-select/scss/multi";
@import "~react-select/scss/spinner";