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: initial assets tab #120

Merged
merged 9 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions packages/devtools-ui-kit/src/components/NCodeBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,47 @@
// This components requires to run in DevTools to render correctly
import { devToolsClient } from '../runtime/client'

defineProps<{
code: string
lang?: string
}>()
withDefaults(
defineProps<{
code: string
lang?: string
lines?: boolean
}>(), {
lines: true,
},
)
</script>

<template>
<template v-if="lang && devToolsClient?.devtools?.renderCodeHighlight">
<pre class="n-code-block" v-html="devToolsClient.devtools.renderCodeHighlight(code, lang)" />
<pre
class="n-code-block"
:class="lines ? 'n-code-block-lines' : ''"
v-html="devToolsClient.devtools.renderCodeHighlight(code, lang)"
/>
</template>
<template v-else>
<pre class="n-code-block" v-text="code" />
<pre
class="n-code-block"
:class="lines ? 'n-code-block-lines' : ''"
v-text="code"
/>
</template>
</template>

<style>
.n-code-block-lines .shiki code {
counter-reset: step;
counter-increment: step calc(var(--start, 1) - 1);
}
.n-code-block-lines .shiki code .line::before {
content: counter(step);
counter-increment: step;
width: 2rem;
padding-right: 0.5rem;
margin-right: 0.5rem;
display: inline-block;
text-align: right;
--at-apply: text-truegray:50;
}
</style>
3 changes: 3 additions & 0 deletions packages/devtools-ui-kit/src/unocss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ export const unocssPreset = (): Preset => ({

// icon
'n-icon': 'flex-none',

// code
'n-code-block': 'dark:bg-[#121212] bg-white',
},
})

Expand Down
194 changes: 194 additions & 0 deletions packages/devtools/client/components/AssetDetails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script setup lang="ts">
import { useTimeAgo } from '@vueuse/core'
import type { AssetInfo } from '~/../src/types'

const props = defineProps<{
asset: AssetInfo
}>()

const imageMeta = computedAsync(() => {
if (props.asset.type !== 'image')
return undefined
return rpc.getImageMeta(props.asset.filePath)
})

const textContent = computedAsync(() => {
if (props.asset.type !== 'text')
return undefined
return rpc.getTextAssetContent(props.asset.filePath)
})

const codeSnippets = computed(() => {
const items: [string, string, string][] = []
if (props.asset.type === 'image') {
const attrs = imageMeta.value?.width
? ` width="${imageMeta.value.width}" height="${imageMeta.value.height}" `
: ' '
items.push(
['html', `<nuxt-image${attrs}src="${props.asset.publicPath}" />`, 'with Nuxt Image'],
['html', `<img${attrs}src="${props.asset.publicPath}" />`, 'Image'],
)
return items
}

items.push(['html', `<a download href="${props.asset.publicPath}">\n Download ${props.asset.path}\n</a>`, 'Download link'])
return items
})

const copy = useCopy()
const timeago = useTimeAgo(() => props.asset.mtime)
const fileSize = computed(() => {
const size = props.asset.size
if (size < 1024)
return `${size} B`
if (size < 1024 * 1024)
return `${(size / 1024).toFixed(2)} KB`
return `${(size / 1024 / 1024).toFixed(2)} MB`
})

const aspectRatio = computed(() => {
if (!imageMeta.value?.width || !imageMeta.value?.height)
return ''
const gcd = (a: number, b: number): number => {
if (!b)
return a
return gcd(b, a % b)
}
const ratio = gcd(imageMeta.value.width, imageMeta.value.height)
if (ratio > 3)
return `${imageMeta.value.width / ratio} : ${imageMeta.value.height / ratio}`
return ''
})

const supportsPreview = computed(() => {
return [
'image',
'text',
'video',
].includes(props.asset.type)
})
</script>

<template>
<div flex="~ col gap-4" w-full of-hidden p2 h-full of-auto>
<template v-if="supportsPreview">
<div op50 mb--2 flex="~ gap2" items-center>
<div x-divider />
<div flex-none>
Preview
</div>
<div x-divider />
</div>

<div flex="~" items-center justify-center>
<AssetPreview
rounded max-h-80 w-auto min-h-20 min-w-20 border="~ base"
:asset="asset"
:text-content="textContent"
/>
</div>
</template>

<div op50 mb--2 flex="~ gap2" items-center>
<div x-divider />
<div flex-none>
Details
</div>
<div x-divider />
</div>

<table w-full>
<tbody>
<tr>
<td op50 text-right pr5 w-max ws-nowrap>
Filepath
</td>
<td ws-w>
<FilepathItem :filepath="asset.filePath" />
</td>
</tr>
<tr>
<td text-right op50 pr5 w-max ws-nowrap>
Public
</td>
<td>{{ asset.publicPath }}</td>
</tr>
<tr>
<td text-right op50 pr5 w-max ws-nowrap>
Type
</td>
<td capitalize>
{{ asset.type }}
</td>
</tr>
<template v-if="imageMeta?.width">
<tr>
<td text-right op50 pr5 w-max ws-nowrap>
Image Size
</td>
<td>{{ imageMeta.width }} x {{ imageMeta.height }}</td>
</tr>
<tr v-if="aspectRatio">
<td text-right op50 pr5 w-max ws-nowrap>
Aspect Ratio
</td>
<td>{{ aspectRatio }}</td>
</tr>
</template>
<tr>
<td text-right op50 pr5 w-max ws-nowrap>
File size
</td>
<td>{{ fileSize }}</td>
</tr>
<tr>
<td text-right op50 pr5 w-max ws-nowrap>
Last modified
</td>
<td>{{ new Date(asset.mtime).toLocaleString() }} <span op70>({{ timeago }})</span></td>
</tr>
</tbody>
</table>

