diff --git a/src/components/DraggableFile.js b/src/components/DraggableFile.js
new file mode 100644
index 00000000..50184e0d
--- /dev/null
+++ b/src/components/DraggableFile.js
@@ -0,0 +1,43 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { DragSource } from "react-dnd";
+import Icon from "../components/Icon";
+import fileIcon from "../assets/file-icon.svg";
+import folderIcon from "../assets/folder-icon.svg";
+import styles from "./DraggableFile.scss";
+import iconStyles from "../components/Icon.scss";
+
+function DraggableFile({ file, selected, onClick, connectDragSource }) {
+ return connectDragSource(
+
+ onClick(e, file)}
+ className={iconStyles.small}
+ />
+
+ );
+}
+
+DraggableFile.propTypes = {
+ file: PropTypes.object,
+ selected: PropTypes.bool,
+ onClick: PropTypes.func,
+ connectDragSource: PropTypes.func
+};
+
+export default DragSource(
+ "file",
+ {
+ beginDrag({ file }) {
+ return { file };
+ }
+ },
+ (connect, monitor) => ({
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging()
+ })
+)(DraggableFile);
diff --git a/src/components/DraggableFile.scss b/src/components/DraggableFile.scss
new file mode 100644
index 00000000..ba5a99d1
--- /dev/null
+++ b/src/components/DraggableFile.scss
@@ -0,0 +1,3 @@
+:local(.draggableFile) {
+ display: flex;
+}
\ No newline at end of file
diff --git a/src/components/FileDropTarget.js b/src/components/FileDropTarget.js
new file mode 100644
index 00000000..17e817ea
--- /dev/null
+++ b/src/components/FileDropTarget.js
@@ -0,0 +1,33 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { DropTarget } from "react-dnd";
+import styles from "./FileDropTarget.scss";
+
+function FileDropTarget({ connectDropTarget, children }) {
+ return connectDropTarget({children}
);
+}
+
+FileDropTarget.propTypes = {
+ connectDropTarget: PropTypes.func,
+ children: PropTypes.node
+};
+
+export default DropTarget(
+ "file",
+ {
+ drop(props, monitor) {
+ const item = monitor.getItem();
+
+ if (props.onDropFile) {
+ props.onDropFile(item.file);
+ }
+
+ return item;
+ }
+ },
+ (connect, monitor) => ({
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop()
+ })
+)(FileDropTarget);
diff --git a/src/components/FileDropTarget.scss b/src/components/FileDropTarget.scss
new file mode 100644
index 00000000..2e6a07ed
--- /dev/null
+++ b/src/components/FileDropTarget.scss
@@ -0,0 +1,3 @@
+:local(.fileDropTarget) {
+ display: flex;
+}
\ No newline at end of file
diff --git a/src/components/Icon.js b/src/components/Icon.js
new file mode 100644
index 00000000..0376bcec
--- /dev/null
+++ b/src/components/Icon.js
@@ -0,0 +1,25 @@
+import React from "react";
+import PropTypes from "prop-types";
+import classNames from "classnames";
+import styles from "./Icon.scss";
+
+export default function Icon({ name, src, selected, onClick, className }) {
+ const fullClassName = classNames(styles.icon, className, {
+ [styles.selected]: selected
+ });
+
+ return (
+
+
+
{name}
+
+ );
+}
+
+Icon.propTypes = {
+ name: PropTypes.string.isRequired,
+ src: PropTypes.string.isRequired,
+ selected: PropTypes.bool,
+ onClick: PropTypes.func,
+ className: PropTypes.string
+};
diff --git a/src/components/Icon.scss b/src/components/Icon.scss
new file mode 100644
index 00000000..13224981
--- /dev/null
+++ b/src/components/Icon.scss
@@ -0,0 +1,46 @@
+@import "../theme";
+
+:local(.icon) {
+ display: flex;
+ flex-wrap: nowrap;
+ flex-direction: column;
+ padding: 25px;
+ align-items: center;
+ width: 180px;
+ height: 180px;
+
+ &:local(.selected) {
+ background-color: $selected;
+ }
+
+ :local(.image) {
+ width: 150px;
+ height: 150px;
+ }
+
+ :local(.name) {
+ margin-top: 12px;
+ text-overflow: ellipsis;
+ word-wrap: break-word;
+ white-space: nowrap;
+ width: 100px;
+ overflow: hidden;
+ text-align: center;
+ }
+}
+
+:local(.small) {
+ padding: 8px;
+ width: 80px;
+ height: 80px;
+
+ :local(.image) {
+ width: 50px;
+ height: 50px;
+ }
+
+ :local(.name) {
+ margin-top: 8px;
+ font-size: 11px;
+ }
+}
\ No newline at end of file
diff --git a/src/components/IconGrid.js b/src/components/IconGrid.js
index 9a267b7d..6178cfd1 100644
--- a/src/components/IconGrid.js
+++ b/src/components/IconGrid.js
@@ -1,36 +1,11 @@
import React from "react";
import PropTypes from "prop-types";
-import classNames from "classnames";
import styles from "./IconGrid.scss";
-export default function IconGrid({ icons, onSelect, small }) {
- const containerClassName = classNames(styles.iconGrid, { [styles.small]: small });
-
- return (
-
- {icons.map(icon => {
- const className = classNames(styles.item, { [styles.selected]: icon.selected });
-
- return (
-
onSelect(icon, e)} className={className}>
-
-
{icon.name}
-
- );
- })}
-
- );
+export default function IconGrid({ children }) {
+ return {children}
;
}
IconGrid.propTypes = {
- small: PropTypes.bool,
- icons: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- src: PropTypes.string.isRequired,
- selected: PropTypes.bool
- })
- ).isRequired,
- onSelect: PropTypes.func.isRequired
+ children: PropTypes.arrayOf(PropTypes.element)
};
diff --git a/src/components/IconGrid.scss b/src/components/IconGrid.scss
index a1f71ec3..21fa1094 100644
--- a/src/components/IconGrid.scss
+++ b/src/components/IconGrid.scss
@@ -1,41 +1,6 @@
-@import "../theme";
-
:local(.iconGrid) {
display: flex;
flex: 1;
-
- :local(.item) {
- width: 200px;
- height: 200px;
- display: flex;
- flex-wrap: wrap;
- flex-direction: column;
- padding: 25px;
- align-items: center;
- justify-content: space-between;
-
- &:local(.selected) {
- background-color: $selected;
- }
-
- :local(.icon) {
- width: 175px;
- height: 175px;
- }
- }
-
- &:local(.small) {
- :local(.item) {
- width: 120px;
- height: 120px;
- padding: 12px;
- }
-
- :local(.icon) {
- width: 100px;
- height: 100px;
- }
- }
}
diff --git a/src/components/InputGroup.js b/src/components/InputGroup.js
index af351115..6b12e226 100644
--- a/src/components/InputGroup.js
+++ b/src/components/InputGroup.js
@@ -13,5 +13,5 @@ export default function InputGroup({ name, children }) {
InputGroup.propTypes = {
name: PropTypes.string,
- children: PropTypes.arrayOf(PropTypes.element)
+ children: PropTypes.any
};
diff --git a/src/components/ProjectModal.js b/src/components/ProjectModal.js
index c92b6c8b..6a9a59eb 100644
--- a/src/components/ProjectModal.js
+++ b/src/components/ProjectModal.js
@@ -3,40 +3,30 @@ import PropTypes from "prop-types";
import styles from "./ProjectModal.scss";
import Header from "./Header";
import IconGrid from "./IconGrid";
+import Icon from "./Icon";
import TabNavigation from "./TabNavigation";
+import Tab from "./Tab";
import NativeFileInput from "./NativeFileInput";
import defaultThumbnail from "../assets/default-thumbnail.png";
-function onSelectIcon(icon, event, projects, onSelectProject) {
- const project = projects.find(({ uri }) => uri === icon.id);
- onSelectProject(project, event);
-}
-
export default function ProjectModal({ tab, projects, onSelectProject, onChangeTab, onOpenProject }) {
- const tabs = [
- {
- name: "Recent Projects",
- selected: tab === "projects",
- onClick: () => onChangeTab("projects")
- },
- {
- name: "Templates",
- selected: tab === "templates",
- onClick: () => onChangeTab("templates")
- }
- ];
-
- const icons = projects.map(project => ({
- id: project.uri,
- src: project.icon || defaultThumbnail,
- name: project.name
- }));
-
return (
-
- onSelectIcon(icon, event, projects, onSelectProject)} />
+
+ onChangeTab("projects")} />
+ onChangeTab("templates")} />
+
+
+ {projects.map(project => (
+ onSelectProject(project)}
+ />
+ ))}
+
);
diff --git a/src/components/Tab.js b/src/components/Tab.js
new file mode 100644
index 00000000..431d7c88
--- /dev/null
+++ b/src/components/Tab.js
@@ -0,0 +1,22 @@
+import React from "react";
+import PropTypes from "prop-types";
+import styles from "./Tab.scss";
+import classNames from "classnames";
+
+export default function Tab({ name, selected, onClick }) {
+ const className = classNames(styles.tab, {
+ [styles.selected]: selected
+ });
+
+ return (
+
+ {name}
+
+ );
+}
+
+Tab.propTypes = {
+ name: PropTypes.string,
+ selected: PropTypes.bool,
+ onClick: PropTypes.func
+};
diff --git a/src/components/Tab.scss b/src/components/Tab.scss
new file mode 100644
index 00000000..46e4ea89
--- /dev/null
+++ b/src/components/Tab.scss
@@ -0,0 +1,26 @@
+@import "../theme";
+
+:local(.tab) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 100px;
+ padding: 0 8px;
+ background-color: $background;
+ border: 1px solid $border;
+ cursor: pointer;
+
+ &:first-child {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ }
+
+ &:last-child {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+
+ &:local(.selected) {
+ background-color: $selected;
+ }
+}
\ No newline at end of file
diff --git a/src/components/TabNavigation.js b/src/components/TabNavigation.js
index bec91e32..f4a29f9a 100644
--- a/src/components/TabNavigation.js
+++ b/src/components/TabNavigation.js
@@ -1,44 +1,11 @@
import React from "react";
import PropTypes from "prop-types";
import styles from "./TabNavigation.scss";
-import classNames from "classnames";
-function Tab({ children, selected, onClick }) {
- const className = classNames(styles.tab, {
- [styles.selected]: selected
- });
-
- return (
-
- {children}
-
- );
-}
-
-Tab.propTypes = {
- children: PropTypes.string,
- selected: PropTypes.bool,
- onClick: PropTypes.func
-};
-
-export default function TabNavigation({ tabs }) {
- return (
-
- {tabs.map(({ selected, name, onClick }) => (
-
- {name}
-
- ))}
-
- );
+export default function TabNavigation({ children }) {
+ return {children}
;
}
TabNavigation.propTypes = {
- tabs: PropTypes.arrayOf(
- PropTypes.shape({
- selected: PropTypes.bool,
- onClick: PropTypes.func,
- name: PropTypes.string
- })
- )
+ children: PropTypes.arrayOf(PropTypes.element)
};
diff --git a/src/components/TabNavigation.scss b/src/components/TabNavigation.scss
index 58cf2d4c..c7e70af9 100644
--- a/src/components/TabNavigation.scss
+++ b/src/components/TabNavigation.scss
@@ -1,32 +1,6 @@
-@import "../theme";
-
:local(.tabNavigation) {
display: flex;
justify-content: center;
height: 24px;
}
-:local(.tab) {
- display: flex;
- align-items: center;
- justify-content: center;
- min-width: 100px;
- padding: 0 8px;
- background-color: $background;
- border: 1px solid $border;
- cursor: pointer;
-
- &:first-child {
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
- }
-
- &:last-child {
- border-top-right-radius: 3px;
- border-bottom-right-radius: 3px;
- }
-
- &:local(.selected) {
- background-color: $selected;
- }
-}
\ No newline at end of file
diff --git a/src/containers/AssetExplorerPanelContainer.js b/src/containers/AssetExplorerPanelContainer.js
index b8a55247..d4659f59 100644
--- a/src/containers/AssetExplorerPanelContainer.js
+++ b/src/containers/AssetExplorerPanelContainer.js
@@ -5,10 +5,9 @@ import "../vendor/react-ui-tree/index.scss";
import classNames from "classnames";
import { withProject } from "./ProjectContext";
import IconGrid from "../components/IconGrid";
-import fileIcon from "../assets/file-icon.svg";
-import folderIcon from "../assets/folder-icon.svg";
import { openFile } from "../api";
import styles from "./AssetExplorerPanelContainer.scss";
+import DraggableFile from "../components/DraggableFile";
class AssetExplorerPanelContainer extends Component {
static propTypes = {
@@ -70,7 +69,7 @@ class AssetExplorerPanelContainer extends Component {
});
};
- onSelectIcon = ({ file }) => {
+ onClickFile = (e, file) => {
if (this.state.singleClickedFile && file.uri === this.state.singleClickedFile.uri) {
if (file.isDirectory) {
this.setState({ selectedDirectory: file });
@@ -117,13 +116,6 @@ class AssetExplorerPanelContainer extends Component {
const selectedDirectory = this.state.selectedDirectory || this.state.tree;
const files = selectedDirectory.files || [];
const selectedFile = this.state.selectedFile;
- const icons = files.map(file => ({
- id: file.uri,
- name: file.name,
- src: file.isDirectory ? folderIcon : fileIcon,
- selected: selectedFile && selectedFile.uri === file.uri,
- file
- }));
return (
@@ -138,7 +130,16 @@ class AssetExplorerPanelContainer extends Component {
/>
-
+
+ {files.map(file => (
+
+ ))}
+
);
diff --git a/src/containers/AssetExplorerPanelContainer.scss b/src/containers/AssetExplorerPanelContainer.scss
index 78f44610..741750be 100644
--- a/src/containers/AssetExplorerPanelContainer.scss
+++ b/src/containers/AssetExplorerPanelContainer.scss
@@ -1,7 +1,7 @@
:local(.assetExplorerPanelContainer) {
display: flex;
flex-direction: row;
- height: 100%;
+ flex: 1;
}
:local(.leftColumn) {
diff --git a/src/containers/EditorContainer.js b/src/containers/EditorContainer.js
index 510db39b..8a209694 100644
--- a/src/containers/EditorContainer.js
+++ b/src/containers/EditorContainer.js
@@ -1,5 +1,7 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
+import { DragDropContextProvider } from "react-dnd";
+import HTML5Backend from "react-dnd-html5-backend";
import Editor from "../components/Editor";
import ProjectModalContainer from "./ProjectModalContainer";
import NewProjectModalContainer from "./NewProjectModalContainer";
@@ -172,13 +174,15 @@ class EditorContainer extends Component {
return (
-
+
+
+
);
}
diff --git a/src/containers/ViewportPanelContainer.js b/src/containers/ViewportPanelContainer.js
index 532e1288..c81e53d1 100644
--- a/src/containers/ViewportPanelContainer.js
+++ b/src/containers/ViewportPanelContainer.js
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import Viewport from "../components/Viewport";
import { withEditor } from "./EditorContext";
import styles from "./ViewportPanelContainer.scss";
+import FileDropTarget from "../components/FileDropTarget";
class ViewportPanelContainer extends Component {
static propTypes = {
@@ -19,10 +20,18 @@ class ViewportPanelContainer extends Component {
this.props.editor.createRenderer(this.canvasRef.current);
}
+ onDropFile = file => {
+ if (file.ext === "gltf") {
+ this.props.editor.loadGLTF(file.uri);
+ }
+ };
+
render() {
return (
-
+
+
+
);
}
diff --git a/src/containers/ViewportPanelContainer.scss b/src/containers/ViewportPanelContainer.scss
index 2ef7aa37..09355fbc 100644
--- a/src/containers/ViewportPanelContainer.scss
+++ b/src/containers/ViewportPanelContainer.scss
@@ -1,4 +1,4 @@
:local(.viewportPanelContainer) {
display: flex;
flex: 1;
-}
\ No newline at end of file
+}
diff --git a/src/editor/Editor.js b/src/editor/Editor.js
index d59bf2b6..4ef5a02b 100644
--- a/src/editor/Editor.js
+++ b/src/editor/Editor.js
@@ -97,6 +97,8 @@ export default class Editor {
this.helpers = {};
this.viewport = null;
+
+ this.gltfLoader = new THREE.GLTFLoader();
}
onWindowResize = () => {
@@ -226,6 +228,12 @@ export default class Editor {
this.textures[texture.uuid] = texture;
}
+ loadGLTF(url) {
+ this.gltfLoader.load(url, ({ scene }) => {
+ this.addObject(scene);
+ });
+ }
+
//
addHelper = (function() {