Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Preview images on /code-checker #10240

Merged
merged 21 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
83a0a2e
add(codeChecker): initial mass preview
hassnian May 6, 2024
e59bd1e
Merge branch 'main' into isssue-10207
hassnian May 6, 2024
57dad11
Merge branch 'main' into isssue-10207
hassnian May 10, 2024
7614a8e
add(code checker): use of playground service to upload html file
hassnian May 10, 2024
f878d1b
add(code checker): upload file on zip upload
hassnian May 11, 2024
0b3a9b7
add(code checker): capture service previews with signed urls
hassnian May 12, 2024
7b3e421
Merge branch 'main' into isssue-10207
hassnian May 12, 2024
8de12c2
add(codeCheker): test out capture service with controls
hassnian May 13, 2024
c85bd77
add(codeChecker): confidential code notice
hassnian May 13, 2024
259b32b
add(codeChecker): add upload file prefix param
hassnian May 13, 2024
a430069
Merge branch 'main' into isssue-10207
hassnian May 17, 2024
cd10b72
ref(code checker): rename index content prop
hassnian May 17, 2024
33749b2
Merge branch 'main' into isssue-10207
hassnian May 18, 2024
4b4f556
ref(code checker): remove uploadPresigned request
hassnian May 20, 2024
7a3d787
ref(CodeCheckerMassPreview.ts): previews default prop
hassnian May 21, 2024
a899503
ref(playground.ts): use real public r2 bucket url
hassnian May 21, 2024
fb34390
ref(PreviewCard.vue): remove define expose and use emit to expose hash
hassnian May 21, 2024
cc2bb3c
ref(MassPreviewGrid.vue): make items only array of loading
hassnian May 21, 2024
f7e97d8
update(codeChecker): props watchers
hassnian May 21, 2024
58553ea
fix(playground.ts): extra / in object url
hassnian May 21, 2024
716ee51
Merge branch 'main' into isssue-10207
prury May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 25 additions & 1 deletion components/codeChecker/CodeChecker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,31 @@
class="w-full lg:w-1/2 flex flex-col items-center lg:mt-4 lg:items-end">
<!-- Content of the second column -->
<CodeCheckerPreviewCard
ref="codeCheckerPreview"
hassnian marked this conversation as resolved.
Show resolved Hide resolved
:selected-file="selectedFile"
:file-name="fileName"
:assets="assets"
:render="Boolean(selectedFile)"
:koda-renderer-used="fileValidity.kodaRendererUsed"
:reload-trigger="reloadTrigger"
@reload="startClock" />

<CodeCheckerMassPreview
v-if="selectedFile && index"
class="!mt-11"
:assets="assets"
:index="index" />

<div class="max-w-[490px] mt-11">
<hr v-if="selectedFile" class="my-2 bg-k-shade2 w-full !mb-11" />

