Skip to content

Commit e8ccea3

Browse files
authored
feat(spx-gui): separate UI and icons for importing Scratch projects and assets (#2535)
* feat(spx-gui): separate UI and icons for importing Scratch projects and assets * feat(spx-gui): add 'Beta' tag to Scratch import option
1 parent 2670460 commit e8ccea3

File tree

9 files changed

+75
-37
lines changed

9 files changed

+75
-37
lines changed

spx-gui/src/apis/common/exception.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export enum ApiExceptionCode {
3030
errorNotFound = 40400,
3131
errorTooManyRequests = 42900,
3232
errorRateLimitExceeded = 42901,
33+
errorScratchFeatureNotSupported = 50101,
3334
errorUnknown = 50000
3435
}
3536

@@ -83,6 +84,10 @@ const codeMessages: Record<ApiExceptionCode, LocaleMessage> = {
8384
en: 'resource not exist',
8485
zh: '资源不存在'
8586
},
87+
[ApiExceptionCode.errorScratchFeatureNotSupported]: {
88+
en: 'Some Scratch features are not supported yet',
89+
zh: '部分 Scratch 特性暂不支持'
90+
},
8691
[ApiExceptionCode.errorUnknown]: {
8792
en: 'something wrong with the server',
8893
zh: '服务器出问题了'

spx-gui/src/apis/sb2xbp.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1+
import { DefaultException } from '@/utils/exception'
12
import { client } from './common'
3+
import { humanizeFileSize } from '@/utils/utils'
4+
5+
const maxFileSize = 32 * 1024 * 1024 // 32MB
26

37
/**
48
* Convert Scratch project (.sb2/.sb3) to XBuilder project (.xbp) via backend `/scratch-convert` endpoint.
59
* The backend is expected to return the converted xbp as binary response (200).
610
*
711
* Uses shared `client` so token, Sentry headers and timeout behavior are consistent with other APIs.
8-
*
9-
* @param file - The Scratch project file (.sb2 or .sb3) to convert
10-
* @param signal - Optional AbortSignal to cancel the request
11-
* @returns Promise that resolves to a Blob containing the converted .xbp file
12-
* @throws {ApiException} When the server returns an error response
13-
* @throws {TimeoutException} When the request exceeds the timeout duration
1412
*/
1513
export async function convertScratchToXbp(file: File, signal?: AbortSignal) {
16-
const MAX_FILE_SIZE = 32 * 1024 * 1024 // 32MB
17-
if (file.size > MAX_FILE_SIZE) {
18-
throw new Error('File too large. Maximum size is 32MB')
14+
if (file.size > maxFileSize) {
15+
const maxFileSizeHumanized = humanizeFileSize(maxFileSize)
16+
throw new DefaultException({
17+
en: `File size exceeds limit (max ${maxFileSizeHumanized.en})`,
18+
zh: `文件尺寸超限(最大 ${maxFileSizeHumanized.zh})`
19+
})
1920
}
2021
const form = new FormData()
2122
form.append('file', file, file.name)

spx-gui/src/components/asset/scratch/LoadFromScratch.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ const importSelected = useMessageHandle(
164164
},
165165
// TODO: more detailed error message
166166
{ en: 'Error encountered when importing assets', zh: '素材导入遇到错误' },
167-
{ en: 'Assets imprted', zh: '素材导入成功' }
167+
{ en: 'Assets imported', zh: '素材导入成功' }
168168
)
169169
</script>
170170

spx-gui/src/components/asset/scratch/LoadFromScratchModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<UIFormModal
33
:radar="{ name: 'Load from Scratch modal', desc: 'Modal for importing assets from Scratch' }"
4-
:title="$t({ en: 'Import assets from Scratch', zh: '从 Scratch 导入资源' })"
4+
:title="$t({ en: 'Import assets from Scratch', zh: '从 Scratch 项目文件导入素材' })"
55
:visible="visible"
66
style="width: 928px"
77
@update:visible="emit('cancelled')"

spx-gui/src/components/editor/navbar/EditorNavbar.vue

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<NavbarDropdown
66
:trigger-radar="{
77
name: 'Project menu',
8-
desc: 'Hover to see project options (create/open/publish/unpublish/remove project, import/export project file, import from Scratch, etc.)'
8+
desc: 'Hover to see project options (create/open/publish/unpublish/remove project, import/export project file, import Scratch project file, import assets from Scratch, etc.)'
99
}"
1010
>
1111
<template #trigger>
@@ -21,19 +21,23 @@
2121
<template #icon><img :src="importProjectSvg" /></template>
2222
{{ $t({ en: 'Import project file...', zh: '导入项目文件...' }) }}
2323
</UIMenuItem>
24-
<UIMenuItem @click="handleExportProjectFile">
25-
<template #icon><img :src="exportProjectSvg" /></template>
26-
{{ $t({ en: 'Export project file', zh: '导出项目文件' }) }}
24+
<UIMenuItem class="import-scratch" @click="handleImportFromScratch">
25+
<template #icon><img :src="importScratchSvg" /></template>
26+
<span class="item-text">
27+
{{ $t({ en: 'Import Scratch project file', zh: '导入 Scratch 项目文件' }) }}
28+
</span>
29+
<!-- TODO: temporary, will be handled uniformly after the tag design specification is complete -->
30+
<div class="beta">Beta</div>
31+
</UIMenuItem>
32+
<UIMenuItem @click="handleImportAssetsFromScratch">
33+
<template #icon><img :src="importAssetsScratchSvg" /></template>
34+
{{ $t({ en: 'Import assets from Scratch', zh: '从 Scratch 项目文件导入素材' }) }}
2735
</UIMenuItem>
2836
</UIMenuGroup>
2937
<UIMenuGroup :disabled="project == null">
30-
<UIMenuItem @click="handleImportFromScratch">
31-
<template #icon><img :src="importScratchSvg" /></template>
32-
{{ $t({ en: 'Import assets from Scratch file', zh: '从 Scratch 项目文件导入' }) }}
33-
</UIMenuItem>
34-
<UIMenuItem @click="handleConvertFromScratch">
35-
<template #icon><img :src="importScratchSvg" /></template>
36-
{{ $t({ en: 'Convert Scratch project to XBuilder', zh: 'Scratch 项目转 XBuilder 项目' }) }}
38+
<UIMenuItem @click="handleExportProjectFile">
39+
<template #icon><img :src="exportProjectSvg" /></template>
40+
{{ $t({ en: 'Export project file', zh: '导出项目文件' }) }}
3741
</UIMenuItem>
3842
</UIMenuGroup>
3943
<UIMenuGroup :disabled="project == null || !isOnline">
@@ -99,7 +103,7 @@
99103
</template>
100104
<template #right>
101105
<UIButtonGroup
102-
v-radar="{ name: 'editor mode menu', desc: 'Hover to see editor mode options (default, map)' }"
106+
v-radar="{ name: 'Editor mode menu', desc: 'Hover to see editor mode options (default, map)' }"
103107
class="editor-mode-wrapper"
104108
type="text"
105109
variant="secondary"
@@ -109,15 +113,15 @@
109113
<UITooltip>
110114
<template #trigger>
111115
<UIButtonGroupItem :value="EditMode.Default">
112-
<div class="icon" v-html="gamePreviewSvg"></div>
116+
<div class="icon" v-html="defaultModeSvg"></div>
113117
</UIButtonGroupItem>
114118
</template>
115119
{{ $t({ en: 'Default Mode', zh: '默认模式' }) }}
116120
</UITooltip>
117121
<UITooltip>
118122
<template #trigger>
119123
<UIButtonGroupItem :value="EditMode.Map">
120-
<div class="icon" v-html="globalConfig"></div>
124+
<div class="icon" v-html="mapEditModeSvg"></div>
121125
</UIButtonGroupItem>
122126
</template>
123127
{{ $t({ en: 'Map Edit Mode', zh: '地图编辑模式' }) }}
@@ -165,15 +169,16 @@ import importProjectSvg from './icons/import-project.svg'
165169
import exportProjectSvg from './icons/export-project.svg'
166170
import removeProjectSvg from './icons/remove-project.svg'
167171
import importScratchSvg from './icons/import-scratch.svg'
172+
import importAssetsScratchSvg from './icons/import-assets-scratch.svg'
168173
import publishSvg from './icons/publish.svg'
169174
import unpublishSvg from './icons/unpublish.svg'
170175
import projectPageSvg from './icons/project-page.svg'
171176
import offlineSvg from './icons/offline.svg?raw'
172177
import savingSvg from './icons/saving.svg?raw'
173178
import failedToSaveSvg from './icons/failed-to-save.svg?raw'
174179
import cloudCheckSvg from './icons/cloud-check.svg?raw'
175-
import gamePreviewSvg from './icons/game-preview.svg?raw'
176-
import globalConfig from './icons/global-config.svg?raw'
180+
import defaultModeSvg from './icons/default-mode.svg?raw'
181+
import mapEditModeSvg from './icons/map-edit-mode.svg?raw'
177182
178183
const props = defineProps<{
179184
project: Project | null
@@ -246,7 +251,7 @@ const handleExportProjectFile = useMessageHandle(
246251
).fn
247252
248253
const loadFromScratchModal = useLoadFromScratchModal()
249-
const handleImportFromScratch = useMessageHandle(
254+
const handleImportAssetsFromScratch = useMessageHandle(
250255
async () => {
251256
const { state, project } = props
252257
if (state == null || project == null) throw new Error('Editor state or project is not available')
@@ -259,9 +264,8 @@ const handleImportFromScratch = useMessageHandle(
259264
}
260265
).fn
261266
262-
const convertScratchMessage = { en: 'Convert Scratch to XBuilder project', zh: 'Scratch 项目转 XBuilder 项目' }
263-
264-
const handleConvertFromScratch = useMessageHandle(
267+
const importScratchMessage = { en: 'Import Scratch project file', zh: '导入 Scratch 项目文件' }
268+
const handleImportFromScratch = useMessageHandle(
265269
async () => {
266270
const { project } = props
267271
if (project == null) throw new Error('project is not available')
@@ -271,7 +275,7 @@ const handleConvertFromScratch = useMessageHandle(
271275
// upload to backend via api helper and get converted xbp blob
272276
const blob = await m.withLoading(
273277
convertScratchToXbp(file, project.getSignal()),
274-
i18n.t({ en: 'Converting Scratch project', zh: '正在转换 Scratch 项目' })
278+
i18n.t({ en: 'Converting Scratch project file', zh: '正在转换 Scratch 项目文件' })
275279
)
276280
277281
if (blob == null) throw new Error('Failed to convert Scratch project')
@@ -280,13 +284,13 @@ const handleConvertFromScratch = useMessageHandle(
280284
type: 'application/zip'
281285
})
282286
283-
const action = { name: convertScratchMessage }
287+
const action = { name: importScratchMessage }
284288
await m.withLoading(
285289
project.history.doAction(action, () => project!.loadXbpFile(xbpFile)),
286-
i18n.t({ en: 'Importing converted project', zh: '导入已转换的项目中' })
290+
i18n.t({ en: 'Importing converted project file', zh: '导入已转换的项目文件' })
287291
)
288292
},
289-
{ en: 'Failed to convert Scratch project', zh: 'Scratch 转换失败' }
293+
{ en: 'Failed to import Scratch project file', zh: '导入 Scratch 项目文件失败' }
290294
).fn
291295
292296
const publishProject = usePublishProject()
@@ -386,6 +390,26 @@ const autoSaveStateIcon = computed<AutoSaveStateIcon | null>(() => {
386390
</script>
387391

388392
<style lang="scss" scoped>
393+
.import-scratch {
394+
padding: 8px;
395+
396+
.item-text {
397+
flex: 1;
398+
margin-right: 8px;
399+
}
400+
401+
.beta {
402+
height: 20px;
403+
border-radius: 4px;
404+
border: 1px solid var(--ui-color-grey-400);
405+
background: var(--ui-color-grey-300);
406+
padding: 0 8px;
407+
font-size: 12px;
408+
line-height: 20px;
409+
color: var(--ui-color-grey-900);
410+
}
411+
}
412+
389413
.owner-info,
390414
.project-name {
391415
overflow: hidden;
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)