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() {