зеркало из https://github.com/mozilla/Spoke.git
Fix up add component flow and no component selected UX.
This commit is contained in:
Родитель
fd89f4896e
Коммит
b104b87c98
|
@ -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;
|
||||
|
|
|
@ -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";
|
Загрузка…
Ссылка в новой задаче