Skip to content

Commit

Permalink
feat(gui): add assets explorer drag & list display
Browse files Browse the repository at this point in the history
  • Loading branch information
YunYouJun committed Jan 5, 2024
1 parent 4fe3c5b commit 1179615
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 80 deletions.
130 changes: 68 additions & 62 deletions packages/gui/client/components/explorer/AGUIAssetsExplorer.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { Pane, Splitpanes } from 'splitpanes'
import { useEventListener } from '@vueuse/core'
import AGUITree from '../tree/AGUITree.vue'
import AGUIFileList from './AGUIFileList.vue'
import AGUIBreadcrumb from './AGUIBreadcrumb.vue'
import type { FileItem } from './types'
import { curFileList, listFilesInDirectory, onOpenDir, tree, vscodeFolderIcon } from './useAssetsExplorer'
const props = withDefaults(defineProps<{
fileList?: FileItem[]
Expand All @@ -18,81 +20,82 @@ const items = ref([
])
const size = ref(64)
const curFileList = ref(props.fileList || [])
const tree = ref(
{
id: '1',
name: 'Assets',
children: [
{
id: '1-1',
name: 'Textures',
children: [
{
id: '1-1-1',
name: 'Texture1',
},
{
id: '1-1-2',
name: 'Texture2',
},
],
},
{
id: '1-2',
name: 'Materials',
children: [
{
id: '1-2-1',
name: 'Material1',
},
{
id: '1-2-2',
name: 'Material2',
},
],
},
],
},
)
async function onOpenDir() {
try {
const dir = await window.showDirectoryPicker()
// directory handle 转换为树结构
// console.log(dir.entries())
for await (const [name, _handle] of dir.entries()) {
curFileList.value.push({
filename: name,
})
}
}
catch (err) {
// console.log(err)
}
if (props.fileList)
curFileList.value = props.fileList
async function onNodeActivated(node: FileItem) {
if (!node.handle)
return
const list = await listFilesInDirectory(node.handle, {
showFiles: true,
})
curFileList.value = list
}
const explorerContent = ref<HTMLDivElement>()
const isDragging = ref(false)
useEventListener(explorerContent, 'dragover', (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
isDragging.value = true
})
function onDragLeave() {
isDragging.value = false
}
useEventListener(explorerContent, 'dragleave', onDragLeave)
useEventListener(explorerContent, 'dragend', onDragLeave)
useEventListener(explorerContent, 'drop', (e) => {
isDragging.value = false
e.preventDefault()
e.stopPropagation()
})
</script>

<template>
<div class="agui-assets-explorer">
<AGUIExplorerControls>
<AGUIIconButton icon="i-ri-folder-line" @click="onOpenDir" />
<!-- <div class="i-ri-folder-line" @click="onOpenDir" /> -->
</AGUIExplorerControls>
<Splitpanes>
<Splitpanes style="height: calc(100% - var(--agui-explorer-controls-height, 32px));">
<Pane size="20">
<AGUITree class="h-full w-full" :data="tree" />
<AGUITree
v-if="tree"
class="h-full w-full"
:data="tree"
@node-activate="onNodeActivated"
/>

<div v-else class="h-full w-full flex flex-col items-center justify-center">
<div class="cursor-pointer text-6xl">
<div
:class="vscodeFolderIcon"
@click="onOpenDir"
/>
</div>
<div class="text-base">
Open a directory to start
</div>
</div>
</Pane>

<Pane>
<div class="agui-assets-panel">
<AGUIBreadcrumb :items="items" />
<div class="agui-explorer-content">
<div class="h-full p-2">

<div ref="explorerContent" class="agui-explorer-content">
<div
class="h-full p-2" :class="{
'is-dragging': isDragging,
}"
>
<AGUIFileList :size="size" :list="curFileList" />
</div>
</div>

<div class="agui-explorer-footer">
<AGUISlider v-model="size" style="width:120px" :max="120" :min="12" />
</div>
Expand All @@ -108,6 +111,7 @@ async function onOpenDir() {
flex-direction: column;
height: 100%;
--agui-explorer-controls-height: 32px;
--agui-explorer-footer-height: 26px;
.agui-assets-panel {
Expand All @@ -120,9 +124,6 @@ async function onOpenDir() {
.agui-explorer-content {
position: relative;
max-height: calc(100% - 20px - var(--agui-explorer-footer-height));
display: flex;
flex-direction: column;
flex-grow: 1;
Expand All @@ -132,6 +133,11 @@ async function onOpenDir() {
overflow-y: auto;
background-color: var(--agui-c-bg-panel);
.is-dragging {
opacity: 0.5;
background-color: rgba(0, 0, 0, 0.5);
}
/* 整个滚动条 */
&::-webkit-scrollbar {
width: 10px; /* 滚动条的宽度 */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
<script lang="ts" setup>
import { AGUIInput } from '..'
</script>

<template>
<div class="agui-explorer-controls">
<div />
<div class="flex-grow" />
<slot />
<AGUIInput class="w-30" />
</div>
</template>

<style>
.agui-explorer-controls {
display: flex;
align-items: center;
padding: 0 4px;
height: var(--agui-explorer-controls-height, 32px);
border-bottom: 1px solid var(--agui-c-border, #222);
}
</style>
34 changes: 26 additions & 8 deletions packages/gui/client/components/explorer/AGUIFileItem.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { onClickOutside, useEventListener } from '@vueuse/core'
import { getIconFromFileType } from '../../utils/fs'
import { getFiletypeFromPath, getIconFromFileType } from '../../utils/fs'
import { curFileList, listFilesInDirectory } from './useAssetsExplorer'
import type { FileItem } from './types'
Expand All @@ -14,6 +15,9 @@ const props = withDefaults(defineProps<{
})
const active = ref(false)
watch(() => props.item, () => {
active.value = false
})
const fileItemRef = ref<HTMLElement>()
onClickOutside(fileItemRef, () => {
Expand All @@ -24,7 +28,7 @@ const fontSize = computed(() => {
const min = 12
const max = 120
const percentage = (props.size - min) / (max - min)
return percentage * 8 + 8
return percentage * 6 + 8
})
const cssVars = computed(() => ({
Expand All @@ -38,9 +42,24 @@ function onDragStart(e: DragEvent) {
}
useEventListener(fileItemRef, 'dragstart', onDragStart)
useEventListener(fileItemRef, 'dblclick', async () => {
if (props.item.kind === 'directory') {
if (props.item.handle) {
const handle = props.item.handle
const list = await listFilesInDirectory(handle, {
showFiles: true,
})
curFileList.value = list
}
}
})
const fileIcon = computed(() => {
const icon = getIconFromFileType(props.item.ext || '')
if (props.item.kind === 'directory')
return 'i-vscode-icons-default-folder'
const ext = getFiletypeFromPath(props.item.filename || props.item.name || '')
const icon = getIconFromFileType(ext)
return props.icon || icon
})
</script>
Expand All @@ -65,7 +84,7 @@ const fileIcon = computed(() => {
</div>

<div class="agui-file-name">
{{ item.filename }}
{{ item.filename || item.name }}
</div>
</div>
</template>
Expand All @@ -78,9 +97,8 @@ const fileIcon = computed(() => {
align-items: center;
.agui-file-name {
display: block;
text-align: center;
justify-content: center;
font-size: var(--font-size, 12px);
}
Expand Down Expand Up @@ -130,7 +148,7 @@ const fileIcon = computed(() => {
width: 100%;
padding: 2px 3px;
padding: 2px;
border-radius: 2px;
overflow: hidden;
Expand Down
9 changes: 1 addition & 8 deletions packages/gui/client/components/explorer/AGUIFileList.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { getFiletypeFromPath } from '../../utils/fs'
import type { FileItem } from './types'
const props = withDefaults(defineProps<{
Expand Down Expand Up @@ -28,13 +27,7 @@ const classes = computed(() => {
})
const fileList = computed(() => {
return props.list?.map((item) => {
const ext = getFiletypeFromPath(item.filename)
return {
...item,
ext,
}
})
return props.list
})
</script>

Expand Down
4 changes: 3 additions & 1 deletion packages/gui/client/components/explorer/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export interface FileItem {
filename: string
filename?: string
name?: string
kind?: 'file' | 'directory'
icon?: string
ext?: string
handle?: FileSystemDirectoryHandle
}
64 changes: 64 additions & 0 deletions packages/gui/client/components/explorer/useAssetsExplorer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ref } from 'vue'
import type { Trees } from '../..'
import type { FileItem } from './types'

export const vscodeFolderIcon = 'i-vscode-icons-default-folder'
export const curFileList = ref<FileItem[]>([])
export const tree = ref()

export async function listFilesInDirectory(dirHandle: FileSystemDirectoryHandle, options: {
showFiles?: boolean
}) {
const files: Trees = []
for await (const entry of dirHandle.values()) {
// exclude
if (entry.name.startsWith('.'))
continue

if (entry.kind === 'directory') {
files.push({
name: entry.name,
kind: entry.kind,
handle: entry,
children: await listFilesInDirectory(entry, options),
})
}

// ignore file
if (options.showFiles) {
if (entry.kind === 'file') {
files.push({
name: entry.name,
kind: entry.kind,
handle: entry,
})
}
}
}

curFileList.value = files
return files
}

export async function onOpenDir() {
const fileList = []
try {
const dirHandle = await window.showDirectoryPicker()

// directory handle 转换为树结构
// console.log(dir.entries())
fileList.push(...await listFilesInDirectory(dirHandle, {
showFiles: false,
}))
tree.value = {
name: dirHandle.name,
handle: dirHandle,
children: fileList,
expanded: true,
}
}
catch (e) {
// user abort
console.error(e)
}
}
1 change: 1 addition & 0 deletions packages/gui/client/components/tree/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface TreeNode {
muted?: boolean
parentUnselectable?: boolean
children?: TreeNode[]
[key: string]: any
}

export type Trees = TreeNode[]

1 comment on commit 1179615

@vercel
Copy link

@vercel vercel bot commented on 1179615 Jan 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.