mirror of
https://github.com/directus/directus.git
synced 2026-02-17 04:11:36 -05:00
Add default-folder option (#3209)
* Allow set folder for imported files * Allow passing folder in file/files component; Allow pick folder for file/files/image interfaces. * Added folder system component for picking folders; Move folder picker the field from data to interface (file, files, image). * Add custom folder interface; use props for interfaces file/files/image in upload component * Allow set folder for imported files * Allow passing folder in file/files component; Allow pick folder for file/files/image interfaces. * Added folder system component for picking folders; Move folder picker the field from data to interface (file, files, image). * Add custom folder interface; use props for interfaces file/files/image in upload component * Update after rebase * Add storage_default_folder setting, use folder when deploy file * Fix files options; Add default label option for folder interface. * Fix set folder for file * UX * Add migration for column, undo seed change * Update nomanclature * Make sure file library always submits folder, cleanup setting retrieval * Use indexName on down migrate * Fix import default folder, rename customPresets->folderPreset * Rename interface import * Use undefined as default folder * Remove deprecated lang file * Fix display of folder interface, treat null as value * Move shared composable * Remove unused ref Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<v-list-item
|
||||
v-if="!folder.children || folder.children.length === 0"
|
||||
:active="currentFolder === folder.id"
|
||||
:disabled="disabled"
|
||||
clickable
|
||||
@click="$emit('click', folder.id)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon :name="currentFolder === folder.id ? 'folder_open' : 'folder'" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-group
|
||||
v-else
|
||||
clickable
|
||||
:active="currentFolder === folder.id"
|
||||
:disabled="disabled"
|
||||
@click="$emit('click', folder.id)"
|
||||
>
|
||||
<template #activator>
|
||||
<v-list-item-icon>
|
||||
<v-icon :name="currentFolder === folder.id ? 'folder_open' : 'folder'" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>{{ folder.name }}</v-list-item-content>
|
||||
</template>
|
||||
<folder-list-item
|
||||
v-for="childFolder in folder.children"
|
||||
:key="childFolder.id"
|
||||
:folder="childFolder"
|
||||
:current-folder="currentFolder"
|
||||
:disabled="disabledFolders.includes(childFolder.id)"
|
||||
:disabled-folders="disabledFolders"
|
||||
@click="$emit('click', $event)"
|
||||
/>
|
||||
</v-list-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
|
||||
type Folder = {
|
||||
id: string;
|
||||
name: string;
|
||||
children: Folder[];
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FolderListItem',
|
||||
props: {
|
||||
folder: {
|
||||
type: Object as PropType<Folder>,
|
||||
required: true,
|
||||
},
|
||||
currentFolder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabledFolders: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
});
|
||||
</script>
|
||||
140
app/src/interfaces/_system/system-folder/folder.vue
Normal file
140
app/src/interfaces/_system/system-folder/folder.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<v-skeleton-loader v-if="loading" />
|
||||
<v-menu
|
||||
v-else
|
||||
class="v-select"
|
||||
:attached="true"
|
||||
:show-arrow="false"
|
||||
:disabled="disabled"
|
||||
:close-on-content-click="true"
|
||||
>
|
||||
<template #activator="{ toggle, active }">
|
||||
<v-input
|
||||
readonly
|
||||
:active="active"
|
||||
:model-value="folderPath"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@click="toggle"
|
||||
>
|
||||
<template #prepend><v-icon :name="!value ? 'folder_special' : 'folder_open'" /></template>
|
||||
<template #append><v-icon name="expand_more" :class="{ active }" /></template>
|
||||
</v-input>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item clickable :active="!value" @click="emitValue(null)">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="folder_special" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>{{ t('interfaces.system-folder.root_name') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-divider v-if="nestedFolders && nestedFolders.length > 0" />
|
||||
<folder-list-item
|
||||
v-for="folder in nestedFolders"
|
||||
:key="folder.id"
|
||||
clickbable
|
||||
:folder="folder"
|
||||
:current-folder="value"
|
||||
:disabled="disabledFolders.includes(folder.id)"
|
||||
:disabled-folders="disabledFolders"
|
||||
@click="emitValue"
|
||||
/>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref } from 'vue';
|
||||
import FolderListItem from './folder-list-item.vue';
|
||||
import useFolders, { Folder } from '@/composables/use-folders';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: { FolderListItem },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
disabledFolders: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['input'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { nestedFolders, folders, loading } = useFolders();
|
||||
|
||||
const folderPath = computed(() => {
|
||||
if (!props.value || !folders.value) {
|
||||
return t('interfaces.system-folder.root_name');
|
||||
}
|
||||
const folder = folders.value.find((folder) => folder.id === props.value);
|
||||
return folder
|
||||
? folderParentPath(folder as Folder, folders.value)
|
||||
.map((folder) => folder.name)
|
||||
.join(' / ')
|
||||
: props.value;
|
||||
});
|
||||
|
||||
return {
|
||||
emitValue,
|
||||
loading,
|
||||
folderPath,
|
||||
nestedFolders,
|
||||
onFolderSelect,
|
||||
t,
|
||||
};
|
||||
|
||||
function emitValue(id: string | null) {
|
||||
return emit('input', id);
|
||||
}
|
||||
|
||||
function folderParentPath(folder: Folder, folders: Folder[]) {
|
||||
const folderMap = new Map(folders.map((folder) => [folder.id, folder]));
|
||||
|
||||
const folderParent = (target: Folder): Folder[] =>
|
||||
(folderMap.has(target.parent) ? folderParent(folderMap.get(target.parent) as Folder) : []).concat(target);
|
||||
|
||||
return folderParent(folder);
|
||||
}
|
||||
|
||||
function onFolderSelect(folderId: string | null) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('input', folderId);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-input {
|
||||
cursor: pointer;
|
||||
|
||||
.v-icon {
|
||||
transition: transform var(--medium) var(--transition-out);
|
||||
|
||||
&.active {
|
||||
transform: scaleY(-1);
|
||||
transition-timing-function: var(--transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(input) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
14
app/src/interfaces/_system/system-folder/index.ts
Normal file
14
app/src/interfaces/_system/system-folder/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineInterface } from '@/interfaces/define';
|
||||
import InterfaceSystemFolder from './folder.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'system-folder',
|
||||
name: '$t:interfaces.system-folder.folder',
|
||||
description: '$t:interfaces.system-folder.description',
|
||||
icon: 'folder',
|
||||
component: InterfaceSystemFolder,
|
||||
types: ['uuid'],
|
||||
options: [],
|
||||
system: true,
|
||||
recommendedDisplays: ['raw'],
|
||||
});
|
||||
Reference in New Issue
Block a user