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
You need to sign in or sign up before continuing.
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