mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Persist open state of folders + add root file library
Fixes #110, fixes #111
This commit is contained in:
@@ -8,11 +8,11 @@
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
|
||||
<transition-expand>
|
||||
<div class="items" v-show="groupActive">
|
||||
<slot />
|
||||
</div>
|
||||
</transition-expand>
|
||||
<!-- <transition-expand> -->
|
||||
<div class="items" v-show="groupActive">
|
||||
<slot />
|
||||
</div>
|
||||
<!-- </transition-expand> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -42,24 +42,33 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
open: {
|
||||
disableGroupableParent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scope: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props, { listeners, emit }) {
|
||||
const { active: groupActive, toggle, activate, deactivate } = useGroupable({
|
||||
active: toRefs(props).open,
|
||||
group: props.scope,
|
||||
value: props.value,
|
||||
});
|
||||
|
||||
// watch(() => props.open, () => props.open ? activate() : deactivate(), { immediate: true });
|
||||
|
||||
useGroupableParent(
|
||||
{},
|
||||
{
|
||||
multiple: toRefs(props).multiple,
|
||||
}
|
||||
);
|
||||
if (props.disableGroupableParent !== true) {
|
||||
useGroupableParent(
|
||||
{},
|
||||
{
|
||||
multiple: toRefs(props).multiple,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return { groupActive, toggle, onClick };
|
||||
|
||||
@@ -83,9 +92,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.activator-icon {
|
||||
color: var(--foreground-subdued);
|
||||
transform: rotate(0deg);
|
||||
transition: transform var(--medium) var(--transition);
|
||||
color: var(--foreground-subdued);
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground-normal);
|
||||
|
||||
@@ -15,6 +15,7 @@ type GroupableOptions = {
|
||||
value?: string | number;
|
||||
group?: string;
|
||||
active?: Ref<boolean>;
|
||||
watch?: boolean;
|
||||
};
|
||||
|
||||
export function useGroupable(options?: GroupableOptions) {
|
||||
@@ -43,14 +44,12 @@ export function useGroupable(options?: GroupableOptions) {
|
||||
toggle: (item: GroupableInstance) => void;
|
||||
} = parentFunctions;
|
||||
|
||||
const active = ref(false);
|
||||
const active = ref(options?.active?.value === true ? true : false);
|
||||
const item = { active, value: options?.value };
|
||||
|
||||
register(item);
|
||||
|
||||
if (options?.active) {
|
||||
if (options.active.value === true) toggle(item);
|
||||
|
||||
if (options?.active !== undefined && options.watch === true) {
|
||||
watch(options.active, () => {
|
||||
if (options.active === undefined) return;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
exact
|
||||
@contextmenu.native.prevent.stop="$refs.contextMenu.activate"
|
||||
>
|
||||
<v-list-item-icon><v-icon name="folder" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon :name="root ? 'folder_special' : 'folder'" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
@@ -16,22 +16,24 @@
|
||||
:to="`/files?folder=${folder.id}`"
|
||||
:active="currentFolder === folder.id"
|
||||
exact
|
||||
:open="isOpen"
|
||||
@contextmenu.native.prevent.stop="$refs.contextMenu.activate"
|
||||
scope="files-navigation"
|
||||
:value="root ? '$root' : folder.id"
|
||||
disable-groupable-parent
|
||||
>
|
||||
<template #activator>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="folder" />
|
||||
<v-icon :name="root ? 'folder_special' : 'folder'" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||
</template>
|
||||
|
||||
<navigation-folder
|
||||
v-for="childFolder in folder.children"
|
||||
:key="childFolder.id"
|
||||
:folder="childFolder"
|
||||
:current-folder="currentFolder"
|
||||
:click-handler="clickHandler"
|
||||
:start-open-folders="startOpenFolders"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
@@ -131,9 +133,9 @@ export default defineComponent({
|
||||
type: Function,
|
||||
default: () => undefined,
|
||||
},
|
||||
startOpenFolders: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
root: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
@@ -143,10 +145,6 @@ export default defineComponent({
|
||||
|
||||
const { fetchFolders } = useFolders();
|
||||
|
||||
const isOpen = computed(() => {
|
||||
return props.startOpenFolders.includes(props.folder.id);
|
||||
});
|
||||
|
||||
return {
|
||||
renameActive,
|
||||
renameValue,
|
||||
@@ -159,7 +157,6 @@ export default defineComponent({
|
||||
deleteActive,
|
||||
deleteSave,
|
||||
deleteSaving,
|
||||
isOpen,
|
||||
};
|
||||
|
||||
function useRenameFolder() {
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list-item to="/files/" exact>
|
||||
<v-list-item-icon><v-icon name="folder_special" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('all_files') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider v-if="loading || nestedFolders.length > 0" />
|
||||
|
||||
<template v-if="loading && (nestedFolders === null || nestedFolders.length === 0)">
|
||||
<v-list-item v-for="n in 4" :key="n">
|
||||
<v-skeleton-loader type="list-item-icon" />
|
||||
@@ -14,21 +7,31 @@
|
||||
</template>
|
||||
|
||||
<div class="folders">
|
||||
<navigation-folder
|
||||
v-for="folder in nestedFolders"
|
||||
:key="folder.id"
|
||||
:folder="folder"
|
||||
:current-folder="currentFolder"
|
||||
:start-open-folders="startOpenFolders"
|
||||
/>
|
||||
<v-item-group scope="files-navigation" multiple v-model="openFolders">
|
||||
<navigation-folder
|
||||
v-for="folder in nestedFolders"
|
||||
:key="folder.id"
|
||||
:folder="folder"
|
||||
:current-folder="currentFolder"
|
||||
root
|
||||
/>
|
||||
</v-item-group>
|
||||
</div>
|
||||
|
||||
<v-divider v-if="loading || nestedFolders.length > 0" />
|
||||
|
||||
<v-list-item to="/files/" exact>
|
||||
<v-list-item-icon><v-icon name="folder_special" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('all_files') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import { defineComponent, computed, ref, watch } from '@vue/composition-api';
|
||||
import useFolders from '../../composables/use-folders';
|
||||
import NavigationFolder from './navigation-folder.vue';
|
||||
import arraysAreEqual from '@/utils/arrays-are-equal';
|
||||
|
||||
export default defineComponent({
|
||||
components: { NavigationFolder },
|
||||
@@ -43,21 +46,38 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { nestedFolders, folders, error, loading } = useFolders();
|
||||
const { nestedFolders, folders, error, loading, openFolders } = useFolders();
|
||||
|
||||
const startOpenFolders = computed(() => {
|
||||
setOpenFolders();
|
||||
|
||||
watch(() => props.currentFolder, setOpenFolders);
|
||||
|
||||
return { folders, nestedFolders, error, loading, openFolders };
|
||||
|
||||
function setOpenFolders() {
|
||||
if (!folders.value) return [];
|
||||
if (!openFolders?.value) return [];
|
||||
|
||||
const openFolders: string[] = [];
|
||||
const shouldBeOpen: string[] = [];
|
||||
const folder = folders.value.find((folder) => folder.id === props.currentFolder);
|
||||
|
||||
if (folder && folder.parent_folder) parseFolder(folder.parent_folder);
|
||||
|
||||
return openFolders;
|
||||
const newOpenFolders = [...openFolders.value];
|
||||
|
||||
for (const folderID of shouldBeOpen) {
|
||||
if (newOpenFolders.includes(folderID) === false) {
|
||||
newOpenFolders.push(folderID);
|
||||
}
|
||||
}
|
||||
|
||||
if (newOpenFolders.length !== 1 && arraysAreEqual(newOpenFolders, openFolders.value) === false) {
|
||||
openFolders.value = newOpenFolders;
|
||||
}
|
||||
|
||||
function parseFolder(id: string) {
|
||||
if (!folders.value) return;
|
||||
openFolders.push(id);
|
||||
shouldBeOpen.push(id);
|
||||
|
||||
const folder = folders.value.find((folder) => folder.id === id);
|
||||
|
||||
@@ -65,9 +85,7 @@ export default defineComponent({
|
||||
parseFolder(folder.parent_folder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { folders, nestedFolders, error, loading, startOpenFolders };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import api from '@/api';
|
||||
import i18n from '@/lang';
|
||||
import { ref, Ref } from '@vue/composition-api';
|
||||
import { TranslateResult } from 'vue-i18n';
|
||||
|
||||
type FolderRaw = {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_folder: string;
|
||||
parent_folder: string | null;
|
||||
};
|
||||
|
||||
export type Folder = {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_folder: string;
|
||||
id: string | null;
|
||||
name: string | TranslateResult;
|
||||
parent_folder: string | null;
|
||||
children?: Folder[];
|
||||
};
|
||||
|
||||
let loading: Ref<boolean> | null = null;
|
||||
let folders: Ref<Folder[] | null> | null = null;
|
||||
let nestedFolders: Ref<Folder[] | null> | null = null;
|
||||
let openFolders: Ref<string[] | null> | null = null;
|
||||
|
||||
let error: Ref<any> | null = null;
|
||||
|
||||
@@ -25,12 +28,13 @@ export default function useFolders() {
|
||||
if (folders === null) folders = ref<Folder[] | null>(null);
|
||||
if (nestedFolders === null) nestedFolders = ref<Folder[] | null>(null);
|
||||
if (error === null) error = ref(null);
|
||||
if (openFolders === null) openFolders = ref(['$root']);
|
||||
|
||||
if (folders.value === null && loading.value === false) {
|
||||
fetchFolders();
|
||||
}
|
||||
|
||||
return { loading, folders, nestedFolders, error, fetchFolders };
|
||||
return { loading, folders, nestedFolders, error, fetchFolders, openFolders };
|
||||
|
||||
async function fetchFolders() {
|
||||
if (loading === null) return;
|
||||
@@ -49,7 +53,14 @@ export default function useFolders() {
|
||||
});
|
||||
|
||||
folders.value = response.data.data;
|
||||
nestedFolders.value = nestFolders(response.data.data);
|
||||
nestedFolders.value = [
|
||||
{
|
||||
id: null,
|
||||
name: i18n.t('file_library'),
|
||||
children: nestFolders(response.data.data),
|
||||
parent_folder: null,
|
||||
},
|
||||
];
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
|
||||
@@ -334,7 +334,12 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('file_library'),
|
||||
to: `/files/`,
|
||||
to: {
|
||||
path: `/files/`,
|
||||
query: {
|
||||
folder: item?.value?.folder,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user