<div class="flex items-center gap-5">
<NeoIcon icon="shield" class="!block text-k-grey" size="large" />
<p class="capitalize text-k-grey">
{{ $t('codeChecker.confidentialCode') }}
</p>
</div>
</div>
</div>
</div>
</template>
Expand Down Expand Up @@ -192,13 +210,15 @@ const validtyDefault: Validity = {

const selectedFile = ref<File | null>(null)
const assets = ref<AssetMessage[]>([])
const index = ref<string>()
const fileName = computed(() => selectedFile.value?.name)
const fileValidity = reactive<Validity>({ ...validtyDefault })
const errorMessage = ref('')
const renderStartTime = ref(0)
const renderEndTime = ref(0)
const reloadTrigger = ref(0)
const firstImage = ref<string>()
const codeCheckerPreview = ref()

const onFileSelected = async (file: File) => {
clear()
Expand All @@ -223,6 +243,7 @@ const onFileSelected = async (file: File) => {
fileValidity.consistent = 'unknown'
}

index.value = indexFile.content
assets.value = await createSandboxAssets(indexFile, entries)
}

Expand All @@ -245,7 +266,10 @@ function hasImage(dataURL: string): boolean {
}

useEventListener(window, 'message', async (res) => {
if (res.data?.type === 'kodahash/render/completed') {
if (
res.data?.type === 'kodahash/render/completed' &&
codeCheckerPreview.value?.hash === res.data.payload.hash
) {
const payload = res.data?.payload
renderEndTime.value = performance.now()
const duration = renderEndTime.value - renderStartTime.value
Expand Down
1 change: 1 addition & 0 deletions components/codeChecker/PreviewCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const exportAsPNG = async () => {
}

watch(() => props.reloadTrigger, replay)
defineExpose({ hash })
</script>
<style scoped lang="scss">
:deep(.o-drop__menu) {
Expand Down
23 changes: 15 additions & 8 deletions components/codeChecker/SandboxIFrame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<NeoIFrameMedia
:key="count"
:class="['sandbox-iframe', customClass]"
:iframe-id="config.iframeId"
:iframe-id="iframeId"
:src="iframeSrc"
sandbox="allow-scripts allow-same-origin"
title="render-preview"
Expand All @@ -16,12 +16,19 @@ import { postAssetsToSandbox } from './utils'
import config from './codechecker.config'
import { AssetMessage } from './types'

const props = defineProps<{
hash: string
assets: Array<AssetMessage>
count: number
customClass?: string | object
}>()
const props = withDefaults(
defineProps<{
hash: string
assets: Array<AssetMessage>
count: number
customClass?: string | object
iframeId?: string
}>(),
{
iframeId: config.iframeId,
customClass: '',
},
)

const emit = defineEmits(['update:count'])

Expand All @@ -38,6 +45,6 @@ watch(
)

function onIframeLoad() {
postAssetsToSandbox(props.assets)
postAssetsToSandbox(props.assets, props.iframeId)
}
</script>
78 changes: 78 additions & 0 deletions components/codeChecker/massPreview/Canvas.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<div>
<div class="flex justify-between items-center">
<p class="font-bold capitalize">
{{ $t('codeChecker.testOutPerformance') }}
</p>

<NeoSwitch v-model="active" />
</div>

<transition name="slide">
<div v-if="active" class="!mt-6">
<CodeCheckerMassPreviewControls
v-model="amount"
:previews="previews"
@retry="generateMassPreview" />

<CodeCheckerMassPreviewGrid :items="previews" class="!mt-4">
<template #default="{ item }: { item: CanvasPreviewItem }">
<CodeCheckerSandboxIFrame
:hash="item.hash"
:assets="assets"
:count="1"
:iframe-id="item.hash"
class="border" />
</template>
</CodeCheckerMassPreviewGrid>
</div>
</transition>
</div>
</template>
<script lang="ts" setup>
import { NeoSwitch } from '@kodadot1/brick'
import { AssetMessage } from '../types'
import { CanvasPreviewItem } from './types'
import { generateRandomHash } from '../utils'

const props = defineProps<{
assets: Array<AssetMessage>
previews: number
}>()

const active = ref(false)
const amount = ref(props.previews)
const previews = ref<CanvasPreviewItem[]>([])

const generateMassPreview = () => {
previews.value = Array.from({ length: amount.value }).map(() => ({
hash: generateRandomHash(),
startedAt: performance.now(),
loading: true,
}))
}

useEventListener(window, 'message', async (res) => {
const hash = res.data.payload.hash
if (
res.data?.type === 'kodahash/render/completed' &&
previews.value.map((p) => p.hash).includes(hash)
) {
previews.value = previews.value.map((preview) =>
preview.hash === hash
? { ...preview, renderedAt: performance.now(), loading: false }
: preview,
)
}
})

watch(
[active, () => props.assets],
([active]) => {
if (active) {
generateMassPreview()
}
},
{ immediate: true },
hassnian marked this conversation as resolved.
Show resolved Hide resolved
)
</script>
168 changes: 168 additions & 0 deletions components/codeChecker/massPreview/Capture.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<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="previews"
@retry="generateMassPreview" />

<CodeCheckerMassPreviewGrid :items="previews">
<template #default="{ item }: { item: CapturePreviewItem }">
<BaseMediaItem v-if="item.image" :src="item.image" class="border" />
</template>
</CodeCheckerMassPreviewGrid>
</div>
</transition>
</div>
</template>
<script lang="ts" setup>
import { NeoSwitch } from '@kodadot1/brick'
import { makeScreenshot } from '@/services/capture'
import { uploadFile, uploadPresigned } from '@/services/playground'
import { AssetMessage } from '../types'
import { CapturePreviewItem } from './types'
import { generateRandomHash } from '../utils'
import { AssetElementMap, AssetReplaceElement } from './utils'

const props = defineProps<{
assets: Array<AssetMessage>
index: string
previews: number
}>()

const { $i18n } = useNuxtApp()

const previews = 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 parser = new DOMParser()
const doc = parser.parseFromString(props.index, 'text/html')

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',
})
indexKey.value = key
} catch (error) {
dangerMessage(`${$i18n.t('codeChecker.failedUploadingIndex')}: ${error}`)
} finally {
uploading.value = false
}
}

const initCapture = async () => {
initScreenshot()
}

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

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

previews.value.forEach(async (preview) => {
try {
const { url } = await uploadPresigned(indexKey.value!, {
hash: preview.hash,
})

preview = { ...preview, startedAt: performance.now() }

const response = await makeScreenshot(url)

preview = {
...preview,
image: URL.createObjectURL(response),
renderedAt: performance.now(),
}
} catch (error) {
} finally {
preview = { ...preview, loading: false }
}

updatePreview(preview)
})
}

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

await initCapture()
}

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

watch(
[active, () => props.assets],
([active]) => {
if (active) {
generateMassPreview()
}
},
{ immediate: true },
)
</script>
22 changes: 22 additions & 0 deletions components/codeChecker/massPreview/CodeCheckerMassPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div class="w-full max-w-[490px] flex flex-col gap-4">
<CodeCheckerMassPreviewCanvas
:assets="assets"
:previews="PREVIEWS_AMOUNT" />

<CodeCheckerMassPreviewCapture
:assets="assets"
:index="index"
:previews="PREVIEWS_AMOUNT" />
</div>
</template>
<script lang="ts" setup>
import { AssetMessage } from '../types'

const PREVIEWS_AMOUNT = 12
hassnian marked this conversation as resolved.
Show resolved Hide resolved

defineProps<{
assets: Array<AssetMessage>
index: string
}>()
</script>