Skip to content

Commit

Permalink
Merge pull request #869 from goplus/dev
Browse files Browse the repository at this point in the history
Release v1.4.3
  • Loading branch information
nighca authored Sep 9, 2024
2 parents 32ed668 + 52ed184 commit cb8d721
Show file tree
Hide file tree
Showing 59 changed files with 985 additions and 521 deletions.
11 changes: 11 additions & 0 deletions spx-gui/.env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Config for env production

# Casdoor configuration
VITE_CASDOOR_ENDPOINT="https://acc.goplus.org"
VITE_CASDOOR_CLIENT_ID="4ff910257e9cdd89b6b8"

# Used not for authentication, but for fetching user profile
VITE_CASDOOR_ORGANIZATION_NAME="built-in"
VITE_CASDOOR_APP_NAME="application_stem"

VITE_API_BASE_URL="https://builder.goplus.org/api"
6 changes: 6 additions & 0 deletions spx-gui/src/apis/common/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type RequestOptions = {
headers?: Headers
/** Timeout duration in milisecond, from request-sent to server-response-got */
timeout?: number
signal?: AbortSignal
}

/** Response body when exception encountered for API calling */
Expand Down Expand Up @@ -74,6 +75,11 @@ export class Client {
const req = await this.prepareRequest(url, payload, options)
const timeout = options?.timeout ?? defaultTimeout
const ctrl = new AbortController()
if (options?.signal != null) {
// TODO: Reimplement this using `AbortSignal.any()` once it is widely supported.
options.signal.throwIfAborted()
options.signal.addEventListener('abort', () => ctrl.abort(options.signal?.reason))
}
const resp = await Promise.race([
fetch(req, { signal: ctrl.signal }),
new Promise<never>((_, reject) => setTimeout(() => reject(new TimeoutException()), timeout))
Expand Down
13 changes: 9 additions & 4 deletions spx-gui/src/apis/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ export type ProjectData = {

export type AddProjectParams = Pick<ProjectData, 'name' | 'isPublic' | 'files'>

export function addProject(params: AddProjectParams) {
return client.post('/project', params) as Promise<ProjectData>
export function addProject(params: AddProjectParams, signal?: AbortSignal) {
return client.post('/project', params, { signal }) as Promise<ProjectData>
}

export type UpdateProjectParams = Pick<ProjectData, 'isPublic' | 'files'>
Expand All @@ -40,8 +40,13 @@ function encode(owner: string, name: string) {
return `${encodeURIComponent(owner)}/${encodeURIComponent(name)}`
}

export function updateProject(owner: string, name: string, params: UpdateProjectParams) {
return client.put(`/project/${encode(owner, name)}`, params) as Promise<ProjectData>
export function updateProject(
owner: string,
name: string,
params: UpdateProjectParams,
signal?: AbortSignal
) {
return client.put(`/project/${encode(owner, name)}`, params, { signal }) as Promise<ProjectData>
}

export function deleteProject(owner: string, name: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<ul class="costume-list">
<CostumeItem
v-for="costume in props.sprite.costumes"
:key="costume.name"
:key="costume.id"
:costume="costume"
:selected="selectedCostumeSet.has(costume)"
@click="handleCostumeClick(costume)"
Expand Down
12 changes: 7 additions & 5 deletions spx-gui/src/components/asset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import { useI18n } from '@/utils/i18n'
import { useNetwork } from '@/utils/network'

function selectAsset(project: Project, asset: AssetModel | undefined) {
if (asset instanceof Sprite) project.select({ type: 'sprite', name: asset.name })
else if (asset instanceof Sound) project.select({ type: 'sound', name: asset.name })
if (asset instanceof Sprite) project.select({ type: 'sprite', id: asset.id })
else if (asset instanceof Sound) project.select({ type: 'sound', id: asset.id })
else if (asset instanceof Backdrop) {
project.select({ type: 'stage' })
project.stage.setDefaultBackdrop(asset.name)
project.stage.setDefaultBackdrop(asset.id)
}
}

Expand Down Expand Up @@ -71,6 +71,7 @@ export function useAddSpriteFromLocalFile() {
}
await project.history.doAction({ name: actionMessage }, async () => {
project.addSprite(sprite)
await sprite.autoFitCostumes()
await sprite.autoFit()
})
selectAsset(project, sprite)
Expand All @@ -89,9 +90,10 @@ export function useAddCostumeFromLocalFile() {
title: actionMessage,
confirmText: { en: 'Add', zh: '添加' }
})
await project.history.doAction({ name: actionMessage }, () => {
await project.history.doAction({ name: actionMessage }, async () => {
for (const costume of costumes) sprite.addCostume(costume)
sprite.setDefaultCostume(costumes[0].name)
await sprite.autoFitCostumes(costumes)
sprite.setDefaultCostume(costumes[0].id)
})
}
}
Expand Down
18 changes: 11 additions & 7 deletions spx-gui/src/components/asset/preprocessing/PreprocessModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
<ul class="costume-list">
<CostumeItem
v-for="costume in costumes"
:key="costume.name"
:key="costume.id"
:costume="costume"
:selected="isCostumeSelected(costume)"
@click="handleCostumeClick(costume)"
Expand Down Expand Up @@ -191,14 +191,14 @@ function handleMethodApplied(method: Method, output: File[]) {
const idx = supportedMethods.value.findIndex((m) => m.value === method)
// methods are applied in order, so we need to unapply the following methods, as thier inputs have changed
outputs.splice(idx)
outputs.push(output)
outputs[idx] = output
updateCostumes(output)
}
function handleMethodCancel(method: Method) {
const idx = supportedMethods.value.findIndex((m) => m.value === method)
outputs.splice(idx)
outputs.push(null)
outputs[idx] = null
updateCostumes(getMethodInput(method))
}
Expand All @@ -220,11 +220,11 @@ async function updateCostumes(files: File[]) {
}
function isCostumeSelected(costume: Costume) {
return selectedCostumes.some((a) => a.name === costume.name)
return selectedCostumes.some((a) => a.id === costume.id)
}
async function handleCostumeClick(costume: Costume) {
const index = selectedCostumes.findIndex((c) => c.name === costume.name)
const index = selectedCostumes.findIndex((c) => c.id === costume.id)
if (index < 0) selectedCostumes.push(costume)
else selectedCostumes.splice(index, 1)
}
Expand All @@ -233,7 +233,11 @@ const handleConfirm = useMessageHandle(
async () => {
if (isOnline.value) {
const files = selectedCostumes
.map((costume) => costume.export(''))
.map((costume) =>
costume.export({
basePath: ''
})
)
.reduce((acc, [, files]) => ({ ...acc, ...files }), {})
await saveFiles(files)
}
Expand Down Expand Up @@ -288,7 +292,7 @@ watch(
margin-bottom: -16px;
}
.footer-title {
color: --ui-color-title;
color: var(--ui-color-title);
}
.costume-wrapper {
width: 100%;
Expand Down
2 changes: 2 additions & 0 deletions spx-gui/src/components/editor/code-editor/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<div class="code-text-editor-wrapper">
<CodeTextEditor
ref="codeTextEditor"
:file="file"
:value="value"
@update:value="(v) => emit('update:value', v)"
/>
Expand Down Expand Up @@ -87,6 +88,7 @@ import { useFileUrl } from '@/utils/file'
withDefaults(
defineProps<{
loading?: boolean
file: string
value: string
}>(),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@
<script lang="ts">
let monaco: typeof import('monaco-editor')
let editorCtx: EditorCtx // define `editorCtx` here so `getProject` in `initMonaco` can get the right `editorCtx.project`
type EditorState = {
scrollLeft: number
scrollTop: number
}
/** Map from file-path to editor-state */
const editorStates = new Map<string, EditorState>()
function saveEditorState(file: string, editor: editor.IStandaloneCodeEditor) {
editorStates.set(file, {
scrollLeft: editor.getScrollLeft(),
scrollTop: editor.getScrollTop()
})
}
function resumeEditorState(file: string, editor: editor.IStandaloneCodeEditor) {
const state = editorStates.get(file)
editorStates.delete(file)
if (state == null) return
editor.setScrollLeft(state.scrollLeft)
editor.setScrollTop(state.scrollTop)
}
</script>
<script setup lang="ts">
import { ref, shallowRef, watch, watchEffect } from 'vue'
Expand All @@ -17,6 +40,7 @@ import { initMonaco, defaultThemeName } from './monaco'
import { useLocalStorage } from '@/utils/utils'
const props = defineProps<{
file: string
value: string
}>()
const emit = defineEmits<{
Expand Down Expand Up @@ -127,21 +151,22 @@ watchEffect(async (onClenaup) => {
})
monacoEditor.value = editor
resumeEditorState(props.file, editor)
onClenaup(() => {
saveEditorState(props.file, editor)
editor.dispose()
})
})
watch(
() => props.value,
(val) => {
if (monaco && monacoEditor.value) {
const editorValue = monacoEditor.value.getValue()
if (val !== editorValue) {
monacoEditor.value.setValue(val)
}
}
() => [props.file, props.value],
([file, value], [oldFile]) => {
const editor = monacoEditor.value
if (editor == null) return
if (file !== oldFile) saveEditorState(oldFile, editor)
if (value !== editor.getValue()) editor.setValue(value)
if (file !== oldFile) resumeEditorState(file, editor)
}
)
Expand Down
16 changes: 8 additions & 8 deletions spx-gui/src/components/editor/panels/EditorPanels.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ watch(
{ immediate: true }
)
const lastSelectedSprite = shallowRef<string | null>(null)
const lastSelectedSound = shallowRef<string | null>(null)
const lastSelectedSpriteId = shallowRef<string | null>(null)
const lastSelectedSoundId = shallowRef<string | null>(null)
watch(
() => [editorCtx.project, editorCtx.project.selected] as const,
([project], [lastProject, lastSelected]) => {
if (project !== lastProject) {
lastSelectedSprite.value = null
lastSelectedSound.value = null
lastSelectedSpriteId.value = null
lastSelectedSoundId.value = null
return
}
if (lastSelected?.type === 'sprite') lastSelectedSprite.value = lastSelected.name
else if (lastSelected?.type === 'sound') lastSelectedSound.value = lastSelected.name
if (lastSelected?.type === 'sprite') lastSelectedSpriteId.value = lastSelected.id
else if (lastSelected?.type === 'sound') lastSelectedSoundId.value = lastSelected.id
}
)
Expand All @@ -60,7 +60,7 @@ watch(
) {
project.select({
type: 'sprite',
name: lastSelectedSprite.value ?? project.sprites[0].name
id: lastSelectedSpriteId.value ?? project.sprites[0].id
})
} else if (
expanded === 'sounds' &&
Expand All @@ -69,7 +69,7 @@ watch(
) {
project.select({
type: 'sound',
name: lastSelectedSound.value ?? editorCtx.project.sounds[0].name
id: lastSelectedSoundId.value ?? editorCtx.project.sounds[0].id
})
}
}
Expand Down
4 changes: 3 additions & 1 deletion spx-gui/src/components/editor/panels/sound/SoundItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ const handleRemove = useMessageHandle(
async () => {
const name = props.sound.name
const action = { name: { en: `Remove sound ${name}`, zh: `删除声音 ${name}` } }
await editorCtx.project.history.doAction(action, () => editorCtx.project.removeSound(name))
await editorCtx.project.history.doAction(action, () =>
editorCtx.project.removeSound(props.sound.id)
)
},
{
en: 'Failed to remove sound',
Expand Down
10 changes: 5 additions & 5 deletions spx-gui/src/components/editor/panels/sound/SoundsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</UIEmpty>
<SoundItem
v-for="sound in sounds"
:key="sound.name"
:key="sound.id"
:sound="sound"
:selected="isSelected(sound)"
@click="handleSoundClick(sound)"
Expand All @@ -37,7 +37,7 @@
<UIEmpty v-if="sounds.length === 0" size="small">
{{ $t({ en: 'Empty', zh: '无' }) }}
</UIEmpty>
<SoundSummaryItem v-for="sound in summaryListData.list" :key="sound.name" :sound="sound" />
<SoundSummaryItem v-for="sound in summaryListData.list" :key="sound.id" :sound="sound" />
</PanelSummaryList>
</template>
</CommonPanel>
Expand Down Expand Up @@ -73,11 +73,11 @@ const summaryList = ref<InstanceType<typeof PanelSummaryList>>()
const summaryListData = useSummaryList(sounds, () => summaryList.value?.listWrapper ?? null)
function isSelected(sound: Sound) {
return sound.name === editorCtx.project.selectedSound?.name
return sound.id === editorCtx.project.selectedSound?.id
}
function handleSoundClick(sound: Sound) {
editorCtx.project.select({ type: 'sound', name: sound.name })
editorCtx.project.select({ type: 'sound', id: sound.id })
}
const addFromLocalFile = useAddSoundFromLocalFile()
Expand All @@ -99,7 +99,7 @@ function handleRecord() {
}
function handleRecorded(sound: Sound) {
editorCtx.project.select({ type: 'sound', name: sound.name })
editorCtx.project.select({ type: 'sound', id: sound.id })
}
</script>

Expand Down
7 changes: 4 additions & 3 deletions spx-gui/src/components/editor/panels/sprite/SpriteItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ const [imgSrc, imgLoading] = useFileUrl(() => props.sprite.defaultCostume?.img)
const handleRemove = useMessageHandle(
async () => {
const sname = props.sprite.name
const action = { name: { en: `Remove sprite ${sname}`, zh: `删除精灵 ${sname}` } }
await editorCtx.project.history.doAction(action, () => editorCtx.project.removeSprite(sname))
const spriteId = props.sprite.id
const spriteName = props.sprite.name
const action = { name: { en: `Remove sprite ${spriteName}`, zh: `删除精灵 ${spriteName}` } }
await editorCtx.project.history.doAction(action, () => editorCtx.project.removeSprite(spriteId))
},
{
en: 'Failed to remove sprite',
Expand Down
8 changes: 4 additions & 4 deletions spx-gui/src/components/editor/panels/sprite/SpritesPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</UIEmpty>
<SpriteItem
v-for="sprite in sprites"
:key="sprite.name"
:key="sprite.id"
:sprite="sprite"
:selected="isSelected(sprite)"
@click="handleSpriteClick(sprite)"
Expand Down Expand Up @@ -52,7 +52,7 @@
</UIEmpty>
<SpriteSummaryItem
v-for="sprite in summaryListData.list"
:key="sprite.name"
:key="sprite.id"
:sprite="sprite"
/>
</PanelSummaryList>
Expand Down Expand Up @@ -93,11 +93,11 @@ const summaryList = ref<InstanceType<typeof PanelSummaryList>>()
const summaryListData = useSummaryList(sprites, () => summaryList.value?.listWrapper ?? null)
function isSelected(sprite: Sprite) {
return sprite.name === editorCtx.project.selectedSprite?.name
return sprite.id === editorCtx.project.selectedSprite?.id
}
function handleSpriteClick(sprite: Sprite) {
editorCtx.project.select({ type: 'sprite', name: sprite.name })
editorCtx.project.select({ type: 'sprite', id: sprite.id })
}
const addFromLocalFile = useAddSpriteFromLocalFile()
Expand Down
Loading

0 comments on commit cb8d721

Please sign in to comment.