UI: Implement folder tree in AppContentList

fixes #1756

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Marcel Klehr 2022-06-11 18:35:30 +02:00
parent fad74190a7
commit 9f5ac85458
6 changed files with 216 additions and 45 deletions

View File

@ -225,6 +225,7 @@ export default {
align-content: start;
gap: 10px;
padding: 0 10px;
padding-top: 10px;
}
.folder--gridview,

View File

@ -285,6 +285,7 @@ export default {
left: 0;
right: 0;
top: 0;
border-bottom: var(--color-border) 1px solid;
}
.controls h2 {
@ -302,7 +303,11 @@ export default {
.controls h2 > .material-design-icon {
position: relative;
top: 4px;
top: 3px;
}
.controls .action-item {
height: 45px;
}
.controls.wide {

View File

@ -0,0 +1,52 @@
<!--
- Copyright (c) 2022. The Nextcloud Bookmarks contributors.
-
- This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
-->
<template>
<AppContentList :show-details="showDetails" @update:show-details="$emit('update:show-details', $event)">
<TreeFolder v-for="folder in rootFolder.children"
:key="folder.id"
:folder="folder"
@select="onSelect($event)" />
</AppContentList>
</template>
<script>
import AppContentList from '@nextcloud/vue/dist/Components/AppContentList'
import TreeFolder from './TreeFolder'
import { privateRoutes } from '../router'
export default {
name: 'FolderOverview',
components: {
TreeFolder,
AppContentList,
},
props: {
showDetails: {
type: Boolean,
required: true,
},
},
computed: {
rootFolder() {
return this.$store.getters.getFolder(-1)[0]
},
},
methods: {
onSelect(folder) {
this.$router.push({ name: privateRoutes.FOLDER, params: { folder } })
this.$emit('update:show-details', true)
},
},
}
</script>
<style>
.app-content-list {
padding: 5px;
padding-top: 45px;
}
</style>

View File

@ -21,14 +21,11 @@
<HomeIcon :fill-color="colorMainText" />
</h2>
</div>
<div v-for="folder of items" :key="folder.id" class="treefolder">
<div class="treefolder__title" @click="folder.children && onSelect(folder.id)">
<h3>
<FolderIcon :fill-color="colorPrimaryElement" />
{{ folder.title }}
</h3>
</div>
</div>
<TreeFolder v-for="folder of items"
:key="folder.id"
:folder="folder"
:show-children="false"
@select="folder.children && onSelect(folder.id)" />
<div class="actions">
<button class="button" @click="onSubmit">
{{ t('bookmarks', 'Choose folder') }}
@ -43,11 +40,17 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import FolderIcon from 'vue-material-design-icons/Folder'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft'
import HomeIcon from 'vue-material-design-icons/Home'
import TreeFolder from './TreeFolder'
export default {
name: 'FolderPicker',
components: {
Actions, ActionButton, FolderIcon, ArrowLeftIcon, HomeIcon,
TreeFolder,
Actions,
ActionButton,
FolderIcon,
ArrowLeftIcon,
HomeIcon,
},
props: {
title: {
@ -108,39 +111,6 @@ export default {
display: flex;
}
.treefolder__title .material-design-icon {
position: relative;
top: 1px;
margin: 0 15px;
}
.treefolder__title {
display: flex;
align-items: center;
padding: 0 10px;
margin: 0 -10px;
cursor: pointer;
}
.treefolder__title * {
cursor: pointer;
}
{
position: relative;
top: 4px;
}
.treefolder__title:hover,
.treefolder__title:focus {
background: var(--color-background-dark);
}
.treefolder__title > h3 {
flex: 1;
display: flex;
}
.folderpicker .actions {
flex-grow: 1;
display: flex;

View File

@ -0,0 +1,125 @@
<!--
- Copyright (c) 2022. The Nextcloud Bookmarks contributors.
-
- This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
-->
<template>
<div :class="{treefolder:true, active}">
<div class="treefolder__title" @click="$emit('select', folder.id)">
<h3>
<FolderIcon v-if="!childrenShown"
class="treefolder__icon-hover"
:fill-color="colorPrimaryElement"
@click.stop="folder.children.length && showChildren && (childrenShown = true)" />
<FolderOpenIcon v-else
class="treefolder__icon-hover"
:fill-color="colorPrimaryElement"
@click.stop="folder.children.length && (childrenShown = false)" />
{{ folder.title }}
</h3>
</div>
<div v-if="showChildren && childrenShown" class="treefolder__children">
<TreeFolder v-for="f in folder.children"
:key="f.id"
:folder="f"
@select="$emit('select', $event)" />
</div>
</div>
</template>
<script>
import FolderIcon from 'vue-material-design-icons/Folder'
import FolderOpenIcon from 'vue-material-design-icons/FolderOpen'
import { privateRoutes } from '../router'
export default {
name: 'TreeFolder',
components: { FolderIcon, FolderOpenIcon },
props: {
folder: {
type: Object,
required: true,
},
showChildren: {
type: Boolean,
default: true,
},
},
data() {
return {
childrenShown: false,
}
},
computed: {
active() {
return this.$route.params.folder === this.folder.id
},
},
watch: {
'$route'() {
if (this.$route.name === privateRoutes.FOLDER
&& (this.$route.params.folder === this.folder.id
|| this.folder.children.find(f => f.id === this.$route.params.folder))
) {
this.childrenShown = true
}
},
},
}
</script>
<style>
.treefolder__title .material-design-icon {
position: relative;
top: 1px;
margin: 0 15px;
}
.treefolder__icon-hover:hover {
opacity: 0.8;
}
.treefolder__title {
display: flex;
align-items: center;
padding: 0 10px;
margin: 0 -10px;
cursor: pointer;
}
.treefolder__title * {
cursor: pointer;
}
.treefolder.active > .treefolder__title,
.treefolder__title:hover,
.treefolder__title:focus {
background: var(--color-background-dark);
}
.treefolder__title > h3 {
flex: 1;
display: flex;
}
.treefolder__children .treefolder__title {
padding-left: 25px;
}
.treefolder__children .treefolder__children .treefolder__title {
padding-left: 50px;
}
.treefolder__children .treefolder__children .treefolder__children .treefolder__title {
padding-left: 75px;
}
.treefolder__children .treefolder__children .treefolder__children .treefolder__children .treefolder__title {
padding-left: 100px;
}
.treefolder__children .treefolder__children .treefolder__children .treefolder__children .treefolder__children .treefolder__title {
padding-left: 125px;
}
</style>

View File

@ -7,7 +7,10 @@
<template>
<Content app-name="bookmarks">
<Navigation />
<AppContent>
<AppContent :show-details.sync="showDetails">
<template v-if="isFolderView && !smallScreen && folders.length" #list>
<FolderOverview :show-details.sync="showDetails" />
</template>
<Controls />
<BookmarksList />
</AppContent>
@ -24,6 +27,7 @@
import Content from '@nextcloud/vue/dist/Components/Content'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import Navigation from './Navigation'
import FolderOverview from './FolderOverview'
import BookmarksList from './BookmarksList'
import Controls from './Controls'
import SidebarBookmark from './SidebarBookmark'
@ -44,6 +48,7 @@ export default {
Navigation,
Content,
AppContent,
FolderOverview,
Controls,
BookmarksList,
SidebarBookmark,
@ -54,6 +59,8 @@ export default {
data() {
return {
newBookmark: false,
showDetails: false,
smallScreen: false,
}
},
computed: {
@ -63,6 +70,9 @@ export default {
tags() {
return this.$store.state.tags
},
isFolderView() {
return this.$route.name === privateRoutes.FOLDER || this.$route.name === privateRoutes.HOME
},
},
watch: {
@ -70,6 +80,10 @@ export default {
},
async created() {
const mediaQuery = window.matchMedia('(max-width: 1024px)')
this.smallScreen = mediaQuery.matches
mediaQuery.addEventListener('change', this.onWindowFormatChange)
if (OCA.Search) {
// legacy search pre nc v20
this.search = new window.OCA.Search(this.onSearch, this.onResetSearch)
@ -188,6 +202,10 @@ export default {
const dataEl = resDocument.querySelector('data')
return dataEl.firstElementChild.textContent
},
onWindowFormatChange(mediaQuery) {
this.smallScreen = mediaQuery.matches
},
},
}
</script>
@ -197,7 +215,7 @@ export default {
min-width: 0;
}
@media only screen and (max-width: 768px) {
@media only screen and (max-width: 720px) {
#app-content {
max-width: 100%;
}