script[setup]: modules/files (#18443)

This commit is contained in:
Rijk van Zanten
2023-05-03 11:34:13 -04:00
committed by GitHub
parent e5897177bf
commit 40849fd477
7 changed files with 705 additions and 843 deletions

View File

@@ -28,59 +28,48 @@
</v-dialog>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, ref } from 'vue';
import { ref } from 'vue';
import { useFolders } from '@/composables/use-folders';
import api from '@/api';
import { useRouter } from 'vue-router';
import { unexpectedError } from '@/utils/unexpected-error';
export default defineComponent({
props: {
parent: {
type: String,
default: null,
},
disabled: {
type: Boolean,
default: false,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
parent?: string;
disabled?: boolean;
}>();
const router = useRouter();
const { t } = useI18n();
const dialogActive = ref(false);
const saving = ref(false);
const newFolderName = ref(null);
const router = useRouter();
const { fetchFolders } = useFolders();
const dialogActive = ref(false);
const saving = ref(false);
const newFolderName = ref(null);
return { t, addFolder, dialogActive, newFolderName, saving };
const { fetchFolders } = useFolders();
async function addFolder() {
saving.value = true;
async function addFolder() {
saving.value = true;
try {
const newFolder = await api.post(`/folders`, {
name: newFolderName.value,
parent: props.parent === 'root' ? null : props.parent,
});
try {
const newFolder = await api.post(`/folders`, {
name: newFolderName.value,
parent: props.parent === 'root' ? null : props.parent,
});
await fetchFolders();
await fetchFolders();
dialogActive.value = false;
newFolderName.value = null;
dialogActive.value = false;
newFolderName.value = null;
router.push({ path: `/files/folders/${newFolder.data.data.id}` });
} catch (err: any) {
unexpectedError(err);
} finally {
saving.value = false;
}
}
},
});
router.push({ path: `/files/folders/${newFolder.data.data.id}` });
} catch (err: any) {
unexpectedError(err);
} finally {
saving.value = false;
}
}
</script>

View File

