Skip to content

Commit

Permalink
hooks for file url
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Apr 9, 2024
1 parent da45ef7 commit bfb456b
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 61 deletions.
11 changes: 3 additions & 8 deletions spx-gui/src/components/editor/panels/sprite/SpriteItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
</template>

<script setup lang="ts">
import { ref, effect, computed } from 'vue'
import { computed } from 'vue'
import { useFileUrl } from '@/utils/file'
import { Sprite } from '@/models/sprite'
const props = defineProps<{
Expand All @@ -19,13 +20,7 @@ const emit = defineEmits<{
remove: []
}>()
const imgSrc = ref<string | null>(null)
effect(async () => {
const img = props.sprite.costume?.img
imgSrc.value = img != null ? await img.url() : null // TODO: race condition
})
const imgSrc = useFileUrl(() => props.sprite.costume?.img)
const imgStyle = computed(() => imgSrc.value && { backgroundImage: `url("${imgSrc.value}")` })
</script>

Expand Down
14 changes: 3 additions & 11 deletions spx-gui/src/components/editor/panels/stage/StagePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
</template>

<script setup lang="ts">
import { computed, ref, effect } from 'vue'
import { computed } from 'vue'
import { NDropdown, NButton } from 'naive-ui'
import { useAddAssetFromLibrary, useAddAssetToLibrary } from '@/components/library'
import { useMessageHandle } from '@/utils/exception'
import { useI18n } from '@/utils/i18n'
import { AssetType } from '@/apis/asset'
import { selectImg } from '@/utils/file'
import { selectImg, useFileUrl } from '@/utils/file'
import { fromNativeFile } from '@/models/common/file'
import { Backdrop } from '@/models/backdrop'
import { stripExt } from '@/utils/path'
Expand All @@ -40,15 +40,7 @@ function select() {
}
const backdrop = computed(() => editorCtx.project.stage.backdrop)
// TODO: we may need a special img component for [File](src/models/common/file.ts)
const imgSrc = ref<string | null>(null)
effect(async () => {
const img = backdrop.value?.img
imgSrc.value = img != null ? await img.url() : null // TODO: race condition
})
const imgSrc = useFileUrl(() => backdrop.value?.img)
const imgStyle = computed(() => imgSrc.value && { backgroundImage: `url("${imgSrc.value}")` })
const handleUpload = useMessageHandle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
</v-layer>
</template>
<script setup lang="ts">
import { defineProps, watch, ref } from 'vue'
import { defineProps } from 'vue'
import { useImgFile } from '@/utils/file'
import type { Stage } from '@/models/stage'
import type { Size } from '@/models/common'

Expand All @@ -35,18 +36,5 @@ const props = defineProps<{
stage: Stage
}>()

const image = ref<HTMLImageElement>()

watch(
() => props.stage.backdrop?.img,
async (backdropImg) => {
image.value?.remove()
if (backdropImg != null) {
const _image = new window.Image()
_image.src = await backdropImg.url()
image.value = _image
}
},
{ immediate: true }
)
const image = useImgFile(() => props.stage.backdrop?.img)
</script>
22 changes: 3 additions & 19 deletions spx-gui/src/components/editor/preview/stage-viewer/Costume.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import type { Sprite } from '@/models/sprite'
import type { SpriteDragMoveEvent, SpriteApperanceChangeEvent } from './common'
import type { Rect } from 'konva/lib/shapes/Rect'
import type { Size } from '@/models/common'
import { useImgFile } from '@/utils/file'

// ----------props & emit------------------------------------
const props = defineProps<{
sprite: Sprite
Expand All @@ -57,7 +59,7 @@ const displayScale = computed(
)

// ----------data related -----------------------------------
const image = ref<HTMLImageElement>()
const image = useImgFile(() => currentCostume.value?.img)
const costume = ref()
// ----------computed properties-----------------------------
// Computed spx's sprite position to konva's relative position by about changing sprite postion
Expand Down Expand Up @@ -88,24 +90,6 @@ watch(
}
)

watch(
() => currentCostume.value,
async (new_costume) => {
if (new_costume != null) {
const _image = new window.Image()
_image.src = await new_costume.img.url()
_image.onload = () => {
image.value = _image
}
} else {
image.value?.remove()
}
},
{
immediate: true
}
)

// ----------methods-----------------------------------------
/**
* @description: map spx's sprite position to konva's relative position
Expand Down
2 changes: 1 addition & 1 deletion spx-gui/src/models/common/disposable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

export type Disposer = () => void

export abstract class Disposble {
export class Disposble {
_disposers: Disposer[] = []

addDisposer(disposer: Disposer) {
Expand Down
12 changes: 9 additions & 3 deletions spx-gui/src/models/common/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import { getMimeFromExt } from '@/utils/file'
import { extname } from '@/utils/path'
import type { Disposer } from './disposable'
import { Cancelled } from '@/utils/exception'

export type Options = {
/** MIME type of file */
Expand Down Expand Up @@ -40,10 +42,14 @@ export class File {
}))
}

