Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion spx-gui/src/components/editor/ProjectEditor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<UICard
v-show="isPreviewMode"
v-radar="{ name: `Editor for ${selected.type}`, desc: `Main editor panel for editing ${selected.type}` }"
class="main"
>
Expand All @@ -20,10 +21,11 @@
<StageEditor v-else-if="selected.type === 'stage'" :stage="project.stage" :state="editorCtx.state.stageState" />
<EditorPlaceholder v-else />
</UICard>
<div class="sider">
<div v-show="isPreviewMode" class="sider">
<EditorPreview />
<EditorPanels />
</div>
<GlobalSettings v-if="!isPreviewMode" />
</template>

<script setup lang="ts">
Expand Down Expand Up @@ -51,11 +53,14 @@ import { genSpriteFromCanvas, genBackdropFromCanvas } from '@/models/common/asse
import { computed, watchEffect } from 'vue'
import type { z } from 'zod'
import { Monitor } from '@/models/widget/monitor'
import GlobalSettings from '@/components/editor/map-editor/GlobalSettings.vue'
import { EditMode } from './editor-state'

const editorCtx = useEditorCtx()
const copilotCtx = useAgentCopilotCtx()
const project = computed(() => editorCtx.project)
const selected = computed(() => editorCtx.state.selected)
const isPreviewMode = computed(() => editorCtx.state.selectedEditMode === EditMode.Preview)

type AddSpriteFromCanvaOptions = z.infer<typeof AddSpriteFromCanvasArgsSchema>
type AddStageBackdropFromCanvasOptions = z.infer<typeof AddStageBackdropFromCanvasArgsSchema>
Expand Down
14 changes: 14 additions & 0 deletions spx-gui/src/components/editor/editor-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface IRouter {
push(to: RouteLocationAsRelativeGeneric): Promise<unknown>
}

export enum EditMode {
Preview = 'preview',
Global = 'global'
}

