Skip to content

Commit

Permalink
project management
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Apr 3, 2024
1 parent b182582 commit 57b6072
Show file tree
Hide file tree
Showing 29 changed files with 520 additions and 824 deletions.
26 changes: 2 additions & 24 deletions spx-gui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@
<n-config-provider :theme-overrides="themeOverrides">
<n-message-provider>
<NModalProvider>
<n-layout embedded>
<n-layout-header>
<TopMenu />
</n-layout-header>
<n-layout-content>
<router-view />
</n-layout-content>
</n-layout>
<router-view />
</NModalProvider>
</n-message-provider>
</n-config-provider>
Expand All @@ -30,12 +23,8 @@
import {
NConfigProvider,
NMessageProvider,
NModalProvider,
NLayout,
NLayoutHeader,
NLayoutContent
NModalProvider
} from 'naive-ui'
import TopMenu from '@/components/top-menu/TopMenu.vue'
import '@/assets/theme'
/**
* @description: Override spx-gui theme
Expand Down Expand Up @@ -115,17 +104,6 @@ body {
font-size: 16px;
}
.n-layout-header {
background: $base-color;
height: 60px;
padding: 13px;
}
.n-layout {
height: 100%;
background: #ffffff00;
}
.n-menu,
.n-menu--horizontal,
.n-menu--responsive {
Expand Down
115 changes: 86 additions & 29 deletions spx-gui/src/components/editor/EditorHomepage.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
<template>
<div class="editor-homepage">
<div class="editor">
<SpxEditor />
</div>
<div class="sider">
<SpxStage :project="projectStore.project" />
<EditorPanels />
</div>
</div>
<section class="editor-home">
<header class="editor-header">
<TopMenu :project="project" />
</header>
<main v-if="userStore.userInfo != null" class="editor-main">
<template v-if="projectName">
<ProjectEditor v-if="project != null" :project="project" :user-info="userStore.userInfo" />
<div v-else class="loading-wrapper">
<NSpin size="large" />
</div>
</template>
<template v-else>
<ProjectList @selected="handleSelected" />
<NButton @click="handleCreate">+</NButton>
</template>
</main>
</section>
</template>

<script setup lang="ts">
import SpxEditor from './SpxEditor.vue'
import SpxStage from './stage/SpxStage.vue'
import EditorPanels from './panels/EditorPanels.vue'
import { useProjectStore, useUserStore } from '@/stores'
import { watchEffect } from 'vue'
import { useRoute } from 'vue-router'
import { watch } from 'vue'
import { watchEffect, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { NButton, NSpin } from 'naive-ui'
import type { ProjectData } from '@/apis/project'
import { useUserStore } from '@/stores'
import { Project } from '@/models/project'
import TopMenu from '@/components/top-menu/TopMenu.vue'
import ProjectList from '@/components/project/ProjectList.vue'
import { useCreateProject } from '@/components/project'
import ProjectEditor from './ProjectEditor.vue'
import { editProjectRouteName } from '@/router'
import { computed } from 'vue'
const localCacheKey = 'TODO_GOPLUS_BUILDER_CACHED_PROJECT'
const userStore = useUserStore()
watchEffect(() => {
Expand All @@ -28,32 +43,74 @@ watchEffect(() => {
}
})
const projectStore = useProjectStore()
const route = useRoute()
const router = useRouter()
const createProject = useCreateProject()
const project = ref<Project | null>(null)
const projectName = computed(() => router.currentRoute.value.params.projectName as string | undefined)
watch(
() => route.params.projectName,
(name) => {
if (!name) return
const owner = userStore.userInfo?.name
if (!owner) return
projectStore.openProject(owner, name as string)
() => projectName.value,
async (projectName) => {
if (userStore.userInfo == null) return
if (projectName == null) {
project.value = null
return
}
// TODO: UI logic to handle conflicts when there are local cache
const newProject = new Project()
await newProject.loadFromCloud(userStore.userInfo.name, projectName)
newProject.syncToLocalCache(localCacheKey)
project.value = newProject
},
{ immediate: true }
)
watch(
// https://vuejs.org/guide/essentials/watchers.html#deep-watchers
// According to the document, we should use `() => project.value` instead of
// `project` to avoid deep watching, which is not expected here.
() => project.value,
(_, oldProject) => {
oldProject?.dispose()
;(window as any).project = project.value // for debug purpose, TODO: remove me
}
)
function openProject(projectName: string) {
router.push({ name: editProjectRouteName, params: { projectName } })
}
function handleSelected(project: ProjectData) {
openProject(project.name)
}
async function handleCreate() {
const newProject = await createProject()
openProject(newProject.name)
}
</script>

<style scoped lang="scss">
.editor-homepage {
.editor-home {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.editor {
.editor-header {
flex: 0 0 auto;
}
.editor-main {
flex: 1 1 0;
display: flex;
}
.sider {
flex: 0 0 40%;
.loading-wrapper {
width: 100%;
height: 400px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
112 changes: 112 additions & 0 deletions spx-gui/src/components/editor/ProjectEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<template>
<div class="main">
<SpxEditor />
</div>
<div class="sider">
<SpxStage :project="props.project" />
<EditorPanels />
</div>
</template>

<script lang="ts">
import { inject } from 'vue'
import { type Sprite } from '@/models/sprite'
type Selected =
| {
type: 'sprite' | 'sound'
name: string
}
| {
type: 'stage'
}
// TODO: move stores/editor here
export type EditorCtx = {
project: Project
userInfo: UserInfo
selected: Selected | null
selectedSprite: Sprite | null
select(selected: null): void
select(type: 'stage'): void
select(type: 'sprite' | 'sound', name: string): void
}
const editorCtxKey: InjectionKey<EditorCtx> = Symbol('editor-ctx')
export function useEditorCtx() {
const ctx = inject(editorCtxKey)
if (ctx == null) throw new Error('useEditorCtx should be called inside of editor')
return ctx
}
</script>

<script setup lang="ts">
import { provide, type InjectionKey, ref, watch, shallowReactive, computed, watchEffect } from 'vue'
import { Project } from '@/models/project'
import type { UserInfo } from '@/stores/user'
import SpxEditor from './SpxEditor.vue'
import SpxStage from './stage/SpxStage.vue'
import EditorPanels from './panels/EditorPanels.vue'
const props = defineProps<{
project: Project
userInfo: UserInfo
}>()
const selectedRef = ref<Selected | null>(null)
/* eslint-disable no-redeclare */ // TODO: there should be no need to configure this
function select(selected: null): void
function select(type: 'stage'): void
function select(type: 'sprite' | 'sound', name: string): void
function select(type: any, name?: string) {
selectedRef.value = type == null ? type : { type, name }
}
/* eslint-enable no-redeclare */
const selectedSpriteName = computed(() => {
const selected = selectedRef.value
return selected?.type === 'sprite' ? selected.name : null
})
const selectedSprite = computed(() => {
if (!selectedSpriteName.value) return null
return props.project.sprites.find((s) => s.name === selectedSpriteName.value) || null
})
watch(
() => props.project,
(project) => {
if (project.sprites.length > 0) {
select('sprite', project.sprites[0].name)
} else {
select(null)
}
}
)
const editorCtx = shallowReactive<EditorCtx>({ select } as any)
watchEffect(() => {
// TODO: any simpler way to archieve this (like what Pinia do with store exposes)?
editorCtx.project = props.project
editorCtx.userInfo = props.userInfo
editorCtx.selected = selectedRef.value
editorCtx.selectedSprite = selectedSprite.value
})
provide(editorCtxKey, editorCtx)
</script>

