Skip to content

Commit

Permalink
Merge pull request #909 from goplus/dev
Browse files Browse the repository at this point in the history
Release v1.4.4
  • Loading branch information
nighca authored Sep 14, 2024
2 parents cb8d721 + bb66c12 commit ad6a994
Show file tree
Hide file tree
Showing 17 changed files with 329 additions and 281 deletions.
2 changes: 1 addition & 1 deletion spx-backend/cmd/spx-backend/gop_autogen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion spx-backend/cmd/spx-backend/main.yap
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ go func() {
}()
<-stopCtx.Done()
if serverErr != nil && !errors.Is(serverErr, http.ErrServerClosed) {
logger.Fatalln("Server error:", err)
logger.Fatalln("Server error:", serverErr)
}

shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
Expand Down
71 changes: 71 additions & 0 deletions spx-gui/src/components/project/ProjectSharingLinkModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<UIFormModal
:title="$t({ en: 'Project sharing link', zh: '项目分享链接' })"
:visible="props.visible"
:auto-focus="false"
@update:visible="emit('cancelled')"
>
<div class="desc">
{{
$t({
en: 'A sharing link to the project has been created. Feel free to copy it below to share the project with others.',
zh: '项目的分享链接已生成。请复制下方链接,与他人分享该项目。'
})
}}
</div>
<div class="link">
<UITextInput :value="projectSharingLink" :readonly="true" @focus="$event.target.select()" />
<UIButton class="copy-button" :loading="handleCopy.isLoading.value" @click="handleCopy.fn">
{{ $t({ en: 'Copy', zh: '复制' }) }}
</UIButton>
</div>
</UIFormModal>
</template>

<script setup lang="ts">
import { UIButton, UIFormModal, UITextInput } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
import { Project } from '@/models/project'
import { computed } from 'vue'
import { getProjectShareRoute } from '@/router'
const props = defineProps<{
visible: boolean
project: Project
}>()
const emit = defineEmits<{
cancelled: []
resolved: []
}>()
const projectSharingLink = computed(() => {
const { owner, name } = props.project
// TODO: the check should be unnecessary
if (owner == null || name == null) throw new Error(`owner (${owner}), name (${name}) required`)
return `${location.origin}${getProjectShareRoute(owner, name)}`
})
const handleCopy = useMessageHandle(
() => navigator.clipboard.writeText(projectSharingLink.value),
{ en: 'Failed to copy link to clipboard', zh: '分享链接复制到剪贴板失败' },
{ en: 'Link copied to clipboard', zh: '分享链接已复制到剪贴板' }
)
</script>

<style scoped lang="scss">
.desc {
color: var(--ui-color-grey-900);
margin-bottom: 8px;
}
.link {
display: flex;
margin-bottom: 16px;
}
.copy-button {
margin-left: 12px;
white-space: nowrap;
}
</style>
108 changes: 38 additions & 70 deletions spx-gui/src/components/project/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useRouter } from 'vue-router'
import { useModal, useConfirmDialog, useMessage } from '@/components/ui'
import { useModal, useConfirmDialog } from '@/components/ui'
import { IsPublic, deleteProject } from '@/apis/project'
import ProjectCreateModal from './ProjectCreateModal.vue'
import ProjectOpenModal from './ProjectOpenModal.vue'
import { useI18n, type LocaleMessage } from '@/utils/i18n'
import ProjectSharingLinkModal from './ProjectSharingLinkModal.vue'
import { useI18n } from '@/utils/i18n'
import type { Project } from '@/models/project'
import { getProjectEditorRoute, getProjectShareRoute } from '@/router'
import { getProjectEditorRoute } from '@/router'

export function useCreateProject() {
const modal = useModal(ProjectCreateModal)
Expand All @@ -26,8 +27,8 @@ export function useOpenProject() {
}

export function useRemoveProject() {
const withConfirm = useConfirmDialog()
const { t } = useI18n()
const withConfirm = useConfirmDialog()

return async function removeProject(owner: string, name: string) {
return withConfirm({
Expand All @@ -41,94 +42,61 @@ export function useRemoveProject() {
}
}

/** Copy sharing link for given project */
export function useShareProject() {
const m = useMessage()
const { t } = useI18n()
return async function shareProject(project: Project) {
const { owner, name } = project
// TODO: the check should be unnecessary
if (owner == null || name == null) throw new Error(`owner (${owner}), name (${name}) required`)
await navigator.clipboard.writeText(`${location.origin}${getProjectShareRoute(owner, name)}`)
m.success(t({ en: 'Link copied to clipboard', zh: '分享链接已复制到剪贴板' }))
export function useCreateProjectSharingLink() {
const modal = useModal(ProjectSharingLinkModal)

return async function createProjectSharingLink(project: Project) {
await modal({ project })
}
}

/**
* Save and share given project
* - save current project state
* Share given project
* - make project public
* - copy sharing link
*/
export function useSaveAndShareProject() {
export function useShareProject() {
const { t } = useI18n()
const withConfirm = useConfirmDialog()
const shareProject = useShareProject()
const createProjectSharingLink = useCreateProjectSharingLink()

async function saveAndShare(project: Project) {
if (project.isPublic !== IsPublic.public || project.hasUnsyncedChanges) {
project.setPublic(IsPublic.public)
await project.saveToCloud()
}
await shareProject(project)
}

async function saveAndShareWithConfirm(project: Project, confirmMessage: LocaleMessage) {
await withConfirm({
title: t({ en: 'Share project', zh: '分享项目' }),
content: t(confirmMessage),
confirmHandler: () => saveAndShare(project)
})
async function makePublic(project: Project) {
project.setPublic(IsPublic.public)
await project.saveToCloud()
}

return async function saveAndShareProject(project: Project) {
const { isPublic, hasUnsyncedChanges } = project
if (isPublic == null) throw new Error('isPublic required')

if (isPublic === IsPublic.public) {
if (!hasUnsyncedChanges) return saveAndShare(project)
else
return saveAndShareWithConfirm(project, {
en: "To share the project, we will save the project's current state to cloud",
zh: '分享操作会将当前项目状态保存到云端'
})
} else {
if (!hasUnsyncedChanges)
return saveAndShareWithConfirm(project, {
en: 'To share the project, we will make the project public',
zh: '分享操作会将当前项目设置为公开'
})
else
return saveAndShareWithConfirm(project, {
en: "To share the project, we will save the project's current state to cloud & make it public",
zh: '分享操作会将当前项目状态保存到云端,并将项目设置为公开'
})
return async function shareProject(project: Project) {
if (project.isPublic !== IsPublic.public) {
await withConfirm({
title: t({ en: 'Share project', zh: '分享项目' }),
content: t({
en: 'To share the current project, it will be made public. Would you like to proceed?',
zh: '为了分享当前项目,它将被设置为公开。确认继续吗?'
}),
confirmHandler: () => makePublic(project)
})
}
return createProjectSharingLink(project)
}
}

export function useStopSharingProject() {
const withConfirm = useConfirmDialog()
const { t } = useI18n()
const withConfirm = useConfirmDialog()

async function makePersonal(project: Project) {
project.setPublic(IsPublic.personal)
await project.saveToCloud()
}

return async function stopSharingProject(project: Project) {
let confirmMessage = {
en: 'If sharing stopped, others will no longer have permission to access the project, and all project-sharing links will expire',
zh: '停止分享后,其他人不再可以访问项目,所有的项目分享链接也将失效'
}
if (project.hasUnsyncedChanges) {
confirmMessage = {
en: `The project's current state will be saved to cloud. ${confirmMessage.en}`,
zh: `当前项目状态将被保存到云端;${confirmMessage.zh}`
}
}
return withConfirm({
title: t({ en: 'Stop sharing project', zh: '停止分享项目' }),
content: t(confirmMessage),
confirmHandler: async () => {
project.setPublic(IsPublic.personal)
await project.saveToCloud()
}
content: t({
en: 'If sharing is stopped, others will no longer have access to the current project, and its sharing links will expire. Would you like to proceed?',
zh: '如果停止分享,其他人将无法访问当前项目,且分享链接将会失效。确认继续吗?'
}),
confirmHandler: () => makePersonal(project)
})
}
}
27 changes: 9 additions & 18 deletions spx-gui/src/components/project/runner/IframeDisplay.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<template>
<div class="iframe-container">
<iframe ref="iframe" class="iframe" frameborder="0" src="about:blank" />
<UILoading :visible="loading" cover />
</div>
<iframe ref="iframe" class="iframe" frameborder="0" src="about:blank" />
</template>
<script setup lang="ts">
const emit = defineEmits<{
console: [type: 'log' | 'warn', args: unknown[]]
loaded: []
}>()
interface IframeWindow extends Window {
Expand All @@ -19,16 +17,16 @@ import rawRunnerHtml from '@/assets/ispx/runner.html?raw'
import wasmExecUrl from '@/assets/wasm_exec.js?url'
import wasmUrl from '@/assets/ispx/main.wasm?url'
import { watch } from 'vue'
import { UILoading } from '@/components/ui'
const { zipData } = defineProps<{ zipData: ArrayBuffer | Uint8Array }>()
const props = defineProps<{ zipData: ArrayBuffer | Uint8Array }>()
const iframe = ref<HTMLIFrameElement>()
const loading = ref(true)
watch(iframe, () => {
const iframeWindow = iframe.value?.contentWindow as IframeWindow | null | undefined
if (!iframe.value) {
return
}
const iframeWindow = iframe.value.contentWindow as IframeWindow | null
if (!iframeWindow) {
return
}
Expand All @@ -39,11 +37,11 @@ watch(iframe, () => {
iframeWindow.document.write(runnerHtml) // This resets the iframe's content, including its window object
iframeWindow.addEventListener('wasmReady', () => {
iframeWindow.startWithZipBuffer(zipData)
iframeWindow.startWithZipBuffer(props.zipData)
const canvas = iframeWindow.document.querySelector('canvas')
if (canvas == null) throw new Error('canvas expected in iframe')
canvas.focus() // focus to canvas by default, so the user can interact with the game immediately
loading.value = false
emit('loaded')
})
iframeWindow.console.log = function (...args: unknown[]) {
// eslint-disable-next-line no-console
Expand All @@ -58,13 +56,6 @@ watch(iframe, () => {
</script>

<style scoped lang="scss">
.iframe-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.iframe {
width: 100%;
height: 100%;
Expand Down
21 changes: 20 additions & 1 deletion spx-gui/src/components/project/runner/ProjectRunner.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
<template>
<IframeDisplay v-if="zipData" :zip-data="zipData" @console="handleConsole" />
<div class="iframe-container">
<IframeDisplay
v-if="zipData"
:zip-data="zipData"
@console="handleConsole"
@loaded="loading = false"
/>
<UILoading :visible="loading" cover />
</div>
</template>

<script lang="ts" setup>
import { onUnmounted, ref } from 'vue'
import { registerPlayer } from '@/utils/player-registry'
import { Project } from '@/models/project'
import IframeDisplay from './IframeDisplay.vue'
import { UILoading } from '@/components/ui'
const props = defineProps<{ project: Project }>()
const zipData = ref<ArrayBuffer | null>(null)
const loading = ref(true)
const emit = defineEmits<{
console: [type: 'log' | 'warn', args: unknown[]]
Expand All @@ -33,6 +43,7 @@ onUnmounted(() => {
defineExpose({
run: async () => {
loading.value = true
registered.onStart()
const gbpFile = await props.project.exportGbpFile()
zipData.value = await gbpFile.arrayBuffer()
Expand All @@ -43,3 +54,11 @@ defineExpose({
}
})
</script>
<style scoped lang="scss">
.iframe-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
</style>
8 changes: 4 additions & 4 deletions spx-gui/src/components/project/runner/RunnerContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import { onMounted, ref, type CSSProperties, watch, nextTick } from 'vue'
import dayjs from 'dayjs'
import type { Project } from '@/models/project'
import ProjectRunner from './ProjectRunner.vue'
import { useSaveAndShareProject, useShareProject } from '@/components/project'
import { useShareProject, useCreateProjectSharingLink } from '@/components/project'
import { UIButton, UIIcon, UIModalClose } from '@/components/ui'
import { useMessageHandle } from '@/utils/exception'
Expand Down Expand Up @@ -122,14 +122,14 @@ const handleRerun = () => {
consoleMessages.value = []
}
const saveAndShareProject = useSaveAndShareProject()
const shareProject = useShareProject()
const createProjectSharingLink = useCreateProjectSharingLink()
const handleShare = useMessageHandle(
() => {
if (props.mode === 'debug') {
return saveAndShareProject(props.project)
} else {
return shareProject(props.project)
} else {
return createProjectSharingLink(props.project)
}
},
{ en: 'Failed to share project', zh: '分享项目失败' }
Expand Down
Loading

0 comments on commit ad6a994

Please sign in to comment.