Skip to content

Commit

Permalink
take screenshot with StageViewer
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Sep 30, 2024
1 parent 0cbf812 commit 1339b50
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,13 @@ effect(async () => {
await nextTick() // Wait to ensure the selected node updated by Konva
transformerNode.nodes([selectedNode])
})
defineExpose({
withHidden<T>(callback: () => T): T {
transformer.value?.getNode().hide()
const ret = callback()
transformer.value?.getNode().show()
return ret
}
})
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
/>
</v-layer>
<v-layer>
<NodeTransformer :node-ready-map="nodeReadyMap" />
<NodeTransformer ref="nodeTransformerRef" :node-ready-map="nodeReadyMap" />
</v-layer>
</v-stage>
<UIDropdown trigger="manual" :visible="menuVisible" :pos="menuPos" placement="bottom-start">
Expand All @@ -52,9 +52,11 @@ import type { Stage } from 'konva/lib/Stage'
import { UIDropdown, UILoading, UIMenu, UIMenuItem } from '@/components/ui'
import { useContentSize } from '@/utils/dom'
import { useFileUrl } from '@/utils/file'
import { until, untilNotNull } from '@/utils/utils'
import type { Sprite } from '@/models/sprite'
import { MapMode } from '@/models/stage'
import type { Widget } from '@/models/widget'
import { createFileWithWebUrl } from '@/models/common/cloud'
import { useEditorCtx } from '../../EditorContextProvider.vue'
import NodeTransformer from './NodeTransformer.vue'
import SpriteNode from './SpriteNode.vue'
Expand All @@ -69,6 +71,7 @@ const stageRef = ref<{
getStage(): Konva.Stage
}>()
const mapSize = computed(() => editorCtx.project.stage.getMapSize())
const nodeTransformerRef = ref<InstanceType<typeof NodeTransformer>>()
const nodeReadyMap = reactive(new Map<string, boolean>())
Expand Down Expand Up @@ -239,6 +242,24 @@ async function moveZorder(direction: 'up' | 'down' | 'top' | 'bottom') {
})
menuVisible.value = false
}
async function takeScreenshot(name: string) {
const stage = await untilNotNull(stageRef)
const nodeTransformer = await untilNotNull(nodeTransformerRef)
await until(() => !loading.value)
// Omit transform control when taking screenshot
const dataUrl = nodeTransformer.withHidden(() =>
stage.getStage().toDataURL({
mimeType: 'image/jpeg'
})
)
return createFileWithWebUrl(dataUrl, `${name}.jpg`)
}
watchEffect((onCleanup) => {
const unbind = editorCtx.project.bindScreenshotTaker(takeScreenshot)
onCleanup(unbind)
})
</script>
<style scoped>
.stage-viewer {
Expand Down
24 changes: 18 additions & 6 deletions spx-gui/src/models/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { join } from '@/utils/path'
import { debounce } from 'lodash'
import { Disposable } from '@/utils/disposable'
import { IsPublic, type ProjectData } from '@/apis/project'
import { toConfig, type Files, fromConfig } from '../common/file'
import { toConfig, type Files, fromConfig, File } from '../common/file'
import * as cloudHelper from '../common/cloud'
import * as localHelper from '../common/local'
import * as gbpHelper from '../common/gbp'
Expand All @@ -23,7 +23,7 @@ import type { RawWidgetConfig } from '../widget'
import { History } from './history'
import Mutex from '@/utils/mutex'
import { Cancelled } from '@/utils/exception'
import { untilConditionMet } from '@/utils/utils'
import { until } from '@/utils/utils'

export type { Action } from './history'

Expand Down Expand Up @@ -92,6 +92,8 @@ type RawProjectConfig = RawStageConfig & {
// TODO: camera
}

export type ScreenshotTaker = (name: string) => Promise<File>

export class Project extends Disposable {
id?: string
owner?: string
Expand Down Expand Up @@ -228,6 +230,17 @@ export class Project extends Disposable {
history: History
historyMutex = new Mutex()

private screenshotTaker: ScreenshotTaker | null = null
bindScreenshotTaker(st: ScreenshotTaker) {
if (this.screenshotTaker != null) throw new Error('screenshotTaker already bound')
this.screenshotTaker = st
return () => {
if (this.screenshotTaker === st) {
this.screenshotTaker = null
}
}
}

constructor() {
super()
const reactiveThis = reactive(this) as this
Expand Down Expand Up @@ -390,6 +403,8 @@ export class Project extends Disposable {
try {
if (this.isDisposed) throw new Error('disposed')
const [metadata, files] = await this.export()
// TODO: take screenshot & save as thumbnail
// test screenshot function with `window.open(await (await project.screenshotTaker()).url(() => null))` in console
const saved = await cloudHelper.save(metadata, files, abortController.signal)
this.loadMetadata(saved.metadata)
this.lastSyncedFilesHash = await hashFiles(files)
Expand Down Expand Up @@ -449,10 +464,7 @@ export class Project extends Disposable {

try {
if (this.isSavingToCloud) {
await untilConditionMet(
() => this.isSavingToCloud,
() => !this.isSavingToCloud
)
await until(() => !this.isSavingToCloud)
}

if (this.hasUnsyncedChanges) await this.saveToCloud()
Expand Down
7 changes: 6 additions & 1 deletion spx-gui/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,19 @@ export function untilNotNull<T>(valueSource: WatchSource<T | null | undefined>)
) as Promise<NonNullable<T>>
}

/** Wait until given condition is met. */
export async function until(conditionSource: WatchSource<boolean>) {
await untilConditionMet(conditionSource, (c) => c)
}

/**
* Wait until a given condition is met for a (reactive) value.
* ```ts
* const foo = await untilConditionMet(fooRef, (value) => value !== null)
* const bar = await untilConditionMet(() => getBar(), (value) => value > 10)
* ```
*/
export function untilConditionMet<T>(
function untilConditionMet<T>(
valueSource: WatchSource<T>,
condition: (value: T) => boolean
): Promise<T> {
Expand Down

0 comments on commit 1339b50

Please sign in to comment.