export class EditorState extends Disposable {
constructor(
private project: Project,
Expand Down Expand Up @@ -75,10 +80,15 @@ export class EditorState extends Disposable {
editing: editing.Editing
stageState: StageEditorState
spriteState: SpriteEditorState | null = null
private selectedEditModeRef = ref<EditMode>(EditMode.Preview)
private selectedTypeRef = ref<SelectedType>('sprite')
private selectedSpriteIdRef = ref<string | null>(null)
private selectedSoundIdRef = ref<string | null>(null)

get selectedEditMode() {
return this.selectedEditModeRef.value
}

get selectedSprite() {
if (this.selectedTypeRef.value !== 'sprite') return null
return this.project.sprites.find((s) => s.id === this.selectedSpriteIdRef.value) ?? null
Expand Down Expand Up @@ -126,6 +136,10 @@ export class EditorState extends Disposable {
}
}

selectEditMode(editMode: EditMode) {
this.selectedEditModeRef.value = editMode
}

select(target: { type: 'stage' } | { type: 'sprite'; id?: string | null } | { type: 'sound'; id?: string | null }) {
switch (target.type) {
case 'stage':
Expand Down
12 changes: 12 additions & 0 deletions spx-gui/src/components/editor/map-editor/GlobalSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script setup lang="ts">
import { useEditorCtx } from '../EditorContextProvider.vue'
import MapEditor from './MapEditor.vue'

const editorCtx = useEditorCtx()
</script>

<template>
<MapEditor :project="editorCtx.project" :selected-sprite-id="editorCtx.state.selectedSprite?.id ?? null" />
</template>

<style lang="scss" scoped></style>
105 changes: 105 additions & 0 deletions spx-gui/src/components/editor/map-editor/MapEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { UICard, UICardHeader } from '@/components/ui'
import type { Project } from '@/models/project'
import MapViewer from './map-viewer/MapViewer.vue'
import SpriteList from './SpriteList.vue'
import MapBasicConfig from './MapBasicConfig.vue'
import type { Sprite } from '@/models/sprite'

const props = defineProps<{
project: Project
selectedSpriteId: string | null
}>()

const selectedSpriteId = ref(props.selectedSpriteId)
const selectedSprite = computed(() => props.project.sprites.find((s) => s.id === selectedSpriteId.value) ?? null)

function handleSpriteSelect(sprite: Sprite | null) {
selectedSpriteId.value = sprite?.id ?? null
}
</script>

<template>
<div class="body">
<div class="main">
<MapViewer :project="project" :selected-sprite="selectedSprite" @update:selected-sprite="handleSpriteSelect" />
</div>
<div class="sider">
<UICard class="collapse-card">
<UICardHeader>
{{
$t({
en: 'Global Configuration',
zh: '全局配置'
})
}}
</UICardHeader>
<MapBasicConfig class="map-config" :project="project" />
</UICard>
<SpriteList
class="sprite-list"
:project="project"
:selected-sprite="selectedSprite"
@update:selected-sprite="handleSpriteSelect"
/>
</div>
</div>
</template>

<style lang="scss" scoped>
@import '@/components/ui/responsive';

.body {
flex: 1 1 0;
display: flex;
flex-direction: row;
gap: var(--ui-gap-middle);
}
.main {
flex: 1;
min-width: 0;
display: flex;
justify-content: center;
align-items: center;
}
.sider {
flex: 0 0 400px;
display: flex;
flex-direction: column;
gap: var(--ui-gap-middle);

@include responsive(desktop-large) {
flex-basis: 492px;
}

.collapse-icon {
transition: transform 0.3s;
margin-left: 8px;
cursor: pointer;

&.collapsed {
transform: rotate(-180deg);
}
}

.v-enter-active {
transition: opacity ease-in 0.2s;
}
.v-leave-active {
transition: opacity ease-out 0.2s;
}

.v-enter-from,
.v-leave-to {
opacity: 0;
}

.map-config {
padding: 16px;
}
}
.sprite-list {
flex: 1 1 0;
}
</style>
75 changes: 6 additions & 69 deletions spx-gui/src/components/editor/map-editor/MapEditorModal.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { UICard, UICardHeader, UIFullScreenModal, UIFullScreenModalHeader } from '@/components/ui'
import { UIFullScreenModal, UIFullScreenModalHeader } from '@/components/ui'
import type { Project } from '@/models/project'
import MapViewer from './map-viewer/MapViewer.vue'
import SpriteList from './SpriteList.vue'
import MapBasicConfig from './MapBasicConfig.vue'
import type { Sprite } from '@/models/sprite'
import MapEditor from './MapEditor.vue'

const props = defineProps<{
defineProps<{
visible: boolean
project: Project
selectedSpriteId: string | null
Expand All @@ -17,13 +13,6 @@ const emit = defineEmits<{
resolved: []
cancelled: []
}>()

const selectedSpriteId = ref(props.selectedSpriteId)
const selectedSprite = computed(() => props.project.sprites.find((s) => s.id === selectedSpriteId.value) ?? null)

function handleSpriteSelect(sprite: Sprite | null) {
selectedSpriteId.value = sprite?.id ?? null
}
</script>

<template>
Expand All @@ -38,71 +27,19 @@ function handleSpriteSelect(sprite: Sprite | null) {
}}
</h2>
</UIFullScreenModalHeader>
<div class="body">
<div class="main">
<MapViewer :project="project" :selected-sprite="selectedSprite" @update:selected-sprite="handleSpriteSelect" />
</div>
<div class="sider">
<UICard class="collapse-card">
<UICardHeader>
{{
$t({
en: 'Global Configuration',
zh: '全局配置'
})
}}
</UICardHeader>
<MapBasicConfig class="map-config" :project="project" />
</UICard>
<SpriteList
class="sprite-list"
:project="project"
:selected-sprite="selectedSprite"
@update:selected-sprite="handleSpriteSelect"
/>
</div>
</div>
<MapEditor class="editor" :project="project" :selected-sprite-id="selectedSpriteId" />
</UIFullScreenModal>
</template>

<style lang="scss" scoped>
@import '@/components/ui/responsive';

.title {
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.body {
flex: 1 1 0;
display: flex;
flex-direction: row;
padding: var(--ui-gap-middle);
gap: var(--ui-gap-middle);
}
.main {
flex: 1;
min-width: 0;
display: flex;
justify-content: center;
align-items: center;
}
.sider {
flex: 0 0 400px;
display: flex;
flex-direction: column;
gap: var(--ui-gap-middle);

@include responsive(desktop-large) {
flex-basis: 492px;
}

.map-config {
padding: 16px;
}
}
.sprite-list {
flex: 1 1 0;
.editor {
padding: var(--ui-gap-middle);
}
</style>
Loading
Loading