Skip to content

Commit

Permalink
Language server integration in code editor - Rename Resources (#1155)
Browse files Browse the repository at this point in the history
* LS integration for Rename Resources

* typo
  • Loading branch information
nighca authored Dec 20, 2024
1 parent b788f67 commit bd404d2
Show file tree
Hide file tree
Showing 59 changed files with 1,442 additions and 1,362 deletions.
82 changes: 82 additions & 0 deletions spx-gui/src/components/asset/RenameModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts">
export interface IRenameTarget {
name: string
validateName(newName: string): LocaleMessage | null | undefined
setName(newName: string): Promise<void>
inputTip: LocaleMessage
}
</script>

<script setup lang="ts">
import { useI18n, type LocaleMessage } from '@/utils/i18n'
import { UIFormModal, UIButton, UITextInput, UIForm, UIFormItem, useForm } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
const props = defineProps<{
visible: boolean
target: IRenameTarget
}>()
const emit = defineEmits<{
resolved: [void]
cancelled: []
}>()
const { t } = useI18n()
const form = useForm({
name: [props.target.name, validateName]
})
const handleSubmit = useMessageHandle(
async () => {
if (form.value.name !== props.target.name) {
await props.target.setName(form.value.name)
}
emit('resolved')
},
{
en: 'Failed to rename',
zh: '重命名失败'
}
)
function validateName(name: string) {
if (name === props.target.name) return
return t(props.target.validateName(name) ?? null)
}
</script>

<template>
<UIFormModal
class="rename-modal"
style="width: 512px"
:title="$t({ en: 'Rename', zh: '重命名' })"
:visible="visible"
@update:visible="emit('cancelled')"
>
<UIForm :form="form" has-success-feedback @submit="handleSubmit.fn">
<UIFormItem path="name">
<UITextInput v-model:value="form.value.name" />
<template #tip>{{ $t(target.inputTip) }}</template>
</UIFormItem>
<footer class="footer">
<UIButton type="boring" @click="emit('cancelled')">
{{ $t({ en: 'Cancel', zh: '取消' }) }}
</UIButton>
<UIButton type="primary" html-type="submit" :loading="handleSubmit.isLoading.value">
{{ $t({ en: 'Confirm', zh: '确认' }) }}
</UIButton>
</footer>
</UIForm>
</UIFormModal>
</template>

<style lang="scss" scoped>
.footer {
margin-top: 40px;
display: flex;
gap: var(--ui-gap-middle);
justify-content: flex-end;
}
</style>
146 changes: 133 additions & 13 deletions spx-gui/src/components/asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ import { Sprite } from '@/models/sprite'
import { Animation } from '@/models/animation'
import { saveFiles } from '@/models/common/cloud'
import { Monitor } from '@/models/widget/monitor'
import * as assetName from '@/models/common/asset-name'
import type { Costume } from '@/models/costume'
import type { Widget } from '@/models/widget'
import SoundRecorderModal from '../editor/sound/SoundRecorderModal.vue'
import { useEditorCtx } from '../editor/EditorContextProvider.vue'
import { useCodeEditorCtx } from '../editor/code-editor/context'
import { getResourceIdentifier } from '../editor/code-editor/common'
import AssetLibraryModal from './library/AssetLibraryModal.vue'
import AssetAddModal from './library/AssetAddModal.vue'
import LoadFromScratchModal from './scratch/LoadFromScratchModal.vue'
import PreprocessModal from './preprocessing/PreprocessModal.vue'
import GroupCostumesModal from './animation/GroupCostumesModal.vue'
// TODO: Consider moving these components from `components/editor/` to `components/asset/`
import SpriteRenameModal from '../editor/panels/sprite/SpriteRenameModal.vue'
import SoundRenameModal from '../editor/sound/SoundRenameModal.vue'
import CostumeRenameModal from '../editor/sprite/CostumeRenameModal.vue'
import BackdropRenameModal from '../editor/stage/backdrop/BackdropRenameModal.vue'
import AnimationRenameModal from '../editor/sprite/AnimationRenameModal.vue'
import WidgetRenameModal from '../editor/stage/widget/WidgetRenameModal.vue'
import RenameModal from './RenameModal.vue'

function selectAsset(project: Project, asset: AssetModel | undefined) {
if (asset instanceof Sprite) project.select({ type: 'sprite', id: asset.id })
Expand Down Expand Up @@ -196,25 +196,145 @@ export function useAddMonitor(autoSelect = true) {
}

export function useRenameSprite() {
return useModal(SpriteRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameSprite(sprite: Sprite) {
return invokeRenameModal({
target: {
name: sprite.name,
validateName(name) {
return assetName.validateSpriteName(name, editorCtx.project)
},
async setName(newName) {
const action = { name: { en: 'Rename sprite', zh: '重命名精灵' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(sprite), newName)
sprite.setName(newName)
})
},
inputTip: assetName.spriteNameTip
}
})
}
}

export function useRenameSound() {
return useModal(SoundRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameSound(sound: Sound) {
return invokeRenameModal({
target: {
name: sound.name,
validateName(name) {
return assetName.validateSoundName(name, editorCtx.project)
},
async setName(newName) {
const action = { name: { en: 'Rename sound', zh: '重命名声音' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(sound), newName)
sound.setName(newName)
})
},
inputTip: assetName.soundNameTip
}
})
}
}

export function useRenameCostume() {
return useModal(CostumeRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameCostume(costume: Costume) {
return invokeRenameModal({
target: {
name: costume.name,
validateName(name) {
return assetName.validateCostumeName(name, costume.parent)
},
async setName(newName) {
const action = { name: { en: 'Rename costume', zh: '重命名造型' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(costume), newName)
costume.setName(newName)
})
},
inputTip: assetName.costumeNameTip
}
})
}
}

export function useRenameBackdrop() {
return useModal(BackdropRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameBackdrop(backdrop: Backdrop) {
return invokeRenameModal({
target: {
name: backdrop.name,
validateName(name) {
return assetName.validateBackdropName(name, editorCtx.project.stage)
},
async setName(newName) {
const action = { name: { en: 'Rename backdrop', zh: '重命名背景' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(backdrop), newName)
backdrop.setName(newName)
})
},
inputTip: assetName.backdropNameTip
}
})
}
}

export function useRenameAnimation() {
return useModal(AnimationRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameAnimation(animation: Animation) {
return invokeRenameModal({
target: {
name: animation.name,
validateName(name) {
return assetName.validateAnimationName(name, animation.sprite)
},
async setName(newName) {
const action = { name: { en: 'Rename animation', zh: '重命名动画' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(animation), newName)
animation.setName(newName)
})
},
inputTip: assetName.animationNameTip
}
})
}
}

export function useRenameWidget() {
return useModal(WidgetRenameModal)
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const invokeRenameModal = useModal(RenameModal)
return async function renameWidget(widget: Widget) {
return invokeRenameModal({
target: {
name: widget.name,
validateName(name) {
return assetName.validateWidgetName(name, editorCtx.project.stage)
},
async setName(newName) {
const action = { name: { en: 'Rename widget', zh: '重命名控件' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.updateResourceReferencesOnRename(getResourceIdentifier(widget), newName)
widget.setName(newName)
})
},
inputTip: assetName.widgetNameTip
}
})
}
}
7 changes: 3 additions & 4 deletions spx-gui/src/components/editor/EditorContextProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,18 @@ export function useEditorCtx() {
import { provide, type InjectionKey } from 'vue'
import { Project } from '@/models/project'
import type { UserInfo } from '@/stores/user'
import { computedShallowReactive, useComputedDisposable } from '@/utils/utils'
import { computedShallowReactive } from '@/utils/utils'
const props = defineProps<{
project: Project
userInfo: UserInfo
runtime: Runtime
}>()
const runtimeRef = useComputedDisposable(() => new Runtime(props.project))
const editorCtx = computedShallowReactive<EditorCtx>(() => ({
project: props.project,
userInfo: props.userInfo,
runtime: runtimeRef.value
runtime: props.runtime
}))
provide(editorCtxKey, editorCtx)
Expand Down
Loading

0 comments on commit bd404d2

Please sign in to comment.