Skip to content

Commit

Permalink
feat(assets): add rename and delete action (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
arashsheyda committed Jul 31, 2023
1 parent 5c26895 commit 55f6863
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/devtools-kit/src/_types/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export interface ServerFunctions {
getImageMeta(filepath: string): Promise<ImageMeta | undefined>
getTextAssetContent(filepath: string, limit?: number): Promise<string | undefined>
writeStaticAssets(token: string, file: { name: string; data: string }[], path: string): Promise<string[]>
deleteStaticAsset(token: string, file: string): Promise<void>
renameStaticAsset(token: string, oldPath: string, newPath: string): Promise<void>

// Actions
customTabAction(name: string, action: number): Promise<boolean>
Expand Down
123 changes: 108 additions & 15 deletions packages/devtools/client/components/AssetDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import { useTimeAgo } from '@vueuse/core'
import type { AssetInfo, CodeSnippet } from '~/../src/types'
const props = defineProps<{
asset: AssetInfo
modelValue: AssetInfo
}>()
const emit = defineEmits<{ (...args: any): void }>()
const asset = useVModel(props, 'modelValue', emit, { passive: true })
const openInEditor = useOpenInEditor()
const imageMeta = computedAsync(() => {
if (props.asset.type !== 'image')
if (asset.value.type !== 'image')
return undefined
return rpc.getImageMeta(props.asset.filePath)
return rpc.getImageMeta(asset.value.filePath)
})
const textContent = computedAsync(() => {
if (props.asset.type !== 'text')
if (asset.value.type !== 'text')
return undefined
return rpc.getTextAssetContent(props.asset.filePath)
return rpc.getTextAssetContent(asset.value.filePath)
})
const config = useServerConfig()
Expand All @@ -28,32 +31,32 @@ const hasNuxtImage = computed(() => {
const codeSnippets = computed(() => {
const items: CodeSnippet[] = []
if (props.asset.type === 'image') {
if (asset.value.type === 'image') {
const attrs = imageMeta.value?.width
? `\n width="${imageMeta.value.width}"\n height="${imageMeta.value.height}" `
: ' '
items.push(
{ lang: 'vue-html', code: `<img${attrs}\n src="${props.asset.publicPath}"\n/>`, name: 'Plain Image' },
{ lang: 'vue-html', code: `<img${attrs}\n src="${asset.value.publicPath}"\n/>`, name: 'Plain Image' },
)
hasNuxtImage.value && items.push(
{ lang: 'vue-html', code: `<NuxtImage${attrs}\n src="${props.asset.publicPath}"\n/>`, name: 'Nuxt Image', docs: 'https://image.nuxtjs.org/components/nuxt-img' },
{ lang: 'vue-html', code: `<NuxtPicture${attrs}\n src="${props.asset.publicPath}"\n/>`, name: 'Nuxt Picture', docs: 'https://image.nuxtjs.org/components/nuxt-picture' },
{ lang: 'vue-html', code: `<NuxtImage${attrs}\n src="${asset.value.publicPath}"\n/>`, name: 'Nuxt Image', docs: 'https://image.nuxtjs.org/components/nuxt-img' },
{ lang: 'vue-html', code: `<NuxtPicture${attrs}\n src="${asset.value.publicPath}"\n/>`, name: 'Nuxt Picture', docs: 'https://image.nuxtjs.org/components/nuxt-picture' },
)
return items
}
items.push({
lang: 'html',
code: `<a download href="${props.asset.publicPath}">\n Download ${props.asset.path.split('/').slice(-1)[0]}\n</a>`,
code: `<a download href="${asset.value.publicPath}">\n Download ${asset.value.path.split('/').slice(-1)[0]}\n</a>`,
name: 'Download link',
})
return items
})
const copy = useCopy()
const timeago = useTimeAgo(() => props.asset.mtime)
const timeAgo = useTimeAgo(() => asset.value.mtime)
const fileSize = computed(() => {
const size = props.asset.size
const size = asset.value.size
if (size < 1024)
return `${size} B`
if (size < 1024 * 1024)
Expand Down Expand Up @@ -81,8 +84,64 @@ const supportsPreview = computed(() => {
'text',
'video',
'font',
].includes(props.asset.type)
].includes(asset.value.type)
})
const deleteDialog = ref(false)
async function deleteAsset() {
try {
await rpc.deleteStaticAsset(await ensureDevAuthToken(), asset.value.filePath)
asset.value = undefined as any
deleteDialog.value = false
showNotification({
message: 'Asset deleted',
icon: 'i-carbon-checkmark',
classes: 'text-green',
})
}
catch (error) {
showNotification({
message: 'Something went wrong!',
icon: 'i-carbon-warning',
classes: 'text-red',
})
}
}
const renameDialog = ref(false)
const newName = ref('')
async function renameAsset() {
const parts = asset.value.filePath.split('/')
const oldName = parts.slice(-1)[0].split('.').slice(0, -1).join('.')
if (!newName.value || newName.value === oldName) {
return showNotification({
message: 'Please enter a new name',
icon: 'i-carbon-warning',
classes: 'text-orange',
})
}
try {
const extension = parts.slice(-1)[0].split('.').slice(-1)[0]
const fullPath = `${parts.slice(0, -1).join('/')}/${newName.value}.${extension}`
await rpc.renameStaticAsset(await ensureDevAuthToken(), asset.value.filePath, fullPath)
asset.value = undefined as any
renameDialog.value = false
showNotification({
message: 'Asset renamed',
icon: 'i-carbon-checkmark',
classes: 'text-green',
})
}
catch (error) {
showNotification({
message: 'Something went wrong!',
icon: 'i-carbon-warning',
classes: 'text-red',
})
}
}
</script>

<template>
Expand Down Expand Up @@ -189,7 +248,7 @@ const supportsPreview = computed(() => {
<td w-30 ws-nowrap pr5 text-right op50>
Last modified
</td>
<td>{{ new Date(asset.mtime).toLocaleString() }} <span op70>({{ timeago }})</span></td>
<td>{{ new Date(asset.mtime).toLocaleString() }} <span op70>({{ timeAgo }})</span></td>
</tr>
</tbody>
</table>
Expand All @@ -202,9 +261,15 @@ const supportsPreview = computed(() => {
<div x-divider />
</div>
<div flex="~ gap2 wrap">
<NButton :to="asset.publicPath" download target="_blank" icon="carbon-download">
<NButton :to="asset.publicPath" download target="_blank" icon="carbon-download" n="green">
Download
</NButton>
<NButton icon="carbon-text-annotation-toggle" n="blue" @click="renameDialog = !renameDialog">
Rename
</NButton>
<NButton icon="carbon-delete" n="red" @click="deleteDialog = !deleteDialog">
Delete
</NButton>
<NButton v-if="asset.type === 'image'" disabled icon="carbon-image-service">
Optimize image (Coming soon)
</NButton>
Expand All @@ -219,4 +284,32 @@ const supportsPreview = computed(() => {
:code-snippets="codeSnippets"
/>
</div>
<NDialog v-model="deleteDialog">
<div flex="~ col gap-4" min-h-full w-full of-hidden p8>
<span>
Are you sure you want to delete this asset?
</span>
<div flex="~ gap2 wrap justify-center">
<NButton icon="carbon-close" @click="deleteDialog = false">
Cancel
</NButton>
<NButton icon="carbon-delete" n="red" @click="deleteAsset">
Delete
</NButton>
</div>
</div>
</NDialog>
<NDialog v-model="renameDialog">
<div flex="~ col gap-4" min-h-full w-full of-hidden p8>
<NTextInput v-model="newName" placeholder="New name" n="blue" />
<div flex="~ gap2 wrap justify-center">
<NButton icon="carbon-close" @click="renameDialog = false">
Cancel
</NButton>
<NButton icon="carbon-text-annotation-toggle" n="blue" @click="renameAsset">
Rename
</NButton>
</div>
</div>
</NDialog>
</template>
2 changes: 1 addition & 1 deletion packages/devtools/client/pages/modules/assets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ const navbar = ref<HTMLElement>()
:navbar="navbar"
@close="selected = undefined"
>
<AssetDetails v-if="selected" :asset="selected" />
<AssetDetails v-if="selected" v-model="selected" />
</DrawerRight>
</div>
</template>
Expand Down
13 changes: 13 additions & 0 deletions packages/devtools/src/server-rpc/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,18 @@ export function setupAssetsRPC({ nuxt, ensureDevAuthToken, refresh }: NuxtDevtoo
}),
)
},
async deleteStaticAsset(token: string, path: string) {
await ensureDevAuthToken(token)

return await fsp.unlink(path)
},
async renameStaticAsset(token: string, oldPath: string, newPath: string) {
await ensureDevAuthToken(token)

const exist = cache?.find(asset => asset.filePath === newPath)
if (exist)
throw new Error(`File ${newPath} already exists`)
return await fsp.rename(oldPath, newPath)
},
} satisfies Partial<ServerFunctions>
}

0 comments on commit 55f6863

Please sign in to comment.