kodadot/nft-gallery

View on GitHub
components/codeChecker/massPreview/Capture.vue

Summary

Maintainability
Test Coverage
<template>
  <div>
    <div class="flex justify-between items-center">
      <p class="font-bold capitalize">
        {{ $t('codeChecker.testOutCapture') }}
      </p>

      <span
        v-if="uploading"
        class="text-sm text-k-grey capitalize"
      >
        {{ $t('codeChecker.uploadingFile') }}</span>
      <NeoSwitch
        v-else-if="indexKey"
        v-model="active"
      />
    </div>

    <transition name="slide">
      <div
        v-if="active"
        class="flex flex-col gap-4 !mt-6"
      >
        <CodeCheckerMassPreviewControls
          v-model="previewAmount"
          :previews="previewItems"
          hide-average
          @retry="generateMassPreview"
        />

        <CodeCheckerMassPreviewGrid :items="previewItems.map((p) => p.loading)">
          <template #default="{ index }">
            <iframe
              title="preview"
              :src="previewItems[index].image"
              class="w-full h-full border border-black border-solid"
            />
          </template>
        </CodeCheckerMassPreviewGrid>
      </div>
    </transition>
  </div>
</template>

<script lang="ts" setup>
import { NeoSwitch } from '@kodadot1/brick'
import type { AssetMessage } from '../types'
import { generateRandomHash, getDocumentFromString } from '../utils'
import type { CapturePreviewItem } from './types'
import { AssetElementMap, AssetReplaceElement } from './utils'
import { getObjectUrl, getUpload, uploadFile } from '@/services/playground'
import { IFRAME_BLOB_URI } from '@/services/capture'

const emit = defineEmits(['upload'])
const props = withDefaults(
  defineProps<{
    assets: Array<AssetMessage>
    indexContent: string
    previews?: number
  }>(),
  {
    previews: 12,
  },
)

const { $i18n } = useNuxtApp()

const previewItems = ref<CapturePreviewItem[]>([])
const previewAmount = ref(props.previews)
const active = ref(false)
const uploading = ref(false)
const indexKey = ref<string>()

const replaceAssetContent = async (doc: Document, asset: AssetMessage) => {
  const response = await $fetch<string>(asset.src)

  const { src: srcAttribute, tag } = AssetElementMap[asset.type]

  const element = doc.querySelector(
    `${tag}[${srcAttribute}="${asset.originalSrc}"]`,
  )

  if (!element) {
    return
  }

  const assetReplace = AssetReplaceElement[asset.type] ?? null

  assetReplace?.({ doc, content: response, element })
}

const buildIndexFile = async (): Promise<Blob> => {
  const doc = getDocumentFromString(props.indexContent)

  await Promise.all(
    props.assets.map(asset => replaceAssetContent(doc, asset)),
  )

  return new Blob([doc.documentElement.outerHTML], {
    type: 'text/html',
  })
}

const uploadIndex = async () => {
  try {
    uploading.value = true
    const file = await buildIndexFile()
    const { key } = await uploadFile({
      file,
      fileName: 'index.html',
      prefix: 'codeChecker',
    })
    await exponentialBackoff(() => getUpload(key)).catch(console.log)
    indexKey.value = key
    emit('upload', getObjectUrl(key))
  }
  catch (error) {
    dangerMessage(`${$i18n.t('codeChecker.failedUploadingIndex')}: ${error}`)
  }
  finally {
    uploading.value = false
  }
}

const updatePreview = (preview: CapturePreviewItem) => {
  previewItems.value = previewItems.value.map(p =>
    p.hash === preview.hash ? preview : p,
  )
}

const initScreenshot = () => {
  if (!indexKey.value) {
    return
  }

  previewItems.value.forEach(async (preview) => {
    try {
      let previewUrl = getObjectUrl(indexKey.value!)
      previewUrl += `?hash=${preview.hash}`

      const iframeUrl = new URL(IFRAME_BLOB_URI)
      iframeUrl.searchParams.set('url', previewUrl)

      preview = {
        ...preview,
        image: iframeUrl.toString(),
      }
    }
    catch (error) {
      console.error(error)
    }
    finally {
      preview = { ...preview, loading: false }
    }

    updatePreview(preview)
  })
}

const generateMassPreview = async () => {
  previewItems.value = Array.from({ length: previewAmount.value }).map(() => ({
    hash: generateRandomHash(),
    loading: true,
  }))

  initScreenshot()
}

watch(
  [() => props.assets, () => props.indexContent],
  ([assets, indexContent]) => {
    if (assets.length && indexContent) {
      uploadIndex()
    }
  },
  { immediate: true },
)

watch(active, (active) => {
  if (active) {
    generateMassPreview()
  }
})
</script>