@@ -23,12 +23,12 @@
<div v-if="file.charset">
<dt>{{ t('charset') }}</dt>
<dd>{{ charset }}</dd>
<dd>{{ file.charset }}</dd>
</div>
<div v-if="file.embed">
<dt>{{ t('embed') }}</dt>
<dd>{{ embed }}</dd>
<dd>{{ file.embed }}</dd>
</div>
<div v-if="creationDate">
@@ -125,188 +125,166 @@
</sidebar-detail>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, ref, watch } from 'vue';
import { readableMimeType } from '@/utils/readable-mime-type';
import { formatFilesize } from '@/utils/format-filesize';
import { localizedFormat } from '@/utils/localized-format';
<script setup lang="ts">
import api, { addTokenToURL } from '@/api';
import { formatFilesize } from '@/utils/format-filesize';
import { getRootPath } from '@/utils/get-root-path';
import { localizedFormat } from '@/utils/localized-format';
import { readableMimeType } from '@/utils/readable-mime-type';
import { userName } from '@/utils/user-name';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
props: {
file: {
type: Object,
default: () => ({}),
},
isNew: {
type: Boolean,
default: false,
},
},
setup(props) {
const { t, n } = useI18n();
const props = withDefaults(
defineProps<{
file?: Record<string, any>;
isNew?: boolean;
}>(),
{
file: () => ({}),
}
);
const size = computed(() => {
if (props.isNew) return null;
const { t, n } = useI18n();
const size = computed(() => {
if (props.isNew) return null;
if (!props.file) return null;
if (!props.file.filesize) return null;
return formatFilesize(props.file.filesize); // { locale: locale.value.split('-')[0] }
});
const { creationDate, modificationDate } = useDates();
const { userCreated, userModified } = useUser();
const { folder, folderLink } = useFolder();
const fileLink = computed(() => {
return addTokenToURL(`${getRootPath()}assets/${props.file.id}`);
});
function useDates() {
const creationDate = ref<string | null>(null);
const modificationDate = ref<string | null>(null);
watch(
() => props.file,
async () => {
if (!props.file) return null;
if (!props.file.filesize) return null;
return formatFilesize(props.file.filesize); // { locale: locale.value.split('-')[0] }
});
creationDate.value = localizedFormat(new Date(props.file.uploaded_on), String(t('date-fns_date_short')));
const { creationDate, modificationDate } = useDates();
const { userCreated, userModified } = useUser();
const { folder, folderLink } = useFolder();
const fileLink = computed(() => {
return addTokenToURL(`${getRootPath()}assets/${props.file.id}`);
});
return {
t,
n,
readableMimeType,
size,
creationDate,
modificationDate,
userCreated,
userModified,
folder,
folderLink,
getRootPath,
fileLink,
};
function useDates() {
const creationDate = ref<string | null>(null);
const modificationDate = ref<string | null>(null);
watch(
() => props.file,
async () => {
if (!props.file) return null;
creationDate.value = localizedFormat(new Date(props.file.uploaded_on), String(t('date-fns_date_short')));
if (props.file.modified_on) {
modificationDate.value = localizedFormat(
new Date(props.file.modified_on),
String(t('date-fns_date_short'))
);
}
},
{ immediate: true }
);
return { creationDate, modificationDate };
}
function useUser() {
type User = {
id: number;
name: string;
link: string;
};
const loading = ref(false);
const userCreated = ref<User | null>(null);
const userModified = ref<User | null>(null);
watch(() => props.file, fetchUser, { immediate: true });
return { userCreated, userModified };
async function fetchUser() {
if (!props.file) return null;
if (!props.file.uploaded_by) return null;
loading.value = true;
try {
const response = await api.get(`/users/${props.file.uploaded_by}`, {
params: {
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
},
});
const user = response.data.data;
userCreated.value = {
id: props.file.uploaded_by,
name: userName(user),
link: `/users/${user.id}`,
};
if (props.file.modified_by) {
const response = await api.get(`/users/${props.file.modified_by}`, {
params: {
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
},
});
const user = response.data.data;
userModified.value = {
id: props.file.modified_by,
name: userName(user),
link: `/users/${user.id}`,
};
}
} finally {
loading.value = false;
}
if (props.file.modified_on) {
modificationDate.value = localizedFormat(new Date(props.file.modified_on), String(t('date-fns_date_short')));
}
}
},
{ immediate: true }
);
function useFolder() {
type Folder = {
id: string;
name: string;
};
return { creationDate, modificationDate };
}
const loading = ref(false);
const folder = ref<Folder | null>(null);
function useUser() {
type User = {
id: number;
name: string;
link: string;
};
const folderLink = computed(() => {
if (folder.value === null) {
return `/files`;
}
const loading = ref(false);
const userCreated = ref<User | null>(null);
const userModified = ref<User | null>(null);
return `/files/folders/${folder.value.id}`;
watch(() => props.file, fetchUser, { immediate: true });
return { userCreated, userModified };
async function fetchUser() {
if (!props.file) return null;
if (!props.file.uploaded_by) return null;
loading.value = true;
try {
const response = await api.get(`/users/${props.file.uploaded_by}`, {
params: {
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
},
});
watch(() => props.file, fetchFolder, { immediate: true });
const user = response.data.data;
return { folder, folderLink };
userCreated.value = {
id: props.file.uploaded_by,
name: userName(user),
link: `/users/${user.id}`,
};
async function fetchFolder() {
if (!props.file) return null;
if (!props.file.folder) return;
loading.value = true;
if (props.file.modified_by) {
const response = await api.get(`/users/${props.file.modified_by}`, {
params: {
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
},
});
try {
const response = await api.get(`/folders/${props.file.folder}`, {
params: {
fields: ['id', 'name'],
},
});
const user = response.data.data;
const { name } = response.data.data;
folder.value = {
id: props.file.folder,
name: name,
};
} finally {
loading.value = false;
}
userModified.value = {
id: props.file.modified_by,
name: userName(user),
link: `/users/${user.id}`,
};
}
} finally {
loading.value = false;
}
},
});
}
}
function useFolder() {
type Folder = {
id: string;
name: string;
};
const loading = ref(false);
const folder = ref<Folder | null>(null);
const folderLink = computed(() => {
if (folder.value === null) {
return `/files`;
}
return `/files/folders/${folder.value.id}`;
});
watch(() => props.file, fetchFolder, { immediate: true });
return { folder, folderLink };
async function fetchFolder() {
if (!props.file) return null;
if (!props.file.folder) return;
loading.value = true;
try {
const response = await api.get(`/folders/${props.file.folder}`, {
params: {
fields: ['id', 'name'],
},
});
const { name } = response.data.data;
folder.value = {
id: props.file.folder,
name: name,
};
} finally {
loading.value = false;
}
}
}
</script>
<style lang="scss" scoped>

View File

@@ -113,180 +113,156 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, ref } from 'vue';
import { ref } from 'vue';
import { useFolders, Folder } from '@/composables/use-folders';
import api from '@/api';
import FolderPicker from '@/views/private/components/folder-picker.vue';
import { useRouter } from 'vue-router';
import { unexpectedError } from '@/utils/unexpected-error';
export default defineComponent({
name: 'NavigationFolder',
components: { FolderPicker },
props: {
folder: {
type: Object as PropType<Folder>,
required: true,
},
currentFolder: {
type: String,
default: null,
},
clickHandler: {
type: Function,
default: () => undefined,
},
},
setup(props) {
const { t } = useI18n();
const props = withDefaults(
defineProps<{
folder: Folder;
currentFolder?: string;
clickHandler?: () => void;
}>(),
{
clickHandler: () => undefined,
}
);
const router = useRouter();
const { t } = useI18n();
const { renameActive, renameValue, renameSave, renameSaving } = useRenameFolder();
const { moveActive, moveValue, moveSave, moveSaving } = useMoveFolder();
const { deleteActive, deleteSave, deleteSaving } = useDeleteFolder();
const router = useRouter();
const { fetchFolders } = useFolders();
const { renameActive, renameValue, renameSave, renameSaving } = useRenameFolder();
const { moveActive, moveValue, moveSave, moveSaving } = useMoveFolder();
const { deleteActive, deleteSave, deleteSaving } = useDeleteFolder();
return {
t,
renameActive,
renameValue,
renameSave,
renameSaving,
moveActive,
moveValue,
moveSave,
moveSaving,
deleteActive,
deleteSave,
deleteSaving,
};
const { fetchFolders } = useFolders();
function useRenameFolder() {
const renameActive = ref(false);
const renameValue = ref(props.folder.name);
const renameSaving = ref(false);
function useRenameFolder() {
const renameActive = ref(false);
const renameValue = ref(props.folder.name);
const renameSaving = ref(false);
return { renameActive, renameValue, renameSave, renameSaving };
return { renameActive, renameValue, renameSave, renameSaving };
async function renameSave() {
renameSaving.value = true;
async function renameSave() {
renameSaving.value = true;
try {
await api.patch(`/folders/${props.folder.id}`, {
name: renameValue.value,
});
} catch (err: any) {
unexpectedError(err);
} finally {
renameSaving.value = false;
await fetchFolders();
renameActive.value = false;
}
}
try {
await api.patch(`/folders/${props.folder.id}`, {
name: renameValue.value,
});
} catch (err: any) {
unexpectedError(err);
} finally {
renameSaving.value = false;
await fetchFolders();
renameActive.value = false;
}
}
}
function useMoveFolder() {
const moveActive = ref(false);
const moveValue = ref(props.folder.parent);
const moveSaving = ref(false);
function useMoveFolder() {
const moveActive = ref(false);
const moveValue = ref(props.folder.parent);
const moveSaving = ref(false);
return { moveActive, moveValue, moveSave, moveSaving };
return { moveActive, moveValue, moveSave, moveSaving };
async function moveSave() {
moveSaving.value = true;
async function moveSave() {
moveSaving.value = true;
try {
await api.patch(`/folders/${props.folder.id}`, {
parent: moveValue.value,
});
} catch (err: any) {
unexpectedError(err);
} finally {
moveSaving.value = false;
await fetchFolders();
moveActive.value = false;
}
}
try {
await api.patch(`/folders/${props.folder.id}`, {
parent: moveValue.value,
});
} catch (err: any) {
unexpectedError(err);
} finally {
moveSaving.value = false;
await fetchFolders();
moveActive.value = false;
}
}
}
function useDeleteFolder() {
const deleteActive = ref(false);
const deleteSaving = ref(false);
function useDeleteFolder() {
const deleteActive = ref(false);
const deleteSaving = ref(false);
return { deleteActive, deleteSave, deleteSaving };
return { deleteActive, deleteSave, deleteSaving };
async function deleteSave() {
deleteSaving.value = true;
async function deleteSave() {
deleteSaving.value = true;
try {
const foldersToUpdate = await api.get('/folders', {
params: {
filter: {
parent: {
_eq: props.folder.id,
},
},
fields: ['id'],
try {
const foldersToUpdate = await api.get('/folders', {
params: {
filter: {
parent: {
_eq: props.folder.id,
},
});
},
fields: ['id'],
},
});
const filesToUpdate = await api.get('/files', {
params: {
filter: {
folder: {
_eq: props.folder.id,
},
},
fields: ['id'],
const filesToUpdate = await api.get('/files', {
params: {
filter: {
folder: {
_eq: props.folder.id,
},
});
},
fields: ['id'],
},
});
const newParent = props.folder.parent || null;
const newParent = props.folder.parent || null;
const folderKeys = foldersToUpdate.data.data.map((folder: { id: string }) => folder.id);
const fileKeys = filesToUpdate.data.data.map((file: { id: string }) => file.id);
const folderKeys = foldersToUpdate.data.data.map((folder: { id: string }) => folder.id);
const fileKeys = filesToUpdate.data.data.map((file: { id: string }) => file.id);
await api.delete(`/folders/${props.folder.id}`);
await api.delete(`/folders/${props.folder.id}`);
if (folderKeys.length > 0) {
await api.patch(`/folders`, {
keys: folderKeys,
data: {
parent: newParent,
},
});
}
if (fileKeys.length > 0) {
await api.patch(`/files`, {
keys: fileKeys,
data: {
folder: newParent,
},
});
}
if (newParent) {
router.replace(`/files/folders/${newParent}`);
} else {
router.replace('/files');
}
deleteActive.value = false;
} catch (err: any) {
unexpectedError(err);
} finally {
await fetchFolders();
deleteSaving.value = false;
}
if (folderKeys.length > 0) {
await api.patch(`/folders`, {
keys: folderKeys,
data: {
parent: newParent,
},
});
}
if (fileKeys.length > 0) {
await api.patch(`/files`, {
keys: fileKeys,
data: {
folder: newParent,
},
});
}
if (newParent) {
router.replace(`/files/folders/${newParent}`);
} else {
router.replace('/files');
}
deleteActive.value = false;
} catch (err: any) {
unexpectedError(err);
} finally {
await fetchFolders();
deleteSaving.value = false;
}
},
});
}
}
</script>
<style scoped>

View File

@@ -61,66 +61,57 @@
</v-list>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, watch } from 'vue';
import { useFolders, Folder } from '@/composables/use-folders';
import NavigationFolder from './navigation-folder.vue';
<script setup lang="ts">
import { Folder, useFolders } from '@/composables/use-folders';
import { isEqual } from 'lodash';
import { watch } from 'vue';
import { useI18n } from 'vue-i18n';
import NavigationFolder from './navigation-folder.vue';
export default defineComponent({
components: { NavigationFolder },
props: {
currentFolder: {
type: String,
default: null,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
currentFolder?: string;
}>();
const { nestedFolders, folders, error, loading, openFolders } = useFolders();
const { t } = useI18n();
setOpenFolders();
const { nestedFolders, folders, loading, openFolders } = useFolders();
watch(() => props.currentFolder, setOpenFolders);
setOpenFolders();
return { t, folders, nestedFolders, error, loading, openFolders };
watch(() => props.currentFolder, setOpenFolders);
function setOpenFolders() {
if (!folders.value) return [];
if (!openFolders?.value) return [];
function setOpenFolders() {
if (!folders.value) return [];
if (!openFolders?.value) return [];
const shouldBeOpen: string[] = [];
const folder = folders.value.find((folder: Folder) => folder.id === props.currentFolder);
const shouldBeOpen: string[] = [];
const folder = folders.value.find((folder: Folder) => folder.id === props.currentFolder);
if (folder && folder.parent) parseFolder(folder.parent);
if (folder && folder.parent) parseFolder(folder.parent);
const newOpenFolders = [...openFolders.value];
const newOpenFolders = [...openFolders.value];
for (const folderID of shouldBeOpen) {
if (newOpenFolders.includes(folderID) === false) {
newOpenFolders.push(folderID);
}
}
if (newOpenFolders.length !== 1 && isEqual(newOpenFolders, openFolders.value) === false) {
openFolders.value = newOpenFolders;
}
function parseFolder(id: string) {
if (!folders.value) return;
shouldBeOpen.push(id);
const folder = folders.value.find((folder: Folder) => folder.id === id);
if (folder && folder.parent) {
parseFolder(folder.parent);
}
}
for (const folderID of shouldBeOpen) {
if (newOpenFolders.includes(folderID) === false) {
newOpenFolders.push(folderID);
}
},
});
}
if (newOpenFolders.length !== 1 && isEqual(newOpenFolders, openFolders.value) === false) {
openFolders.value = newOpenFolders;
}
function parseFolder(id: string) {
if (!folders.value) return;
shouldBeOpen.push(id);
const folder = folders.value.find((folder: Folder) => folder.id === id);
if (folder && folder.parent) {
parseFolder(folder.parent);
}
}
}
</script>
<style lang="scss" scoped>

View File

@@ -12,31 +12,22 @@
</v-dialog>
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent } from 'vue';
import { useRouter } from 'vue-router';
<script setup lang="ts">
import { useDialogRoute } from '@/composables/use-dialog-route';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
export default defineComponent({
props: {
folder: {
type: String,
default: null,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
folder?: string;
}>();
const router = useRouter();
const { t } = useI18n();
const isOpen = useDialogRoute();
const router = useRouter();
return { t, isOpen, close };
const isOpen = useDialogRoute();
function close() {
router.push(props.folder ? { path: `/files/folders/${props.folder}` } : { path: '/files' });
}
},
});
function close() {
router.push(props.folder ? { path: `/files/folders/${props.folder}` } : { path: '/files' });
}
</script>

View File

@@ -189,7 +189,7 @@
</component>
</template>
<script lang="ts">
<script setup lang="ts">
import api from '@/api';
import { useEventListener } from '@/composables/use-event-listener';
import { useExtension } from '@/composables/use-extension';
@@ -209,7 +209,7 @@ import { useLayout } from '@directus/composables';
import { Filter } from '@directus/types';
import { mergeFilters } from '@directus/utils';
import { subDays } from 'date-fns';
import { PropType, computed, defineComponent, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRouter } from 'vue-router';
import AddFolder from '../components/add-folder.vue';
@@ -219,458 +219,402 @@ type Item = {
[field: string]: any;
};
export default defineComponent({
name: 'FilesCollection',
components: {
FilesNavigation,
LayoutSidebarDetail,
AddFolder,
SearchInput,
FolderPicker,
DrawerBatch,
},
props: {
folder: {
type: String,
default: null,
},
special: {
type: String as PropType<'all' | 'recent' | 'mine'>,
default: null,
},
},
setup(props) {
const { t } = useI18n();
const props = defineProps<{
folder?: string;
special?: 'all' | 'recent' | 'mine';
}>();
const router = useRouter();
const { t } = useI18n();
const notificationsStore = useNotificationsStore();
const permissionsStore = usePermissionsStore();
const { folders } = useFolders();
const router = useRouter();
const layoutRef = ref();
const selection = ref<Item[]>([]);
const notificationsStore = useNotificationsStore();
const permissionsStore = usePermissionsStore();
const { folders } = useFolders();
const userStore = useUserStore();
const layoutRef = ref();
const selection = ref<Item[]>([]);
const { layout, layoutOptions, layoutQuery, filter, search, resetPreset } = usePreset(ref('directus_files'));
const userStore = useUserStore();
const currentLayout = useExtension('layout', layout);
const { layout, layoutOptions, layoutQuery, filter, search, resetPreset } = usePreset(ref('directus_files'));
const { confirmDelete, deleting, batchDelete, error: deleteError, batchEditActive } = useBatch();
const currentLayout = useExtension('layout', layout);
const { breadcrumb, title } = useBreadcrumb();
const { confirmDelete, deleting, batchDelete, batchEditActive } = useBatch();
const folderTypeFilter = computed(() => {
const filterParsed: Filter = {
_and: [
{
type: {
_nnull: true,
},
},
],
};
const { breadcrumb, title } = useBreadcrumb();
if (props.special === null) {
if (props.folder !== null) {
filterParsed._and.push({
folder: {
_eq: props.folder,
},
});
} else {
filterParsed._and.push({
folder: {
_null: true,
},
});
}
}
const folderTypeFilter = computed(() => {
const filterParsed: Filter = {
_and: [
{
type: {
_nnull: true,
},
},
],
};
if (props.special === 'mine' && userStore.currentUser) {
filterParsed._and.push({
uploaded_by: {
_eq: userStore.currentUser.id,
},
});
}
if (props.special === null) {
if (props.folder !== null) {
filterParsed._and.push({
folder: {
_eq: props.folder,
},
});
} else {
filterParsed._and.push({
folder: {
_null: true,
},
});
}
}
if (props.special === 'recent') {
filterParsed._and.push({
uploaded_on: {
_gt: subDays(new Date(), 5).toISOString(),
},
});
}
return filterParsed;
if (props.special === 'mine' && userStore.currentUser) {
filterParsed._and.push({
uploaded_by: {
_eq: userStore.currentUser.id,
},
});
}
const { layoutWrapper } = useLayout(layout);
if (props.special === 'recent') {
filterParsed._and.push({
uploaded_on: {
_gt: subDays(new Date(), 5).toISOString(),
},
});
}
const { moveToDialogActive, moveToFolder, moving, selectedFolder } = useMovetoFolder();
return filterParsed;
});
onMounted(() => emitter.on(Events.upload, refresh));
onUnmounted(() => emitter.off(Events.upload, refresh));
const { layoutWrapper } = useLayout(layout);
const { moveToDialogActive, moveToFolder, moving, selectedFolder } = useMovetoFolder();
onMounted(() => emitter.on(Events.upload, refresh));
onUnmounted(() => emitter.off(Events.upload, refresh));
onBeforeRouteLeave(() => {
selection.value = [];
});
onBeforeRouteUpdate(() => {
selection.value = [];
});
const { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging } = useFileUpload();
useEventListener(window, 'dragenter', onDragEnter);
useEventListener(window, 'dragover', onDragOver);
useEventListener(window, 'dragleave', onDragLeave);
useEventListener(window, 'drop', onDrop);
const { batchEditAllowed, batchDeleteAllowed, createAllowed, createFolderAllowed } = usePermissions();
function useBatch() {
const confirmDelete = ref(false);
const deleting = ref(false);
const batchEditActive = ref(false);
const error = ref<any>();
return { batchEditActive, confirmDelete, deleting, batchDelete, error };
async function batchDelete() {
deleting.value = true;
const batchPrimaryKeys = selection.value;
try {
await api.delete('/files', {
data: batchPrimaryKeys,
});
await refresh();
onBeforeRouteLeave(() => {
selection.value = [];
});
} catch (err: any) {
unexpectedError(err);
error.value = err;
} finally {
confirmDelete.value = false;
deleting.value = false;
}
}
}
function useBreadcrumb() {
const title = computed(() => {
if (props.special === 'all') {
return t('all_files');
}
if (props.special === 'mine') {
return t('my_files');
}
if (props.special === 'recent') {
return t('recent_files');
}
if (props.folder) {
const folder = folders.value?.find((folder: Folder) => folder.id === props.folder);
if (folder) {
return folder.name;
}
}
return t('file_library');
});
const breadcrumb = computed(() => {
if (title.value !== t('file_library')) {
return [
{
name: t('file_library'),
to: `/files`,
},
];
}
return null;
});
return { breadcrumb, title };
}
function useMovetoFolder() {
const moveToDialogActive = ref(false);
const moving = ref(false);
const selectedFolder = ref<number | null>();
return { moveToDialogActive, moving, moveToFolder, selectedFolder };
async function moveToFolder() {
moving.value = true;
try {
await api.patch(`/files`, {
keys: selection.value,
data: {
folder: selectedFolder.value,
},
});
onBeforeRouteUpdate(() => {
selection.value = [];
if (selectedFolder.value) {
router.push(`/files/folders/${selectedFolder.value}`);
}
await nextTick();
await refresh();
} catch (err: any) {
unexpectedError(err);
} finally {
moveToDialogActive.value = false;
moving.value = false;
}
}
}
async function refresh() {
await layoutRef.value?.state?.refresh?.();
}
function clearFilters() {
filter.value = null;
search.value = null;
}
function usePermissions() {
const batchEditAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === 'directus_files'
);
return !!updatePermissions;
});
const batchDeleteAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === 'directus_files'
);
return !!deletePermissions;
});
const createAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_files'
);
return !!createPermissions;
});
const createFolderAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_folders'
);
return !!createPermissions;
});
return { batchEditAllowed, batchDeleteAllowed, createAllowed, createFolderAllowed };
}
function useFileUpload() {
const showDropEffect = ref(false);
let dragNotificationID: string;
let fileUploadNotificationID: string;
const dragCounter = ref(0);
const dragging = computed(() => dragCounter.value > 0);
return { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging };
function enableDropEffect() {
showDropEffect.value = true;
dragNotificationID = notificationsStore.add({
title: t('drop_to_upload'),
icon: 'cloud_upload',
type: 'info',
persist: true,
closeable: false,
});
}
function disableDropEffect() {
showDropEffect.value = false;
if (dragNotificationID) {
notificationsStore.remove(dragNotificationID);
}
}
function onDragEnter(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
dragCounter.value++;
const isDropzone = event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '';
if (dragCounter.value === 1 && showDropEffect.value === false && isDropzone === false) {
enableDropEffect();
}
if (isDropzone) {
disableDropEffect();
dragCounter.value = 0;
}
}
function onDragOver(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
}
function onDragLeave(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
dragCounter.value--;
if (dragCounter.value === 0) {
disableDropEffect();
}
if (event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '') {
enableDropEffect();
dragCounter.value = 1;
}
}
async function onDrop(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
showDropEffect.value = false;
dragCounter.value = 0;
if (dragNotificationID) {
notificationsStore.remove(dragNotificationID);
}
const files = [...(event.dataTransfer.files as any)];
fileUploadNotificationID = notificationsStore.add({
title: t(
'upload_files_indeterminate',
{
done: 0,
total: files.length,
},
files.length
),
type: 'info',
persist: true,
closeable: false,
loading: true,
});
const { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging } = useFileUpload();
await uploadFiles(files, {
preset: {
folder: props.folder,
},
onProgressChange: (progress) => {
const percentageDone = progress.reduce((val, cur) => (val += cur)) / progress.length;
useEventListener(window, 'dragenter', onDragEnter);
useEventListener(window, 'dragover', onDragOver);
useEventListener(window, 'dragleave', onDragLeave);
useEventListener(window, 'drop', onDrop);
const total = files.length;
const done = progress.filter((p) => p === 100).length;
const { batchEditAllowed, batchDeleteAllowed, createAllowed, createFolderAllowed } = usePermissions();
return {
t,
breadcrumb,
title,
layoutRef,
layoutWrapper,
selection,
layoutOptions,
layoutQuery,
layout,
folderTypeFilter,
search,
moveToDialogActive,
moveToFolder,
moving,
selectedFolder,
refresh,
clearFilters,
onDragEnter,
onDragLeave,
showDropEffect,
onDrop,
dragging,
batchEditAllowed,
batchDeleteAllowed,
createAllowed,
createFolderAllowed,
resetPreset,
confirmDelete,
deleting,
batchDelete,
deleteError,
batchEditActive,
filter,
mergeFilters,
currentLayout,
};
function useBatch() {
const confirmDelete = ref(false);
const deleting = ref(false);
const batchEditActive = ref(false);
const error = ref<any>();
return { batchEditActive, confirmDelete, deleting, batchDelete, error };
async function batchDelete() {
deleting.value = true;
const batchPrimaryKeys = selection.value;
try {
await api.delete('/files', {
data: batchPrimaryKeys,
});
await refresh();
selection.value = [];
} catch (err: any) {
unexpectedError(err);
error.value = err;
} finally {
confirmDelete.value = false;
deleting.value = false;
}
}
}
function useBreadcrumb() {
const title = computed(() => {
if (props.special === 'all') {
return t('all_files');
}
if (props.special === 'mine') {
return t('my_files');
}
if (props.special === 'recent') {
return t('recent_files');
}
if (props.folder) {
const folder = folders.value?.find((folder: Folder) => folder.id === props.folder);
if (folder) {
return folder.name;
}
}
return t('file_library');
});
const breadcrumb = computed(() => {
if (title.value !== t('file_library')) {
return [
{
name: t('file_library'),
to: `/files`,
},
];
}
return null;
});
return { breadcrumb, title };
}
function useMovetoFolder() {
const moveToDialogActive = ref(false);
const moving = ref(false);
const selectedFolder = ref<number | null>();
return { moveToDialogActive, moving, moveToFolder, selectedFolder };
async function moveToFolder() {
moving.value = true;
try {
await api.patch(`/files`, {
keys: selection.value,
data: {
folder: selectedFolder.value,
},
});
selection.value = [];
if (selectedFolder.value) {
router.push(`/files/folders/${selectedFolder.value}`);
}
await nextTick();
await refresh();
} catch (err: any) {
unexpectedError(err);
} finally {
moveToDialogActive.value = false;
moving.value = false;
}
}
}
async function refresh() {
await layoutRef.value?.state?.refresh?.();
}
function clearFilters() {
filter.value = null;
search.value = null;
}
function usePermissions() {
const batchEditAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === 'directus_files'
);
return !!updatePermissions;
});
const batchDeleteAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === 'directus_files'
);
return !!deletePermissions;
});
const createAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_files'
);
return !!createPermissions;
});
const createFolderAllowed = computed(() => {
const admin = userStore?.currentUser?.role.admin_access === true;
if (admin) return true;
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_folders'
);
return !!createPermissions;
});
return { batchEditAllowed, batchDeleteAllowed, createAllowed, createFolderAllowed };
}
function useFileUpload() {
const showDropEffect = ref(false);
let dragNotificationID: string;
let fileUploadNotificationID: string;
const dragCounter = ref(0);
const dragging = computed(() => dragCounter.value > 0);
return { onDragEnter, onDragLeave, onDrop, onDragOver, showDropEffect, dragging };
function enableDropEffect() {
showDropEffect.value = true;
dragNotificationID = notificationsStore.add({
title: t('drop_to_upload'),
icon: 'cloud_upload',
type: 'info',
persist: true,
closeable: false,
});
}
function disableDropEffect() {
showDropEffect.value = false;
if (dragNotificationID) {
notificationsStore.remove(dragNotificationID);
}
}
function onDragEnter(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
dragCounter.value++;
const isDropzone = event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '';
if (dragCounter.value === 1 && showDropEffect.value === false && isDropzone === false) {
enableDropEffect();
}
if (isDropzone) {
disableDropEffect();
dragCounter.value = 0;
}
}
function onDragOver(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
}
function onDragLeave(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
dragCounter.value--;
if (dragCounter.value === 0) {
disableDropEffect();
}
if (event.target && (event.target as HTMLElement).getAttribute?.('data-dropzone') === '') {
enableDropEffect();
dragCounter.value = 1;
}
}
async function onDrop(event: DragEvent) {
if (!event.dataTransfer) return;
if (event.dataTransfer?.types.indexOf('Files') === -1) return;
event.preventDefault();
showDropEffect.value = false;
dragCounter.value = 0;
if (dragNotificationID) {
notificationsStore.remove(dragNotificationID);
}
const files = [...(event.dataTransfer.files as any)];
fileUploadNotificationID = notificationsStore.add({
notificationsStore.update(fileUploadNotificationID, {
title: t(
'upload_files_indeterminate',
{
done: 0,
total: files.length,
done,
total,
},
files.length
),
type: 'info',
persist: true,
closeable: false,
loading: true,
loading: false,
progress: percentageDone,
});
},
});
await uploadFiles(files, {
preset: {
folder: props.folder,
},
onProgressChange: (progress) => {
const percentageDone = progress.reduce((val, cur) => (val += cur)) / progress.length;
const total = files.length;
const done = progress.filter((p) => p === 100).length;
notificationsStore.update(fileUploadNotificationID, {
title: t(
'upload_files_indeterminate',
{
done,
total,
},
files.length
),
loading: false,
progress: percentageDone,
});
},
});
notificationsStore.remove(fileUploadNotificationID);
emitter.emit(Events.upload);
}
}
},
});
notificationsStore.remove(fileUploadNotificationID);
emitter.emit(Events.upload);
}
}
</script>
<style lang="scss" scoped>

View File

@@ -12,18 +12,11 @@
</private-view>
</template>
<script lang="ts">
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent } from 'vue';
import FilesNavigation from '../components/navigation.vue';
export default defineComponent({
components: { FilesNavigation },
setup() {
const { t } = useI18n();
return { t };
},
});
const { t } = useI18n();
</script>
<style lang="scss" scoped>