Skip to content
This repository has been archived by the owner on Jan 6, 2024. It is now read-only.

Commit

Permalink
feat(assets): add rename and delete action (#183)
Browse files Browse the repository at this point in the history
* feat(assets): add rename and delete action

* fix(ui-kit): dialog closing and a11y
  • Loading branch information
OneGIl committed Aug 7, 2023
1 parent 6fdfca3 commit a075e8a
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 42 deletions.
119 changes: 108 additions & 11 deletions packages/client/components/AssetDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,29 @@ import { useDevToolsClient } from '~/logic/client'
import { rpc } from '~/logic/rpc'
const props = defineProps<{
asset: AssetInfo
modelValue: AssetInfo
}>()
const emit = defineEmits<{ (...args: any): void }>()
const asset = useVModel(props, 'modelValue', emit, { passive: true })
const showNotification = useNotification()
const origin = window.parent.location.origin
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 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 @@ -51,9 +53,65 @@ 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(asset.value.filePath)
asset.value = undefined as any
deleteDialog.value = false
showNotification({
text: 'Asset deleted',
icon: 'carbon-checkmark',
type: 'primary',
})
}
catch (error) {
deleteDialog.value = false
showNotification({
text: 'Something went wrong!',
icon: 'carbon-warning',
type: 'error',
})
}
}
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({
text: 'Please enter a new name',
icon: 'carbon-warning',
type: 'error',
})
}
try {
const extension = parts.slice(-1)[0].split('.').slice(-1)[0]
const fullPath = `${parts.slice(0, -1).join('/')}/${newName.value}.${extension}`
await rpc.renameStaticAsset(asset.value.filePath, fullPath)
asset.value = undefined as any
renameDialog.value = false
showNotification({
text: 'Asset renamed',
icon: 'carbon-checkmark',
type: 'primary',
})
}
catch (error) {
showNotification({
text: 'Something went wrong!',
icon: 'carbon-warning',
type: 'error',
})
}
}
const client = useDevToolsClient()
</script>

Expand Down Expand Up @@ -161,7 +219,7 @@ const client = useDevToolsClient()
<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 @@ -174,11 +232,50 @@ const client = useDevToolsClient()
<div x-divider />
</div>
<div flex="~ gap2 wrap">
<VDButton :to="`${origin}${asset.publicPath}`" download target="_blank" icon="carbon-download">
<VDButton :to="`${origin}${asset.publicPath}`" download target="_blank" icon="carbon-download" n="green">
Download
</VDButton>
<VDButton icon="carbon-text-annotation-toggle" n="blue" @click="renameDialog = !renameDialog">
Rename
</VDButton>
<VDButton icon="carbon-delete" n="red" @click="deleteDialog = !deleteDialog">
Delete
</VDButton>
</div>
<div flex-auto />
<VDDialog
v-model="deleteDialog" @close="deleteDialog = false"
>
<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">
<VDButton icon="carbon-close" @click="deleteDialog = false">
Cancel
</VDButton>
<VDButton icon="carbon-delete" n="red" @click="deleteAsset">
Delete
</VDButton>
</div>
</div>
</VDDialog>
<VDDialog
v-model="renameDialog" @close="deleteDialog = false"
>
<div flex="~ col gap-4" min-h-full w-full of-hidden p8>
<VDTextInput v-model="newName" placeholder="New name" n="blue" />
<div flex="~ gap2 wrap justify-center">
<VDButton icon="carbon-close" @click="renameDialog = false">
Cancel
</VDButton>
<VDButton icon="carbon-text-annotation-toggle" n="blue" @click="renameAsset">
Rename
</VDButton>
</div>
</div>
</VDDialog>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/client/components/DrawerRight.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ onClickOutside(el, () => {
if (props.modelValue && props.autoClose)
emit('close')
}, {
ignore: ['a', 'button', 'summary'],
ignore: ['a', 'button', 'summary', '[role="dialog"]'],
})
</script>