<style scoped lang="scss">
.main {
flex: 1 1 0;
overflow-x: auto;
}
.sider {
flex: 0 0 40%;
display: flex;
flex-direction: column;
}
</style>
6 changes: 3 additions & 3 deletions spx-gui/src/components/editor/panels/EditorPanels.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@

<script setup lang="ts">
import { ref, watch } from 'vue'
import { useEditorCtx } from '@/components/editor/ProjectEditor.vue'
import SoundsPanel from './sound/SoundsPanel.vue'
import SpritesPanel from './sprite/SpritesPanel.vue'
import StagePanel from './stage/StagePanel.vue'
import { useEditorStore } from '@/stores/editor'
const activePanel = ref<'sprites' | 'sounds'>('sprites')
function activate(panel: 'sprites' | 'sounds') {
activePanel.value = panel
}
const editorStore = useEditorStore()
const editorCtx = useEditorCtx()
watch(
() => editorStore.selected,
() => editorCtx.selected,
(selected) => {
if (selected?.type === 'sprite' && activePanel.value !== 'sprites') activate('sprites')
if (selected?.type === 'sound' && activePanel.value !== 'sounds') activate('sounds')
Expand Down
12 changes: 5 additions & 7 deletions spx-gui/src/components/editor/panels/sprite/SpriteList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
<!-- E Component Add Button type second step -->
<!-- S Component ImageCardCom -->
<ImageCardCom
v-for="asset in projectStore.project.sprites"
v-for="asset in editorCtx.project.sprites"
:key="asset.name"
:type="'sprite'"
:asset="asset"
:active="asset === editorStore.selectedSprite"
:active="asset === editorCtx.selectedSprite"
@click="toggleCodeById(asset)"
/>
<!-- E Component ImageCardCom -->
Expand Down Expand Up @@ -63,23 +63,21 @@
import { ref } from 'vue'
import { NGrid, NGridItem, NFlex, NButton, NModal } from 'naive-ui'
import LoadFromScratch from '@/components/library/LoadFromScratch.vue'
import { useProjectStore } from '@/stores'
import { useEditorCtx } from '@/components/editor/ProjectEditor.vue'
import type { Sprite } from '@/models/sprite'
import { useEditorStore } from '@/stores/editor'
import { AssetType } from '@/apis/asset'
import SpriteEditBtn from '../todo/SpriteEditBtn.vue'
import ImageCardCom from '../todo/ImageCardCom.vue'
import AssetAddBtn from '../todo/AssetAddBtn.vue'
const projectStore = useProjectStore()
const editorStore = useEditorStore()
const editorCtx = useEditorCtx()
const bodyStyle = { margin: 'auto' }
// Ref about show import asset modal or not.
const showImportModal = ref<boolean>(false)
const toggleCodeById = (sprite: Sprite) => {
editorStore.select('sprite', sprite.name)
editorCtx.select('sprite', sprite.name)
}
</script>

Expand Down
7 changes: 3 additions & 4 deletions spx-gui/src/components/editor/panels/stage/BackdropList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@
<div class="stage-list">
<div class="stage-list-space">
<AssetAddBtn :type="AssetType.Backdrop" />
<ImageCardCom :type="'bg'" :asset="projectStore.project.stage" />
<ImageCardCom :type="'bg'" :asset="editorCtx.project.stage" />
</div>
</div>
</template>

<script setup lang="ts">
import ImageCardCom from '../todo/ImageCardCom.vue'
import AssetAddBtn from '../todo/AssetAddBtn.vue'
import { useProjectStore } from '@/stores'
import { useEditorCtx } from '@/components/editor/ProjectEditor.vue'
import { AssetType } from '@/apis/asset'
// ----------props & emit------------------------------------
const projectStore = useProjectStore()
const editorCtx = useEditorCtx()
</script>

<style scoped lang="scss">
Expand Down
Loading

0 comments on commit 57b6072

Please sign in to comment.