<div op50 mb--2 flex="~ gap2" items-center>
<div x-divider />
<div flex-none>
Actions
</div>
<div x-divider />
</div>
<div flex="~ gap2 wrap">
<NButton icon="i-carbon-code" @click="openInEditor(asset.filePath)">
Open in Editor
</NButton>
<NButton icon="carbon-launch" :to="asset.publicPath" target="_blank">
Open in browser
</NButton>
<NButton icon="carbon-copy" @click="copy(asset.publicPath)">
Copy public path
</NButton>
<!-- <NButton v-if="asset.type === 'image'" disabled icon="carbon-image-service">
Optimize image (WIP)
</NButton> -->
</div>

<div flex-auto />

<div v-if="codeSnippets?.length" n-code-block border="~ base rounded">
<template v-for="cs, idx of codeSnippets" :key="idx">
<div v-if="idx" x-divider />
<div v-if="cs" of-hidden p2>
<div items-center flex justify-between pb2>
<div op50 ml1>
{{ cs[2] }}
</div>
<NButton icon="carbon-copy" n="sm primary" @click="copy(cs[1])">
Copy
</NButton>
</div>
<NCodeBlock :code="cs[1]" :lang="cs[0]" of-auto w-full :lines="false" px1 />
</div>
</template>
</div>
</div>
</template>
23 changes: 23 additions & 0 deletions packages/devtools/client/components/AssetFontPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
import { hash } from 'ohash'
import type { AssetInfo } from '~/../src/types'

const props = defineProps<{
asset: AssetInfo
}>()

const id = computed(() => `devtools-assets-${hash(props.asset)}`)

useStyleTag(computed(() => `
@font-face {
font-family: '${id.value}';
src: url('${props.asset.publicPath}');
}
`))
</script>

<template>
<div of-hidden :style="{ fontFamily: `'${id}'` }">
Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz
</div>
</template>
23 changes: 23 additions & 0 deletions packages/devtools/client/components/AssetGridItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
import type { AssetInfo } from '~/../src/types'

const props = defineProps<{
asset: AssetInfo
folder?: string
}>()

const path = computed(() => {
if (props.folder && props.asset.path.startsWith(props.folder))
return props.asset.path.slice(props.folder.length)
return props.asset.path
})
</script>

<template>
<button flex="~ col gap-1" hover="bg-active" rounded p2 of-hidden items-center>
<AssetPreview rounded w-30 h-30 border="~ base" :asset="asset" />
<div of-hidden w-full text-center ws-nowrap truncate text-xs>
{{ path }}
</div>
</button>
</template>
32 changes: 32 additions & 0 deletions packages/devtools/client/components/AssetListItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import type { AssetInfo } from '~/../src/types'

const props = defineProps<{
asset: AssetInfo
}>()

const icon = computed(() => {
if (props.asset.type === 'image')
return 'i-carbon-image'
if (props.asset.type === 'video')
return 'i-carbon-video'
if (props.asset.type === 'audio')
return 'i-carbon-volume-up'
if (props.asset.type === 'font')
return 'i-carbon-text-small-caps'
if (props.asset.type === 'text')
return 'i-carbon-document'
if (props.asset.type === 'json')
return 'i-carbon-json'
return 'i-carbon-document-blank'
})
</script>

<template>
<button flex="~ gap-1" w-full items-center hover="bg-active" rounded px4 py2>
<div :class="icon" />
<div text-center ws-nowrap of-hidden truncate>
{{ asset.path }}
</div>
</button>
</template>
22 changes: 22 additions & 0 deletions packages/devtools/client/components/AssetPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { AssetInfo } from '~/../src/types'

defineProps<{
asset: AssetInfo
textContent?: string
}>()
</script>

<template>
<div flex items-center of-hidden justify-center p1 bg-active object-cover>
<template v-if="asset.type === 'image'">
<img :src="asset.publicPath">
</template>
<AssetFontPreview v-else-if="asset.type === 'font'" :key="asset.publicPath" :asset="asset" p2 self-stretch text-2xl />
<div v-else-if="asset.type === 'text' && !textContent" op20 text-3xl i-carbon-document />
<div v-else-if="asset.type === 'text' && textContent" w-full p4 self-start>
<pre of-hidden text-xs font-mono max-h-10rem v-text="textContent" />
</div>
<div v-else op20 text-3xl i-carbon-help />
</div>
</template>
4 changes: 2 additions & 2 deletions packages/devtools/client/components/ComponentDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const copy = useCopy()
</script>

<template>
<div flex="~ col gap1">
<div flex="~ col gap1" of-hidden items-start>
<div flex="~ gap2">
<div>
<ComponentName :component="component" />
Expand All @@ -32,7 +32,7 @@ const copy = useCopy()
<FilepathItem
v-if="filePath"
:filepath="filePath"
text-sm op25 group-hover:op75
w-full text-sm op25 group-hover:op75
/>
<slot />
</div>
Expand Down
Loading