Expand Down
33 changes: 15 additions & 18 deletions packages/client/components/Notification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
const show = ref(false)
const icon = ref<string | undefined>()
const text = ref<string | undefined>()
const type = ref<'primary' | 'error' | undefined>()
const type = ref<'primary' | 'error' | 'warning' | undefined>()
const duration = ref<number>()
let timer: ReturnType<typeof setTimeout> | undefined
provideNotification((opt: {
text: string
icon?: string
type?: 'primary' | 'error'
type?: 'primary' | 'error' | 'warning'
duration?: number
}) => {
text.value = opt.text
Expand All @@ -20,6 +20,17 @@ provideNotification((opt: {
createTimer()
})
const textColor = computed(() => {
switch (type.value) {
case 'warning':
return 'text-orange'
case 'error':
return 'text-red'
default:
return 'text-primary'
}
})
function clearTimer() {
if (timer) {
clearTimeout(timer)
Expand All @@ -40,25 +51,11 @@ function createTimer() {
:class="show ? '' : 'pointer-events-none overflow-hidden'"
>
<div
v-if="type === 'error'"
border="~ base"
flex="~ inline gap2"
m-3 inline-block items-center rounded px-4 py-1 text-red transition-all duration-300 bg-base
:style="show ? {} : { transform: 'translateY(-300%)' }"
:class="show ? 'shadow' : 'shadow-none'"
@mouseenter="clearTimer"
@mouseleave="createTimer"
>
<div v-if="icon" :class="`i-${icon}`" />
<div>{{ text }}</div>
</div>
<div
v-else
border="~ base"
flex="~ inline gap2"
m-3 inline-block items-center rounded px-4 py-1 text-primary transition-all duration-300 bg-base
m-3 inline-block items-center rounded px-4 py-1 transition-all duration-300 bg-base
:style="show ? {} : { transform: 'translateY(-300%)' }"
:class="show ? 'shadow' : 'shadow-none'"
:class="[show ? 'shadow' : 'shadow-none', textColor]"
@mouseenter="clearTimer"
@mouseleave="createTimer"
>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/composables/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const [
] = useSingleton<(opt: {
text: string
icon?: string
type?: 'primary' | 'error'
type?: 'primary' | 'warning' | 'error'
duration?: number
}) => void>()

Expand Down
3 changes: 3 additions & 0 deletions packages/client/logic/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ export const rpc
onTerminalExit({ data }: { id?: string; data: string }) {
hookApi.hook.emit('__vue-devtools:terminal:exit__', data)
},
onFileWatch(data: { event: string; path: string }) {
hookApi.hook.emit('__vue-devtools:file-watch', data)
},
})
26 changes: 21 additions & 5 deletions packages/client/pages/assets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@
import { onKeyDown } from '@vueuse/core'
import Fuse from 'fuse.js'
import { rpc } from '~/logic/rpc'
import { hookApi } from '~/logic/hook'
import { rootPath } from '~/logic/global'
const assets = ref<AssetInfo[]>([])
function useAssets() {
const assets = ref<AssetInfo[]>([])
async function getAssets() {
assets.value = await rpc.staticAssets()
getAssets()
const debounceAssets = useDebounceFn(() => {
getAssets()
}, 100)
async function getAssets() {
assets.value = await rpc.staticAssets()
}
hookApi.hook.on('__vue-devtools:file-watch', ({ event, path }) => {
if (path.startsWith(rootPath) && ['add', 'unlink'].includes(event))
debounceAssets()
})
return { assets }
}
getAssets()
const { assets } = useAssets()
const search = ref('')
Expand Down Expand Up @@ -125,7 +141,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>
<VDPanelGrids v-else px5>
Expand Down
3 changes: 3 additions & 0 deletions packages/node/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ declare interface RPCFunctions {
staticAssets(): Promise<AssetInfo[]>
getImageMeta(path: string): Promise<ImageMeta | undefined>
getTextAssetContent(path: string): Promise<string | undefined>
deleteStaticAsset(path: string): Promise<string | undefined>
renameStaticAsset(oldPath: string, newPath: string): Promise<string | undefined>
getPackages(): Promise<Record<string, Omit<PackageMeta, 'name'>>>
getVueSFCList(): Promise<string[]>
getComponentInfo(filename: string): Promise<Record<string, unknown>>
onTerminalData(_: { id?: string; data: string }): void
onTerminalExit(_: { id?: string; data?: string }): void
onFileWatch(_: { event: string; path: string }): void
installPackage(packages: string[], options?: ExecNpmScriptOptions): Promise<void>
uninstallPackage(packages: string[], options?: ExecNpmScriptOptions): Promise<void>
root(): string
Expand Down
22 changes: 21 additions & 1 deletion packages/node/src/features/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { ResolvedConfig } from 'vite'
import { imageMeta } from 'image-meta'

const _imageMetaCache = new Map<string, ImageMeta | undefined>()
let cache: AssetInfo[] | null = null

function guessType(path: string): AssetType {
if (/\.(a?png|jpe?g|jxl|gif|svg|webp|avif|ico|bmp|tiff?)$/i.test(path))
Expand Down Expand Up @@ -42,7 +43,7 @@ export async function getStaticAssets(config: ResolvedConfig): Promise<AssetInfo
ignore: ['**/node_modules/**', '**/dist/**'],
})

return await Promise.all(files.map(async (path) => {
cache = await Promise.all(files.map(async (path) => {
const filePath = resolve(dir, path)
const stat = await fs.lstat(filePath)
const publicDirname = p.relative(config.root, config.publicDir)
Expand All @@ -56,6 +57,8 @@ export async function getStaticAssets(config: ResolvedConfig): Promise<AssetInfo
mtime: stat.mtimeMs,
}
}))

return cache
}

export async function getImageMeta(filepath: string) {
Expand Down Expand Up @@ -83,3 +86,20 @@ export async function getTextAssetContent(filepath: string, limit = 300) {
return undefined
}
}

export async function deleteStaticAsset(filepath: string) {
try {
return await fs.unlink(filepath)
}
catch (e) {
console.error(e)
throw e
}
}

export async function renameStaticAsset(oldPath: string, newPath: string) {
const exist = cache?.find(asset => asset.filePath === newPath)
if (exist)
throw new Error(`File ${newPath} already exists`)
return await fs.rename(oldPath, newPath)
}
8 changes: 8 additions & 0 deletions packages/node/src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { AnalyzeOptions, DeepRequired } from '@vite-plugin-vue-devtools/cor
import { analyzeCode, analyzeOptionsDefault } from '@vite-plugin-vue-devtools/core/compiler'
import { DIR_CLIENT } from './dir'
import {
deleteStaticAsset,
execNpmScript,
getComponentInfo,
getComponentsRelationships,
Expand All @@ -18,6 +19,7 @@ import {
getStaticAssets,
getTextAssetContent,
getVueSFCList,
renameStaticAsset,
} from './features'

function getVueDevtoolsPath() {
Expand Down Expand Up @@ -84,6 +86,8 @@ export default function VitePluginVueDevTools(options?: VitePluginVueDevToolsOpt
staticAssets: () => getStaticAssets(config),
getImageMeta,
getTextAssetContent,
deleteStaticAsset,
renameStaticAsset,
getPackages: () => getPackages(config.root),
getVueSFCList: () => getVueSFCList(config.root),
getComponentInfo: (filename: string) => getComponentInfo(config.root, filename),
Expand Down Expand Up @@ -112,6 +116,10 @@ export default function VitePluginVueDevTools(options?: VitePluginVueDevToolsOpt
},
}),
})

server.watcher.on('all', (event, path) => {
rpc.onFileWatch({ event, path })
})
}
const plugin = <PluginOption>{
name: PLUGIN_NAME,
Expand Down
Loading

0 comments on commit a075e8a

Please sign in to comment.