mirror of
https://github.com/directus/directus.git
synced 2026-02-15 14:54:56 -05:00
script[setup]: modules/files (#18443)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user