Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
0cbeec37
Unverified
Commit
0cbeec37
authored
Nov 20, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: file manager tree view component
parent
4e34151d
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
415 additions
and
102 deletions
+415
-102
3.0.0.js
server/db/migrations/3.0.0.js
+16
-0
fluent-folder-tree.svg
ux/public/_assets/icons/fluent-folder-tree.svg
+2
-0
fluent-ftp.svg
ux/public/_assets/icons/fluent-ftp.svg
+2
-0
fluent-opened-folder.svg
ux/public/_assets/icons/fluent-opened-folder.svg
+2
-0
FileManager.vue
ux/src/components/FileManager.vue
+70
-93
PageNewMenu.vue
ux/src/components/PageNewMenu.vue
+14
-4
TreeLevel.vue
ux/src/components/TreeLevel.vue
+83
-0
TreeNav.vue
ux/src/components/TreeNav.vue
+118
-4
TreeNode.vue
ux/src/components/TreeNode.vue
+107
-0
AdminIcons.vue
ux/src/pages/AdminIcons.vue
+1
-1
No files found.
server/db/migrations/3.0.0.js
View file @
0cbeec37
...
...
@@ -435,6 +435,22 @@ exports.up = async knex => {
}
},
{
key
:
'icons'
,
value
:
{
fa
:
{
isActive
:
true
,
config
:
{
version
:
6
,
license
:
'free'
,
token
:
''
}
},
la
:
{
isActive
:
true
}
}
},
{
key
:
'mail'
,
value
:
{
senderName
:
''
,
...
...
ux/public/_assets/icons/fluent-folder-tree.svg
0 → 100644
View file @
0cbeec37
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 48 48"
width=
"96px"
height=
"96px"
><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wa"
x1=
"3.584"
x2=
"29.287"
y1=
"26.5"
y2=
"26.5"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#a1aab3"
/><stop
offset=
"1"
stop-color=
"#8f979e"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wa)"
d=
"M29,24v-2H13v-9h-2v26c0,0.552,0.448,1,1,1h17v-2H13V24H29z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wb"
x1=
"5"
x2=
"20"
y1=
"11.5"
y2=
"11.5"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#eba600"
/><stop
offset=
"1"
stop-color=
"#c28200"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wb)"
d=
"M18.5,7H12l-0.707-0.707C11.105,6.105,10.851,6,10.586,6H6C5.448,6,5,6.448,5,7v8.5 C5,16.325,5.675,17,6.5,17h12c0.825,0,1.5-0.675,1.5-1.5v-7C20,7.675,19.325,7,18.5,7z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wc"
x1=
"11.505"
x2=
"13.359"
y1=
"6.814"
y2=
"16.489"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#ffd869"
/><stop
offset=
"1"
stop-color=
"#fec52b"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wc)"
d=
"M18.5,7L12,6.978l-0.801,0.752C11.013,7.903,10.769,8,10.514,8H6C5.448,8,5,8.448,5,9v6.5 C5,16.325,5.675,17,6.5,17h12c0.825,0,1.5-0.675,1.5-1.5v-7C20,7.7,19.3,7,18.5,7z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wd"
x1=
"29"
x2=
"44"
y1=
"22.5"
y2=
"22.5"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#eba600"
/><stop
offset=
"1"
stop-color=
"#c28200"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wd)"
d=
"M42.5,18H36l-0.707-0.707C35.105,17.105,34.851,17,34.586,17H30c-0.552,0-1,0.448-1,1v8.5 c0,0.825,0.675,1.5,1.5,1.5h12c0.825,0,1.5-0.675,1.5-1.5v-7C44,18.675,43.325,18,42.5,18z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~We"
x1=
"35.505"
x2=
"37.359"
y1=
"17.814"
y2=
"27.489"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#ffd869"
/><stop
offset=
"1"
stop-color=
"#fec52b"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~We)"
d=
"M42.5,18L36,17.978l-0.801,0.752C35.013,18.903,34.769,19,34.514,19H30c-0.552,0-1,0.448-1,1 v6.5c0,0.825,0.675,1.5,1.5,1.5h12c0.825,0,1.5-0.675,1.5-1.5v-7C44,18.7,43.3,18,42.5,18z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wf"
x1=
"29"
x2=
"44"
y1=
"38.5"
y2=
"38.5"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#eba600"
/><stop
offset=
"1"
stop-color=
"#c28200"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wf)"
d=
"M42.5,34H36l-0.707-0.707C35.105,33.105,34.851,33,34.586,33H30c-0.552,0-1,0.448-1,1v8.5 c0,0.825,0.675,1.5,1.5,1.5h12c0.825,0,1.5-0.675,1.5-1.5v-7C44,34.675,43.325,34,42.5,34z"
/><linearGradient
id=
"BC9FGaQYSWC9vfPQ436~Wg"
x1=
"35.505"
x2=
"37.359"
y1=
"33.814"
y2=
"43.489"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#ffd869"
/><stop
offset=
"1"
stop-color=
"#fec52b"
/></linearGradient><path
fill=
"url(#BC9FGaQYSWC9vfPQ436~Wg)"
d=
"M42.5,34L36,33.978l-0.801,0.752C35.013,34.903,34.769,35,34.514,35H30c-0.552,0-1,0.448-1,1 v6.5c0,0.825,0.675,1.5,1.5,1.5h12c0.825,0,1.5-0.675,1.5-1.5v-7C44,34.7,43.3,34,42.5,34z"
/></svg>
\ No newline at end of file
ux/public/_assets/icons/fluent-ftp.svg
0 → 100644
View file @
0cbeec37
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 48 48"
width=
"96px"
height=
"96px"
><linearGradient
id=
"73hvxuIu2KoRU1i7qzd8qa"
x1=
"23"
x2=
"23"
y1=
"548.505"
y2=
"556.288"
gradientTransform=
"translate(0 -541.78)"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#eba600"
/><stop
offset=
"1"
stop-color=
"#c28200"
/></linearGradient><path
fill=
"url(#73hvxuIu2KoRU1i7qzd8qa)"
d=
"M22.414,10.414l-2.536-2.536C19.316,7.316,18.553,7,17.757,7H5C3.895,7,3,7.895,3,9v8l2,22h38 V13l-18.569-2h-0.603C23.298,11,22.789,10.789,22.414,10.414z"
/><linearGradient
id=
"73hvxuIu2KoRU1i7qzd8qb"
x1=
"24"
x2=
"24"
y1=
"552.634"
y2=
"582.763"
gradientTransform=
"translate(0 -541.78)"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#ffd869"
/><stop
offset=
"1"
stop-color=
"#fec52b"
/></linearGradient><path
fill=
"url(#73hvxuIu2KoRU1i7qzd8qb)"
d=
"M20.586,14.414l3.268-3.268C23.947,11.053,24.074,11,24.207,11H28l17,17v11 c0,1.105-0.895,2-2,2H5c-1.105,0-2-0.895-2-2V15.5C3,15.224,3.224,15,3.5,15h15.672C19.702,15,20.211,14.789,20.586,14.414z"
/><path
d=
"M45,22.99v6.98C44.66,29.99,44.33,30,44,30c-4.78,0-9.3-1.88-12.72-5.28 c-3.62-3.64-5.53-8.63-5.25-13.72h6.98v0.04c-0.11,3.03,0.99,6.03,3.02,8.23C38.2,21.64,41.29,23,44.5,23c0.16,0,0.3,0,0.44-0.01H45 z"
opacity=
".05"
/><path
d=
"M45,23.49v5.98c-0.34,0.02-0.67,0.03-1,0.03c-4.65,0-9.04-1.82-12.36-5.14 c-3.53-3.53-5.39-8.4-5.11-13.36h5.98v0.02c-0.12,3.16,1.03,6.29,3.15,8.59c2.27,2.47,5.49,3.89,8.84,3.89h0.03 c0.14,0,0.29,0,0.44-0.01H45z"
opacity=
".07"
/><linearGradient
id=
"73hvxuIu2KoRU1i7qzd8qc"
x1=
"27"
x2=
"45"
y1=
"27.89"
y2=
"27.89"
gradientTransform=
"matrix(1 0 0 -1 0 47.89)"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#7819a2"
/><stop
offset=
"1"
stop-color=
"#771aa9"
/></linearGradient><path
fill=
"url(#73hvxuIu2KoRU1i7qzd8qc)"
d=
"M31.99,24.01c-3.298-3.309-5.254-7.936-4.961-13.01h4.981c-0.12,3.29,1.05,6.53,3.28,8.95 C37.65,22.52,41.01,24,44.5,24c0.16,0,0.33,0,0.5-0.01v4.981C39.926,29.264,35.299,27.308,31.99,24.01z"
/><path
fill=
"#a238c2"
d=
"M31,11.5c0-0.17,0-0.34,0.01-0.5h5.01c-0.13,2.3,0.67,4.56,2.21,6.25C39.84,19,42.12,20,44.5,20 c0.17,0,0.33,0,0.5-0.02v5.01C44.84,25,44.67,25,44.5,25C37.06,25,31,18.94,31,11.5z"
/><path
fill=
"#ba54d9"
d=
"M35,11.5c0-0.17,0-0.33,0.01-0.5h5.08c0.2,1.26,0.8,2.38,1.67,3.24c0.86,0.87,1.98,1.47,3.24,1.67 v5.08C44.83,21,44.67,21,44.5,21C39.26,21,35,16.74,35,11.5z"
/><path
fill=
"#c767e5"
d=
"M39.021,11.008c0.003-0.003,0.005-0.005,0.009-0.008H43c1.105,0,2,0.893,2,1.998v3.944 c-0.004,0.036-0.004,0.036-0.008,0.037C41.565,17.276,38.724,14.436,39.021,11.008z"
/></svg>
\ No newline at end of file
ux/public/_assets/icons/fluent-opened-folder.svg
0 → 100644
View file @
0cbeec37
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 48 48"
width=
"96px"
height=
"96px"
><linearGradient
id=
"xGIh33lbYX9pWIYWeZsuka"
x1=
"24"
x2=
"24"
y1=
"6.955"
y2=
"23.167"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#eba600"
/><stop
offset=
"1"
stop-color=
"#c28200"
/></linearGradient><path
fill=
"url(#xGIh33lbYX9pWIYWeZsuka)"
d=
"M24.414,10.414l-2.536-2.536C21.316,7.316,20.553,7,19.757,7H5C3.895,7,3,7.895,3,9v30 c0,1.105,0.895,2,2,2h38c1.105,0,2-0.895,2-2V13c0-1.105-0.895-2-2-2H25.828C25.298,11,24.789,10.789,24.414,10.414z"
/><linearGradient
id=
"xGIh33lbYX9pWIYWeZsukb"
x1=
"24.066"
x2=
"24.066"
y1=
"19.228"
y2=
"33.821"
gradientTransform=
"matrix(-1 0 0 1 48 0)"
gradientUnits=
"userSpaceOnUse"
><stop
offset=
"0"
stop-color=
"#ffd869"
/><stop
offset=
"1"
stop-color=
"#fec52b"
/></linearGradient><path
fill=
"url(#xGIh33lbYX9pWIYWeZsukb)"
d=
"M24,23l3.854-3.854C27.947,19.053,28.074,19,28.207,19H44.81c1.176,0,2.098,1.01,1.992,2.181 l-1.636,18C45.072,40.211,44.208,41,43.174,41H4.79c-1.019,0-1.875-0.766-1.988-1.779L1.062,23.555C1.029,23.259,1.261,23,1.559,23 H24z"
/></svg>
\ No newline at end of file
ux/src/components/FileManager.vue
View file @
0cbeec37
<
template
lang=
"pug"
>
q-layout(view='hHh lpR
fFf
', container)
q-layout(view='hHh lpR
lFr
', container)
q-header.card-header
q-toolbar(dark)
q-icon(name='img:/_assets/icons/fluent-folder.svg', left, size='md')
...
...
@@ -25,58 +25,11 @@ q-layout(view='hHh lpR fFf', container)
)
q-toolbar(dark)
q-space
q-btn.q-mr-sm(
flat
dense
color='blue-4'
:aria-label='t(`common.actions.upload`)'
icon='las la-plus-circle'
@click=''
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.upload`
)
}}
q-btn(
flat
dense
color='positive'
:aria-label='t(`common.actions.upload`)'
icon='las la-cloud-upload-alt'
@click=''
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.upload`
)
}}
q-separator.q-mx-sm(vertical, dark, inset)
q-btn.q-mr-sm(
flat
dense
color='blue-grey-4'
:aria-label='t(`common.actions.upload`)'
icon='las la-check-square'
@click=''
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.upload`
)
}}
q-btn.q-mr-sm(
flat
dense
color='blue-grey-4'
:aria-label='t(`common.actions.upload`)'
icon='las la-list'
@click=''
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.upload`
)
}}
q-btn(
flat
dense
color='blue-grey-4'
:aria-label='t(`common.actions.refresh`)'
icon='las la-redo-alt'
@click=''
:loading='state.loading > 0'
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.refresh`
)
}}
q-separator.q-mx-sm(vertical, dark, inset)
q-btn(
flat
dense
color='white'
:label='t(`common.actions.close`)'
:aria-label='t(`common.actions.close`)'
icon='las la-times'
@click='close'
...
...
@@ -84,16 +37,10 @@ q-layout(view='hHh lpR fFf', container)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.close`
)
}}
q-drawer.bg-blue-grey-1(:model-value='true', :width='350')
.q-px-md.q-pb-sm
q-tree.fileman-toc(
:nodes='state.tree'
icon='las la-caret-right'
node-key='key'
dense
accordion
no-connectors
v-model:expanded='state.treeExpanded'
v-model:selected='state.treeSelected'
@click='openFolder'
tree(
:nodes='state.treeNodes'
:roots='state.treeRoots'
v-model:selected='state.currentFolderId'
)
q-drawer.bg-grey-1(:model-value='true', :width='350', side='right')
.q-pa-md
...
...
@@ -105,8 +52,40 @@ q-layout(view='hHh lpR fFf', container)
)
q-page-container
q-page.bg-white
q-bar.bg-blue-grey-1
small.text-caption.text-grey-7 / foo / bar
q-toolbar.bg-grey-1
q-space
q-btn.q-mr-sm(
flat
dense
no-caps
color='grey'
:aria-label='t(`common.actions.refresh`)'
icon='las la-redo-alt'
@click=''
)
q-tooltip(anchor='bottom middle', self='top middle')
{{
t
(
`common.actions.refresh`
)
}}
q-separator.q-mr-sm(inset, vertical)
q-btn.q-mr-sm(
flat
dense
no-caps
color='blue'
:label='t(`common.actions.new`)'
:aria-label='t(`common.actions.new`)'
icon='las la-plus-circle'
@click=''
)
new-menu(hide-asset-btn)
q-btn(
flat
dense
no-caps
color='positive'
:label='t(`common.actions.upload`)'
:aria-label='t(`common.actions.upload`)'
icon='las la-cloud-upload-alt'
@click=''
)
q-list.fileman-filelist
q-item(clickable)
q-item-section(avatar)
...
...
@@ -141,12 +120,18 @@ q-layout(view='hHh lpR fFf', container)
q-item-section(side)
.text-caption 2022/01/01
q-footer
q-bar.bg-blue-grey-1
small.text-caption.text-grey-7 / foo / bar
</
template
>
<
script
setup
>
import
{
useI18n
}
from
'vue-i18n'
import
{
reactive
}
from
'vue'
import
NewMenu
from
'./PageNewMenu.vue'
import
Tree
from
'./TreeNav.vue'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
...
...
@@ -164,33 +149,31 @@ const { t } = useI18n()
const
state
=
reactive
({
loading
:
0
,
search
:
''
,
tree
:
[
{
key
:
'root'
,
label
:
'Root'
,
children
:
[
{
key
:
'1'
,
label
:
'guides'
,
icon
:
'las la-folder'
,
children
:
[
{
key
:
'3'
,
label
:
'offline'
,
icon
:
'las la-folder'
}
]
},
{
key
:
'2'
,
label
:
'administration'
,
icon
:
'las la-folder'
}
]
currentFolderId
:
'boop'
,
treeNodes
:
{
beep
:
{
text
:
'Beep'
,
children
:
[
'foo'
,
'bar'
]
},
foo
:
{
text
:
'Foo'
},
bar
:
{
text
:
'Bar'
,
children
:
[
'boop'
]
},
boop
:
{
text
:
'Boop'
},
bop
:
{
text
:
'Bop'
,
children
:
[
'bap'
]
},
bap
:
{
text
:
'Bap'
}
],
treeExpanded
:
[
'root'
],
treeSelected
:
[]
},
treeRoots
:
[
'beep'
,
'bop'
]
})
// METHODS
...
...
@@ -215,11 +198,5 @@ function openFolder (node, noder) {
border-radius
:
8px
;
}
}
&
-toc
{
&
.q-tree--dense
.q-tree__node
{
padding-bottom
:
5px
;
}
}
}
</
style
>
ux/src/components/PageNewMenu.vue
View file @
0cbeec37
...
...
@@ -23,10 +23,11 @@ q-menu.translucent-menu(
q-item(clickable, @click='create(`redirect`)')
blueprint-icon(icon='advance')
q-item-section.q-pr-sm New Redirection
q-separator.q-my-sm(inset)
q-item(clickable, @click='openFileManager')
blueprint-icon(icon='add-image')
q-item-section.q-pr-sm Upload Media Asset
template(v-if='props.hideAssetBtn === false')
q-separator.q-my-sm(inset)
q-item(clickable, @click='openFileManager')
blueprint-icon(icon='add-image')
q-item-section.q-pr-sm Upload Media Asset
</
template
>
<
script
setup
>
...
...
@@ -36,6 +37,15 @@ import { useQuasar } from 'quasar'
import
{
usePageStore
}
from
'src/stores/page'
import
{
useSiteStore
}
from
'src/stores/site'
// PROPS
const
props
=
defineProps
({
hideAssetBtn
:
{
type
:
Boolean
,
default
:
false
}
})
// QUASAR
const
$q
=
useQuasar
()
...
...
ux/src/components/TreeLevel.vue
0 → 100644
View file @
0cbeec37
<
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
>
ux/src/components/TreeNav.vue
View file @
0cbeec37
<
template
lang=
"pug"
>
.treenav
.treeview
tree-level(
:depth='0'
:parent-id='null'
)
</
template
>
<
script
setup
>
import
{
computed
,
onMounted
,
provide
,
reactive
}
from
'vue'
import
{
findKey
}
from
'lodash-es'
import
TreeLevel
from
'./TreeLevel.vue'
// PROPS
defineProps
({
const
props
=
defineProps
({
nodes
:
{
type
:
Object
,
default
:
()
=>
({})
},
roots
:
{
type
:
Array
,
default
:
()
=>
[]
},
selected
:
{
type
:
String
,
default
:
null
}
})
// 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
>
<
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
>
ux/src/components/TreeNode.vue
0 → 100644
View file @
0cbeec37
<
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
>
ux/src/pages/AdminIcons.vue
View file @
0cbeec37
...
...
@@ -44,7 +44,7 @@ q-page.admin-icons
.text-caption.text-red-1
{{
t
(
'admin.icons.warnHint'
)
}}
q-list(separator)
q-item(v-for='pack of combinedPacks', :key='pack.key')
blueprint-icon(icon='small-icons', :hueRotate='
14
0')
blueprint-icon(icon='small-icons', :hueRotate='
3
0')
q-item-section
q-item-label: strong
{{
pack
.
label
}}
q-item-label(caption, v-if='pack.isMandatory')
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment