mirror of https://github.com/requarks/wiki
parent
4e34151d15
commit
0cbeec37d6
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,83 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
ul.treeview-level
|
||||||
|
//- ROOT NODE
|
||||||
|
li.treeview-node(v-if='!props.parentId')
|
||||||
|
.treeview-label(@click='setRoot', :class='{ "active": !selection }')
|
||||||
|
q-icon(name='img:/_assets/icons/fluent-ftp.svg', size='sm')
|
||||||
|
em.text-purple root
|
||||||
|
q-menu(
|
||||||
|
touch-position
|
||||||
|
context-menu
|
||||||
|
auto-close
|
||||||
|
transition-show='jump-down'
|
||||||
|
transition-hide='jump-up'
|
||||||
|
)
|
||||||
|
q-card.q-pa-sm
|
||||||
|
q-list(dense, style='min-width: 150px;')
|
||||||
|
q-item(clickable)
|
||||||
|
q-item-section(side)
|
||||||
|
q-icon(name='las la-plus-circle', color='primary')
|
||||||
|
q-item-section New Folder
|
||||||
|
//- NORMAL NODES
|
||||||
|
tree-node(
|
||||||
|
v-for='node of level'
|
||||||
|
:key='node.id'
|
||||||
|
:node='node'
|
||||||
|
:depth='props.depth'
|
||||||
|
:parent-id='props.parentId'
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, inject } from 'vue'
|
||||||
|
|
||||||
|
import TreeNode from './TreeNode.vue'
|
||||||
|
|
||||||
|
// PROPS
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
depth: {
|
||||||
|
required: true,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
parentId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// INJECT
|
||||||
|
|
||||||
|
const roots = inject('roots', [])
|
||||||
|
const nodes = inject('nodes')
|
||||||
|
const selection = inject('selection')
|
||||||
|
|
||||||
|
// COMPUTED
|
||||||
|
|
||||||
|
const level = computed(() => {
|
||||||
|
const items = []
|
||||||
|
if (!props.parentId) {
|
||||||
|
for (const root of roots) {
|
||||||
|
items.push({
|
||||||
|
id: root,
|
||||||
|
...nodes[root]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const node of nodes[props.parentId].children) {
|
||||||
|
items.push({
|
||||||
|
id: node,
|
||||||
|
...nodes[node]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
|
||||||
|
function setRoot () {
|
||||||
|
selection.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
@ -1,21 +1,135 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.treenav
|
.treeview
|
||||||
|
tree-level(
|
||||||
|
:depth='0'
|
||||||
|
:parent-id='null'
|
||||||
|
)
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed, onMounted, provide, reactive } from 'vue'
|
||||||
|
import { findKey } from 'lodash-es'
|
||||||
|
|
||||||
|
import TreeLevel from './TreeLevel.vue'
|
||||||
|
|
||||||
// PROPS
|
// PROPS
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
nodes: {
|
nodes: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
roots: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// EMITS
|
// EMITS
|
||||||
|
|
||||||
const emits = defineEmits(['selected'])
|
const emit = defineEmits(['update:selected'])
|
||||||
|
|
||||||
|
// DATA
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
opened: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
// COMPOUTED
|
||||||
|
|
||||||
|
const selection = computed({
|
||||||
|
get () {
|
||||||
|
return props.selected
|
||||||
|
},
|
||||||
|
set (val) {
|
||||||
|
emit('update:selected', val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
|
||||||
|
// PROVIDE
|
||||||
|
|
||||||
|
provide('roots', props.roots)
|
||||||
|
provide('nodes', props.nodes)
|
||||||
|
provide('opened', state.opened)
|
||||||
|
provide('selection', selection)
|
||||||
|
|
||||||
|
// MOUNTED
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.selected) {
|
||||||
|
let foundRoot = false
|
||||||
|
let currentId = props.selected
|
||||||
|
while (!foundRoot) {
|
||||||
|
const parentId = findKey(props.nodes, n => n.children?.includes(currentId))
|
||||||
|
if (parentId) {
|
||||||
|
state.opened[parentId] = true
|
||||||
|
currentId = parentId
|
||||||
|
} else {
|
||||||
|
foundRoot = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.opened[props.selected] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.treeview {
|
||||||
|
&-level {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .treeview-level {
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
> .treeview-node {
|
||||||
|
border-left: none;
|
||||||
|
|
||||||
|
> .treeview-label {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-node {
|
||||||
|
display: block;
|
||||||
|
border-left: 2px solid rgba(0,0,0,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 0 5px 5px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
transition: background-color .4s ease;
|
||||||
|
|
||||||
|
&:hover, &:focus, &.active {
|
||||||
|
background-color: rgba(0,0,0,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .q-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animations
|
||||||
|
|
||||||
|
&-enter-active, &-leave-active {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from, &-leave-to {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
li.treeview-node
|
||||||
|
//- NODE
|
||||||
|
.treeview-label(@click='toggleNode', :class='{ "active": isActive }')
|
||||||
|
q-icon(:name='icon', size='sm')
|
||||||
|
span {{node.text}}
|
||||||
|
//- RIGHT-CLICK MENU
|
||||||
|
q-menu(
|
||||||
|
touch-position
|
||||||
|
context-menu
|
||||||
|
auto-close
|
||||||
|
transition-show='jump-down'
|
||||||
|
transition-hide='jump-up'
|
||||||
|
@before-show='state.isContextMenuShown = true'
|
||||||
|
@before-hide='state.isContextMenuShown = false'
|
||||||
|
)
|
||||||
|
q-card.q-pa-sm
|
||||||
|
q-list(dense, style='min-width: 150px;')
|
||||||
|
q-item(clickable)
|
||||||
|
q-item-section(side)
|
||||||
|
q-icon(name='las la-plus-circle', color='primary')
|
||||||
|
q-item-section New Folder
|
||||||
|
q-item(clickable)
|
||||||
|
q-item-section(side)
|
||||||
|
q-icon(name='las la-redo', color='teal')
|
||||||
|
q-item-section Rename...
|
||||||
|
q-item(clickable)
|
||||||
|
q-item-section(side)
|
||||||
|
q-icon(name='las la-arrow-right', color='teal')
|
||||||
|
q-item-section Move to...
|
||||||
|
q-item(clickable)
|
||||||
|
q-item-section(side)
|
||||||
|
q-icon(name='las la-trash-alt', color='negative')
|
||||||
|
q-item-section.text-negative Delete
|
||||||
|
//- SUB-LEVEL
|
||||||
|
transition(name='treeview')
|
||||||
|
tree-level(
|
||||||
|
v-if='hasChildren && isOpened'
|
||||||
|
:parent-id='props.node.id'
|
||||||
|
:depth='props.depth + 1'
|
||||||
|
)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, inject, reactive } from 'vue'
|
||||||
|
|
||||||
|
import TreeLevel from './TreeLevel.vue'
|
||||||
|
|
||||||
|
// PROPS
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
depth: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
required: true,
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
parentId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// INJECT
|
||||||
|
|
||||||
|
const opened = inject('opened')
|
||||||
|
const selection = inject('selection')
|
||||||
|
|
||||||
|
// DATA
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
isContextMenuShown: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// COMPUTED
|
||||||
|
|
||||||
|
const icon = computed(() => {
|
||||||
|
if (props.node.icon) {
|
||||||
|
return props.node.icon
|
||||||
|
}
|
||||||
|
return hasChildren.value && isOpened.value ? 'img:/_assets/icons/fluent-opened-folder.svg' : 'img:/_assets/icons/fluent-folder.svg'
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasChildren = computed(() => {
|
||||||
|
return props.node.children?.length > 0
|
||||||
|
})
|
||||||
|
const isOpened = computed(() => {
|
||||||
|
return opened[props.node.id]
|
||||||
|
})
|
||||||
|
const isActive = computed(() => {
|
||||||
|
return state.isContextMenuShown || selection.value === props.node.id
|
||||||
|
})
|
||||||
|
|
||||||
|
// METHODS
|
||||||
|
|
||||||
|
function toggleNode () {
|
||||||
|
selection.value = props.node.id
|
||||||
|
|
||||||
|
if (selection.value !== props.node.id && opened[props.node.id]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opened[props.node.id] = !(opened[props.node.id] === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
Loading…
Reference in new issue