// TODO: remember to do URL.revokeObjectURL
async url() {
async url(onCleanup: (disposer: Disposer) => void) {
let cancelled = false
onCleanup(() => cancelled = true)
const ab = await this.arrayBuffer()
return URL.createObjectURL(new Blob([ab]))
if (cancelled) throw new Cancelled()
const url = URL.createObjectURL(new Blob([ab]))
onCleanup(() => URL.revokeObjectURL(url))
return url
}
}

Expand Down
9 changes: 6 additions & 3 deletions spx-gui/src/models/costume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { File, type Files } from './common/file'
import { type Size } from './common'
import type { Sprite } from './sprite'
import { getCostumeName, validateCostumeName } from './common/asset'
import { Disposble } from './common/disposable'

export type CostumeInits = {
x?: number
Expand Down Expand Up @@ -57,21 +58,23 @@ export class Costume {
}

async getSize() {
const imgUrl = await this.img.url()
const d = new Disposble()
const imgUrl = await this.img.url(fn => d.addDisposer(fn))
return new Promise<Size>((resolve, reject) => {
const img = new window.Image()
d.addDisposer(() => img.remove())
img.src = imgUrl
img.onload = () => {
resolve({
width: img.width / this.bitmapResolution,
height: img.height / this.bitmapResolution
})
img.remove()
}
img.onerror = (e) => {
reject(new Error(`load image failed: ${e.toString()}`))
img.remove()
}
}).finally(() => {
d.dispose()
})
}

Expand Down
40 changes: 39 additions & 1 deletion spx-gui/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ref, watch, type WatchSource } from 'vue'
import type { File } from '@/models/common/file'

/**
* Map file extension to mime type.
*/
Expand Down Expand Up @@ -32,7 +35,7 @@ export type FileSelectOptions = {
}

function _selectFile({ accept = '', multiple = false }: FileSelectOptions) {
return new Promise<File[]>((resolve) => {
return new Promise<globalThis.File[]>((resolve) => {
const input = document.createElement('input')
input.type = 'file'
input.accept = accept
Expand Down Expand Up @@ -70,3 +73,38 @@ export function selectImgs() {
const accept = imgExts.map((ext) => `.${ext}`).join(',')
return selectFiles({ accept })
}

/** Get url for File */
export function useFileUrl(fileSource: WatchSource<File | undefined>) {
const urlRef = ref<string | null>(null)
watch(
fileSource,
(file, _, onCleanup) => {
if (file == null) return
file.url(onCleanup).then(url => {
urlRef.value = url
})
},
{ immediate: true }
)
return urlRef
}

export function useImgFile(fileSource: WatchSource<File | undefined>) {
const urlRef = useFileUrl(fileSource)
const imgRef = ref<HTMLImageElement | null>(null)

watch(urlRef, (url, _, onCleanup) => {
onCleanup(() => {
imgRef.value?.remove()
imgRef.value = null
})
if (url != null) {
const img = new window.Image()
img.src = url
imgRef.value = img
}
})

return imgRef
}

0 comments on commit bfb456b

Please sign in to comment.