From f31570d5f57f4f60b9688afa8ebc220193284edb Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Wed, 29 May 2019 21:43:06 +0200 Subject: [PATCH] Refactored code so that new tools can now be added easily. --- src/actions/{ => quest-editor}/loadFile.ts | 18 +-- .../questEditor.ts} | 36 ++--- .../visibleQuestEntities.ts | 2 +- src/rendering/Renderer.ts | 6 +- .../{AppStateStore.ts => QuestEditorStore.ts} | 4 +- src/ui/ApplicationComponent.css | 11 +- src/ui/ApplicationComponent.tsx | 151 +++--------------- .../EntityInfoComponent.css | 0 .../EntityInfoComponent.tsx | 2 +- src/ui/quest-editor/QuestEditorComponent.css | 18 +++ src/ui/quest-editor/QuestEditorComponent.tsx | 149 +++++++++++++++++ .../{ => quest-editor}/QuestInfoComponent.css | 0 .../{ => quest-editor}/QuestInfoComponent.tsx | 2 +- .../{ => quest-editor}/RendererComponent.tsx | 4 +- 14 files changed, 229 insertions(+), 174 deletions(-) rename src/actions/{ => quest-editor}/loadFile.ts (86%) rename src/actions/{appState.ts => quest-editor/questEditor.ts} (54%) rename src/actions/{ => quest-editor}/visibleQuestEntities.ts (82%) rename src/stores/{AppStateStore.ts => QuestEditorStore.ts} (78%) rename src/ui/{ => quest-editor}/EntityInfoComponent.css (100%) rename src/ui/{ => quest-editor}/EntityInfoComponent.tsx (98%) create mode 100644 src/ui/quest-editor/QuestEditorComponent.css create mode 100644 src/ui/quest-editor/QuestEditorComponent.tsx rename src/ui/{ => quest-editor}/QuestInfoComponent.css (100%) rename src/ui/{ => quest-editor}/QuestInfoComponent.tsx (98%) rename src/ui/{ => quest-editor}/RendererComponent.tsx (92%) diff --git a/src/actions/loadFile.ts b/src/actions/quest-editor/loadFile.ts similarity index 86% rename from src/actions/loadFile.ts rename to src/actions/quest-editor/loadFile.ts index efe9323d..ff5a9d03 100644 --- a/src/actions/loadFile.ts +++ b/src/actions/quest-editor/loadFile.ts @@ -1,14 +1,14 @@ import { action } from 'mobx'; import { Object3D } from 'three'; -import { ArrayBufferCursor } from '../data/ArrayBufferCursor'; -import { getAreaSections } from '../data/loading/areas'; -import { getNpcGeometry, getObjectGeometry } from '../data/loading/entities'; -import { parseNj, parseXj } from '../data/parsing/ninja'; -import { parseQuest } from '../data/parsing/quest'; -import { AreaVariant, Section, Vec3, VisibleQuestEntity } from '../domain'; -import { createNpcMesh, createObjectMesh } from '../rendering/entities'; -import { createModelMesh } from '../rendering/models'; -import { setModel, setQuest } from './appState'; +import { ArrayBufferCursor } from '../../data/ArrayBufferCursor'; +import { getAreaSections } from '../../data/loading/areas'; +import { getNpcGeometry, getObjectGeometry } from '../../data/loading/entities'; +import { parseNj, parseXj } from '../../data/parsing/ninja'; +import { parseQuest } from '../../data/parsing/quest'; +import { AreaVariant, Section, Vec3, VisibleQuestEntity } from '../../domain'; +import { createNpcMesh, createObjectMesh } from '../../rendering/entities'; +import { createModelMesh } from '../../rendering/models'; +import { setModel, setQuest } from './questEditor'; export function loadFile(file: File) { const reader = new FileReader(); diff --git a/src/actions/appState.ts b/src/actions/quest-editor/questEditor.ts similarity index 54% rename from src/actions/appState.ts rename to src/actions/quest-editor/questEditor.ts index 0dda5416..fea5255d 100644 --- a/src/actions/appState.ts +++ b/src/actions/quest-editor/questEditor.ts @@ -1,6 +1,6 @@ -import { writeQuestQst } from '../data/parsing/quest'; -import { VisibleQuestEntity, Quest } from '../domain'; -import { appStateStore } from '../stores/AppStateStore'; +import { writeQuestQst } from '../../data/parsing/quest'; +import { VisibleQuestEntity, Quest } from '../../domain'; +import { questEditorStore } from '../../stores/QuestEditorStore'; import { action } from 'mobx'; import { Object3D } from 'three'; @@ -9,7 +9,7 @@ import { Object3D } from 'three'; */ export const setModel = action('setModel', (model?: Object3D) => { resetModelAndQuestState(); - appStateStore.currentModel = model; + questEditorStore.currentModel = model; }); /** @@ -17,39 +17,39 @@ export const setModel = action('setModel', (model?: Object3D) => { */ export const setQuest = action('setQuest', (quest?: Quest) => { resetModelAndQuestState(); - appStateStore.currentQuest = quest; + questEditorStore.currentQuest = quest; if (quest && quest.areaVariants.length) { - appStateStore.currentArea = quest.areaVariants[0].area; + questEditorStore.currentArea = quest.areaVariants[0].area; } }); function resetModelAndQuestState() { - appStateStore.currentQuest = undefined; - appStateStore.currentArea = undefined; - appStateStore.selectedEntity = undefined; - appStateStore.currentModel = undefined; + questEditorStore.currentQuest = undefined; + questEditorStore.currentArea = undefined; + questEditorStore.selectedEntity = undefined; + questEditorStore.currentModel = undefined; } export const setSelectedEntity = action('setSelectedEntity', (entity?: VisibleQuestEntity) => { - appStateStore.selectedEntity = entity; + questEditorStore.selectedEntity = entity; }); export const setCurrentAreaId = action('setCurrentAreaId', (areaId?: number) => { - appStateStore.selectedEntity = undefined; + questEditorStore.selectedEntity = undefined; if (areaId == null) { - appStateStore.currentArea = undefined; - } else if (appStateStore.currentQuest) { - const areaVariant = appStateStore.currentQuest.areaVariants.find( + questEditorStore.currentArea = undefined; + } else if (questEditorStore.currentQuest) { + const areaVariant = questEditorStore.currentQuest.areaVariants.find( variant => variant.area.id === areaId); - appStateStore.currentArea = areaVariant && areaVariant.area; + questEditorStore.currentArea = areaVariant && areaVariant.area; } }); export const saveCurrentQuestToFile = (fileName: string) => { - if (appStateStore.currentQuest) { - const cursor = writeQuestQst(appStateStore.currentQuest, fileName); + if (questEditorStore.currentQuest) { + const cursor = writeQuestQst(questEditorStore.currentQuest, fileName); if (!fileName.endsWith('.qst')) { fileName += '.qst'; diff --git a/src/actions/visibleQuestEntities.ts b/src/actions/quest-editor/visibleQuestEntities.ts similarity index 82% rename from src/actions/visibleQuestEntities.ts rename to src/actions/quest-editor/visibleQuestEntities.ts index b89720e2..b73e5163 100644 --- a/src/actions/visibleQuestEntities.ts +++ b/src/actions/quest-editor/visibleQuestEntities.ts @@ -1,5 +1,5 @@ import { action } from "mobx"; -import { VisibleQuestEntity, Vec3, Section } from "../domain"; +import { VisibleQuestEntity, Vec3, Section } from "../../domain"; export const setPositionOnVisibleQuestEntity = action('setPositionOnVisibleQuestEntity', (entity: VisibleQuestEntity, position: Vec3, section?: Section) => { diff --git a/src/rendering/Renderer.ts b/src/rendering/Renderer.ts index 1a33ef10..5e833cda 100644 --- a/src/rendering/Renderer.ts +++ b/src/rendering/Renderer.ts @@ -26,8 +26,8 @@ import { NPC_HOVER_COLOR, NPC_SELECTED_COLOR } from './entities'; -import { setSelectedEntity } from '../actions/appState'; -import { setPositionOnVisibleQuestEntity as setPositionAndSectionOnVisibleQuestEntity } from '../actions/visibleQuestEntities'; +import { setSelectedEntity } from '../actions/quest-editor/questEditor'; +import { setPositionOnVisibleQuestEntity as setPositionAndSectionOnVisibleQuestEntity } from '../actions/quest-editor/visibleQuestEntities'; const OrbitControls = OrbitControlsCreator(THREE); @@ -41,7 +41,7 @@ interface PickEntityResult { } /** - * Renders one quest area at a time. + * Renders a quest area or an NJ/XJ model. */ export class Renderer { private renderer = new WebGLRenderer({ antialias: true }); diff --git a/src/stores/AppStateStore.ts b/src/stores/QuestEditorStore.ts similarity index 78% rename from src/stores/AppStateStore.ts rename to src/stores/QuestEditorStore.ts index 63c1d3bb..b794927b 100644 --- a/src/stores/AppStateStore.ts +++ b/src/stores/QuestEditorStore.ts @@ -2,11 +2,11 @@ import { observable } from 'mobx'; import { Object3D } from 'three'; import { Area, Quest, VisibleQuestEntity } from '../domain'; -class AppStateStore { +class QuestEditorStore { @observable currentModel?: Object3D; @observable currentQuest?: Quest; @observable currentArea?: Area; @observable selectedEntity?: VisibleQuestEntity; } -export const appStateStore = new AppStateStore(); +export const questEditorStore = new QuestEditorStore(); diff --git a/src/ui/ApplicationComponent.css b/src/ui/ApplicationComponent.css index 66edceb2..e194dcf4 100644 --- a/src/ui/ApplicationComponent.css +++ b/src/ui/ApplicationComponent.css @@ -8,8 +8,8 @@ right: 0; } -.ApplicationComponent-heading { - font-size: 22px !important; +div.ApplicationComponent .ApplicationComponent-heading { + font-size: 22px; } .ApplicationComponent-beta { @@ -18,16 +18,13 @@ margin-left: 2; } -.ApplicationComponent-button-bar > * { - margin-right: 10px; -} - .ApplicationComponent-main { flex: 1; display: flex; + align-items: stretch; overflow: hidden; } -.ApplicationComponent-main div:nth-child(2) { +.ApplicationComponent-main>* { flex: 1; } \ No newline at end of file diff --git a/src/ui/ApplicationComponent.tsx b/src/ui/ApplicationComponent.tsx index 089577f3..71f6d321 100644 --- a/src/ui/ApplicationComponent.tsx +++ b/src/ui/ApplicationComponent.tsx @@ -1,32 +1,22 @@ -import { Button, Dialog, Intent, Classes, Navbar, NavbarGroup, NavbarHeading, FileInput, HTMLSelect, FormGroup, InputGroup } from '@blueprintjs/core'; +import { Classes, Navbar, NavbarGroup, NavbarHeading, Button } from '@blueprintjs/core'; import { observer } from 'mobx-react'; -import React, { ChangeEvent, KeyboardEvent } from 'react'; -import { saveCurrentQuestToFile, setCurrentAreaId } from '../actions/appState'; -import { loadFile } from '../actions/loadFile'; -import { appStateStore } from '../stores/AppStateStore'; +import React from 'react'; import './ApplicationComponent.css'; -import { RendererComponent } from './RendererComponent'; -import { EntityInfoComponent } from './EntityInfoComponent'; -import { QuestInfoComponent } from './QuestInfoComponent'; +import { QuestEditorComponent } from './quest-editor/QuestEditorComponent'; +import { observable, action } from 'mobx'; @observer -export class ApplicationComponent extends React.Component<{}, { - filename?: string, - saveDialogOpen: boolean, - saveDialogFilename: string -}> { - state = { - filename: undefined, - saveDialogOpen: false, - saveDialogFilename: 'Untitled', - }; +export class ApplicationComponent extends React.Component { + @observable private tool = 'quest-editor'; render() { - const quest = appStateStore.currentQuest; - const model = appStateStore.currentModel; - const areas = quest && Array.from(quest.areaVariants).map(a => a.area); - const area = appStateStore.currentArea; - const areaId = area && String(area.id); + let toolComponent; + + switch (this.tool) { + case 'quest-editor': + toolComponent = ; + break; + } return (
@@ -34,121 +24,22 @@ export class ApplicationComponent extends React.Component<{}, { Phantasmal World - BETA - this.setTool('quest-editor')} /> - {areas ? ( - - {areas.map(area => - - )} - - ) : null} - {quest ? ( -
- - ); } - private onFileChange = (e: ChangeEvent) => { - if (e.currentTarget.files) { - const file = e.currentTarget.files[0]; - - if (file) { - this.setState({ - filename: file.name - }); - loadFile(file); - } - } - } - - private onAreaSelectChange = (e: ChangeEvent) => { - const areaId = parseInt(e.currentTarget.value, 10); - setCurrentAreaId(areaId); - } - - private onSaveAsClick = () => { - let name = this.state.filename || 'Untitled'; - name = name.endsWith('.qst') ? name.slice(0, -4) : name; - - this.setState({ - saveDialogOpen: true, - saveDialogFilename: name - }); - } - - private onSaveDialogNameChange = (e: ChangeEvent) => { - this.setState({ saveDialogFilename: e.currentTarget.value }); - } - - private onSaveDialogNameKeyUp = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - this.onSaveDialogSaveClick(); - } - } - - private onSaveDialogSaveClick = () => { - saveCurrentQuestToFile(this.state.saveDialogFilename); - this.setState({ saveDialogOpen: false }); - } - - private onSaveDialogClose = () => { - this.setState({ saveDialogOpen: false }); - } + private setTool = action('setTool', (tool: string) => { + this.tool = tool; + }); } diff --git a/src/ui/EntityInfoComponent.css b/src/ui/quest-editor/EntityInfoComponent.css similarity index 100% rename from src/ui/EntityInfoComponent.css rename to src/ui/quest-editor/EntityInfoComponent.css diff --git a/src/ui/EntityInfoComponent.tsx b/src/ui/quest-editor/EntityInfoComponent.tsx similarity index 98% rename from src/ui/EntityInfoComponent.tsx rename to src/ui/quest-editor/EntityInfoComponent.tsx index 4ea4917a..658f7604 100644 --- a/src/ui/EntityInfoComponent.tsx +++ b/src/ui/quest-editor/EntityInfoComponent.tsx @@ -1,7 +1,7 @@ import { NumericInput } from '@blueprintjs/core'; import { observer } from 'mobx-react'; import React from 'react'; -import { QuestNpc, QuestObject, VisibleQuestEntity } from '../domain'; +import { QuestNpc, QuestObject, VisibleQuestEntity } from '../../domain'; import './EntityInfoComponent.css'; interface Props { diff --git a/src/ui/quest-editor/QuestEditorComponent.css b/src/ui/quest-editor/QuestEditorComponent.css new file mode 100644 index 00000000..f88048d4 --- /dev/null +++ b/src/ui/quest-editor/QuestEditorComponent.css @@ -0,0 +1,18 @@ +.QuestEditorComponent { + display: flex; + flex-direction: column; +} + +.QuestEditorComponent-main { + flex: 1; + display: flex; + overflow: hidden; +} + +.QuestEditorComponent-button-bar>* { + margin-right: 10px; +} + +.QuestEditorComponent-main div:nth-child(2) { + flex: 1; +} \ No newline at end of file diff --git a/src/ui/quest-editor/QuestEditorComponent.tsx b/src/ui/quest-editor/QuestEditorComponent.tsx new file mode 100644 index 00000000..f0f844cd --- /dev/null +++ b/src/ui/quest-editor/QuestEditorComponent.tsx @@ -0,0 +1,149 @@ +import { Button, Classes, Dialog, FileInput, FormGroup, HTMLSelect, InputGroup, Intent, Navbar, NavbarGroup } from "@blueprintjs/core"; +import { observer } from "mobx-react"; +import React, { ChangeEvent, KeyboardEvent } from "react"; +import { saveCurrentQuestToFile, setCurrentAreaId } from "../../actions/quest-editor/questEditor"; +import { loadFile } from "../../actions/quest-editor/loadFile"; +import { questEditorStore } from "../../stores/QuestEditorStore"; +import { EntityInfoComponent } from "./EntityInfoComponent"; +import './QuestEditorComponent.css'; +import { QuestInfoComponent } from "./QuestInfoComponent"; +import { RendererComponent } from "./RendererComponent"; + +@observer +export class QuestEditorComponent extends React.Component<{}, { + filename?: string, + saveDialogOpen: boolean, + saveDialogFilename: string +}> { + state = { + filename: undefined, + saveDialogOpen: false, + saveDialogFilename: 'Untitled', + }; + + render() { + const quest = questEditorStore.currentQuest; + const model = questEditorStore.currentModel; + const areas = quest && Array.from(quest.areaVariants).map(a => a.area); + const area = questEditorStore.currentArea; + const areaId = area && String(area.id); + + return ( +
+ + + + {areas ? ( + + {areas.map(area => + + )} + + ) : null} + {quest ? ( +
+ + + + ); + } + + private onFileChange = (e: ChangeEvent) => { + if (e.currentTarget.files) { + const file = e.currentTarget.files[0]; + + if (file) { + this.setState({ + filename: file.name + }); + loadFile(file); + } + } + } + + private onAreaSelectChange = (e: ChangeEvent) => { + const areaId = parseInt(e.currentTarget.value, 10); + setCurrentAreaId(areaId); + } + + private onSaveAsClick = () => { + let name = this.state.filename || 'Untitled'; + name = name.endsWith('.qst') ? name.slice(0, -4) : name; + + this.setState({ + saveDialogOpen: true, + saveDialogFilename: name + }); + } + + private onSaveDialogNameChange = (e: ChangeEvent) => { + this.setState({ saveDialogFilename: e.currentTarget.value }); + } + + private onSaveDialogNameKeyUp = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + this.onSaveDialogSaveClick(); + } + } + + private onSaveDialogSaveClick = () => { + saveCurrentQuestToFile(this.state.saveDialogFilename); + this.setState({ saveDialogOpen: false }); + } + + private onSaveDialogClose = () => { + this.setState({ saveDialogOpen: false }); + } +} \ No newline at end of file diff --git a/src/ui/QuestInfoComponent.css b/src/ui/quest-editor/QuestInfoComponent.css similarity index 100% rename from src/ui/QuestInfoComponent.css rename to src/ui/quest-editor/QuestInfoComponent.css diff --git a/src/ui/QuestInfoComponent.tsx b/src/ui/quest-editor/QuestInfoComponent.tsx similarity index 98% rename from src/ui/QuestInfoComponent.tsx rename to src/ui/quest-editor/QuestInfoComponent.tsx index 23127b96..7f3b15b4 100644 --- a/src/ui/QuestInfoComponent.tsx +++ b/src/ui/quest-editor/QuestInfoComponent.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { NpcType, Quest } from '../domain'; +import { NpcType, Quest } from '../../domain'; import { Pre } from '@blueprintjs/core'; import './QuestInfoComponent.css'; diff --git a/src/ui/RendererComponent.tsx b/src/ui/quest-editor/RendererComponent.tsx similarity index 92% rename from src/ui/RendererComponent.tsx rename to src/ui/quest-editor/RendererComponent.tsx index 938aa7f6..304ea85c 100644 --- a/src/ui/RendererComponent.tsx +++ b/src/ui/quest-editor/RendererComponent.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Object3D } from 'three'; -import { Area, Quest } from '../domain'; -import { Renderer } from '../rendering/Renderer'; +import { Area, Quest } from '../../domain'; +import { Renderer } from '../../rendering/Renderer'; interface Props { quest?: Quest;